@0xsequence/relayer 0.40.6 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/0xsequence-relayer.cjs.dev.js +172 -57
- package/dist/0xsequence-relayer.cjs.prod.js +172 -57
- package/dist/0xsequence-relayer.esm.js +172 -57
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/local-relayer.d.ts +2 -2
- package/dist/declarations/src/provider-relayer.d.ts +2 -2
- package/dist/declarations/src/rpc-relayer/index.d.ts +3 -3
- package/package.json +5 -5
- package/src/index.ts +6 -2
- package/src/local-relayer.ts +11 -3
- package/src/provider-relayer.ts +115 -49
- package/src/rpc-relayer/index.ts +105 -28
|
@@ -229,50 +229,95 @@ class ProviderRelayer extends BaseRelayer {
|
|
|
229
229
|
return encodeNonce(space, nonce);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
async wait(metaTxnId, timeout) {
|
|
232
|
+
async wait(metaTxnId, timeout, delay = this.waitPollRate, maxFails = 5) {
|
|
233
|
+
var _this2 = this;
|
|
234
|
+
|
|
233
235
|
if (typeof metaTxnId !== 'string') {
|
|
234
|
-
logger.info(
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
// get all nonce changes and look for metaTxnIds in between logs
|
|
236
|
+
logger.info('computing id', metaTxnId.config, metaTxnId.context, metaTxnId.chainId, ...metaTxnId.transactions);
|
|
237
|
+
metaTxnId = computeMetaTxnHash(addressOf(metaTxnId.config, metaTxnId.context), metaTxnId.chainId, ...metaTxnId.transactions);
|
|
238
|
+
}
|
|
238
239
|
|
|
240
|
+
let timedOut = false;
|
|
239
241
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
+
const retry = async function retry(f, errorMessage) {
|
|
243
|
+
let fails = 0;
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
while (!timedOut) {
|
|
246
|
+
try {
|
|
247
|
+
return await f();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
fails++;
|
|
247
250
|
|
|
248
|
-
|
|
251
|
+
if (maxFails !== undefined && fails >= maxFails) {
|
|
252
|
+
logger.error(`giving up after ${fails} failed attempts${errorMessage ? `: ${errorMessage}` : ''}`, error);
|
|
253
|
+
throw error;
|
|
254
|
+
} else {
|
|
255
|
+
logger.warn(`attempt #${fails} failed${errorMessage ? `: ${errorMessage}` : ''}`, error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
249
258
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
toBlock: block,
|
|
255
|
-
// Nonce change event topic
|
|
256
|
-
topics: ['0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881']
|
|
257
|
-
});
|
|
258
|
-
lastBlock = block; // Get receipts of all transactions
|
|
259
|
+
if (delay > 0) {
|
|
260
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
259
263
|
|
|
260
|
-
|
|
264
|
+
throw new Error(`timed out after ${fails} failed attempts${errorMessage ? `: ${errorMessage}` : ''}`);
|
|
265
|
+
};
|
|
261
266
|
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
const waitReceipt = async function waitReceipt() {
|
|
268
|
+
// Transactions can only get executed on nonce change
|
|
269
|
+
// get all nonce changes and look for metaTxnIds in between logs
|
|
270
|
+
let lastBlock = _this2.fromBlockLog;
|
|
264
271
|
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
if (lastBlock < 0) {
|
|
273
|
+
const block = await retry(() => _this2.provider.getBlockNumber(), 'unable to get latest block number');
|
|
274
|
+
lastBlock = block + lastBlock;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (typeof metaTxnId !== 'string') {
|
|
278
|
+
throw new Error('impossible');
|
|
279
|
+
}
|
|
270
280
|
|
|
281
|
+
const normalMetaTxnId = metaTxnId.replace('0x', '');
|
|
271
282
|
|
|
272
|
-
|
|
273
|
-
|
|
283
|
+
while (!timedOut) {
|
|
284
|
+
const block = await retry(() => _this2.provider.getBlockNumber(), 'unable to get latest block number');
|
|
285
|
+
const logs = await retry(() => _this2.provider.getLogs({
|
|
286
|
+
fromBlock: Math.max(0, lastBlock - _this2.deltaBlocksLog),
|
|
287
|
+
toBlock: block,
|
|
288
|
+
// Nonce change event topic
|
|
289
|
+
topics: ['0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881']
|
|
290
|
+
}), `unable to get NonceChange logs for blocks ${Math.max(0, lastBlock - _this2.deltaBlocksLog)} to ${block}`);
|
|
291
|
+
lastBlock = block; // Get receipts of all transactions
|
|
292
|
+
|
|
293
|
+
const txs = await Promise.all(logs.map(l => retry(() => _this2.provider.getTransactionReceipt(l.transactionHash), `unable to get receipt for transaction ${l.transactionHash}`))); // Find a transaction with a TxExecuted log
|
|
294
|
+
|
|
295
|
+
const found = txs.find(tx => tx.logs.find(l => l.topics.length === 0 && l.data.replace('0x', '') === normalMetaTxnId || l.topics.length === 1 && // TxFailed event topic
|
|
296
|
+
l.topics[0] === '0x3dbd1590ea96dd3253a91f24e64e3a502e1225d602a5731357bc12643070ccd7' && l.data.length >= 64 && l.data.replace('0x', '').startsWith(normalMetaTxnId))); // If found return that
|
|
297
|
+
|
|
298
|
+
if (found) {
|
|
299
|
+
return _extends({
|
|
300
|
+
receipt: found
|
|
301
|
+
}, await retry(() => _this2.provider.getTransaction(found.transactionHash), `unable to get transaction ${found.transactionHash}`));
|
|
302
|
+
} // Otherwise wait and try again
|
|
274
303
|
|
|
275
|
-
|
|
304
|
+
|
|
305
|
+
if (!timedOut) {
|
|
306
|
+
await new Promise(r => setTimeout(r, delay));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
throw new Error(`Timeout waiting for transaction receipt ${metaTxnId}`);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (timeout !== undefined) {
|
|
314
|
+
return Promise.race([waitReceipt(), new Promise((_, reject) => setTimeout(() => {
|
|
315
|
+
timedOut = true;
|
|
316
|
+
reject(`Timeout waiting for transaction receipt ${metaTxnId}`);
|
|
317
|
+
}, timeout))]);
|
|
318
|
+
} else {
|
|
319
|
+
return waitReceipt();
|
|
320
|
+
}
|
|
276
321
|
}
|
|
277
322
|
|
|
278
323
|
}
|
|
@@ -321,7 +366,7 @@ class LocalRelayer extends ProviderRelayer {
|
|
|
321
366
|
this.txnOptions = transactionRequest;
|
|
322
367
|
}
|
|
323
368
|
|
|
324
|
-
async relay(signedTxs, quote) {
|
|
369
|
+
async relay(signedTxs, quote, waitForReceipt = true) {
|
|
325
370
|
if (quote !== undefined) {
|
|
326
371
|
logger.warn(`LocalRelayer doesn't accept fee quotes`);
|
|
327
372
|
}
|
|
@@ -340,10 +385,18 @@ class LocalRelayer extends ProviderRelayer {
|
|
|
340
385
|
// const gasLimit = signedTxs.transactions.reduce((sum, tx) => sum.add(tx.gasLimit), ethers.BigNumber.from(0))
|
|
341
386
|
// txRequest.gasLimit = gasLimit
|
|
342
387
|
|
|
343
|
-
|
|
388
|
+
const responsePromise = this.signer.sendTransaction(_extends({
|
|
344
389
|
to,
|
|
345
390
|
data
|
|
346
391
|
}, this.txnOptions));
|
|
392
|
+
|
|
393
|
+
if (waitForReceipt) {
|
|
394
|
+
const response = await responsePromise;
|
|
395
|
+
response.receipt = await response.wait();
|
|
396
|
+
return response;
|
|
397
|
+
} else {
|
|
398
|
+
return responsePromise;
|
|
399
|
+
}
|
|
347
400
|
}
|
|
348
401
|
|
|
349
402
|
}
|
|
@@ -618,7 +671,8 @@ var relayer_gen = /*#__PURE__*/Object.freeze({
|
|
|
618
671
|
Relayer: Relayer
|
|
619
672
|
});
|
|
620
673
|
|
|
621
|
-
const
|
|
674
|
+
const FINAL_STATUSES = [ETHTxnStatus.DROPPED, ETHTxnStatus.SUCCEEDED, ETHTxnStatus.PARTIALLY_FAILED, ETHTxnStatus.FAILED];
|
|
675
|
+
const FAILED_STATUSES = [ETHTxnStatus.DROPPED, ETHTxnStatus.PARTIALLY_FAILED, ETHTxnStatus.FAILED];
|
|
622
676
|
function isRpcRelayerOptions(obj) {
|
|
623
677
|
return obj.url !== undefined && typeof obj.url === 'string';
|
|
624
678
|
}
|
|
@@ -629,26 +683,50 @@ class RpcRelayer extends BaseRelayer {
|
|
|
629
683
|
this.service = new Relayer(options.url, fetchPonyfill().fetch);
|
|
630
684
|
}
|
|
631
685
|
|
|
632
|
-
async waitReceipt(
|
|
633
|
-
if (typeof
|
|
634
|
-
logger.info('computing id',
|
|
635
|
-
|
|
686
|
+
async waitReceipt(metaTxnId, delay = 1000, maxFails = 5, isCancelled) {
|
|
687
|
+
if (typeof metaTxnId !== 'string') {
|
|
688
|
+
logger.info('computing id', metaTxnId.config, metaTxnId.context, metaTxnId.chainId, ...metaTxnId.transactions);
|
|
689
|
+
metaTxnId = computeMetaTxnHash(addressOf(metaTxnId.config, metaTxnId.context), metaTxnId.chainId, ...metaTxnId.transactions);
|
|
636
690
|
}
|
|
637
691
|
|
|
638
|
-
logger.info(`[rpc-relayer/waitReceipt] waiting for ${
|
|
639
|
-
let
|
|
640
|
-
metaTxID: metaTxnHash
|
|
641
|
-
}); // TODO: remove check for 'UNKNOWN' status when 'QUEUED' status is supported
|
|
642
|
-
// TODO: fix backend to not return literal 'null' txnReceipt
|
|
692
|
+
logger.info(`[rpc-relayer/waitReceipt] waiting for ${metaTxnId}`);
|
|
693
|
+
let fails = 0;
|
|
643
694
|
|
|
644
|
-
while (
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
695
|
+
while (isCancelled === undefined || !isCancelled()) {
|
|
696
|
+
try {
|
|
697
|
+
const {
|
|
698
|
+
receipt
|
|
699
|
+
} = await this.service.getMetaTxnReceipt({
|
|
700
|
+
metaTxID: metaTxnId
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
if (!receipt) {
|
|
704
|
+
throw new Error('missing expected receipt');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (!receipt.txnReceipt) {
|
|
708
|
+
throw new Error('missing expected transaction receipt');
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (FINAL_STATUSES.includes(receipt.status)) {
|
|
712
|
+
return {
|
|
713
|
+
receipt
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
} catch (e) {
|
|
717
|
+
fails++;
|
|
718
|
+
|
|
719
|
+
if (fails === maxFails) {
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (isCancelled === undefined || !isCancelled()) {
|
|
725
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
726
|
+
}
|
|
649
727
|
}
|
|
650
728
|
|
|
651
|
-
|
|
729
|
+
throw new Error(`Cancelled waiting for transaction receipt ${metaTxnId}`);
|
|
652
730
|
}
|
|
653
731
|
|
|
654
732
|
async simulate(wallet, ...transactions) {
|
|
@@ -773,7 +851,9 @@ class RpcRelayer extends BaseRelayer {
|
|
|
773
851
|
return nonce;
|
|
774
852
|
}
|
|
775
853
|
|
|
776
|
-
async relay(signedTxs, quote) {
|
|
854
|
+
async relay(signedTxs, quote, waitForReceipt = true) {
|
|
855
|
+
var _this = this;
|
|
856
|
+
|
|
777
857
|
logger.info(`[rpc-relayer/relay] relaying signed meta-transactions ${JSON.stringify(signedTxs)} with quote ${JSON.stringify(quote)}`);
|
|
778
858
|
let typecheckedQuote;
|
|
779
859
|
|
|
@@ -806,15 +886,50 @@ class RpcRelayer extends BaseRelayer {
|
|
|
806
886
|
quote: typecheckedQuote
|
|
807
887
|
});
|
|
808
888
|
logger.info(`[rpc-relayer/relay] got relay result ${JSON.stringify(metaTxn)}`);
|
|
809
|
-
|
|
889
|
+
|
|
890
|
+
if (waitForReceipt) {
|
|
891
|
+
return this.wait(metaTxn.txnHash);
|
|
892
|
+
} else {
|
|
893
|
+
const response = {
|
|
894
|
+
hash: metaTxn.txnHash,
|
|
895
|
+
confirmations: 0,
|
|
896
|
+
from: walletAddress,
|
|
897
|
+
wait: _confirmations => Promise.reject(new Error('impossible'))
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
const wait = async function wait(confirmations) {
|
|
901
|
+
var _waitResponse$receipt;
|
|
902
|
+
|
|
903
|
+
if (!_this.provider) {
|
|
904
|
+
throw new Error('cannot wait for receipt, relayer has no provider set');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const waitResponse = await _this.wait(metaTxn.txnHash);
|
|
908
|
+
const transactionHash = (_waitResponse$receipt = waitResponse.receipt) == null ? void 0 : _waitResponse$receipt.transactionHash;
|
|
909
|
+
|
|
910
|
+
if (!transactionHash) {
|
|
911
|
+
throw new Error('cannot wait for receipt, unknown native transaction hash');
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
Object.assign(response, waitResponse);
|
|
915
|
+
return _this.provider.waitForTransaction(transactionHash, confirmations);
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
response.wait = wait;
|
|
919
|
+
return response;
|
|
920
|
+
}
|
|
810
921
|
}
|
|
811
922
|
|
|
812
|
-
async wait(
|
|
813
|
-
var
|
|
923
|
+
async wait(metaTxnId, timeout, delay = 1000, maxFails = 5) {
|
|
924
|
+
var _this2 = this;
|
|
814
925
|
|
|
926
|
+
let timedOut = false;
|
|
815
927
|
const {
|
|
816
928
|
receipt
|
|
817
|
-
} = await this.waitReceipt(
|
|
929
|
+
} = await (timeout !== undefined ? Promise.race([this.waitReceipt(metaTxnId, delay, maxFails, () => timedOut), new Promise((_, reject) => setTimeout(() => {
|
|
930
|
+
timedOut = true;
|
|
931
|
+
reject(`Timeout waiting for transaction receipt ${metaTxnId}`);
|
|
932
|
+
}, timeout))]) : this.waitReceipt(metaTxnId, delay, maxFails));
|
|
818
933
|
|
|
819
934
|
if (!receipt.txnReceipt || FAILED_STATUSES.includes(receipt.status)) {
|
|
820
935
|
throw new MetaTransactionResponseException(receipt);
|
|
@@ -825,13 +940,13 @@ class RpcRelayer extends BaseRelayer {
|
|
|
825
940
|
blockHash: txReceipt.blockHash,
|
|
826
941
|
blockNumber: ethers.BigNumber.from(txReceipt.blockNumber).toNumber(),
|
|
827
942
|
confirmations: 1,
|
|
828
|
-
from: typeof
|
|
943
|
+
from: typeof metaTxnId === 'string' ? undefined : addressOf(metaTxnId.config, metaTxnId.context),
|
|
829
944
|
hash: txReceipt.transactionHash,
|
|
830
945
|
raw: receipt.txnReceipt,
|
|
831
946
|
receipt: txReceipt,
|
|
832
947
|
// extended type which is Sequence-specific. Contains the decoded metaTxReceipt
|
|
833
948
|
wait: async function (confirmations) {
|
|
834
|
-
return
|
|
949
|
+
return _this2.provider.waitForTransaction(txReceipt.transactionHash, confirmations);
|
|
835
950
|
}
|
|
836
951
|
};
|
|
837
952
|
}
|
|
@@ -12,8 +12,8 @@ export interface Relayer {
|
|
|
12
12
|
}>;
|
|
13
13
|
gasRefundOptions(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<FeeOption[]>;
|
|
14
14
|
getNonce(config: WalletConfig, context: WalletContext, space?: ethers.BigNumberish, blockTag?: providers.BlockTag): Promise<ethers.BigNumberish>;
|
|
15
|
-
relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse>;
|
|
16
|
-
wait(metaTxnId: string | SignedTransactions, timeout
|
|
15
|
+
relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse>;
|
|
16
|
+
wait(metaTxnId: string | SignedTransactions, timeout?: number, delay?: number, maxFails?: number): Promise<TransactionResponse>;
|
|
17
17
|
}
|
|
18
18
|
export * from './local-relayer';
|
|
19
19
|
export * from './base-relayer';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TransactionRequest } from '@ethersproject/providers';
|
|
1
|
+
import { TransactionReceipt, TransactionRequest } from '@ethersproject/providers';
|
|
2
2
|
import { Signer as AbstractSigner } from 'ethers';
|
|
3
3
|
import { SignedTransactions, Transaction, TransactionResponse } from '@0xsequence/transactions';
|
|
4
4
|
import { WalletContext } from '@0xsequence/network';
|
|
@@ -19,5 +19,5 @@ export declare class LocalRelayer extends ProviderRelayer implements Relayer {
|
|
|
19
19
|
}>;
|
|
20
20
|
gasRefundOptions(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<FeeOption[]>;
|
|
21
21
|
setTransactionOptions(transactionRequest: TransactionRequest): void;
|
|
22
|
-
relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse
|
|
22
|
+
relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse<TransactionReceipt>>;
|
|
23
23
|
}
|
|
@@ -25,11 +25,11 @@ export declare abstract class ProviderRelayer extends BaseRelayer implements Rel
|
|
|
25
25
|
quote?: FeeQuote;
|
|
26
26
|
}>;
|
|
27
27
|
abstract gasRefundOptions(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<FeeOption[]>;
|
|
28
|
-
abstract relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse>;
|
|
28
|
+
abstract relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse>;
|
|
29
29
|
simulate(wallet: string, ...transactions: Transaction[]): Promise<SimulateResult[]>;
|
|
30
30
|
estimateGasLimits(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<Transaction[]>;
|
|
31
31
|
getNonce(config: WalletConfig, context: WalletContext, space?: ethers.BigNumberish, blockTag?: BlockTag): Promise<ethers.BigNumberish>;
|
|
32
|
-
wait(metaTxnId: string | SignedTransactions, timeout
|
|
32
|
+
wait(metaTxnId: string | SignedTransactions, timeout?: number, delay?: number, maxFails?: number): Promise<providers.TransactionResponse & {
|
|
33
33
|
receipt: providers.TransactionReceipt;
|
|
34
34
|
}>;
|
|
35
35
|
}
|
|
@@ -13,7 +13,7 @@ export declare function isRpcRelayerOptions(obj: any): obj is RpcRelayerOptions;
|
|
|
13
13
|
export declare class RpcRelayer extends BaseRelayer implements Relayer {
|
|
14
14
|
private readonly service;
|
|
15
15
|
constructor(options: RpcRelayerOptions);
|
|
16
|
-
waitReceipt(
|
|
16
|
+
waitReceipt(metaTxnId: string | SignedTransactions, delay?: number, maxFails?: number, isCancelled?: () => boolean): Promise<proto.GetMetaTxnReceiptReturn>;
|
|
17
17
|
simulate(wallet: string, ...transactions: Transaction[]): Promise<SimulateResult[]>;
|
|
18
18
|
estimateGasLimits(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<Transaction[]>;
|
|
19
19
|
getFeeOptions(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<{
|
|
@@ -22,8 +22,8 @@ export declare class RpcRelayer extends BaseRelayer implements Relayer {
|
|
|
22
22
|
}>;
|
|
23
23
|
gasRefundOptions(config: WalletConfig, context: WalletContext, ...transactions: Transaction[]): Promise<FeeOption[]>;
|
|
24
24
|
getNonce(config: WalletConfig, context: WalletContext, space?: ethers.BigNumberish): Promise<ethers.BigNumberish>;
|
|
25
|
-
relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse
|
|
26
|
-
wait(
|
|
25
|
+
relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse<RelayerTxReceipt>>;
|
|
26
|
+
wait(metaTxnId: string | SignedTransactions, timeout?: number, delay?: number, maxFails?: number): Promise<TransactionResponse<RelayerTxReceipt>>;
|
|
27
27
|
}
|
|
28
28
|
export declare type RelayerTxReceipt = {
|
|
29
29
|
blockHash: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xsequence/relayer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "relayer sub-package for Sequence",
|
|
5
5
|
"repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/relayer",
|
|
6
6
|
"source": "src/index.ts",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"typecheck": "tsc --noEmit"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@0xsequence/abi": "^0.
|
|
21
|
-
"@0xsequence/config": "^0.
|
|
22
|
-
"@0xsequence/transactions": "^0.
|
|
23
|
-
"@0xsequence/utils": "^0.
|
|
20
|
+
"@0xsequence/abi": "^0.41.0",
|
|
21
|
+
"@0xsequence/config": "^0.41.0",
|
|
22
|
+
"@0xsequence/transactions": "^0.41.0",
|
|
23
|
+
"@0xsequence/utils": "^0.41.0",
|
|
24
24
|
"@ethersproject/providers": "^5.5.1",
|
|
25
25
|
"ethers": "^5.5.2",
|
|
26
26
|
"fetch-ponyfill": "^7.1.0"
|
package/src/index.ts
CHANGED
|
@@ -39,10 +39,14 @@ export interface Relayer {
|
|
|
39
39
|
|
|
40
40
|
// relayer will submit the transaction(s) to the network and return the transaction response.
|
|
41
41
|
// The quote should be the one returned from getFeeOptions, if any.
|
|
42
|
-
|
|
42
|
+
// waitForReceipt must default to true.
|
|
43
|
+
relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse>
|
|
43
44
|
|
|
44
45
|
// wait for transaction confirmation
|
|
45
|
-
|
|
46
|
+
// timeout is the maximum time to wait for the transaction response
|
|
47
|
+
// delay is the polling interval, i.e. the time to wait between requests
|
|
48
|
+
// maxFails is the maximum number of hard failures to tolerate before giving up
|
|
49
|
+
wait(metaTxnId: string | SignedTransactions, timeout?: number, delay?: number, maxFails?: number): Promise<TransactionResponse>
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export * from './local-relayer'
|
package/src/local-relayer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TransactionRequest } from '@ethersproject/providers'
|
|
1
|
+
import { TransactionReceipt, TransactionRequest } from '@ethersproject/providers'
|
|
2
2
|
import { Signer as AbstractSigner, ethers } from 'ethers'
|
|
3
3
|
import { walletContracts } from '@0xsequence/abi'
|
|
4
4
|
import { SignedTransactions, Transaction, sequenceTxAbiEncode, TransactionResponse } from '@0xsequence/transactions'
|
|
@@ -57,7 +57,7 @@ export class LocalRelayer extends ProviderRelayer implements Relayer {
|
|
|
57
57
|
this.txnOptions = transactionRequest
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse
|
|
60
|
+
async relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt: boolean = true): Promise<TransactionResponse<TransactionReceipt>> {
|
|
61
61
|
if (quote !== undefined) {
|
|
62
62
|
logger.warn(`LocalRelayer doesn't accept fee quotes`)
|
|
63
63
|
}
|
|
@@ -80,6 +80,14 @@ export class LocalRelayer extends ProviderRelayer implements Relayer {
|
|
|
80
80
|
// const gasLimit = signedTxs.transactions.reduce((sum, tx) => sum.add(tx.gasLimit), ethers.BigNumber.from(0))
|
|
81
81
|
// txRequest.gasLimit = gasLimit
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
const responsePromise = this.signer.sendTransaction({ to, data, ...this.txnOptions })
|
|
84
|
+
|
|
85
|
+
if (waitForReceipt) {
|
|
86
|
+
const response: TransactionResponse = await responsePromise
|
|
87
|
+
response.receipt = await response.wait()
|
|
88
|
+
return response
|
|
89
|
+
} else {
|
|
90
|
+
return responsePromise
|
|
91
|
+
}
|
|
84
92
|
}
|
|
85
93
|
}
|
package/src/provider-relayer.ts
CHANGED
|
@@ -54,7 +54,7 @@ export abstract class ProviderRelayer extends BaseRelayer implements Relayer {
|
|
|
54
54
|
...transactions: Transaction[]
|
|
55
55
|
): Promise<FeeOption[]>
|
|
56
56
|
|
|
57
|
-
abstract relay(signedTxs: SignedTransactions, quote?: FeeQuote): Promise<TransactionResponse>
|
|
57
|
+
abstract relay(signedTxs: SignedTransactions, quote?: FeeQuote, waitForReceipt?: boolean): Promise<TransactionResponse>
|
|
58
58
|
|
|
59
59
|
async simulate(wallet: string, ...transactions: Transaction[]): Promise<SimulateResult[]> {
|
|
60
60
|
return (await Promise.all(transactions.map(async tx => {
|
|
@@ -128,66 +128,132 @@ export abstract class ProviderRelayer extends BaseRelayer implements Relayer {
|
|
|
128
128
|
return encodeNonce(space, nonce)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
async wait(
|
|
131
|
+
async wait(
|
|
132
|
+
metaTxnId: string | SignedTransactions,
|
|
133
|
+
timeout?: number,
|
|
134
|
+
delay: number = this.waitPollRate,
|
|
135
|
+
maxFails: number = 5
|
|
136
|
+
): Promise<providers.TransactionResponse & { receipt: providers.TransactionReceipt }> {
|
|
132
137
|
if (typeof metaTxnId !== 'string') {
|
|
133
|
-
logger.info(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
timeout
|
|
137
|
-
)
|
|
138
|
+
logger.info('computing id', metaTxnId.config, metaTxnId.context, metaTxnId.chainId, ...metaTxnId.transactions)
|
|
139
|
+
|
|
140
|
+
metaTxnId = computeMetaTxnHash(addressOf(metaTxnId.config, metaTxnId.context), metaTxnId.chainId, ...metaTxnId.transactions)
|
|
138
141
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
|
|
143
|
+
let timedOut = false
|
|
144
|
+
|
|
145
|
+
const retry = async <T>(f: () => Promise<T>, errorMessage: string): Promise<T> => {
|
|
146
|
+
let fails = 0
|
|
147
|
+
|
|
148
|
+
while (!timedOut) {
|
|
149
|
+
try {
|
|
150
|
+
return await f()
|
|
151
|
+
} catch (error) {
|
|
152
|
+
fails++
|
|
153
|
+
|
|
154
|
+
if (maxFails !== undefined && fails >= maxFails) {
|
|
155
|
+
logger.error(`giving up after ${fails} failed attempts${errorMessage ? `: ${errorMessage}` : ''}`, error)
|
|
156
|
+
throw error
|
|
157
|
+
} else {
|
|
158
|
+
logger.warn(`attempt #${fails} failed${errorMessage ? `: ${errorMessage}` : ''}`, error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (delay > 0) {
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error(`timed out after ${fails} failed attempts${errorMessage ? `: ${errorMessage}` : ''}`)
|
|
148
168
|
}
|
|
149
169
|
|
|
150
|
-
const
|
|
170
|
+
const waitReceipt = async (): Promise<providers.TransactionResponse & { receipt: providers.TransactionReceipt }> => {
|
|
171
|
+
// Transactions can only get executed on nonce change
|
|
172
|
+
// get all nonce changes and look for metaTxnIds in between logs
|
|
173
|
+
let lastBlock: number = this.fromBlockLog
|
|
151
174
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
toBlock: block,
|
|
157
|
-
// Nonce change event topic
|
|
158
|
-
topics: ['0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881']
|
|
159
|
-
})
|
|
175
|
+
if (lastBlock < 0) {
|
|
176
|
+
const block = await retry(() => this.provider.getBlockNumber(), 'unable to get latest block number')
|
|
177
|
+
lastBlock = block + lastBlock
|
|
178
|
+
}
|
|
160
179
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
if (typeof metaTxnId !== 'string') {
|
|
181
|
+
throw new Error('impossible')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const normalMetaTxnId = metaTxnId.replace('0x', '')
|
|
185
|
+
|
|
186
|
+
while (!timedOut) {
|
|
187
|
+
const block = await retry(() => this.provider.getBlockNumber(), 'unable to get latest block number')
|
|
188
|
+
|
|
189
|
+
const logs = await retry(
|
|
190
|
+
() =>
|
|
191
|
+
this.provider.getLogs({
|
|
192
|
+
fromBlock: Math.max(0, lastBlock - this.deltaBlocksLog),
|
|
193
|
+
toBlock: block,
|
|
194
|
+
// Nonce change event topic
|
|
195
|
+
topics: ['0x1f180c27086c7a39ea2a7b25239d1ab92348f07ca7bb59d1438fcf527568f881']
|
|
196
|
+
}),
|
|
197
|
+
`unable to get NonceChange logs for blocks ${Math.max(0, lastBlock - this.deltaBlocksLog)} to ${block}`
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
lastBlock = block
|
|
201
|
+
|
|
202
|
+
// Get receipts of all transactions
|
|
203
|
+
const txs = await Promise.all(
|
|
204
|
+
logs.map(l =>
|
|
205
|
+
retry(
|
|
206
|
+
() => this.provider.getTransactionReceipt(l.transactionHash),
|
|
207
|
+
`unable to get receipt for transaction ${l.transactionHash}`
|
|
208
|
+
)
|
|
209
|
+
)
|
|
176
210
|
)
|
|
177
|
-
)))
|
|
178
211
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
212
|
+
// Find a transaction with a TxExecuted log
|
|
213
|
+
const found = txs.find(tx =>
|
|
214
|
+
tx.logs.find(
|
|
215
|
+
l =>
|
|
216
|
+
(l.topics.length === 0 && l.data.replace('0x', '') === normalMetaTxnId) ||
|
|
217
|
+
(l.topics.length === 1 &&
|
|
218
|
+
// TxFailed event topic
|
|
219
|
+
l.topics[0] === '0x3dbd1590ea96dd3253a91f24e64e3a502e1225d602a5731357bc12643070ccd7' &&
|
|
220
|
+
l.data.length >= 64 &&
|
|
221
|
+
l.data.replace('0x', '').startsWith(normalMetaTxnId))
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
// If found return that
|
|
226
|
+
if (found) {
|
|
227
|
+
return {
|
|
228
|
+
receipt: found,
|
|
229
|
+
...(await retry(
|
|
230
|
+
() => this.provider.getTransaction(found.transactionHash),
|
|
231
|
+
`unable to get transaction ${found.transactionHash}`
|
|
232
|
+
))
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Otherwise wait and try again
|
|
237
|
+
if (!timedOut) {
|
|
238
|
+
await new Promise(r => setTimeout(r, delay))
|
|
184
239
|
}
|
|
185
240
|
}
|
|
186
241
|
|
|
187
|
-
|
|
188
|
-
await new Promise(r => setTimeout(r, this.waitPollRate))
|
|
242
|
+
throw new Error(`Timeout waiting for transaction receipt ${metaTxnId}`)
|
|
189
243
|
}
|
|
190
244
|
|
|
191
|
-
|
|
245
|
+
if (timeout !== undefined) {
|
|
246
|
+
return Promise.race([
|
|
247
|
+
waitReceipt(),
|
|
248
|
+
new Promise<providers.TransactionResponse & { receipt: providers.TransactionReceipt }>((_, reject) =>
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
timedOut = true
|
|
251
|
+
reject(`Timeout waiting for transaction receipt ${metaTxnId}`)
|
|
252
|
+
}, timeout)
|
|
253
|
+
)
|
|
254
|
+
])
|
|
255
|
+
} else {
|
|
256
|
+
return waitReceipt()
|
|
257
|
+
}
|
|
192
258
|
}
|
|
193
259
|
}
|