@atomiqlabs/chain-evm 1.0.0-dev.100

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 (185) hide show
  1. package/LICENSE +201 -0
  2. package/dist/chains/botanix/BotanixChainType.d.ts +14 -0
  3. package/dist/chains/botanix/BotanixChainType.js +2 -0
  4. package/dist/chains/botanix/BotanixInitializer.d.ts +30 -0
  5. package/dist/chains/botanix/BotanixInitializer.js +125 -0
  6. package/dist/chains/citrea/CitreaBtcRelay.d.ts +21 -0
  7. package/dist/chains/citrea/CitreaBtcRelay.js +43 -0
  8. package/dist/chains/citrea/CitreaChainType.d.ts +14 -0
  9. package/dist/chains/citrea/CitreaChainType.js +2 -0
  10. package/dist/chains/citrea/CitreaFees.d.ts +29 -0
  11. package/dist/chains/citrea/CitreaFees.js +67 -0
  12. package/dist/chains/citrea/CitreaInitializer.d.ts +30 -0
  13. package/dist/chains/citrea/CitreaInitializer.js +132 -0
  14. package/dist/chains/citrea/CitreaSpvVaultContract.d.ts +15 -0
  15. package/dist/chains/citrea/CitreaSpvVaultContract.js +74 -0
  16. package/dist/chains/citrea/CitreaSwapContract.d.ts +22 -0
  17. package/dist/chains/citrea/CitreaSwapContract.js +96 -0
  18. package/dist/chains/citrea/CitreaTokens.d.ts +9 -0
  19. package/dist/chains/citrea/CitreaTokens.js +20 -0
  20. package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -0
  21. package/dist/evm/btcrelay/BtcRelayAbi.js +261 -0
  22. package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -0
  23. package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -0
  24. package/dist/evm/btcrelay/EVMBtcRelay.d.ts +197 -0
  25. package/dist/evm/btcrelay/EVMBtcRelay.js +435 -0
  26. package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -0
  27. package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -0
  28. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -0
  29. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -0
  30. package/dist/evm/chain/EVMChainInterface.d.ts +60 -0
  31. package/dist/evm/chain/EVMChainInterface.js +107 -0
  32. package/dist/evm/chain/EVMModule.d.ts +9 -0
  33. package/dist/evm/chain/EVMModule.js +13 -0
  34. package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -0
  35. package/dist/evm/chain/modules/ERC20Abi.js +225 -0
  36. package/dist/evm/chain/modules/EVMAddresses.d.ts +10 -0
  37. package/dist/evm/chain/modules/EVMAddresses.js +30 -0
  38. package/dist/evm/chain/modules/EVMBlocks.d.ts +27 -0
  39. package/dist/evm/chain/modules/EVMBlocks.js +73 -0
  40. package/dist/evm/chain/modules/EVMEvents.d.ts +46 -0
  41. package/dist/evm/chain/modules/EVMEvents.js +151 -0
  42. package/dist/evm/chain/modules/EVMFees.d.ts +36 -0
  43. package/dist/evm/chain/modules/EVMFees.js +74 -0
  44. package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -0
  45. package/dist/evm/chain/modules/EVMSignatures.js +68 -0
  46. package/dist/evm/chain/modules/EVMTokens.d.ts +70 -0
  47. package/dist/evm/chain/modules/EVMTokens.js +142 -0
  48. package/dist/evm/chain/modules/EVMTransactions.d.ts +94 -0
  49. package/dist/evm/chain/modules/EVMTransactions.js +307 -0
  50. package/dist/evm/contract/EVMContractBase.d.ts +22 -0
  51. package/dist/evm/contract/EVMContractBase.js +34 -0
  52. package/dist/evm/contract/EVMContractModule.d.ts +8 -0
  53. package/dist/evm/contract/EVMContractModule.js +11 -0
  54. package/dist/evm/contract/modules/EVMContractEvents.d.ts +43 -0
  55. package/dist/evm/contract/modules/EVMContractEvents.js +76 -0
  56. package/dist/evm/events/EVMChainEvents.d.ts +22 -0
  57. package/dist/evm/events/EVMChainEvents.js +69 -0
  58. package/dist/evm/events/EVMChainEventsBrowser.d.ts +102 -0
  59. package/dist/evm/events/EVMChainEventsBrowser.js +412 -0
  60. package/dist/evm/providers/JsonRpcProviderWithRetries.d.ts +16 -0
  61. package/dist/evm/providers/JsonRpcProviderWithRetries.js +27 -0
  62. package/dist/evm/providers/ReconnectingWebSocketProvider.d.ts +22 -0
  63. package/dist/evm/providers/ReconnectingWebSocketProvider.js +91 -0
  64. package/dist/evm/providers/SocketProvider.d.ts +111 -0
  65. package/dist/evm/providers/SocketProvider.js +336 -0
  66. package/dist/evm/providers/WebSocketProviderWithRetries.d.ts +17 -0
  67. package/dist/evm/providers/WebSocketProviderWithRetries.js +23 -0
  68. package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +110 -0
  69. package/dist/evm/spv_swap/EVMSpvVaultContract.js +629 -0
  70. package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +40 -0
  71. package/dist/evm/spv_swap/EVMSpvVaultData.js +184 -0
  72. package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -0
  73. package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -0
  74. package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -0
  75. package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -0
  76. package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -0
  77. package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -0
  78. package/dist/evm/swaps/EVMSwapContract.d.ts +199 -0
  79. package/dist/evm/swaps/EVMSwapContract.js +394 -0
  80. package/dist/evm/swaps/EVMSwapData.d.ts +66 -0
  81. package/dist/evm/swaps/EVMSwapData.js +260 -0
  82. package/dist/evm/swaps/EVMSwapModule.d.ts +9 -0
  83. package/dist/evm/swaps/EVMSwapModule.js +11 -0
  84. package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -0
  85. package/dist/evm/swaps/EscrowManagerAbi.js +985 -0
  86. package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -0
  87. package/dist/evm/swaps/EscrowManagerTypechain.js +2 -0
  88. package/dist/evm/swaps/handlers/IHandler.d.ts +13 -0
  89. package/dist/evm/swaps/handlers/IHandler.js +2 -0
  90. package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -0
  91. package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -0
  92. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -0
  93. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -0
  94. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -0
  95. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -0
  96. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -0
  97. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -0
  98. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -0
  99. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -0
  100. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -0
  101. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -0
  102. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -0
  103. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -0
  104. package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -0
  105. package/dist/evm/swaps/modules/EVMLpVault.js +134 -0
  106. package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +54 -0
  107. package/dist/evm/swaps/modules/EVMSwapClaim.js +137 -0
  108. package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -0
  109. package/dist/evm/swaps/modules/EVMSwapInit.js +274 -0
  110. package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -0
  111. package/dist/evm/swaps/modules/EVMSwapRefund.js +167 -0
  112. package/dist/evm/typechain/common.d.ts +50 -0
  113. package/dist/evm/typechain/common.js +2 -0
  114. package/dist/evm/wallet/EVMBrowserSigner.d.ts +5 -0
  115. package/dist/evm/wallet/EVMBrowserSigner.js +11 -0
  116. package/dist/evm/wallet/EVMPersistentSigner.d.ts +29 -0
  117. package/dist/evm/wallet/EVMPersistentSigner.js +230 -0
  118. package/dist/evm/wallet/EVMSigner.d.ts +12 -0
  119. package/dist/evm/wallet/EVMSigner.js +28 -0
  120. package/dist/index.d.ts +44 -0
  121. package/dist/index.js +60 -0
  122. package/dist/utils/Utils.d.ts +19 -0
  123. package/dist/utils/Utils.js +98 -0
  124. package/package.json +39 -0
  125. package/src/chains/botanix/BotanixChainType.ts +30 -0
  126. package/src/chains/botanix/BotanixInitializer.ts +175 -0
  127. package/src/chains/citrea/CitreaBtcRelay.ts +58 -0
  128. package/src/chains/citrea/CitreaChainType.ts +30 -0
  129. package/src/chains/citrea/CitreaFees.ts +77 -0
  130. package/src/chains/citrea/CitreaInitializer.ts +182 -0
  131. package/src/chains/citrea/CitreaSpvVaultContract.ts +75 -0
  132. package/src/chains/citrea/CitreaSwapContract.ts +103 -0
  133. package/src/chains/citrea/CitreaTokens.ts +22 -0
  134. package/src/evm/btcrelay/BtcRelayAbi.ts +258 -0
  135. package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -0
  136. package/src/evm/btcrelay/EVMBtcRelay.ts +537 -0
  137. package/src/evm/btcrelay/headers/EVMBtcHeader.ts +110 -0
  138. package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +153 -0
  139. package/src/evm/chain/EVMChainInterface.ts +189 -0
  140. package/src/evm/chain/EVMModule.ts +21 -0
  141. package/src/evm/chain/modules/ERC20Abi.ts +222 -0
  142. package/src/evm/chain/modules/EVMAddresses.ts +29 -0
  143. package/src/evm/chain/modules/EVMBlocks.ts +86 -0
  144. package/src/evm/chain/modules/EVMEvents.ts +182 -0
  145. package/src/evm/chain/modules/EVMFees.ts +104 -0
  146. package/src/evm/chain/modules/EVMSignatures.ts +76 -0
  147. package/src/evm/chain/modules/EVMTokens.ts +155 -0
  148. package/src/evm/chain/modules/EVMTransactions.ts +346 -0
  149. package/src/evm/contract/EVMContractBase.ts +63 -0
  150. package/src/evm/contract/EVMContractModule.ts +16 -0
  151. package/src/evm/contract/modules/EVMContractEvents.ts +104 -0
  152. package/src/evm/events/EVMChainEvents.ts +82 -0
  153. package/src/evm/events/EVMChainEventsBrowser.ts +533 -0
  154. package/src/evm/providers/JsonRpcProviderWithRetries.ts +34 -0
  155. package/src/evm/providers/ReconnectingWebSocketProvider.ts +107 -0
  156. package/src/evm/providers/SocketProvider.ts +371 -0
  157. package/src/evm/providers/WebSocketProviderWithRetries.ts +35 -0
  158. package/src/evm/spv_swap/EVMSpvVaultContract.ts +779 -0
  159. package/src/evm/spv_swap/EVMSpvVaultData.ts +228 -0
  160. package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -0
  161. package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -0
  162. package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -0
  163. package/src/evm/swaps/EVMSwapContract.ts +621 -0
  164. package/src/evm/swaps/EVMSwapData.ts +378 -0
  165. package/src/evm/swaps/EVMSwapModule.ts +16 -0
  166. package/src/evm/swaps/EscrowManagerAbi.ts +982 -0
  167. package/src/evm/swaps/EscrowManagerTypechain.ts +723 -0
  168. package/src/evm/swaps/handlers/IHandler.ts +17 -0
  169. package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -0
  170. package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +47 -0
  171. package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -0
  172. package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -0
  173. package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -0
  174. package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -0
  175. package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +38 -0
  176. package/src/evm/swaps/modules/EVMLpVault.ts +155 -0
  177. package/src/evm/swaps/modules/EVMSwapClaim.ts +173 -0
  178. package/src/evm/swaps/modules/EVMSwapInit.ts +329 -0
  179. package/src/evm/swaps/modules/EVMSwapRefund.ts +229 -0
  180. package/src/evm/typechain/common.ts +131 -0
  181. package/src/evm/wallet/EVMBrowserSigner.ts +12 -0
  182. package/src/evm/wallet/EVMPersistentSigner.ts +307 -0
  183. package/src/evm/wallet/EVMSigner.ts +35 -0
  184. package/src/index.ts +53 -0
  185. package/src/utils/Utils.ts +111 -0
