@atomiqlabs/chain-evm 1.0.0-dev.22

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.
Files changed (146) hide show
  1. package/LICENSE +201 -0
  2. package/dist/chains/citrea/CitreaChainType.d.ts +13 -0
  3. package/dist/chains/citrea/CitreaChainType.js +2 -0
  4. package/dist/chains/citrea/CitreaInitializer.d.ts +30 -0
  5. package/dist/chains/citrea/CitreaInitializer.js +120 -0
  6. package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -0
  7. package/dist/evm/btcrelay/BtcRelayAbi.js +261 -0
  8. package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -0
  9. package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -0
  10. package/dist/evm/btcrelay/EVMBtcRelay.d.ts +188 -0
  11. package/dist/evm/btcrelay/EVMBtcRelay.js +419 -0
  12. package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -0
  13. package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -0
  14. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -0
  15. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -0
  16. package/dist/evm/chain/EVMChainInterface.d.ts +51 -0
  17. package/dist/evm/chain/EVMChainInterface.js +90 -0
  18. package/dist/evm/chain/EVMModule.d.ts +9 -0
  19. package/dist/evm/chain/EVMModule.js +13 -0
  20. package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -0
  21. package/dist/evm/chain/modules/ERC20Abi.js +225 -0
  22. package/dist/evm/chain/modules/EVMAddresses.d.ts +9 -0
  23. package/dist/evm/chain/modules/EVMAddresses.js +26 -0
  24. package/dist/evm/chain/modules/EVMBlocks.d.ts +20 -0
  25. package/dist/evm/chain/modules/EVMBlocks.js +64 -0
  26. package/dist/evm/chain/modules/EVMEvents.d.ts +36 -0
  27. package/dist/evm/chain/modules/EVMEvents.js +122 -0
  28. package/dist/evm/chain/modules/EVMFees.d.ts +35 -0
  29. package/dist/evm/chain/modules/EVMFees.js +73 -0
  30. package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -0
  31. package/dist/evm/chain/modules/EVMSignatures.js +68 -0
  32. package/dist/evm/chain/modules/EVMTokens.d.ts +49 -0
  33. package/dist/evm/chain/modules/EVMTokens.js +105 -0
  34. package/dist/evm/chain/modules/EVMTransactions.d.ts +89 -0
  35. package/dist/evm/chain/modules/EVMTransactions.js +216 -0
  36. package/dist/evm/contract/EVMContractBase.d.ts +22 -0
  37. package/dist/evm/contract/EVMContractBase.js +34 -0
  38. package/dist/evm/contract/EVMContractModule.d.ts +8 -0
  39. package/dist/evm/contract/EVMContractModule.js +11 -0
  40. package/dist/evm/contract/modules/EVMContractEvents.d.ts +42 -0
  41. package/dist/evm/contract/modules/EVMContractEvents.js +75 -0
  42. package/dist/evm/events/EVMChainEvents.d.ts +22 -0
  43. package/dist/evm/events/EVMChainEvents.js +67 -0
  44. package/dist/evm/events/EVMChainEventsBrowser.d.ts +86 -0
  45. package/dist/evm/events/EVMChainEventsBrowser.js +294 -0
  46. package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +64 -0
  47. package/dist/evm/spv_swap/EVMSpvVaultContract.js +410 -0
  48. package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +38 -0
  49. package/dist/evm/spv_swap/EVMSpvVaultData.js +159 -0
  50. package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -0
  51. package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -0
  52. package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -0
  53. package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -0
  54. package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -0
  55. package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -0
  56. package/dist/evm/swaps/EVMSwapContract.d.ts +192 -0
  57. package/dist/evm/swaps/EVMSwapContract.js +373 -0
  58. package/dist/evm/swaps/EVMSwapData.d.ts +64 -0
  59. package/dist/evm/swaps/EVMSwapData.js +254 -0
  60. package/dist/evm/swaps/EVMSwapModule.d.ts +9 -0
  61. package/dist/evm/swaps/EVMSwapModule.js +11 -0
  62. package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -0
  63. package/dist/evm/swaps/EscrowManagerAbi.js +985 -0
  64. package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -0
  65. package/dist/evm/swaps/EscrowManagerTypechain.js +2 -0
  66. package/dist/evm/swaps/handlers/IHandler.d.ts +13 -0
  67. package/dist/evm/swaps/handlers/IHandler.js +2 -0
  68. package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -0
  69. package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -0
  70. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -0
  71. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -0
  72. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -0
  73. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -0
  74. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -0
  75. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -0
  76. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -0
  77. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -0
  78. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -0
  79. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -0
  80. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -0
  81. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -0
  82. package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -0
  83. package/dist/evm/swaps/modules/EVMLpVault.js +131 -0
  84. package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +53 -0
  85. package/dist/evm/swaps/modules/EVMSwapClaim.js +101 -0
  86. package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -0
  87. package/dist/evm/swaps/modules/EVMSwapInit.js +241 -0
  88. package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -0
  89. package/dist/evm/swaps/modules/EVMSwapRefund.js +132 -0
  90. package/dist/evm/typechain/common.d.ts +50 -0
  91. package/dist/evm/typechain/common.js +2 -0
  92. package/dist/evm/wallet/EVMSigner.d.ts +9 -0
  93. package/dist/evm/wallet/EVMSigner.js +16 -0
  94. package/dist/index.d.ts +37 -0
  95. package/dist/index.js +53 -0
  96. package/dist/utils/Utils.d.ts +15 -0
  97. package/dist/utils/Utils.js +71 -0
  98. package/package.json +37 -0
  99. package/src/chains/citrea/CitreaChainType.ts +28 -0
  100. package/src/chains/citrea/CitreaInitializer.ts +167 -0
  101. package/src/evm/btcrelay/BtcRelayAbi.ts +258 -0
  102. package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -0
  103. package/src/evm/btcrelay/EVMBtcRelay.ts +517 -0
  104. package/src/evm/btcrelay/headers/EVMBtcHeader.ts +110 -0
  105. package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +153 -0
  106. package/src/evm/chain/EVMChainInterface.ts +157 -0
  107. package/src/evm/chain/EVMModule.ts +21 -0
  108. package/src/evm/chain/modules/ERC20Abi.ts +222 -0
  109. package/src/evm/chain/modules/EVMAddresses.ts +24 -0
  110. package/src/evm/chain/modules/EVMBlocks.ts +75 -0
  111. package/src/evm/chain/modules/EVMEvents.ts +139 -0
  112. package/src/evm/chain/modules/EVMFees.ts +105 -0
  113. package/src/evm/chain/modules/EVMSignatures.ts +76 -0
  114. package/src/evm/chain/modules/EVMTokens.ts +115 -0
  115. package/src/evm/chain/modules/EVMTransactions.ts +246 -0
  116. package/src/evm/contract/EVMContractBase.ts +63 -0
  117. package/src/evm/contract/EVMContractModule.ts +16 -0
  118. package/src/evm/contract/modules/EVMContractEvents.ts +102 -0
  119. package/src/evm/events/EVMChainEvents.ts +81 -0
  120. package/src/evm/events/EVMChainEventsBrowser.ts +390 -0
  121. package/src/evm/spv_swap/EVMSpvVaultContract.ts +533 -0
  122. package/src/evm/spv_swap/EVMSpvVaultData.ts +201 -0
  123. package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -0
  124. package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -0
  125. package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -0
  126. package/src/evm/swaps/EVMSwapContract.ts +590 -0
  127. package/src/evm/swaps/EVMSwapData.ts +367 -0
  128. package/src/evm/swaps/EVMSwapModule.ts +16 -0
  129. package/src/evm/swaps/EscrowManagerAbi.ts +982 -0
  130. package/src/evm/swaps/EscrowManagerTypechain.ts +723 -0
  131. package/src/evm/swaps/handlers/IHandler.ts +17 -0
  132. package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -0
  133. package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +47 -0
  134. package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -0
  135. package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -0
  136. package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -0
  137. package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -0
  138. package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +38 -0
  139. package/src/evm/swaps/modules/EVMLpVault.ts +153 -0
  140. package/src/evm/swaps/modules/EVMSwapClaim.ts +141 -0
  141. package/src/evm/swaps/modules/EVMSwapInit.ts +292 -0
  142. package/src/evm/swaps/modules/EVMSwapRefund.ts +198 -0
  143. package/src/evm/typechain/common.ts +131 -0
  144. package/src/evm/wallet/EVMSigner.ts +23 -0
  145. package/src/index.ts +44 -0
  146. package/src/utils/Utils.ts +81 -0
