Skip to content

Commit

Permalink
Add waitForTransaction method to Network type (#48)
Browse files Browse the repository at this point in the history
* Add waitForTransaction method to Network type

* Fix function and add test

* Add test for timeout

* Remove default in coment

* Add TransactionReceipt type

* Add to field to TransactionReceipt

* Add waitForTransaction to ethers binding

* Update comments and remove type from receipt

* Remove type field from ethers binding

* Add waitForTransaction to viem binding

* Add changeset

* Switch to patch change
  • Loading branch information
ryangoree authored Mar 1, 2024
1 parent 7ef7f33 commit 5f6a374
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 2 deletions.
7 changes: 7 additions & 0 deletions .changeset/funny-pugs-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@delvtech/evm-client-ethers": patch
"@delvtech/evm-client-viem": patch
"@delvtech/evm-client": patch
---

Add `waitForTransaction` method to `Network` type.
25 changes: 25 additions & 0 deletions packages/evm-client-ethers/src/network/createNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,30 @@ export function createNetwork(provider: Provider): Network {
transactionIndex: index,
};
},

async waitForTransaction(hash, options) {
const transaction = await provider.waitForTransaction(
hash,
undefined,
options?.timeout,
);

if (!transaction) {
return;
}

return {
blockHash: transaction.blockHash as `0x${string}`,
blockNumber: BigInt(transaction.blockNumber),
from: transaction.from as `0x${string}`,
to: transaction.to as `0x${string}` | null,
cumulativeGasUsed: BigInt(transaction.cumulativeGasUsed),
gasUsed: BigInt(transaction.gasUsed),
logsBloom: transaction.logsBloom as `0x${string}`,
transactionHash: transaction.hash as `0x${string}`,
transactionIndex: transaction.index,
effectiveGasPrice: BigInt(transaction.gasPrice),
};
},
};
}
7 changes: 7 additions & 0 deletions packages/evm-client-viem/src/network/createNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,12 @@ export function createNetwork(publicClient: PublicClient): Network {
transactionIndex: transactionIndex ?? undefined,
};
},

async waitForTransaction(hash, options) {
return await publicClient.waitForTransactionReceipt({
hash,
timeout: options?.timeout,
});
},
};
}
47 changes: 47 additions & 0 deletions packages/evm-client/src/network/stubs/NetworkStub.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
NetworkStub,
transactionToReceipt,
} from 'src/network/stubs/NetworkStub';
import { describe, expect, it } from 'vitest';

describe('NetworkStub', () => {
it('waits for stubbed transactions', async () => {
const network = new NetworkStub();

const txHash = '0x123abc';
const stubbedTx = {
gas: 100n,
gasPrice: 100n,
input: '0x456def',
nonce: 0,
type: '0x0',
value: 0n,
} as const;

const waitPromise = network.waitForTransaction(txHash);

await new Promise((resolve) => {
setTimeout(() => {
network.stubGetTransaction({
args: [txHash],
value: stubbedTx,
});
resolve(undefined);
}, 1000);
});

const tx = await waitPromise;

expect(tx).toEqual(transactionToReceipt(stubbedTx));
});

it('reaches timeout when waiting for transactions that are never stubbed', async () => {
const network = new NetworkStub();

const waitPromise = await network.waitForTransaction('0x123abc', {
timeout: 1000,
});

expect(waitPromise).toBe(undefined);
});
});
47 changes: 46 additions & 1 deletion packages/evm-client/src/network/stubs/NetworkStub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
Network,
NetworkGetBlockArgs,
NetworkGetTransactionArgs,
NetworkWaitForTransactionArgs,
} from 'src/network/types/Network';
import { Transaction } from 'src/network/types/Transaction';
import { Transaction, TransactionReceipt } from 'src/network/types/Transaction';

/**
* A mock implementation of a `Network` designed to facilitate unit
Expand Down Expand Up @@ -78,4 +79,48 @@ export class NetworkStub implements Network {
}
return this.getTransactionStub(args);
}

async waitForTransaction(
...[hash, { timeout = 60_000 } = {}]: NetworkWaitForTransactionArgs
): Promise<TransactionReceipt | undefined> {
return new Promise(async (resolve) => {
let transaction: Transaction | undefined;

transaction = await this.getTransactionStub?.([hash]).catch();

if (transaction) {
return resolve(transactionToReceipt(transaction));
}

// Poll for the transaction until it's found or the timeout is reached
let waitedTime = 0;
const interval = setInterval(async () => {
waitedTime += 1000;
transaction = await this.getTransactionStub?.([hash]).catch();
if (transaction || waitedTime >= timeout) {
clearInterval(interval);
resolve(transactionToReceipt(transaction));
}
}, 1000);
});
}
}

export function transactionToReceipt(
transaction: Transaction | undefined,
): TransactionReceipt | undefined {
return transaction
? {
blockHash: transaction.blockHash!,
blockNumber: transaction.blockNumber!,
from: transaction.from!,
to: transaction.to!,
transactionIndex: transaction.transactionIndex!,
cumulativeGasUsed: 0n,
effectiveGasPrice: 0n,
transactionHash: transaction.hash!,
gasUsed: 0n,
logsBloom: '0x',
}
: undefined;
}
20 changes: 19 additions & 1 deletion packages/evm-client/src/network/types/Network.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Block, BlockTag } from 'src/network/types/Block';
import { Transaction } from 'src/network/types/Transaction';
import { Transaction, TransactionReceipt } from 'src/network/types/Transaction';

// https://ethereum.github.io/execution-apis/api-documentation/

Expand All @@ -19,6 +19,13 @@ export interface Network {
getTransaction(
...args: NetworkGetTransactionArgs
): Promise<Transaction | undefined>;

/**
* Wait for a transaction to be mined.
*/
waitForTransaction(
...args: NetworkWaitForTransactionArgs
): Promise<TransactionReceipt | undefined>;
}

export type NetworkGetBlockOptions =
Expand All @@ -41,3 +48,14 @@ export type NetworkGetBlockOptions =
export type NetworkGetBlockArgs = [options?: NetworkGetBlockOptions];

export type NetworkGetTransactionArgs = [hash: `0x${string}`];

export type NetworkWaitForTransactionArgs = [
hash: `0x${string}`,
options?: {
/**
* The number of milliseconds to wait for the transaction until rejecting
* the promise.
*/
timeout?: number;
},
];
31 changes: 31 additions & 0 deletions packages/evm-client/src/network/types/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,34 @@ export interface Transaction extends TransactionInfo {
}

export type MinedTransaction = Transaction & Required<TransactionInfo>;

// https://github.com/ethereum/execution-apis/blob/e3d2745289bd2bb61dc8593069871be4be441952/src/schemas/receipt.yaml#L37
export interface TransactionReceipt {
blockHash: `0x${string}`;
blockNumber: bigint;
from: `0x${string}`;
/**
* Address of the receiver or `null` in a contract creation transaction.
*/
to: `0x${string}` | null;
/**
* The sum of gas used by this transaction and all preceding transactions in
* the same block.
*/
cumulativeGasUsed: bigint;
/**
* The amount of gas used for this specific transaction alone.
*/
gasUsed: bigint;
// TODO:
// logs: Log[];
logsBloom: `0x${string}`;
transactionHash: `0x${string}`;
transactionIndex: number;
/**
* The actual value per gas deducted from the sender's account. Before
* EIP-1559, this is equal to the transaction's gas price. After, it is equal
* to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).
*/
effectiveGasPrice: bigint;
}

0 comments on commit 5f6a374

Please sign in to comment.