@@ -0,0 +1,779 @@
1
+ import {
2
+ BigIntBufferUtils,
3
+ BitcoinRpc,
4
+ BtcTx,
5
+ RelaySynchronizer,
6
+ SpvVaultContract,
7
+ SpvVaultTokenData,
8
+ SpvWithdrawalState,
9
+ SpvWithdrawalStateType,
10
+ SpvWithdrawalTransactionData,
11
+ TransactionConfirmationOptions
12
+ } from "@atomiqlabs/base";
13
+ import {Buffer} from "buffer";
14
+ import {EVMTx} from "../chain/modules/EVMTransactions";
15
+ import {EVMContractBase} from "../contract/EVMContractBase";
16
+ import {EVMSigner} from "../wallet/EVMSigner";
17
+ import {SpvVaultContractAbi} from "./SpvVaultContractAbi";
18
+ import {SpvVaultManager, SpvVaultParametersStructOutput} from "./SpvVaultContractTypechain";
19
+ import {EVMBtcRelay} from "../btcrelay/EVMBtcRelay";
20
+ import {getLogger} from "../../utils/Utils";
21
+ import {EVMChainInterface} from "../chain/EVMChainInterface";
22
+ import {AbiCoder, getAddress, hexlify, keccak256, TransactionRequest, ZeroAddress, ZeroHash} from "ethers";
23
+ import {EVMAddresses} from "../chain/modules/EVMAddresses";
24
+ import {EVMSpvVaultData, getVaultParamsCommitment, getVaultUtxoFromState} from "./EVMSpvVaultData";
25
+ import {EVMSpvWithdrawalData} from "./EVMSpvWithdrawalData";
26
+ import {EVMFees} from "../chain/modules/EVMFees";
27
+ import {EVMBtcStoredHeader} from "../btcrelay/headers/EVMBtcStoredHeader";
28
+ import {TypedEventLog} from "../typechain/common";
29
+ import {PromiseLruCache} from "promise-cache-ts";
30
+
31
+ function decodeUtxo(utxo: string): {txHash: string, vout: bigint} {
32
+ const [txId, vout] = utxo.split(":");
33
+ return {
34
+ txHash: "0x"+Buffer.from(txId, "hex").reverse().toString("hex"),
35
+ vout: BigInt(vout)
36
+ }
37
+ }
38
+
39
+ export function packOwnerAndVaultId(owner: string, vaultId: bigint): string {
40
+ if(owner.length!==42) throw new Error("Invalid owner address");
41
+ return owner.toLowerCase() + BigIntBufferUtils.toBuffer(vaultId, "be", 12).toString("hex");
42
+ }
43
+
44
+ export function unpackOwnerAndVaultId(data: string): [string, bigint] {
45
+ return [getAddress(data.substring(0, 42)), BigInt("0x"+data.substring(42, 66))];
46
+ }
47
+
48
+ export class EVMSpvVaultContract<ChainId extends string>
49
+ extends EVMContractBase<SpvVaultManager>
50
+ implements SpvVaultContract<
51
+ EVMTx,
52
+ EVMSigner,
53
+ ChainId,
54
+ EVMSpvVaultData,
55
+ EVMSpvWithdrawalData
56
+ >
57
+ {
58
+ public static readonly GasCosts = {
59
+ DEPOSIT_BASE: 15_000 + 21_000,
60
+ DEPOSIT_ERC20: 40_000,
61
+
62
+ OPEN: 80_000 + 21_000,
63
+
64
+ CLAIM_BASE: 85_000 + 21_000,
65
+ CLAIM_NATIVE_TRANSFER: 35_000,
66
+ CLAIM_ERC20_TRANSFER: 40_000,
67
+ CLAIM_EXECUTION_SCHEDULE: 30_000,
68
+
69
+ FRONT_BASE: 75_000 + 21_000,
70
+ FRONT_NATIVE_TRANSFER: 35_000,
71
+ FRONT_ERC20_TRANSFER: 40_000,
72
+ FRONT_EXECUTION_SCHEDULE: 30_000
73
+ };
74
+
75
+ readonly chainId: ChainId;
76
+
77
+ readonly btcRelay: EVMBtcRelay<any>;
78
+ readonly bitcoinRpc: BitcoinRpc<any>;
79
+ readonly claimTimeout: number = 180;
80
+
81
+ readonly logger = getLogger("EVMSpvVaultContract: ");
82
+
83
+ constructor(
84
+ chainInterface: EVMChainInterface<ChainId>,
85
+ btcRelay: EVMBtcRelay<any>,
86
+ bitcoinRpc: BitcoinRpc<any>,
87
+ contractAddress: string,
88
+ contractDeploymentHeight?: number
89
+ ) {
90
+ super(chainInterface, contractAddress, SpvVaultContractAbi, contractDeploymentHeight);
91
+ this.btcRelay = btcRelay;
92
+ this.bitcoinRpc = bitcoinRpc;
93
+ }
94
+
95
+ //Transactions
96
+ protected async Open(signer: string, vault: EVMSpvVaultData, feeRate: string): Promise<TransactionRequest> {
97
+ const {txHash, vout} = decodeUtxo(vault.getUtxo());
98
+
99
+ const tokens = vault.getTokenData();
100
+ if(tokens.length!==2) throw new Error("Must specify exactly 2 tokens for vault!");
101
+
102
+ const tx = await this.contract.open.populateTransaction(vault.vaultId, vault.getVaultParamsStruct(), txHash, vout);
103
+ tx.from = signer;
104
+ EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.OPEN, feeRate);
105
+
106
+ return tx;
107
+ }
108
+
109
+ protected async Deposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate: string): Promise<TransactionRequest> {
110
+ let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
111
+ let value = 0n;
112
+ if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
113
+ value += rawAmounts[0] * vault.token0.multiplier;
114
+ } else {
115
+ if(rawAmounts[0] > 0n) totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
116
+ }
117
+ if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase()) {
118
+ value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
119
+ } else {
120
+ if(rawAmounts[1]!=null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase()!==vault.token1.token.toLowerCase())
121
+ totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
122
+ }
123
+
124
+ const tx = await this.contract.deposit.populateTransaction(
125
+ vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
126
+ rawAmounts[0], rawAmounts[1] ?? 0n, { value }
127
+ );
128
+ tx.from = signer;
129
+ EVMFees.applyFeeRate(tx, totalGas, feeRate);
130
+
131
+ return tx;
132
+ }
133
+
134
+ protected async Front(
135
+ signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData, withdrawalSequence: number, feeRate: string
136
+ ): Promise<TransactionRequest> {
137
+ let value = 0n;
138
+ const frontingAmount = data.getFrontingAmount();
139
+ if(vault.token0.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
140
+ value += frontingAmount[0] * vault.token0.multiplier;
141
+ if(vault.token1.token.toLowerCase()===this.Chain.getNativeCurrencyAddress().toLowerCase())
142
+ value += (frontingAmount[1] ?? 0n) * vault.token1.multiplier;
143
+
144
+ const tx = await this.contract.front.populateTransaction(
145
+ vault.owner, vault.vaultId, vault.getVaultParamsStruct(),
146
+ withdrawalSequence, data.getTxHash(), data.serializeToStruct(),
147
+ { value }
148
+ );
149
+ tx.from = signer;
150
+ EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
151
+
152
+ return tx;
153
+ }
154
+
155
+ protected async Claim(
156
+ signer: string, vault: EVMSpvVaultData, data: EVMSpvWithdrawalData,
157
+ blockheader: EVMBtcStoredHeader, merkle: Buffer[], position: number, feeRate: string
158
+ ): Promise<TransactionRequest> {
159
+ const tx = await this.contract.claim.populateTransaction(
160
+ vault.owner, vault.vaultId, vault.getVaultParamsStruct(), "0x"+data.btcTx.hex,
161
+ blockheader.serializeToStruct(), merkle, position
162
+ )
163
+
164
+ tx.from = signer;
165
+ EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
166
+
167
+ return tx;
168
+ }
169
+
170
+ async checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void> {
171
+ const result = await this.contract.parseBitcoinTx(Buffer.from(tx.btcTx.hex, "hex"));
172
+ if(result==null) throw new Error("Failed to parse transaction!");
173
+ }
174
+
175
+ createVaultData(
176
+ owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]
177
+ ): Promise<EVMSpvVaultData> {
178
+ if(tokenData.length!==2) throw new Error("Must specify 2 tokens in tokenData!");
179
+
180
+ const vaultParams = {
181
+ btcRelayContract: this.btcRelay.contractAddress,
182
+ token0: tokenData[0].token,
183
+ token1: tokenData[1].token,
184
+ token0Multiplier: tokenData[0].multiplier,
185
+ token1Multiplier: tokenData[1].multiplier,
186
+ confirmations: BigInt(confirmations)
187
+ };
188
+
189
+ const spvVaultParametersCommitment = keccak256(AbiCoder.defaultAbiCoder().encode(
190
+ ["address", "address", "address", "uint192", "uint192", "uint256"],
191
+ [vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]
192
+ ));
193
+
194
+ return Promise.resolve(new EVMSpvVaultData(owner, vaultId, {
195
+ spvVaultParametersCommitment,
196
+ utxoTxHash: ZeroHash,
197
+ utxoVout: 0n,
198
+ openBlockheight: 0n,
199
+ withdrawCount: 0n,
200
+ depositCount: 0n,
201
+ token0Amount: 0n,
202
+ token1Amount: 0n
203
+ }, vaultParams, utxo));
204
+ }
205
+
206
+ //Getters
207
+ async getFronterAddress(owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData): Promise<string | null> {
208
+ const frontingAddress = await this.contract.getFronterById(owner, vaultId, "0x"+withdrawal.getFrontingId());
209
+ if(frontingAddress===ZeroAddress) return null;
210
+ return frontingAddress;
211
+ }
212
+
213
+ async getFronterAddresses(withdrawals: {owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData}[]): Promise<{[btcTxId: string]: string | null}> {
214
+ const result: {
215
+ [btcTxId: string]: string | null
216
+ } = {};
217
+ let promises: Promise<void>[] = [];
218
+ //TODO: We can upgrade this to use multicall
219
+ for(let {owner, vaultId, withdrawal} of withdrawals) {
220
+ promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
221
+ result[withdrawal.getTxId()] = val;
222
+ }));
223
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
224
+ await Promise.all(promises);
225
+ promises = [];
226
+ }
227
+ }
228
+ await Promise.all(promises);
229
+ return result;
230
+ }
231
+
232
+ private vaultParamsCache: PromiseLruCache<string, SpvVaultParametersStructOutput> = new PromiseLruCache<string, SpvVaultParametersStructOutput>(5000);
233
+
234
+ async getVaultData(owner: string, vaultId: bigint): Promise<EVMSpvVaultData> {
235
+ const vaultState = await this.contract.getVault(owner, vaultId);
236
+
237
+ const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
238
+ const blockheight = Number(vaultState.openBlockheight);
239
+ const events = await this.Events.getContractBlockEvents(
240
+ ["Opened"],
241
+ [
242
+ "0x"+owner.substring(2).padStart(64, "0"),
243
+ hexlify(BigIntBufferUtils.toBuffer(vaultId, "be", 32))
244
+ ],
245
+ blockheight
246
+ );
247
+
248
+ const foundEvent = events.find(
249
+ event => getVaultParamsCommitment(event.args.params)===vaultState.spvVaultParametersCommitment
250
+ );
251
+ if(foundEvent==null) throw new Error("Valid open event not found!");
252
+
253
+ return foundEvent.args.params;
254
+ });
255
+
256
+ if(vaultParams.btcRelayContract.toLowerCase()!==this.btcRelay.contractAddress.toLowerCase()) return null;
257
+
258
+ return new EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
259
+ }
260
+
261
+ async getMultipleVaultData(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: EVMSpvVaultData}}> {
262
+ const result: {[owner: string]: {[vaultId: string]: EVMSpvVaultData}} = {};
263
+ let promises: Promise<void>[] = [];
264
+ //TODO: We can upgrade this to use multicall
265
+ for(let {owner, vaultId} of vaults) {
266
+ promises.push(this.getVaultData(owner, vaultId).then(val => {
267
+ result[owner] ??= {};
268
+ result[owner][vaultId.toString(10)] = val;
269
+ }));
270
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
271
+ await Promise.all(promises);
272
+ promises = [];
273
+ }
274
+ }
275
+ await Promise.all(promises);
276
+ return result;
277
+ }
278
+
279
+ async getVaultLatestUtxo(owner: string, vaultId: bigint): Promise<string | null> {
280
+ const vaultState = await this.contract.getVault(owner, vaultId);
281
+ const utxo = getVaultUtxoFromState(vaultState);
282
+ if(utxo==="0000000000000000000000000000000000000000000000000000000000000000:0") return null;
283
+ return utxo;
284
+ }
285
+
286
+ async getVaultLatestUtxos(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: string | null}}> {
287
+ const result: {[owner: string]: {[vaultId: string]: string | null}} = {};
288
+ let promises: Promise<void>[] = [];
289
+ //TODO: We can upgrade this to use multicall
290
+ for(let {owner, vaultId} of vaults) {
291
+ promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
292
+ result[owner] ??= {};
293
+ result[owner][vaultId.toString(10)] = val;
294
+ }));
295
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
296
+ await Promise.all(promises);
297
+ promises = [];
298
+ }
299
+ }
300
+ await Promise.all(promises);
301
+ return result;
302
+ }
303
+
304
+ async getAllVaults(owner?: string): Promise<EVMSpvVaultData[]> {
305
+ const openedVaults = new Map<string, SpvVaultParametersStructOutput>();
306
+ await this.Events.findInContractEventsForward(
307
+ ["Opened", "Closed"],
308
+ owner==null ? null : [
309
+ "0x"+owner.substring(2).padStart(64, "0")
310
+ ],
311
+ (event) => {
312
+ const vaultIdentifier = event.args.owner+":"+event.args.vaultId.toString(10);
313
+ if(event.eventName==="Opened") {
314
+ const _event = event as TypedEventLog<SpvVaultManager["filters"]["Opened"]>;
315
+ openedVaults.set(vaultIdentifier, _event.args.params);
316
+ } else {
317
+ openedVaults.delete(vaultIdentifier);
318
+ }
319
+ return null;
320
+ }
321
+ );
322
+ const vaults: EVMSpvVaultData[] = [];
323
+ for(let [identifier, vaultParams] of openedVaults.entries()) {
324
+ const [owner, vaultIdStr] = identifier.split(":");
325
+
326
+ const vaultState = await this.contract.getVault(owner, BigInt(vaultIdStr));
327
+ if(vaultState.spvVaultParametersCommitment === getVaultParamsCommitment(vaultParams)) {
328
+ vaults.push(new EVMSpvVaultData(owner, BigInt(vaultIdStr), vaultState, vaultParams))
329
+ }
330
+ }
331
+ return vaults;
332
+ }
333
+
334
+ private parseWithdrawalEvent(event: TypedEventLog<SpvVaultManager["filters"][keyof SpvVaultManager["filters"]]>): SpvWithdrawalState | null {
335
+ switch(event.eventName) {
336
+ case "Fronted":
337
+ const frontedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Fronted"]>;
338
+ const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
339
+ return {
340
+ type: SpvWithdrawalStateType.FRONTED,
341
+ txId: event.transactionHash,
342
+ owner: ownerFront,
343
+ vaultId: vaultIdFront,
344
+ recipient: frontedEvent.args.recipient,
345
+ fronter: frontedEvent.args.caller
346
+ };
347
+ case "Claimed":
348
+ const claimedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Claimed"]>;
349
+ const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
350
+ return {
351
+ type: SpvWithdrawalStateType.CLAIMED,
352
+ txId: event.transactionHash,
353
+ owner: ownerClaim,
354
+ vaultId: vaultIdClaim,
355
+ recipient: claimedEvent.args.recipient,
356
+ claimer: claimedEvent.args.caller,
357
+ fronter: claimedEvent.args.frontingAddress
358
+ };
359
+ case "Closed":
360
+ const closedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Closed"]>;
361
+ return {
362
+ type: SpvWithdrawalStateType.CLOSED,
363
+ txId: event.transactionHash,
364
+ owner: closedEvent.args.owner,
365
+ vaultId: closedEvent.args.vaultId,
366
+ error: closedEvent.args.error
367
+ };
368
+ default:
369
+ return null;
370
+ }
371
+ }
372
+
373
+ async getWithdrawalState(withdrawalTx: EVMSpvWithdrawalData, scStartHeight?: number): Promise<SpvWithdrawalState> {
374
+ const txHash = Buffer.from(withdrawalTx.getTxId(), "hex").reverse();
375
+
376
+ const events: ["Fronted", "Claimed", "Closed"] = ["Fronted", "Claimed", "Closed"];
377
+ const keys = [null, null, hexlify(txHash)];
378
+
379
+ let result: SpvWithdrawalState;
380
+ if(scStartHeight==null) {
381
+ result = await this.Events.findInContractEvents(
382
+ events, keys,
383
+ async (event) => {
384
+ const result = this.parseWithdrawalEvent(event);
385
+ if(result!=null) return result;
386
+ }
387
+ );
388
+ } else {
389
+ result = await this.Events.findInContractEventsForward(
390
+ events, keys,
391
+ async (event) => {
392
+ const result = this.parseWithdrawalEvent(event);
393
+ if(result==null) return;
394
+ if(result.type===SpvWithdrawalStateType.FRONTED) {
395
+ //Check if still fronted
396
+ const fronterAddress = await this.getFronterAddress(result.owner, result.vaultId, withdrawalTx);
397
+ //Not fronted now, there should be a claim/close event after the front event, continue
398
+ if(fronterAddress==null) return;
399
+ }
400
+ return result;
401
+ },
402
+ scStartHeight
403
+ );
404
+ }
405
+ result ??= {
406
+ type: SpvWithdrawalStateType.NOT_FOUND
407
+ };
408
+ return result;
409
+ }
410
+
411
+ async getWithdrawalStates(withdrawalTxs: {withdrawal: EVMSpvWithdrawalData, scStartHeight?: number}[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
412
+ const result: {[btcTxId: string]: SpvWithdrawalState} = {};
413
+
414
+ const events: ["Fronted", "Claimed", "Closed"] = ["Fronted", "Claimed", "Closed"];
415
+
416
+ for(let i=0;i<withdrawalTxs.length;i+=this.Chain.config.maxLogTopics) {
417
+ const checkWithdrawalTxs = withdrawalTxs.slice(i, i+this.Chain.config.maxLogTopics);
418
+ const checkWithdrawalTxsMap = new Map(checkWithdrawalTxs.map(val => [val.withdrawal.getTxId() as string, val.withdrawal]));
419
+
420
+ let scStartHeight = null;
421
+ for(let val of checkWithdrawalTxs) {
422
+ if(val.scStartHeight==null) {
423
+ scStartHeight = null;
424
+ break;
425
+ }
426
+ scStartHeight = Math.min(scStartHeight ?? Infinity, val.scStartHeight);
427
+ }
428
+
429
+ const keys = [null, null, checkWithdrawalTxs.map(withdrawal => hexlify(Buffer.from(withdrawal.withdrawal.getTxId(), "hex").reverse()))];
430
+
431
+ if(scStartHeight==null) {
432
+ await this.Events.findInContractEvents(
433
+ events, keys,
434
+ async (event) => {
435
+ const _event = event as TypedEventLog<SpvVaultManager["filters"]["Fronted" | "Claimed" | "Closed"]>;
436
+ const btcTxId = Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
437
+ if(!checkWithdrawalTxsMap.has(btcTxId)) {
438
+ this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`)
439
+ return null;
440
+ }
441
+ const eventResult = this.parseWithdrawalEvent(event);
442
+ if(eventResult==null) return null;
443
+ checkWithdrawalTxsMap.delete(btcTxId);
444
+ result[btcTxId] = eventResult;
445
+ if(checkWithdrawalTxsMap.size===0) return true; //All processed
446
+ }
447
+ );
448
+ } else {
449
+ await this.Events.findInContractEventsForward(
450
+ events, keys,
451
+ async (event) => {
452
+ const _event = event as TypedEventLog<SpvVaultManager["filters"]["Fronted" | "Claimed" | "Closed"]>;
453
+ const btcTxId = Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
454
+ const withdrawalTx = checkWithdrawalTxsMap.get(btcTxId);
455
+ if(withdrawalTx==null) {
456
+ this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`)
457
+ return;
458
+ }
459
+ const eventResult = this.parseWithdrawalEvent(event);
460
+ if(eventResult==null) return;
461
+
462
+ if(eventResult.type===SpvWithdrawalStateType.FRONTED) {
463
+ //Check if still fronted
464
+ const fronterAddress = await this.getFronterAddress(eventResult.owner, eventResult.vaultId, withdrawalTx);
465
+ //Not fronted now, so there should be a claim/close event after the front event, continue
466
+ if(fronterAddress==null) return;
467
+ //Fronted still, so this should be the latest current state
468
+ }
469
+
470
+ checkWithdrawalTxsMap.delete(btcTxId);
471
+ result[btcTxId] = eventResult;
472
+ if(checkWithdrawalTxsMap.size===0) return true; //All processed
473
+ },
474
+ scStartHeight
475
+ );
476
+ }
477
+ }
478
+
479
+ for(let val of withdrawalTxs) {
480
+ result[val.withdrawal.getTxId()] ??= {
481
+ type: SpvWithdrawalStateType.NOT_FOUND
482
+ };
483
+ }
484
+
485
+ return result;
486
+ }
487
+
488
+ getWithdrawalData(btcTx: BtcTx): Promise<EVMSpvWithdrawalData> {
489
+ return Promise.resolve(new EVMSpvWithdrawalData(btcTx));
490
+ }
491
+
492
+ //OP_RETURN data encoding/decoding
493
+ fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
494
+ return EVMSpvVaultContract.fromOpReturnData(data);
495
+ }
496
+ static fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
497
+ let rawAmount0: bigint = 0n;
498
+ let rawAmount1: bigint = 0n;
499
+ let executionHash: string = null;
500
+ if(data.length===28) {
501
+ rawAmount0 = data.readBigInt64BE(20).valueOf();
502
+ } else if(data.length===36) {
503
+ rawAmount0 = data.readBigInt64BE(20).valueOf();
504
+ rawAmount1 = data.readBigInt64BE(28).valueOf();
505
+ } else if(data.length===60) {
506
+ rawAmount0 = data.readBigInt64BE(20).valueOf();
507
+ executionHash = data.slice(28, 60).toString("hex");
508
+ } else if(data.length===68) {
509
+ rawAmount0 = data.readBigInt64BE(20).valueOf();
510
+ rawAmount1 = data.readBigInt64BE(28).valueOf();
511
+ executionHash = data.slice(36, 68).toString("hex");
512
+ } else {
513
+ throw new Error("Invalid OP_RETURN data length!");
514
+ }
515
+
516
+ const recipient = "0x"+data.slice(0, 20).toString("hex");
517
+ if(!EVMAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
518
+
519
+ return {executionHash, rawAmounts: [rawAmount0, rawAmount1], recipient: getAddress(recipient)};
520
+ }
521
+
522
+ toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
523
+ return EVMSpvVaultContract.toOpReturnData(recipient, rawAmounts, executionHash);
524
+ }
525
+ static toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
526
+ if(!EVMAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
527
+ if(rawAmounts.length < 1) throw new Error("At least 1 amount needs to be specified");
528
+ if(rawAmounts.length > 2) throw new Error("At most 2 amounts need to be specified");
529
+ rawAmounts.forEach(val => {
530
+ if(val < 0n) throw new Error("Negative raw amount specified");
531
+ if(val >= 2n**64n) throw new Error("Raw amount overflow");
532
+ });
533
+ if(executionHash!=null) {
534
+ if(Buffer.from(executionHash, "hex").length !== 32)
535
+ throw new Error("Invalid execution hash");
536
+ }
537
+ const recipientBuffer = Buffer.from(recipient.substring(2).padStart(40, "0"), "hex");
538
+ const amount0Buffer = BigIntBufferUtils.toBuffer(rawAmounts[0], "be", 8);
539
+ const amount1Buffer = rawAmounts[1]==null || rawAmounts[1]===0n ? Buffer.alloc(0) : BigIntBufferUtils.toBuffer(rawAmounts[1], "be", 8);
540
+ const executionHashBuffer = executionHash==null ? Buffer.alloc(0) : Buffer.from(executionHash, "hex");
541
+
542
+ return Buffer.concat([
543
+ recipientBuffer,
544
+ amount0Buffer,
545
+ amount1Buffer,
546
+ executionHashBuffer
547
+ ]);
548
+ }
549
+
550
+ //Actions
551
+ async claim(signer: EVMSigner, vault: EVMSpvVaultData, txs: {tx: EVMSpvWithdrawalData, storedHeader?: EVMBtcStoredHeader}[], synchronizer?: RelaySynchronizer<any, any, any>, initAta?: boolean, txOptions?: TransactionConfirmationOptions): Promise<string> {
552
+ const result = await this.txsClaim(signer.getAddress(), vault, txs, synchronizer, initAta, txOptions?.feeRate);
553
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
554
+ return signature;
555
+ }
556
+
557
+ async deposit(signer: EVMSigner, vault: EVMSpvVaultData, rawAmounts: bigint[], txOptions?: TransactionConfirmationOptions): Promise<string> {
558
+ const result = await this.txsDeposit(signer.getAddress(), vault, rawAmounts, txOptions?.feeRate);
559
+ const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
560
+ return txHashes[txHashes.length - 1];
561
+ }
562
+
563
+ async frontLiquidity(signer: EVMSigner, vault: EVMSpvVaultData, realWithdrawalTx: EVMSpvWithdrawalData, withdrawSequence: number, txOptions?: TransactionConfirmationOptions): Promise<string> {
564
+ const result = await this.txsFrontLiquidity(signer.getAddress(), vault, realWithdrawalTx, withdrawSequence, txOptions?.feeRate);
565
+ const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
566
+ return txHashes[txHashes.length - 1];
567
+ }
568
+
569
+ async open(signer: EVMSigner, vault: EVMSpvVaultData, txOptions?: TransactionConfirmationOptions): Promise<string> {
570
+ const result = await this.txsOpen(signer.getAddress(), vault, txOptions?.feeRate);
571
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
572
+ return signature;
573
+ }
574
+
575
+ //Transactions
576
+ async txsClaim(
577
+ signer: string, vault: EVMSpvVaultData, txs: {
578
+ tx: EVMSpvWithdrawalData,
579
+ storedHeader?: EVMBtcStoredHeader
580
+ }[], synchronizer?: RelaySynchronizer<any, any, any>,
581
+ initAta?: boolean, feeRate?: string
582
+ ): Promise<EVMTx[]> {
583
+ if(!vault.isOpened()) throw new Error("Cannot claim from a closed vault!");
584
+ feeRate ??= await this.Chain.Fees.getFeeRate();
585
+
586
+ const txsWithMerkleProofs: {
587
+ tx: EVMSpvWithdrawalData,
588
+ reversedTxId: Buffer,
589
+ pos: number,
590
+ blockheight: number,
591
+ merkle: Buffer[],
592
+ storedHeader?: EVMBtcStoredHeader
593
+ }[] = [];
594
+ for(let tx of txs) {
595
+ const merkleProof = await this.bitcoinRpc.getMerkleProof(tx.tx.btcTx.txid, tx.tx.btcTx.blockhash);
596
+ this.logger.debug("txsClaim(): merkle proof computed: ", merkleProof);
597
+ txsWithMerkleProofs.push({
598
+ ...merkleProof,
599
+ ...tx
600
+ });
601
+ }
602
+
603
+ const evmTxs: EVMTx[] = [];
604
+ const storedHeaders: {[blockhash: string]: EVMBtcStoredHeader} = await EVMBtcRelay.getCommitedHeadersAndSynchronize(
605
+ signer, this.btcRelay, txsWithMerkleProofs.filter(tx => tx.storedHeader==null).map(tx => {
606
+ return {
607
+ blockhash: tx.tx.btcTx.blockhash,
608
+ blockheight: tx.blockheight,
609
+ requiredConfirmations: vault.getConfirmations()
610
+ }
611
+ }), evmTxs, synchronizer, feeRate
612
+ );
613
+ if(storedHeaders==null) throw new Error("Cannot fetch committed header!");
614
+
615
+ for(let tx of txsWithMerkleProofs) {
616
+ evmTxs.push(await this.Claim(signer, vault, tx.tx, tx.storedHeader ?? storedHeaders[tx.tx.btcTx.blockhash], tx.merkle, tx.pos, feeRate));
617
+ }
618
+
619
+ this.logger.debug("txsClaim(): "+evmTxs.length+" claim TXs created claiming "+txs.length+" txs, owner: "+vault.getOwner()+
620
+ " vaultId: "+vault.getVaultId().toString(10));
621
+
622
+ return evmTxs;
623
+ }
624
+
625
+ async txsDeposit(signer: string, vault: EVMSpvVaultData, rawAmounts: bigint[], feeRate?: string): Promise<EVMTx[]> {
626
+ if(!vault.isOpened()) throw new Error("Cannot deposit to a closed vault!");
627
+ feeRate ??= await this.Chain.Fees.getFeeRate();
628
+
629
+ const txs: EVMTx[] = [];
630
+
631
+ let realAmount0: bigint = 0n;
632
+ let realAmount1: bigint = 0n;
633
+
634
+ //Approve first
635
+ const requiredApprovals: {[address: string]: bigint} = {};
636
+ if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
637
+ if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
638
+ realAmount0 = rawAmounts[0] * vault.token0.multiplier;
639
+ requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
640
+ }
641
+ }
642
+ if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
643
+ if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
644
+ realAmount1 = rawAmounts[1] * vault.token1.multiplier;
645
+ requiredApprovals[vault.token1.token.toLowerCase()] ??= 0n;
646
+ requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
647
+ }
648
+ }
649
+
650
+ const requiredApprovalTxns = await Promise.all(
651
+ Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate))
652
+ );
653
+ requiredApprovalTxns.forEach(tx => tx!=null && txs.push(tx));
654
+
655
+ txs.push(await this.Deposit(signer, vault, rawAmounts, feeRate));
656
+
657
+ this.logger.debug("txsDeposit(): deposit TX created,"+
658
+ " token0: "+vault.token0.token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
659
+ " token1: "+vault.token1.token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
660
+
661
+ return txs;
662
+ }
663
+
664
+ async txsFrontLiquidity(signer: string, vault: EVMSpvVaultData, realWithdrawalTx: EVMSpvWithdrawalData, withdrawSequence: number, feeRate?: string): Promise<EVMTx[]> {
665
+ if(!vault.isOpened()) throw new Error("Cannot front on a closed vault!");
666
+ feeRate ??= await this.Chain.Fees.getFeeRate();
667
+
668
+ const txs: EVMTx[] = [];
669
+
670
+ let realAmount0 = 0n;
671
+ let realAmount1 = 0n;
672
+
673
+ //Approve first
674
+ const rawAmounts = realWithdrawalTx.getFrontingAmount();
675
+ //Approve first
676
+ const requiredApprovals: {[address: string]: bigint} = {};
677
+ if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
678
+ if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
679
+ realAmount0 = rawAmounts[0] * vault.token0.multiplier;
680
+ requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
681
+ }
682
+ }
683
+ if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
684
+ if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
685
+ realAmount1 = rawAmounts[1] * vault.token1.multiplier;
686
+ requiredApprovals[vault.token1.token.toLowerCase()] ??= 0n;
687
+ requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
688
+ }
689
+ }
690
+
691
+ const requiredApprovalTxns = await Promise.all(
692
+ Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate))
693
+ );
694
+ requiredApprovalTxns.forEach(tx => tx!=null && txs.push(tx));
695
+
696
+ txs.push(await this.Front(signer, vault, realWithdrawalTx, withdrawSequence, feeRate));
697
+
698
+ this.logger.debug("txsFrontLiquidity(): front TX created,"+
699
+ " token0: "+vault.token0.token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
700
+ " token1: "+vault.token1.token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
701
+
702
+ return txs;
703
+ }
704
+
705
+ async txsOpen(signer: string, vault: EVMSpvVaultData, feeRate?: string): Promise<EVMTx[]> {
706
+ if(vault.isOpened()) throw new Error("Cannot open an already opened vault!");
707
+ feeRate ??= await this.Chain.Fees.getFeeRate();
708
+
709
+ const tx = await this.Open(signer, vault, feeRate);
710
+
711
+ this.logger.debug("txsOpen(): open TX created, owner: "+vault.getOwner()+
712
+ " vaultId: "+vault.getVaultId().toString(10));
713
+
714
+ return [tx];
715
+ }
716
+
717
+ getClaimGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
718
+ let totalGas = EVMSpvVaultContract.GasCosts.CLAIM_BASE;
719
+
720
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
721
+ const transferFee = vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
722
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
723
+ totalGas += transferFee;
724
+ if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
725
+ if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
726
+ }
727
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
728
+ const transferFee = vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
729
+ EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
730
+ totalGas += transferFee;
731
+ if (data==null || data.frontingFeeRate > 0n) totalGas += transferFee; //Also needs to pay out to fronter
732
+ if (data==null || (data.callerFeeRate > 0n && !data.isRecipient(signer))) totalGas += transferFee; //Also needs to pay out to caller
733
+ }
734
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.CLAIM_EXECUTION_SCHEDULE;
735
+
736
+ return totalGas;
737
+ }
738
+
739
+ getFrontGas(signer: string, vault: EVMSpvVaultData, data?: EVMSpvWithdrawalData): number {
740
+ let totalGas = EVMSpvVaultContract.GasCosts.FRONT_BASE;
741
+
742
+ if (data==null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
743
+ totalGas += vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
744
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
745
+ }
746
+ if (data==null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
747
+ totalGas += vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
748
+ EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
749
+ }
750
+ if (data==null || (data.executionHash != null && data.executionHash !== ZeroHash)) totalGas += EVMSpvVaultContract.GasCosts.FRONT_EXECUTION_SCHEDULE;
751
+
752
+ return totalGas;
753
+ }
754
+
755
+ async getClaimFee(signer: string, vault: EVMSpvVaultData, withdrawalData: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
756
+ feeRate ??= await this.Chain.Fees.getFeeRate();
757
+ return EVMFees.getGasFee(this.getClaimGas(signer, vault, withdrawalData), feeRate);
758
+ }
759
+
760
+ async getFrontFee(signer: string, vault?: EVMSpvVaultData, withdrawalData?: EVMSpvWithdrawalData, feeRate?: string): Promise<bigint> {
761
+ vault ??= EVMSpvVaultData.randomVault();
762
+ feeRate ??= await this.Chain.Fees.getFeeRate();
763
+ let totalFee = EVMFees.getGasFee(this.getFrontGas(signer, vault, withdrawalData), feeRate);
764
+ if(withdrawalData==null || (withdrawalData.rawAmounts[0]!=null && withdrawalData.rawAmounts[0]>0n)) {
765
+ if(vault.token0.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
766
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
767
+ }
768
+ }
769
+ if(withdrawalData==null || (withdrawalData.rawAmounts[1]!=null && withdrawalData.rawAmounts[1]>0n)) {
770
+ if(vault.token1.token.toLowerCase()!==this.Chain.getNativeCurrencyAddress().toLowerCase()) {
771
+ if(vault.token1.token.toLowerCase()!==vault.token0.token.toLowerCase() || withdrawalData==null || withdrawalData.rawAmounts[0]==null || withdrawalData.rawAmounts[0]===0n) {
772
+ totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
773
+ }
774
+ }
775
+ }
776
+ return totalFee;
777
+ }
778
+
779
+ }