-
Notifications
You must be signed in to change notification settings - Fork 33
/
provider.ts
3670 lines (3448 loc) · 113 KB
/
provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import {
ethers,
BigNumberish,
BytesLike,
Contract,
BlockTag,
Filter,
FilterByBlockHash,
TransactionRequest as EthersTransactionRequest,
JsonRpcTransactionRequest,
Networkish,
Eip1193Provider,
JsonRpcError,
JsonRpcResult,
JsonRpcPayload,
resolveProperties,
FetchRequest,
} from 'ethers';
import {
IERC20__factory,
IEthToken__factory,
IL2Bridge,
IL2Bridge__factory,
IL2SharedBridge,
IL2SharedBridge__factory,
} from './typechain';
import {
Address,
TransactionResponse,
TransactionRequest,
TransactionStatus,
PriorityOpResponse,
BalancesMap,
TransactionReceipt,
Block,
Log,
TransactionDetails,
BlockDetails,
ContractAccountInfo,
Network as ZkSyncNetwork,
BatchDetails,
Fee,
Transaction,
RawBlockTransaction,
PaymasterParams,
StorageProof,
LogProof,
Token,
ProtocolVersion,
FeeParams,
TransactionWithDetailedOutput,
} from './types';
import {
getL2HashFromPriorityOp,
CONTRACT_DEPLOYER_ADDRESS,
CONTRACT_DEPLOYER,
sleep,
EIP712_TX_TYPE,
REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT,
BOOTLOADER_FORMAL_ADDRESS,
ETH_ADDRESS_IN_CONTRACTS,
L2_BASE_TOKEN_ADDRESS,
LEGACY_ETH_ADDRESS,
isAddressEq,
getERC20DefaultBridgeData,
getERC20BridgeCalldata,
applyL1ToL2Alias,
} from './utils';
import {Signer} from './signer';
import {
formatLog,
formatBlock,
formatTransactionResponse,
formatTransactionReceipt,
formatFee,
} from './format';
import {makeError} from 'ethers';
type Constructor<T = {}> = new (...args: any[]) => T;
export function JsonRpcApiProvider<
TBase extends Constructor<ethers.JsonRpcApiProvider>,
>(ProviderType: TBase) {
return class Provider extends ProviderType {
/**
* Sends a JSON-RPC `_payload` (or a batch) to the underlying channel.
*
* @param _payload The JSON-RPC payload or batch of payloads to send.
* @returns A promise that resolves to the result of the JSON-RPC request(s).
*/
override _send(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_payload: JsonRpcPayload | Array<JsonRpcPayload>
): Promise<Array<JsonRpcResult | JsonRpcError>> {
throw new Error('Must be implemented by the derived class!');
}
/**
* Returns the addresses of the main contract and default ZKsync Era bridge contracts on both L1 and L2.
*/
contractAddresses(): {
bridgehubContract?: Address;
mainContract?: Address;
erc20BridgeL1?: Address;
erc20BridgeL2?: Address;
wethBridgeL1?: Address;
wethBridgeL2?: Address;
sharedBridgeL1?: Address;
sharedBridgeL2?: Address;
baseToken?: Address;
} {
throw new Error('Must be implemented by the derived class!');
}
override _getBlockTag(blockTag?: BlockTag): string | Promise<string> {
if (blockTag === 'committed') {
return 'committed';
} else if (blockTag === 'l1_committed') {
return 'l1_committed';
}
return super._getBlockTag(blockTag);
}
override _wrapLog(value: any): Log {
return new Log(formatLog(value), this);
}
override _wrapBlock(value: any): Block {
return new Block(formatBlock(value), this);
}
override _wrapTransactionResponse(value: any): TransactionResponse {
const tx: any = formatTransactionResponse(value);
return new TransactionResponse(tx, this);
}
override _wrapTransactionReceipt(value: any): TransactionReceipt {
const receipt: any = formatTransactionReceipt(value);
return new TransactionReceipt(receipt, this);
}
/**
* Resolves to the transaction receipt for `txHash`, if mined.
* If the transaction has not been mined, is unknown or on pruning nodes which discard old transactions
* this resolves to `null`.
*
* @param txHash The hash of the transaction.
*/
override async getTransactionReceipt(
txHash: string
): Promise<TransactionReceipt | null> {
return (await super.getTransactionReceipt(
txHash
)) as TransactionReceipt | null;
}
/**
* Resolves to the transaction for `txHash`.
* If the transaction is unknown or on pruning nodes which discard old transactions this resolves to `null`.
*
* @param txHash The hash of the transaction.
*/
override async getTransaction(
txHash: string
): Promise<TransactionResponse> {
return (await super.getTransaction(txHash)) as TransactionResponse;
}
/**
* Resolves to the block corresponding to the provided `blockHashOrBlockTag`.
* If `includeTxs` is set to `true` and the backend supports including transactions with block requests,
* all transactions will be included in the returned block object, eliminating the need for remote calls
* to fetch transactions separately.
*
* @param blockHashOrBlockTag The hash or tag of the block to retrieve.
* @param [includeTxs] A flag indicating whether to include transactions in the block.
*/
override async getBlock(
blockHashOrBlockTag: BlockTag,
includeTxs?: boolean
): Promise<Block> {
return (await super.getBlock(blockHashOrBlockTag, includeTxs)) as Block;
}
/**
* Resolves to the list of Logs that match `filter`.
*
* @param filter The filter criteria to apply.
*/
override async getLogs(filter: Filter | FilterByBlockHash): Promise<Log[]> {
return (await super.getLogs(filter)) as Log[];
}
/**
* Returns the account balance for the specified account `address`, `blockTag`, and `tokenAddress`.
* If `blockTag` and `tokenAddress` are not provided, the balance for the latest committed block and ETH token
* is returned by default.
*
* @param address The account address for which the balance is retrieved.
* @param [blockTag] The block tag for getting the balance on. Latest committed block is the default.
* @param [tokenAddress] The token address. ETH is the default token.
*/
override async getBalance(
address: Address,
blockTag?: BlockTag,
tokenAddress?: Address
): Promise<bigint> {
if (!tokenAddress) {
tokenAddress = L2_BASE_TOKEN_ADDRESS;
} else if (
isAddressEq(tokenAddress, LEGACY_ETH_ADDRESS) ||
isAddressEq(tokenAddress, ETH_ADDRESS_IN_CONTRACTS)
) {
tokenAddress = await this.l2TokenAddress(tokenAddress);
}
if (isAddressEq(tokenAddress, L2_BASE_TOKEN_ADDRESS)) {
return await super.getBalance(address, blockTag);
} else {
try {
const token = IERC20__factory.connect(tokenAddress, this);
return await token.balanceOf(address, {blockTag});
} catch {
return 0n;
}
}
}
/**
* Returns the L2 token address equivalent for a L1 token address as they are not equal.
* ETH address is set to zero address.
*
* @remarks Only works for tokens bridged on default ZKsync Era bridges.
*
* @param token The address of the token on L1.
* @param bridgeAddress The address of custom bridge, which will be used to get l2 token address.
*/
async l2TokenAddress(
token: Address,
bridgeAddress?: Address
): Promise<string> {
if (isAddressEq(token, LEGACY_ETH_ADDRESS)) {
token = ETH_ADDRESS_IN_CONTRACTS;
}
const baseToken = await this.getBaseTokenContractAddress();
if (isAddressEq(token, baseToken)) {
return L2_BASE_TOKEN_ADDRESS;
}
bridgeAddress ??= (await this.getDefaultBridgeAddresses()).sharedL2;
return await (
await this.connectL2Bridge(bridgeAddress)
).l2TokenAddress(token);
}
/**
* Returns the L1 token address equivalent for a L2 token address as they are not equal.
* ETH address is set to zero address.
*
* @remarks Only works for tokens bridged on default ZKsync Era bridges.
*
* @param token The address of the token on L2.
*/
async l1TokenAddress(token: Address): Promise<string> {
if (isAddressEq(token, LEGACY_ETH_ADDRESS)) {
return LEGACY_ETH_ADDRESS;
}
const bridgeAddresses = await this.getDefaultBridgeAddresses();
const sharedBridge = IL2Bridge__factory.connect(
bridgeAddresses.sharedL2,
this
);
return await sharedBridge.l1TokenAddress(token);
}
/**
* Return the protocol version.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks_getprotocolversion zks_getProtocolVersion} JSON-RPC method.
*
* @param [id] Specific version ID.
*/
async getProtocolVersion(id?: number): Promise<ProtocolVersion> {
return await this.send('zks_getProtocolVersion', [id]);
}
/**
* Returns an estimate of the amount of gas required to submit a transaction from L1 to L2 as a bigint object.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-estimategasl1tol2 zks_estimateL1ToL2} JSON-RPC method.
*
* @param transaction The transaction request.
*/
async estimateGasL1(transaction: TransactionRequest): Promise<bigint> {
return await this.send('zks_estimateGasL1ToL2', [
this.getRpcTransaction(transaction),
]);
}
/**
* Returns an estimated {@link Fee} for requested transaction.
*
* @param transaction The transaction request.
*/
async estimateFee(transaction: TransactionRequest): Promise<Fee> {
const fee = await this.send('zks_estimateFee', [
await this.getRpcTransaction(transaction),
]);
return formatFee(fee);
}
/**
* Returns the current fee parameters.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks_getFeeParams zks_getFeeParams} JSON-RPC method.
*/
async getFeeParams(): Promise<FeeParams> {
return await this.send('zks_getFeeParams', []);
}
/**
* Returns an estimate (best guess) of the gas price to use in a transaction.
*/
async getGasPrice(): Promise<bigint> {
const feeData = await this.getFeeData();
return feeData.gasPrice!;
}
/**
* Returns the proof for a transaction's L2 to L1 log sent via the `L1Messenger` system contract.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getl2tol1logproof zks_getL2ToL1LogProof} JSON-RPC method.
*
* @param txHash The hash of the L2 transaction the L2 to L1 log was produced within.
* @param [index] The index of the L2 to L1 log in the transaction.
*/
async getLogProof(
txHash: BytesLike,
index?: number
): Promise<LogProof | null> {
return await this.send('zks_getL2ToL1LogProof', [
ethers.hexlify(txHash),
index,
]);
}
/**
* Returns the range of blocks contained within a batch given by batch number.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getl1batchblockrange zks_getL1BatchBlockRange} JSON-RPC method.
*
* @param l1BatchNumber The L1 batch number.
*/
async getL1BatchBlockRange(
l1BatchNumber: number
): Promise<[number, number] | null> {
const range = await this.send('zks_getL1BatchBlockRange', [
l1BatchNumber,
]);
if (!range) {
return null;
}
return [parseInt(range[0], 16), parseInt(range[1], 16)];
}
/**
* Returns the Bridgehub smart contract address.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgehubcontract zks_getBridgehubContract} JSON-RPC method.
*/
async getBridgehubContractAddress(): Promise<Address> {
if (!this.contractAddresses().bridgehubContract) {
this.contractAddresses().bridgehubContract = await this.send(
'zks_getBridgehubContract',
[]
);
}
return this.contractAddresses().bridgehubContract!;
}
/**
* Returns the main ZKsync Era smart contract address.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getmaincontract zks_getMainContract} JSON-RPC method.
*/
async getMainContractAddress(): Promise<Address> {
if (!this.contractAddresses().mainContract) {
this.contractAddresses().mainContract = await this.send(
'zks_getMainContract',
[]
);
}
return this.contractAddresses().mainContract!;
}
/**
* Returns the L1 base token address.
*/
async getBaseTokenContractAddress(): Promise<Address> {
if (!this.contractAddresses().baseToken) {
this.contractAddresses().baseToken = await this.send(
'zks_getBaseTokenL1Address',
[]
);
}
return ethers.getAddress(this.contractAddresses().baseToken!);
}
/**
* Returns whether the chain is ETH-based.
*/
async isEthBasedChain(): Promise<boolean> {
return isAddressEq(
await this.getBaseTokenContractAddress(),
ETH_ADDRESS_IN_CONTRACTS
);
}
/**
* Returns whether the `token` is the base token.
*/
async isBaseToken(token: Address): Promise<boolean> {
return (
isAddressEq(token, await this.getBaseTokenContractAddress()) ||
isAddressEq(token, L2_BASE_TOKEN_ADDRESS)
);
}
/**
* Returns the testnet {@link https://docs.zksync.io/build/developer-reference/account-abstraction.html#paymasters paymaster address}
* if available, or `null`.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-gettestnetpaymaster zks_getTestnetPaymaster} JSON-RPC method.
*/
async getTestnetPaymasterAddress(): Promise<Address | null> {
// Unlike contract's addresses, the testnet paymaster is not cached, since it can be trivially changed
// on the fly by the server and should not be relied on to be constant
return await this.send('zks_getTestnetPaymaster', []);
}
/**
* Returns the addresses of the default ZKsync Era bridge contracts on both L1 and L2.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getbridgecontracts zks_getBridgeContracts} JSON-RPC method.
*/
async getDefaultBridgeAddresses(): Promise<{
erc20L1: string;
erc20L2: string;
wethL1: string;
wethL2: string;
sharedL1: string;
sharedL2: string;
}> {
if (!this.contractAddresses().erc20BridgeL1) {
const addresses: {
l1Erc20DefaultBridge: string;
l2Erc20DefaultBridge: string;
l1WethBridge: string;
l2WethBridge: string;
l1SharedDefaultBridge: string;
l2SharedDefaultBridge: string;
} = await this.send('zks_getBridgeContracts', []);
this.contractAddresses().erc20BridgeL1 = addresses.l1Erc20DefaultBridge;
this.contractAddresses().erc20BridgeL2 = addresses.l2Erc20DefaultBridge;
this.contractAddresses().wethBridgeL1 = addresses.l1WethBridge;
this.contractAddresses().wethBridgeL2 = addresses.l2WethBridge;
this.contractAddresses().sharedBridgeL1 =
addresses.l1SharedDefaultBridge;
this.contractAddresses().sharedBridgeL2 =
addresses.l2SharedDefaultBridge;
}
return {
erc20L1: this.contractAddresses().erc20BridgeL1!,
erc20L2: this.contractAddresses().erc20BridgeL2!,
wethL1: this.contractAddresses().wethBridgeL1!,
wethL2: this.contractAddresses().wethBridgeL2!,
sharedL1: this.contractAddresses().sharedBridgeL1!,
sharedL2: this.contractAddresses().sharedBridgeL2!,
};
}
/**
* Returns contract wrapper. If given address is shared bridge address it returns Il2SharedBridge and if its legacy it returns Il2Bridge.
**
* @param address The bridge address.
*
* @example
*
* import { Provider, types, utils } from "zksync-ethers";
*
* const provider = Provider.getDefaultProvider(types.Network.Sepolia);
* const l2Bridge = await provider.connectL2Bridge("<L2_BRIDGE_ADDRESS>");
*/
async connectL2Bridge(
address: Address
): Promise<IL2SharedBridge | IL2Bridge> {
if (await this.isL2BridgeLegacy(address)) {
return IL2Bridge__factory.connect(address, this);
}
return IL2SharedBridge__factory.connect(address, this);
}
/**
* Returns true if passed bridge address is legacy and false if its shared bridge.
**
* @param address The bridge address.
*
* @example
*
* import { Provider, types, utils } from "zksync-ethers";
*
* const provider = Provider.getDefaultProvider(types.Network.Sepolia);
* const isBridgeLegacy = await provider.isL2BridgeLegacy("<L2_BRIDGE_ADDRESS>");
* console.log(isBridgeLegacy);
*/
async isL2BridgeLegacy(address: Address): Promise<boolean> {
const bridge = IL2SharedBridge__factory.connect(address, this);
try {
await bridge.l1SharedBridge();
return false;
} catch (e) {
// skip
}
return true;
}
/**
* Returns all balances for confirmed tokens given by an account address.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getallaccountbalances zks_getAllAccountBalances} JSON-RPC method.
*
* @param address The account address.
*/
async getAllAccountBalances(address: Address): Promise<BalancesMap> {
const balances = await this.send('zks_getAllAccountBalances', [address]);
for (const token in balances) {
balances[token] = BigInt(balances[token]);
}
return balances;
}
/**
* Returns confirmed tokens. Confirmed token is any token bridged to ZKsync Era via the official bridge.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks_getconfirmedtokens zks_getConfirmedTokens} JSON-RPC method.
*
* @param start The token id from which to start.
* @param limit The maximum number of tokens to list.
*/
async getConfirmedTokens(start = 0, limit = 255): Promise<Token[]> {
const tokens: Token[] = await this.send('zks_getConfirmedTokens', [
start,
limit,
]);
return tokens.map(token => ({address: token.l2Address, ...token}));
}
/**
* @deprecated In favor of {@link getL1ChainId}
*
* Returns the L1 chain ID.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-l1chainid zks_L1ChainId} JSON-RPC method.
*/
async l1ChainId(): Promise<number> {
const res = await this.send('zks_L1ChainId', []);
return Number(res);
}
/**
* Returns the L1 chain ID.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-l1chainid zks_L1ChainId} JSON-RPC method.
*/
async getL1ChainId(): Promise<number> {
const res = await this.send('zks_L1ChainId', []);
return Number(res);
}
/**
* Returns the latest L1 batch number.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-l1batchnumber zks_L1BatchNumber} JSON-RPC method.
*/
async getL1BatchNumber(): Promise<number> {
const number = await this.send('zks_L1BatchNumber', []);
return Number(number);
}
/**
* Returns data pertaining to a given batch.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getl1batchdetails zks_getL1BatchDetails} JSON-RPC method.
*
* @param number The L1 batch number.
*/
async getL1BatchDetails(number: number): Promise<BatchDetails> {
return await this.send('zks_getL1BatchDetails', [number]);
}
/**
* Returns additional zkSync-specific information about the L2 block.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getblockdetails zks_getBlockDetails} JSON-RPC method.
*
* @param number The block number.
*/
async getBlockDetails(number: number): Promise<BlockDetails> {
return await this.send('zks_getBlockDetails', [number]);
}
/**
* Returns data from a specific transaction given by the transaction hash.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-gettransactiondetails zks_getTransactionDetails} JSON-RPC method.
*
* @param txHash The transaction hash.
*/
async getTransactionDetails(
txHash: BytesLike
): Promise<TransactionDetails> {
return await this.send('zks_getTransactionDetails', [txHash]);
}
/**
* Returns bytecode of a contract given by its hash.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getbytecodebyhash zks_getBytecodeByHash} JSON-RPC method.
*
* @param bytecodeHash The bytecode hash.
*/
async getBytecodeByHash(bytecodeHash: BytesLike): Promise<Uint8Array> {
return await this.send('zks_getBytecodeByHash', [bytecodeHash]);
}
/**
* Returns data of transactions in a block.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getrawblocktransactions zks_getRawBlockTransactions} JSON-RPC method.
*
* @param number The block number.
*/
async getRawBlockTransactions(
number: number
): Promise<RawBlockTransaction[]> {
return await this.send('zks_getRawBlockTransactions', [number]);
}
/**
* Returns Merkle proofs for one or more storage values at the specified account along with a Merkle proof
* of their authenticity.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks-getproof zks_getProof} JSON-RPC method.
*
* @param address The account to fetch storage values and proofs for.
* @param keys The vector of storage keys in the account.
* @param l1BatchNumber The number of the L1 batch specifying the point in time at which the requested values are returned.
*/
async getProof(
address: Address,
keys: string[],
l1BatchNumber: number
): Promise<StorageProof> {
return await this.send('zks_getProof', [address, keys, l1BatchNumber]);
}
/**
* Executes a transaction and returns its hash, storage logs, and events that would have been generated if the
* transaction had already been included in the block. The API has a similar behaviour to `eth_sendRawTransaction`
* but with some extra data returned from it.
*
* With this API Consumer apps can apply "optimistic" events in their applications instantly without having to
* wait for ZKsync block confirmation time.
*
* It’s expected that the optimistic logs of two uncommitted transactions that modify the same state will not
* have causal relationships between each other.
*
* Calls the {@link https://docs.zksync.io/build/api.html#zks_sendRawTransactionWithDetailedOutput zks_sendRawTransactionWithDetailedOutput} JSON-RPC method.
*
* @param signedTx The signed transaction that needs to be broadcasted.
*/
async sendRawTransactionWithDetailedOutput(
signedTx: string
): Promise<TransactionWithDetailedOutput> {
return await this.send('zks_sendRawTransactionWithDetailedOutput', [
signedTx,
]);
}
/**
* Returns the populated withdrawal transaction.
*
* @param transaction The transaction details.
* @param transaction.amount The amount of token.
* @param transaction.token The token address.
* @param [transaction.from] The sender's address.
* @param [transaction.to] The recipient's address.
* @param [transaction.bridgeAddress] The bridge address.
* @param [transaction.paymasterParams] Paymaster parameters.
* @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`.
*/
async getWithdrawTx(transaction: {
amount: BigNumberish;
token?: Address;
from?: Address;
to?: Address;
bridgeAddress?: Address;
paymasterParams?: PaymasterParams;
overrides?: ethers.Overrides;
}): Promise<EthersTransactionRequest> {
const {...tx} = transaction;
tx.token ??= L2_BASE_TOKEN_ADDRESS;
if (
isAddressEq(tx.token, LEGACY_ETH_ADDRESS) ||
isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS)
) {
tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS);
}
if (
(tx.to === null || tx.to === undefined) &&
(tx.from === null || tx.from === undefined)
) {
throw new Error('Withdrawal target address is undefined!');
}
tx.to ??= tx.from;
tx.overrides ??= {};
tx.overrides.from ??= tx.from;
tx.overrides.type ??= EIP712_TX_TYPE;
if (isAddressEq(tx.token, L2_BASE_TOKEN_ADDRESS)) {
if (!tx.overrides.value) {
tx.overrides.value = tx.amount;
}
const passedValue = BigInt(tx.overrides.value);
if (passedValue !== BigInt(tx.amount)) {
// To avoid users shooting themselves into the foot, we will always use the amount to withdraw
// as the value
throw new Error('The tx.value is not equal to the value withdrawn!');
}
const ethL2Token = IEthToken__factory.connect(
L2_BASE_TOKEN_ADDRESS,
this
);
const populatedTx = await ethL2Token.withdraw.populateTransaction(
tx.to!,
tx.overrides
);
if (tx.paymasterParams) {
return {
...populatedTx,
customData: {
paymasterParams: tx.paymasterParams,
},
};
}
return populatedTx;
}
if (!tx.bridgeAddress) {
const bridgeAddresses = await this.getDefaultBridgeAddresses();
tx.bridgeAddress = bridgeAddresses.sharedL2;
}
const bridge = await this.connectL2Bridge(tx.bridgeAddress!);
const populatedTx = await bridge.withdraw.populateTransaction(
tx.to!,
tx.token,
tx.amount,
tx.overrides
);
if (tx.paymasterParams) {
return {
...populatedTx,
customData: {
paymasterParams: tx.paymasterParams,
},
};
}
return populatedTx;
}
/**
* Returns the gas estimation for a withdrawal transaction.
*
* @param transaction The transaction details.
* @param transaction.token The token address.
* @param transaction.amount The amount of token.
* @param [transaction.from] The sender's address.
* @param [transaction.to] The recipient's address.
* @param [transaction.bridgeAddress] The bridge address.
* @param [transaction.paymasterParams] Paymaster parameters.
* @param [transaction.overrides] Transaction overrides including `gasLimit`, `gasPrice`, and `value`.
*/
async estimateGasWithdraw(transaction: {
token: Address;
amount: BigNumberish;
from?: Address;
to?: Address;
bridgeAddress?: Address;
paymasterParams?: PaymasterParams;
overrides?: ethers.Overrides;
}): Promise<bigint> {
const withdrawTx = await this.getWithdrawTx(transaction);
return await this.estimateGas(withdrawTx);
}
/**
* Returns the populated transfer transaction.
*
* @param transaction Transfer transaction request.
* @param transaction.to The address of the recipient.
* @param transaction.amount The amount of the token to transfer.
* @param [transaction.token] The address of the token. Defaults to ETH.
* @param [transaction.paymasterParams] Paymaster parameters.
* @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc.
*/
async getTransferTx(transaction: {
to: Address;
amount: BigNumberish;
from?: Address;
token?: Address;
paymasterParams?: PaymasterParams;
overrides?: ethers.Overrides;
}): Promise<EthersTransactionRequest> {
const {...tx} = transaction;
if (!tx.token) {
tx.token = L2_BASE_TOKEN_ADDRESS;
} else if (
isAddressEq(tx.token, LEGACY_ETH_ADDRESS) ||
isAddressEq(tx.token, ETH_ADDRESS_IN_CONTRACTS)
) {
tx.token = await this.l2TokenAddress(ETH_ADDRESS_IN_CONTRACTS);
}
tx.overrides ??= {};
tx.overrides.from ??= tx.from;
tx.overrides.type ??= EIP712_TX_TYPE;
if (isAddressEq(tx.token, L2_BASE_TOKEN_ADDRESS)) {
if (tx.paymasterParams) {
return {
...tx.overrides,
type: EIP712_TX_TYPE,
to: tx.to,
value: tx.amount,
customData: {
paymasterParams: tx.paymasterParams,
},
};
}
return {
...tx.overrides,
to: tx.to,
value: tx.amount,
};
} else {
const token = IERC20__factory.connect(tx.token, this);
const populatedTx = await token.transfer.populateTransaction(
tx.to,
tx.amount,
tx.overrides
);
if (tx.paymasterParams) {
return {
...populatedTx,
customData: {
paymasterParams: tx.paymasterParams,
},
};
}
return populatedTx;
}
}
/**
* Returns the gas estimation for a transfer transaction.
*
* @param transaction Transfer transaction request.
* @param transaction.to The address of the recipient.
* @param transaction.amount The amount of the token to transfer.
* @param [transaction.token] The address of the token. Defaults to ETH.
* @param [transaction.paymasterParams] Paymaster parameters.
* @param [transaction.overrides] Transaction's overrides which may be used to pass L2 `gasLimit`, `gasPrice`, `value`, etc.
*/
async estimateGasTransfer(transaction: {
to: Address;
amount: BigNumberish;
from?: Address;
token?: Address;
paymasterParams?: PaymasterParams;
overrides?: ethers.Overrides;
}): Promise<bigint> {
const transferTx = await this.getTransferTx(transaction);
return await this.estimateGas(transferTx);
}
/**
* Returns a new filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newFilter}
* and passing a filter object.
*
* @param filter The filter query to apply.
*/
async newFilter(filter: FilterByBlockHash | Filter): Promise<bigint> {
const id = await this.send('eth_newFilter', [
await this._getFilter(filter),
]);
return BigInt(id);
}
/**
* Returns a new block filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newBlockFilter}.
*/
async newBlockFilter(): Promise<bigint> {
const id = await this.send('eth_newBlockFilter', []);
return BigInt(id);
}
/**
* Returns a new pending transaction filter by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_newPendingTransactionFilter}.
*/
async newPendingTransactionsFilter(): Promise<bigint> {
const id = await this.send('eth_newPendingTransactionFilter', []);
return BigInt(id);
}
/**
* Returns an array of logs by calling {@link https://ethereum.github.io/execution-apis/api-documentation/ eth_getFilterChanges}.
*
* @param idx The filter index.
*/
async getFilterChanges(idx: bigint): Promise<Array<Log | string>> {
const logs = await this.send('eth_getFilterChanges', [
ethers.toBeHex(idx),
]);
return typeof logs[0] === 'string'
? logs
: logs.map((log: any) => this._wrapLog(log));
}
/**
* Returns the status of a specified transaction.
*
* @param txHash The hash of the transaction.
*/
// This is inefficient. Status should probably be indicated in the transaction receipt.
async getTransactionStatus(txHash: string): Promise<TransactionStatus> {
const tx = await this.getTransaction(txHash);
if (!tx) {
return TransactionStatus.NotFound;
}
if (!tx.blockNumber) {
return TransactionStatus.Processing;
}
const verifiedBlock = (await this.getBlock('finalized')) as Block;
if (tx.blockNumber <= verifiedBlock.number) {
return TransactionStatus.Finalized;
}
return TransactionStatus.Committed;
}
/**
* Broadcasts the `signedTx` to the network, adding it to the memory pool of any node for which the transaction
* meets the rebroadcast requirements.
*
* @param signedTx The signed transaction that needs to be broadcasted.
* @returns A promise that resolves with the transaction response.
*/
override async broadcastTransaction(
signedTx: string
): Promise<TransactionResponse> {
const {blockNumber, hash} = await resolveProperties({
blockNumber: this.getBlockNumber(),
hash: this._perform({
method: 'broadcastTransaction',
signedTransaction: signedTx,
}),
network: this.getNetwork(),
});
const tx = Transaction.from(signedTx);
if (tx.hash !== hash) {
throw new Error('@TODO: the returned hash did not match!');
}
return this._wrapTransactionResponse(<any>tx).replaceableTransaction(
blockNumber