@@ -0,0 +1,139 @@
1
+ import {EVMModule} from "../EVMModule";
2
+ import {Log} from "ethers";
3
+
4
+ export class EVMEvents extends EVMModule<any> {
5
+
6
+ /**
7
+ * Returns the all the events occuring in a block range as identified by the contract and keys
8
+ *
9
+ * @param contract
10
+ * @param topics
11
+ * @param startBlock
12
+ * @param endBlock
13
+ * @param abortSignal
14
+ */
15
+ public async getBlockEvents(
16
+ contract: string, topics: (string[] | string | null)[], startBlock?: number, endBlock: number = startBlock, abortSignal?: AbortSignal
17
+ ): Promise<Log[]> {
18
+ let events: Log[] = [];
19
+
20
+ if(startBlock===endBlock) {
21
+ events = await this.root.provider.getLogs({
22
+ address: contract,
23
+ fromBlock: startBlock==null ? this.root.config.safeBlockTag : startBlock,
24
+ toBlock: endBlock==null ? this.root.config.safeBlockTag : endBlock,
25
+ topics
26
+ });
27
+ } else if(endBlock==null) {
28
+ const safeBlock = await this.root.provider.getBlock(this.root.config.safeBlockTag);
29
+ if(safeBlock.number - startBlock > this.root.config.maxLogsBlockRange) {
30
+ for(let i = startBlock + this.root.config.maxLogsBlockRange; i < safeBlock.number; i += this.root.config.maxLogsBlockRange) {
31
+ events.push(...await this.root.provider.getLogs({
32
+ address: contract,
33
+ fromBlock: i - this.root.config.maxLogsBlockRange,
34
+ toBlock: i,
35
+ topics
36
+ }));
37
+ startBlock = i;
38
+ }
39
+ }
40
+ events.push(...await this.root.provider.getLogs({
41
+ address: contract,
42
+ fromBlock: startBlock==null ? this.root.config.safeBlockTag : startBlock,
43
+ toBlock: endBlock==null ? this.root.config.safeBlockTag : endBlock,
44
+ topics
45
+ }));
46
+ } else {
47
+ //Both numeric
48
+ if(endBlock - startBlock > this.root.config.maxLogsBlockRange) {
49
+ for(let i = startBlock + this.root.config.maxLogsBlockRange; i < endBlock; i += this.root.config.maxLogsBlockRange) {
50
+ events.push(...await this.root.provider.getLogs({
51
+ address: contract,
52
+ fromBlock: i - this.root.config.maxLogsBlockRange,
53
+ toBlock: i,
54
+ topics
55
+ }));
56
+ startBlock = i;
57
+ }
58
+ }
59
+ events.push(...await this.root.provider.getLogs({
60
+ address: contract,
61
+ fromBlock: startBlock,
62
+ toBlock: endBlock,
63
+ topics
64
+ }));
65
+ }
66
+
67
+ return events.filter(val => !val.removed);
68
+ }
69
+
70
+ /**
71
+ * Runs a search backwards in time, processing events from a specific contract and keys
72
+ *
73
+ * @param contract
74
+ * @param topics
75
+ * @param processor called for every batch of returned signatures, should return a value if the correct signature
76
+ * was found, or null if the search should continue
77
+ * @param abortSignal
78
+ * @param genesisHeight Height when the contract was deployed
79
+ */
80
+ public async findInEvents<T>(
81
+ contract: string, topics: (string[] | string | null)[],
82
+ processor: (signatures: Log[]) => Promise<T>,
83
+ abortSignal?: AbortSignal,
84
+ genesisHeight?: number
85
+ ): Promise<T> {
86
+ const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag);
87
+
88
+ for(let blockNumber = latestBlockNumber; blockNumber >= (genesisHeight ?? 0); blockNumber-=this.root.config.maxLogsBlockRange) {
89
+ const eventsResult = await this.provider.getLogs({
90
+ address: contract,
91
+ topics,
92
+ fromBlock: Math.max(blockNumber-this.root.config.maxLogsBlockRange, 0),
93
+ toBlock: blockNumber===latestBlockNumber ? this.root.config.safeBlockTag : blockNumber
94
+ });
95
+
96
+ if(abortSignal!=null) abortSignal.throwIfAborted();
97
+
98
+ const result: T = await processor(eventsResult.reverse()); //Newest events first
99
+ if(result!=null) return result;
100
+ }
101
+ return null;
102
+ }
103
+
104
+ /**
105
+ * Runs a search forwards in time, processing events from a specific contract and keys
106
+ *
107
+ * @param contract
108
+ * @param topics
109
+ * @param processor called for every batch of returned signatures, should return a value if the correct signature
110
+ * was found, or null if the search should continue
111
+ * @param abortSignal
112
+ * @param startHeight Blockheight at which to start
113
+ */
114
+ public async findInEventsForward<T>(
115
+ contract: string, topics: (string[] | string | null)[],
116
+ processor: (signatures: Log[]) => Promise<T>,
117
+ abortSignal?: AbortSignal,
118
+ startHeight?: number
119
+ ): Promise<T> {
120
+ const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag);
121
+
122
+ for(let blockNumber = startHeight ?? 0; blockNumber < latestBlockNumber; blockNumber += this.root.config.maxLogsBlockRange) {
123
+ const eventsResult = await this.provider.getLogs({
124
+ address: contract,
125
+ topics,
126
+ fromBlock: blockNumber,
127
+ toBlock: (blockNumber + this.root.config.maxLogsBlockRange) > latestBlockNumber ? this.root.config.safeBlockTag : blockNumber + this.root.config.maxLogsBlockRange
128
+ });
129
+
130
+ if(abortSignal!=null) abortSignal.throwIfAborted();
131
+
132
+ const result: T = await processor(eventsResult); //Oldest events first
133
+ if(result!=null) return result;
134
+ }
135
+
136
+ return null;
137
+ }
138
+
139
+ }
@@ -0,0 +1,105 @@
1
+ import { getLogger } from "../../../utils/Utils";
2
+ import {Provider, TransactionRequest} from "ethers";
3
+
4
+ const MAX_FEE_AGE = 5000;
5
+
6
+ export type EVMFeeRate = {
7
+ maxFeePerGas: bigint;
8
+ maxPriorityFee: bigint;
9
+ };
10
+
11
+ export class EVMFees {
12
+
13
+ private readonly logger = getLogger("EVMFees: ");
14
+
15
+ private readonly provider: Provider;
16
+ private readonly maxFeeRatePerGas: bigint;
17
+ private readonly priorityFee: bigint;
18
+
19
+ private readonly feeMultiplierPPM: bigint;
20
+
21
+ private blockFeeCache: {
22
+ timestamp: number,
23
+ feeRate: Promise<bigint>
24
+ } = null;
25
+
26
+ constructor(
27
+ provider: Provider,
28
+ maxFeeRatePerGas: bigint = 500n * 1_000_000_000n,
29
+ priorityFee: bigint = 1n * 1_000_000_000n,
30
+ feeMultiplier: number = 1.25,
31
+ ) {
32
+ this.provider = provider;
33
+ this.maxFeeRatePerGas = maxFeeRatePerGas;
34
+ this.priorityFee = priorityFee;
35
+ this.feeMultiplierPPM = BigInt(Math.floor(feeMultiplier * 1_000_000));
36
+ }
37
+
38
+ /**
39
+ * Gets evm fee rate
40
+ *
41
+ * @private
42
+ * @returns {Promise<bigint>} L1 gas price denominated in Wei
43
+ */
44
+ private async _getFeeRate(): Promise<bigint> {
45
+ const block = await this.provider.getBlock("latest");
46
+
47
+ const baseFee = block.baseFeePerGas * this.feeMultiplierPPM / 1_000_000n;
48
+ this.logger.debug("_getFeeRate(): Base fee rate: "+baseFee.toString(10));
49
+
50
+ return baseFee;
51
+ }
52
+
53
+ /**
54
+ * Gets the gas price with caching, format: <gas price in Wei>;<transaction version: v1/v3>
55
+ *
56
+ * @private
57
+ */
58
+ public async getFeeRate(): Promise<string> {
59
+ if(this.blockFeeCache==null || Date.now() - this.blockFeeCache.timestamp > MAX_FEE_AGE) {
60
+ let obj = {
61
+ timestamp: Date.now(),
62
+ feeRate: null
63
+ };
64
+ obj.feeRate = this._getFeeRate().catch(e => {
65
+ if(this.blockFeeCache===obj) this.blockFeeCache=null;
66
+ throw e;
67
+ });
68
+ this.blockFeeCache = obj;
69
+ }
70
+
71
+ let baseFee = await this.blockFeeCache.feeRate;
72
+ if(baseFee>this.maxFeeRatePerGas) baseFee = this.maxFeeRatePerGas;
73
+
74
+ const fee = baseFee.toString(10)+","+this.priorityFee.toString(10);
75
+
76
+ this.logger.debug("getFeeRate(): calculated fee: "+fee);
77
+
78
+ return fee;
79
+ }
80
+
81
+ /**
82
+ * Calculates the total gas fee paid for a given gas limit at a given fee rate
83
+ *
84
+ * @param gas
85
+ * @param feeRate
86
+ */
87
+ public static getGasFee(gas: number, feeRate: string): bigint {
88
+ if(feeRate==null) return 0n;
89
+
90
+ const [baseFee, priorityFee] = feeRate.split(",");
91
+
92
+ return BigInt(gas) * (BigInt(baseFee) + BigInt(priorityFee));
93
+ }
94
+
95
+ public static applyFeeRate(tx: TransactionRequest, gas: number, feeRate: string) {
96
+ if(feeRate==null) return null;
97
+
98
+ const [baseFee, priorityFee] = feeRate.split(",");
99
+
100
+ tx.maxFeePerGas = BigInt(baseFee) + BigInt(priorityFee);
101
+ tx.maxPriorityFeePerGas = BigInt(priorityFee);
102
+ tx.gasLimit = BigInt(gas) + 21_000n;
103
+ }
104
+
105
+ }
@@ -0,0 +1,76 @@
1
+ import {EVMSigner} from "../../wallet/EVMSigner";
2
+ import {EVMModule} from "../EVMModule";
3
+ import {EVMChainInterface} from "../EVMChainInterface";
4
+ import {sha256, verifyTypedData, TypedDataField} from "ethers";
5
+
6
+ const DataHash = [
7
+ { name: "dataHash", type: "bytes32" }
8
+ ];
9
+
10
+ export class EVMSignatures extends EVMModule<any> {
11
+
12
+ private readonly domainName: string;
13
+
14
+ constructor(root: EVMChainInterface<any>, domainName: string = "atomiq.exchange") {
15
+ super(root);
16
+ this.domainName = domainName;
17
+ }
18
+
19
+ public async signTypedMessage(contract: string, signer: EVMSigner, type: TypedDataField[], typeName: string, message: object): Promise<string> {
20
+ return signer.account.signTypedData({
21
+ name: this.domainName,
22
+ version: "1",
23
+ chainId: BigInt(this.root.evmChainId),
24
+ verifyingContract: contract
25
+ }, {[typeName]: type}, message);
26
+ }
27
+
28
+ public async isValidSignature(contract: string, signature: string, address: string, type: TypedDataField[], typeName: string, message: object): Promise<boolean> {
29
+ return Promise.resolve(address === verifyTypedData({
30
+ name: this.domainName,
31
+ version: "1",
32
+ chainId: BigInt(this.root.evmChainId),
33
+ verifyingContract: contract
34
+ }, {[typeName]: type}, message, signature));
35
+ }
36
+
37
+ ///////////////////
38
+ //// Data signatures
39
+ /**
40
+ * Produces a signature over the sha256 of a specified data Buffer, only works with providers which
41
+ * expose their private key (i.e. backend based, not browser wallet based)
42
+ *
43
+ * @param signer
44
+ * @param data data to sign
45
+ */
46
+ public getDataSignature(signer: EVMSigner, data: Buffer): Promise<string> {
47
+ return signer.account.signTypedData({
48
+ name: this.domainName,
49
+ version: "1",
50
+ chainId: BigInt(this.root.evmChainId),
51
+ verifyingContract: "0x0000000000000000000000000000000000000000"
52
+ }, {DataHash}, {
53
+ dataHash: sha256(data)
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Checks whether a signature is a valid signature produced by the account over a data message (computes
59
+ * sha256 hash of the message)
60
+ *
61
+ * @param data signed data
62
+ * @param signature data signature
63
+ * @param address public key of the signer
64
+ */
65
+ public isValidDataSignature(data: Buffer, signature: string, address: string): Promise<boolean> {
66
+ return Promise.resolve(address === verifyTypedData({
67
+ name: this.domainName,
68
+ version: "1",
69
+ chainId: BigInt(this.root.evmChainId),
70
+ verifyingContract: "0x0000000000000000000000000000000000000000"
71
+ }, {DataHash}, {
72
+ dataHash: sha256(data)
73
+ }, signature));
74
+ }
75
+
76
+ }
@@ -0,0 +1,115 @@
1
+ import {EVMModule} from "../EVMModule";
2
+ import {Contract, TransactionRequest} from "ethers";
3
+ import {ERC20Abi} from "./ERC20Abi";
4
+ import {EVMAddresses} from "./EVMAddresses";
5
+ import {EVMFees} from "./EVMFees";
6
+
7
+
8
+ export class EVMTokens extends EVMModule<any> {
9
+
10
+ public static readonly ETH_ADDRESS = "0x0000000000000000000000000000000000000000";
11
+
12
+ public static readonly GasCosts = {
13
+ TRANSFER: 80_000,
14
+ APPROVE: 80_000
15
+ };
16
+
17
+ private getContract(address: string) {
18
+ return new Contract(address, ERC20Abi, this.root.provider);
19
+ }
20
+
21
+ ///////////////////
22
+ //// Tokens
23
+ /**
24
+ * Checks if the provided string is a valid starknet token
25
+ *
26
+ * @param token
27
+ */
28
+ public isValidToken(token: string) {
29
+ return EVMAddresses.isValidAddress(token);
30
+ }
31
+
32
+ /**
33
+ * Returns the token balance of the address
34
+ *
35
+ * @param address
36
+ * @param token
37
+ */
38
+ public async getTokenBalance(address: string, token: string): Promise<bigint> {
39
+ let balance: bigint;
40
+ if(token === "0x0000000000000000000000000000000000000000") {
41
+ balance = await this.provider.getBalance(address);
42
+ } else {
43
+ const erc20 = this.getContract(token);
44
+ balance = await erc20.balanceOf(address);
45
+ }
46
+ this.logger.debug("getTokenBalance(): token balance fetched, token: "+token+
47
+ " address: "+address+" amount: "+balance.toString(10));
48
+
49
+ return balance;
50
+ }
51
+
52
+ /**
53
+ * Returns the native currency address
54
+ */
55
+ public getNativeCurrencyAddress(): string {
56
+ return "0x0000000000000000000000000000000000000000";
57
+ }
58
+
59
+ ///////////////////
60
+ //// Transfers
61
+ /**
62
+ * Creates transactions for sending the over the tokens
63
+ *
64
+ * @param signer
65
+ * @param token token to send
66
+ * @param amount amount of the token to send
67
+ * @param recipient recipient's address
68
+ * @param feeRate fee rate to use for the transactions
69
+ * @private
70
+ */
71
+ public async Transfer(signer: string, token: string, amount: bigint, recipient: string, feeRate?: string): Promise<TransactionRequest> {
72
+ let tx: TransactionRequest;
73
+ if(token===this.getNativeCurrencyAddress()) {
74
+ tx = {
75
+ to: recipient,
76
+ value: amount
77
+ };
78
+ } else {
79
+ tx = await this.getContract(token).transfer.populateTransaction(recipient, amount);
80
+ }
81
+ tx.from = signer;
82
+ EVMFees.applyFeeRate(tx, EVMTokens.GasCosts.TRANSFER, feeRate ?? await this.root.Fees.getFeeRate());
83
+
84
+ this.logger.debug("txsTransfer(): transfer TX created, recipient: "+recipient.toString()+
85
+ " token: "+token.toString()+ " amount: "+amount.toString(10));
86
+
87
+ return tx;
88
+ }
89
+
90
+ ///////////////////
91
+ //// Approval
92
+ /**
93
+ * Creates transactions for approving spending of tokens
94
+ *
95
+ * @param signer
96
+ * @param token token to send
97
+ * @param amount amount of the token to send
98
+ * @param spender recipient's address
99
+ * @param feeRate fee rate to use for the transactions
100
+ * @private
101
+ */
102
+ public async Approve(signer: string, token: string, amount: bigint, spender: string, feeRate?: string): Promise<TransactionRequest> {
103
+ if(token===this.getNativeCurrencyAddress()) return null;
104
+
105
+ const tx = await this.getContract(token).approve.populateTransaction(spender, amount);
106
+ tx.from = signer;
107
+ EVMFees.applyFeeRate(tx, EVMTokens.GasCosts.APPROVE, feeRate ?? await this.root.Fees.getFeeRate());
108
+
109
+ this.logger.debug("txsTransfer(): approve TX created, spender: "+spender.toString()+
110
+ " token: "+token.toString()+ " amount: "+amount.toString(10));
111
+
112
+ return tx;
113
+ }
114
+
115
+ }
@@ -0,0 +1,246 @@
1
+ import {EVMModule} from "../EVMModule";
2
+ import {Transaction, TransactionRequest} from "ethers";
3
+ import {timeoutPromise} from "../../../utils/Utils";
4
+ import {EVMSigner} from "../../wallet/EVMSigner";
5
+
6
+ export type EVMTx = TransactionRequest;
7
+
8
+ export type EVMTxTrace = {
9
+ from: string,
10
+ gas: string,
11
+ gasused: string,
12
+ to: string,
13
+ input: string,
14
+ output: string,
15
+ error: string,
16
+ revertReason: string,
17
+ calls: EVMTxTrace[],
18
+ type: "CREATE" | "CALL" | "STATICCALL"
19
+ };
20
+
21
+ export class EVMTransactions extends EVMModule<any> {
22
+
23
+ private readonly latestConfirmedNonces: {[address: string]: number} = {};
24
+
25
+ private cbkBeforeTxSigned: (tx: TransactionRequest) => Promise<void>;
26
+ private cbkSendTransaction: (tx: string) => Promise<string>;
27
+
28
+ /**
29
+ * Waits for transaction confirmation using WS subscription and occasional HTTP polling, also re-sends
30
+ * the transaction at regular interval
31
+ *
32
+ * @param tx EVM transaction to wait for confirmation for
33
+ * @param abortSignal signal to abort waiting for tx confirmation
34
+ * @private
35
+ */
36
+ private async confirmTransaction(tx: Transaction, abortSignal?: AbortSignal) {
37
+ let state = "pending";
38
+ while(state==="pending" || state==="not_found") {
39
+ await timeoutPromise(3000, abortSignal);
40
+ state = await this.getTxIdStatus(tx.hash);
41
+ //Don't re-send transactions
42
+ // if(state==="not_found") await this.sendSignedTransaction(tx).catch(e => {
43
+ // if(e.baseError?.code === 59) return; //Transaction already in the mempool
44
+ // this.logger.error("confirmTransaction(): Error on transaction re-send: ", e);
45
+ // });
46
+ }
47
+ const nextAccountNonce = tx.nonce + 1;
48
+ const currentNonce = this.latestConfirmedNonces[tx.from];
49
+ if(currentNonce==null || nextAccountNonce > currentNonce) {
50
+ this.latestConfirmedNonces[tx.from] = nextAccountNonce;
51
+ }
52
+ if(state==="reverted") throw new Error("Transaction reverted!");
53
+ }
54
+
55
+ /**
56
+ * Prepares starknet transactions, checks if the account is deployed, assigns nonces if needed & calls beforeTxSigned callback
57
+ *
58
+ * @param signer
59
+ * @param txs
60
+ * @private
61
+ */
62
+ private async prepareTransactions(signer: EVMSigner, txs: TransactionRequest[]): Promise<void> {
63
+ let nonce: number = (await signer.getNonce()) ?? await this.root.provider.getTransactionCount(signer.getAddress(), "pending");
64
+ const latestConfirmedNonce = this.latestConfirmedNonces[signer.getAddress()];
65
+ if(latestConfirmedNonce!=null && latestConfirmedNonce > nonce) {
66
+ this.logger.debug("prepareTransactions(): Using nonce from local cache!");
67
+ nonce = latestConfirmedNonce;
68
+ }
69
+
70
+ for(let i=0;i<txs.length;i++) {
71
+ const tx = txs[i];
72
+ tx.chainId = this.root.evmChainId;
73
+ tx.from = signer.getAddress();
74
+ if(tx.nonce!=null) nonce = tx.nonce; //Take the nonce from last tx
75
+ if(nonce==null) nonce = await this.root.provider.getTransactionCount(signer.getAddress(), "pending"); //Fetch the nonce
76
+ if(tx.nonce==null) tx.nonce = nonce;
77
+
78
+ this.logger.debug("sendAndConfirm(): transaction prepared ("+(i+1)+"/"+txs.length+"), nonce: "+tx.nonce);
79
+
80
+ nonce++;
81
+
82
+ if(this.cbkBeforeTxSigned!=null) await this.cbkBeforeTxSigned(tx);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Sends out a signed transaction to the RPC
88
+ *
89
+ * @param tx EVM tx to send
90
+ * @param onBeforePublish a callback called before every transaction is published
91
+ * @private
92
+ */
93
+ private async sendSignedTransaction(
94
+ tx: Transaction,
95
+ onBeforePublish?: (txId: string, rawTx: string) => Promise<void>,
96
+ ): Promise<string> {
97
+ if(onBeforePublish!=null) await onBeforePublish(tx.hash, await this.serializeTx(tx));
98
+ this.logger.debug("sendSignedTransaction(): sending transaction: ", tx.hash);
99
+
100
+ const serializedTx = tx.serialized;
101
+
102
+ let result: string;
103
+ if(this.cbkSendTransaction!=null) result = await this.cbkSendTransaction(serializedTx);
104
+ if(result==null) {
105
+ const broadcastResult = await this.provider.broadcastTransaction(tx.serialized);
106
+ result = broadcastResult.hash;
107
+ }
108
+
109
+ this.logger.info("sendSignedTransaction(): tx sent, txHash: "+result);
110
+ return result;
111
+ }
112
+
113
+ /**
114
+ * Prepares, signs, sends (in parallel or sequentially) & optionally waits for confirmation
115
+ * of a batch of EVM transactions
116
+ *
117
+ * @param signer
118
+ * @param txs transactions to send
119
+ * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
120
+ * are re-sent at regular intervals)
121
+ * @param abortSignal abort signal to abort waiting for transaction confirmations
122
+ * @param parallel whether the send all the transaction at once in parallel or sequentially (such that transactions
123
+ * are executed in order)
124
+ * @param onBeforePublish a callback called before every transaction is published
125
+ */
126
+ public async sendAndConfirm(signer: EVMSigner, txs: TransactionRequest[], waitForConfirmation?: boolean, abortSignal?: AbortSignal, parallel?: boolean, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<string[]> {
127
+ await this.prepareTransactions(signer, txs);
128
+ const signedTxs: Transaction[] = [];
129
+
130
+ //TODO: Maybe don't separate the signing process from the sending when using browser-based wallet,
131
+ // like with Starknet
132
+ for(let i=0;i<txs.length;i++) {
133
+ const tx = txs[i];
134
+ const signedTx = Transaction.from(await signer.account.signTransaction(tx));
135
+ signedTxs.push(signedTx);
136
+ this.logger.debug("sendAndConfirm(): transaction signed ("+(i+1)+"/"+txs.length+"): "+signedTx);
137
+ }
138
+
139
+ this.logger.debug("sendAndConfirm(): sending transactions, count: "+txs.length+
140
+ " waitForConfirmation: "+waitForConfirmation+" parallel: "+parallel);
141
+
142
+ const txIds: string[] = [];
143
+ if(parallel) {
144
+ const promises: Promise<void>[] = [];
145
+ for(let i=0;i<signedTxs.length;i++) {
146
+ const signedTx = signedTxs[i];
147
+ const txId = await this.sendSignedTransaction(signedTx, onBeforePublish);
148
+ if(waitForConfirmation) promises.push(this.confirmTransaction(signedTx, abortSignal));
149
+ txIds.push(txId);
150
+ this.logger.debug("sendAndConfirm(): transaction sent ("+(i+1)+"/"+signedTxs.length+"): "+signedTx.hash);
151
+ }
152
+ if(promises.length>0) await Promise.all(promises);
153
+ } else {
154
+ for(let i=0;i<signedTxs.length;i++) {
155
+ const signedTx = signedTxs[i];
156
+ const txId = await this.sendSignedTransaction(signedTx, onBeforePublish);
157
+ const confirmPromise = this.confirmTransaction(signedTx, abortSignal);
158
+ this.logger.debug("sendAndConfirm(): transaction sent ("+(i+1)+"/"+txs.length+"): "+signedTx.hash);
159
+ //Don't await the last promise when !waitForConfirmation
160
+ if(i<txs.length-1 || waitForConfirmation) await confirmPromise;
161
+ txIds.push(txId);
162
+ }
163
+ }
164
+
165
+ this.logger.info("sendAndConfirm(): sent transactions, count: "+txs.length+
166
+ " waitForConfirmation: "+waitForConfirmation+" parallel: "+parallel);
167
+
168
+ return txIds;
169
+ }
170
+
171
+ /**
172
+ * Serializes the signed EVM transaction
173
+ *
174
+ * @param tx
175
+ */
176
+ public serializeTx(tx: Transaction): Promise<string> {
177
+ return Promise.resolve(tx.serialized);
178
+ }
179
+
180
+ /**
181
+ * Deserializes signed EVM transaction
182
+ *
183
+ * @param txData
184
+ */
185
+ public deserializeTx(txData: string): Promise<Transaction> {
186
+ return Promise.resolve(Transaction.from(txData));
187
+ }
188
+
189
+ /**
190
+ * Gets the status of the raw starknet transaction
191
+ *
192
+ * @param tx
193
+ */
194
+ public async getTxStatus(tx: string): Promise<"pending" | "success" | "not_found" | "reverted"> {
195
+ const parsedTx: Transaction = await this.deserializeTx(tx);
196
+ return await this.getTxIdStatus(parsedTx.hash);
197
+ }
198
+
199
+ /**
200
+ * Gets the status of the starknet transaction with a specific txId
201
+ *
202
+ * @param txId
203
+ */
204
+ public async getTxIdStatus(txId: string): Promise<"pending" | "success" | "not_found" | "reverted"> {
205
+ const txResponse = await this.provider.getTransaction(txId);
206
+ if(txResponse==null) return "not_found";
207
+ if(txResponse.blockHash==null) return "pending";
208
+
209
+ const [safeBlockNumber, txReceipt] = await Promise.all([
210
+ this.root.config.safeBlockTag==="latest" ? Promise.resolve(null) : this.provider.getBlock(this.root.config.safeBlockTag).then(res => res.number),
211
+ this.provider.getTransactionReceipt(txId)
212
+ ]);
213
+
214
+ if(txReceipt==null || (safeBlockNumber!=null && txReceipt.blockNumber < safeBlockNumber)) return "pending";
215
+ if(txReceipt.status===0) return "reverted";
216
+ return "success";
217
+ }
218
+
219
+ public onBeforeTxSigned(callback: (tx: TransactionRequest) => Promise<void>): void {
220
+ this.cbkBeforeTxSigned = callback;
221
+ }
222
+
223
+ public offBeforeTxSigned(callback: (tx: TransactionRequest) => Promise<void>): boolean {
224
+ this.cbkBeforeTxSigned = null;
225
+ return true;
226
+ }
227
+
228
+ public onSendTransaction(callback: (tx: string) => Promise<string>): void {
229
+ this.cbkSendTransaction = callback;
230
+ }
231
+
232
+ public offSendTransaction(callback: (tx: string) => Promise<string>): boolean {
233
+ this.cbkSendTransaction = null;
234
+ return true;
235
+ }
236
+
237
+ public traceTransaction(txId: string): Promise<EVMTxTrace> {
238
+ return this.provider.send("debug_traceTransaction", [
239
+ txId,
240
+ {
241
+ tracer: "callTracer"
242
+ }
243
+ ]);
244
+ }
245
+
246
+ }