@atomiqlabs/chain-evm 1.0.0-dev.38 → 1.0.0-dev.40

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