@atomiqlabs/chain-starknet 4.0.0-dev.3 → 4.0.0-dev.31

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 (154) hide show
  1. package/LICENSE +201 -201
  2. package/dist/index.d.ts +39 -38
  3. package/dist/index.js +55 -54
  4. package/dist/starknet/StarknetChainType.d.ts +13 -13
  5. package/dist/starknet/StarknetChainType.js +2 -2
  6. package/dist/starknet/StarknetInitializer.d.ts +28 -27
  7. package/dist/starknet/StarknetInitializer.js +69 -69
  8. package/dist/starknet/btcrelay/BtcRelayAbi.d.ts +250 -250
  9. package/dist/starknet/btcrelay/BtcRelayAbi.js +341 -341
  10. package/dist/starknet/btcrelay/StarknetBtcRelay.d.ts +186 -186
  11. package/dist/starknet/btcrelay/StarknetBtcRelay.js +379 -379
  12. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.d.ts +31 -31
  13. package/dist/starknet/btcrelay/headers/StarknetBtcHeader.js +74 -74
  14. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.d.ts +51 -51
  15. package/dist/starknet/btcrelay/headers/StarknetBtcStoredHeader.js +113 -113
  16. package/dist/starknet/chain/StarknetAction.d.ts +19 -19
  17. package/dist/starknet/chain/StarknetAction.js +73 -73
  18. package/dist/starknet/chain/StarknetChainInterface.d.ts +54 -52
  19. package/dist/starknet/chain/StarknetChainInterface.js +97 -91
  20. package/dist/starknet/chain/StarknetModule.d.ts +9 -14
  21. package/dist/starknet/chain/StarknetModule.js +13 -13
  22. package/dist/starknet/chain/modules/ERC20Abi.d.ts +755 -755
  23. package/dist/starknet/chain/modules/ERC20Abi.js +1032 -1032
  24. package/dist/starknet/chain/modules/StarknetAccounts.d.ts +6 -6
  25. package/dist/starknet/chain/modules/StarknetAccounts.js +24 -24
  26. package/dist/starknet/chain/modules/StarknetAddresses.d.ts +9 -9
  27. package/dist/starknet/chain/modules/StarknetAddresses.js +26 -26
  28. package/dist/starknet/chain/modules/StarknetBlocks.d.ts +20 -20
  29. package/dist/starknet/chain/modules/StarknetBlocks.js +64 -64
  30. package/dist/starknet/chain/modules/StarknetEvents.d.ts +44 -44
  31. package/dist/starknet/chain/modules/StarknetEvents.js +88 -88
  32. package/dist/starknet/chain/modules/StarknetFees.d.ts +82 -77
  33. package/dist/starknet/chain/modules/StarknetFees.js +121 -114
  34. package/dist/starknet/chain/modules/StarknetSignatures.d.ts +29 -29
  35. package/dist/starknet/chain/modules/StarknetSignatures.js +72 -72
  36. package/dist/starknet/chain/modules/StarknetTokens.d.ts +69 -69
  37. package/dist/starknet/chain/modules/StarknetTokens.js +102 -98
  38. package/dist/starknet/chain/modules/StarknetTransactions.d.ts +111 -93
  39. package/dist/starknet/chain/modules/StarknetTransactions.js +381 -255
  40. package/dist/starknet/contract/StarknetContractBase.d.ts +13 -13
  41. package/dist/starknet/contract/StarknetContractBase.js +20 -16
  42. package/dist/starknet/contract/StarknetContractModule.d.ts +8 -8
  43. package/dist/starknet/contract/StarknetContractModule.js +11 -11
  44. package/dist/starknet/contract/modules/StarknetContractEvents.d.ts +51 -51
  45. package/dist/starknet/contract/modules/StarknetContractEvents.js +97 -97
  46. package/dist/starknet/events/StarknetChainEvents.d.ts +21 -21
  47. package/dist/starknet/events/StarknetChainEvents.js +52 -52
  48. package/dist/starknet/events/StarknetChainEventsBrowser.d.ts +84 -90
  49. package/dist/starknet/events/StarknetChainEventsBrowser.js +307 -292
  50. package/dist/starknet/provider/RpcProviderWithRetries.d.ts +41 -21
  51. package/dist/starknet/provider/RpcProviderWithRetries.js +70 -32
  52. package/dist/starknet/spv_swap/SpvVaultContractAbi.d.ts +488 -488
  53. package/dist/starknet/spv_swap/SpvVaultContractAbi.js +656 -656
  54. package/dist/starknet/spv_swap/StarknetSpvVaultContract.d.ts +89 -66
  55. package/dist/starknet/spv_swap/StarknetSpvVaultContract.js +477 -382
  56. package/dist/starknet/spv_swap/StarknetSpvVaultData.d.ts +49 -49
  57. package/dist/starknet/spv_swap/StarknetSpvVaultData.js +145 -145
  58. package/dist/starknet/spv_swap/StarknetSpvWithdrawalData.d.ts +25 -25
  59. package/dist/starknet/spv_swap/StarknetSpvWithdrawalData.js +72 -72
  60. package/dist/starknet/swaps/EscrowManagerAbi.d.ts +431 -434
  61. package/dist/starknet/swaps/EscrowManagerAbi.js +583 -587
  62. package/dist/starknet/swaps/StarknetSwapContract.d.ts +197 -190
  63. package/dist/starknet/swaps/StarknetSwapContract.js +440 -409
  64. package/dist/starknet/swaps/StarknetSwapData.d.ts +74 -67
  65. package/dist/starknet/swaps/StarknetSwapData.js +325 -290
  66. package/dist/starknet/swaps/StarknetSwapModule.d.ts +10 -10
  67. package/dist/starknet/swaps/StarknetSwapModule.js +11 -11
  68. package/dist/starknet/swaps/handlers/IHandler.d.ts +13 -13
  69. package/dist/starknet/swaps/handlers/IHandler.js +2 -2
  70. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.d.ts +13 -13
  71. package/dist/starknet/swaps/handlers/claim/ClaimHandlers.js +13 -13
  72. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.d.ts +21 -21
  73. package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.js +44 -44
  74. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -24
  75. package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +48 -48
  76. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -25
  77. package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +40 -40
  78. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +20 -20
  79. package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +30 -30
  80. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +45 -45
  81. package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +52 -52
  82. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -17
  83. package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.js +27 -27
  84. package/dist/starknet/swaps/modules/StarknetLpVault.d.ts +69 -69
  85. package/dist/starknet/swaps/modules/StarknetLpVault.js +122 -122
  86. package/dist/starknet/swaps/modules/StarknetSwapClaim.d.ts +53 -53
  87. package/dist/starknet/swaps/modules/StarknetSwapClaim.js +100 -100
  88. package/dist/starknet/swaps/modules/StarknetSwapInit.d.ts +94 -86
  89. package/dist/starknet/swaps/modules/StarknetSwapInit.js +235 -224
  90. package/dist/starknet/swaps/modules/StarknetSwapRefund.d.ts +62 -62
  91. package/dist/starknet/swaps/modules/StarknetSwapRefund.js +128 -128
  92. package/dist/starknet/wallet/StarknetBrowserSigner.d.ts +5 -0
  93. package/dist/starknet/wallet/StarknetBrowserSigner.js +11 -0
  94. package/dist/starknet/wallet/StarknetKeypairWallet.d.ts +7 -7
  95. package/dist/starknet/wallet/StarknetKeypairWallet.js +35 -30
  96. package/dist/starknet/wallet/StarknetPersistentSigner.d.ts +33 -0
  97. package/dist/starknet/wallet/StarknetPersistentSigner.js +244 -0
  98. package/dist/starknet/wallet/StarknetSigner.d.ts +18 -12
  99. package/dist/starknet/wallet/StarknetSigner.js +69 -46
  100. package/dist/starknet/wallet/accounts/StarknetKeypairWallet.d.ts +7 -0
  101. package/dist/starknet/wallet/accounts/StarknetKeypairWallet.js +35 -0
  102. package/dist/utils/Utils.d.ts +39 -37
  103. package/dist/utils/Utils.js +264 -261
  104. package/package.json +45 -37
  105. package/src/index.ts +48 -47
  106. package/src/starknet/StarknetChainType.ts +28 -28
  107. package/src/starknet/StarknetInitializer.ts +110 -108
  108. package/src/starknet/btcrelay/BtcRelayAbi.ts +338 -338
  109. package/src/starknet/btcrelay/StarknetBtcRelay.ts +494 -494
  110. package/src/starknet/btcrelay/headers/StarknetBtcHeader.ts +100 -100
  111. package/src/starknet/btcrelay/headers/StarknetBtcStoredHeader.ts +141 -141
  112. package/src/starknet/chain/StarknetAction.ts +85 -85
  113. package/src/starknet/chain/StarknetChainInterface.ts +165 -149
  114. package/src/starknet/chain/StarknetModule.ts +19 -19
  115. package/src/starknet/chain/modules/ERC20Abi.ts +1029 -1029
  116. package/src/starknet/chain/modules/StarknetAccounts.ts +25 -25
  117. package/src/starknet/chain/modules/StarknetAddresses.ts +22 -22
  118. package/src/starknet/chain/modules/StarknetBlocks.ts +75 -74
  119. package/src/starknet/chain/modules/StarknetEvents.ts +104 -104
  120. package/src/starknet/chain/modules/StarknetFees.ts +162 -154
  121. package/src/starknet/chain/modules/StarknetSignatures.ts +91 -91
  122. package/src/starknet/chain/modules/StarknetTokens.ts +120 -116
  123. package/src/starknet/chain/modules/StarknetTransactions.ts +424 -277
  124. package/src/starknet/contract/StarknetContractBase.ts +30 -26
  125. package/src/starknet/contract/StarknetContractModule.ts +16 -16
  126. package/src/starknet/contract/modules/StarknetContractEvents.ts +134 -134
  127. package/src/starknet/events/StarknetChainEvents.ts +67 -67
  128. package/src/starknet/events/StarknetChainEventsBrowser.ts +420 -410
  129. package/src/starknet/provider/RpcProviderWithRetries.ts +83 -43
  130. package/src/starknet/spv_swap/SpvVaultContractAbi.ts +656 -656
  131. package/src/starknet/spv_swap/StarknetSpvVaultContract.ts +591 -483
  132. package/src/starknet/spv_swap/StarknetSpvVaultData.ts +195 -195
  133. package/src/starknet/spv_swap/StarknetSpvWithdrawalData.ts +79 -79
  134. package/src/starknet/swaps/EscrowManagerAbi.ts +582 -586
  135. package/src/starknet/swaps/StarknetSwapContract.ts +668 -628
  136. package/src/starknet/swaps/StarknetSwapData.ts +455 -403
  137. package/src/starknet/swaps/StarknetSwapModule.ts +17 -17
  138. package/src/starknet/swaps/handlers/IHandler.ts +20 -20
  139. package/src/starknet/swaps/handlers/claim/ClaimHandlers.ts +23 -23
  140. package/src/starknet/swaps/handlers/claim/HashlockClaimHandler.ts +53 -53
  141. package/src/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +73 -73
  142. package/src/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +67 -67
  143. package/src/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +50 -50
  144. package/src/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +102 -102
  145. package/src/starknet/swaps/handlers/refund/TimelockRefundHandler.ts +38 -38
  146. package/src/starknet/swaps/modules/StarknetLpVault.ts +147 -147
  147. package/src/starknet/swaps/modules/StarknetSwapClaim.ts +141 -141
  148. package/src/starknet/swaps/modules/StarknetSwapInit.ts +300 -286
  149. package/src/starknet/swaps/modules/StarknetSwapRefund.ts +196 -196
  150. package/src/starknet/wallet/StarknetBrowserSigner.ts +12 -0
  151. package/src/starknet/wallet/StarknetPersistentSigner.ts +311 -0
  152. package/src/starknet/wallet/StarknetSigner.ts +84 -55
  153. package/src/starknet/wallet/{StarknetKeypairWallet.ts → accounts/StarknetKeypairWallet.ts} +44 -39
  154. package/src/utils/Utils.ts +262 -252
@@ -1,483 +1,591 @@
1
- import {
2
- BitcoinRpc,
3
- BtcTx,
4
- RelaySynchronizer,
5
- SpvVaultContract,
6
- SpvVaultTokenData,
7
- SpvWithdrawalState,
8
- SpvWithdrawalStateType,
9
- SpvWithdrawalTransactionData,
10
- TransactionConfirmationOptions
11
- } from "@atomiqlabs/base";
12
- import {Buffer} from "buffer";
13
- import {StarknetTx} from "../chain/modules/StarknetTransactions";
14
- import {StarknetContractBase} from "../contract/StarknetContractBase";
15
- import {StarknetChainInterface} from "../chain/StarknetChainInterface";
16
- import {StarknetBtcRelay} from "../btcrelay/StarknetBtcRelay";
17
- import {cairo, constants, merkle} from "starknet";
18
- import {StarknetAction} from "../chain/StarknetAction";
19
- import {SpvVaultContractAbi} from "./SpvVaultContractAbi";
20
- import {StarknetSigner} from "../wallet/StarknetSigner";
21
- import {StarknetSpvVaultData} from "./StarknetSpvVaultData";
22
- import {StarknetSpvWithdrawalData} from "./StarknetSpvWithdrawalData";
23
- import {bigNumberishToBuffer, bufferToByteArray, bufferToU32Array, getLogger, toBigInt, toHex} from "../../utils/Utils";
24
- import {StarknetBtcStoredHeader} from "../btcrelay/headers/StarknetBtcStoredHeader";
25
- import {StarknetAddresses} from "../chain/modules/StarknetAddresses";
26
- import {StarknetFees} from "../chain/modules/StarknetFees";
27
-
28
- const spvVaultContractAddreses = {
29
- [constants.StarknetChainId.SN_SEPOLIA]: "0x047961ea0687a2e3207478d386779bd5ec22aa8abc234319ccd723e2d7191a0c",
30
- [constants.StarknetChainId.SN_MAIN]: "0x06ee5228af01baa443657ccda27b80637a609d43a97ed2f8fd478313e10abf4e"
31
- };
32
-
33
- const STARK_PRIME_MOD: bigint = 2n**251n + 17n * 2n**192n + 1n;
34
-
35
- function decodeUtxo(utxo: string): {txHash: bigint, vout: bigint} {
36
- const [txId, vout] = utxo.split(":");
37
- return {
38
- txHash: BigInt("0x"+Buffer.from(txId, "hex").reverse().toString("hex")),
39
- vout: BigInt(vout)
40
- }
41
- }
42
-
43
- export class StarknetSpvVaultContract
44
- extends StarknetContractBase<typeof SpvVaultContractAbi>
45
- implements SpvVaultContract<
46
- StarknetTx,
47
- StarknetSigner,
48
- "STARKNET",
49
- StarknetSpvVaultData,
50
- StarknetSpvWithdrawalData
51
- >
52
- {
53
- private static readonly GasCosts = {
54
- DEPOSIT: {l1DataGas: 400, l2Gas: 4_000_000, l1Gas: 0},
55
- OPEN: {l1DataGas: 1200, l2Gas: 3_200_000, l1Gas: 0},
56
- FRONT: {l1DataGas: 800, l2Gas: 12_000_000, l1Gas: 0},
57
- CLAIM: {l1DataGas: 1000, l2Gas: 400_000_000, l1Gas: 0}
58
- };
59
-
60
- readonly chainId = "STARKNET";
61
-
62
- readonly btcRelay: StarknetBtcRelay<any>;
63
- readonly bitcoinRpc: BitcoinRpc<any>;
64
- readonly claimTimeout: number = 180;
65
- readonly maxClaimsPerTx: number = 10;
66
-
67
- readonly logger = getLogger("StarknetSpvVaultContract: ");
68
-
69
- constructor(
70
- chainInterface: StarknetChainInterface,
71
- btcRelay: StarknetBtcRelay<any>,
72
- bitcoinRpc: BitcoinRpc<any>,
73
- contractAddress: string = spvVaultContractAddreses[chainInterface.starknetChainId]
74
- ) {
75
- super(chainInterface, contractAddress, SpvVaultContractAbi);
76
- this.btcRelay = btcRelay;
77
- this.bitcoinRpc = bitcoinRpc;
78
- }
79
-
80
- //StarknetActions
81
- protected Open(signer: string, vault: StarknetSpvVaultData): StarknetAction {
82
- const {txHash, vout} = decodeUtxo(vault.getUtxo());
83
-
84
- const tokens = vault.getTokenData();
85
- if(tokens.length!==2) throw new Error("Must specify exactly 2 tokens for vault!");
86
-
87
- return new StarknetAction(signer, this.Chain,
88
- this.contract.populateTransaction.open(
89
- vault.getVaultId(), this.btcRelay.contract.address,
90
- cairo.tuple(cairo.uint256(txHash), vout), vault.getConfirmations(),
91
- tokens[0].token, tokens[1].token, tokens[0].multiplier, tokens[1].multiplier
92
- ),
93
- StarknetSpvVaultContract.GasCosts.OPEN
94
- );
95
- }
96
-
97
- protected Deposit(signer: string, vault: StarknetSpvVaultData, rawAmounts: bigint[]): StarknetAction {
98
- return new StarknetAction(signer, this.Chain,
99
- this.contract.populateTransaction.deposit(vault.getOwner(), vault.getVaultId(), rawAmounts[0], rawAmounts[1] ?? 0n),
100
- StarknetSpvVaultContract.GasCosts.DEPOSIT
101
- );
102
- }
103
-
104
- protected Front(signer: string, vault: StarknetSpvVaultData, data: StarknetSpvWithdrawalData, withdrawalSequence: number) {
105
- return new StarknetAction(signer, this.Chain,
106
- this.contract.populateTransaction.front(
107
- vault.getOwner(), vault.getVaultId(), BigInt(withdrawalSequence),
108
- data.getTxHash(), data.serializeToStruct()
109
- ),
110
- StarknetSpvVaultContract.GasCosts.FRONT
111
- );
112
- }
113
-
114
- protected Claim(
115
- signer: string, vault: StarknetSpvVaultData, data: StarknetSpvWithdrawalData,
116
- blockheader: StarknetBtcStoredHeader, merkle: Buffer[], position: number
117
- ) {
118
- return new StarknetAction(signer, this.Chain,
119
- {
120
- contractAddress: this.contract.address,
121
- entrypoint: "claim",
122
- calldata: [
123
- vault.getOwner(),
124
- vault.getVaultId(),
125
- ...bufferToByteArray(Buffer.from(data.btcTx.hex, "hex")),
126
- ...blockheader.serialize(),
127
- merkle.length,
128
- ...merkle.map(bufferToU32Array).flat(),
129
- position,
130
- ].map(val => toHex(val, 0))
131
- },
132
- StarknetSpvVaultContract.GasCosts.CLAIM
133
- );
134
- }
135
-
136
- async checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void> {
137
- const result = await this.Chain.provider.callContract({
138
- contractAddress: this.contract.address,
139
- entrypoint: "parse_bitcoin_tx",
140
- calldata: bufferToByteArray(Buffer.from(tx.btcTx.hex, "hex"))
141
- });
142
- if(result==null) throw new Error("Failed to parse transaction!");
143
- }
144
-
145
- createVaultData(owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]): Promise<StarknetSpvVaultData> {
146
- if(tokenData.length!==2) throw new Error("Must specify 2 tokens in tokenData!");
147
- return Promise.resolve(new StarknetSpvVaultData(owner, vaultId, {
148
- relay_contract: this.btcRelay.contract.address,
149
- token_0: tokenData[0].token,
150
- token_1: tokenData[1].token,
151
- token_0_multiplier: tokenData[0].multiplier,
152
- token_1_multiplier: tokenData[1].multiplier,
153
- utxo: cairo.tuple(cairo.uint256(0), 0),
154
- confirmations: confirmations,
155
- withdraw_count: 0,
156
- deposit_count: 0,
157
- token_0_amount: 0n,
158
- token_1_amount: 0n
159
- }, utxo));
160
- }
161
-
162
- //Getters
163
- async getVaultData(owner: string, vaultId: bigint): Promise<StarknetSpvVaultData> {
164
- const struct = await this.contract.get_vault(owner, vaultId);
165
- if(toHex(struct.relay_contract)!==toHex(this.btcRelay.contract.address)) return null;
166
- return new StarknetSpvVaultData(owner, vaultId, struct);
167
- }
168
-
169
- async getAllVaults(owner?: string): Promise<StarknetSpvVaultData[]> {
170
- const openedVaults = new Set<string>();
171
- await this.Events.findInContractEventsForward(
172
- ["spv_swap_vault::events::Opened", "spv_swap_vault::events::Closed"],
173
- owner==null ? null : [null, owner],
174
- (event) => {
175
- const owner = toHex(event.keys[2]);
176
- const vaultId = toBigInt(event.keys[3]);
177
- const vaultIdentifier = owner+":"+vaultId.toString(10);
178
- if(event.name==="spv_swap_vault::events::Opened") {
179
- openedVaults.add(vaultIdentifier);
180
- } else {
181
- openedVaults.delete(vaultIdentifier);
182
- }
183
- return null;
184
- }
185
- );
186
- const vaults: StarknetSpvVaultData[] = [];
187
- for(let identifier of openedVaults.keys()) {
188
- const [owner, vaultIdStr] = identifier.split(":");
189
- const vaultData = await this.getVaultData(owner, BigInt(vaultIdStr));
190
- if(vaultData!=null) vaults.push(vaultData);
191
- }
192
- return vaults;
193
- }
194
-
195
- async getFronterAddress(owner: string, vaultId: bigint, withdrawal: StarknetSpvWithdrawalData): Promise<string | null> {
196
- const fronterAddress = await this.contract.get_fronter_address_by_id(owner, vaultId, "0x"+withdrawal.getFrontingId());
197
- if(toHex(fronterAddress, 64)==="0x0000000000000000000000000000000000000000000000000000000000000000") return null;
198
- return fronterAddress;
199
- }
200
-
201
- async getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState> {
202
- const txHash = Buffer.from(btcTxId, "hex").reverse();
203
- const txHashU256 = cairo.uint256("0x"+txHash.toString("hex"));
204
- let result: SpvWithdrawalState = {
205
- type: SpvWithdrawalStateType.NOT_FOUND
206
- };
207
- await this.Events.findInContractEventsForward(
208
- ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"],
209
- [
210
- toHex(txHashU256.low),
211
- toHex(txHashU256.high)
212
- ],
213
- async (event) => {
214
- switch(event.name) {
215
- case "spv_swap_vault::events::Fronted":
216
- result = {
217
- type: SpvWithdrawalStateType.FRONTED,
218
- txId: event.txHash,
219
- owner: toHex(event.keys[2]),
220
- vaultId: toBigInt(event.keys[3]),
221
- recipient: toHex(event.keys[4]),
222
- fronter: toHex(event.keys[6])
223
- };
224
- break;
225
- case "spv_swap_vault::events::Claimed":
226
- result = {
227
- type: SpvWithdrawalStateType.CLAIMED,
228
- txId: event.txHash,
229
- owner: toHex(event.keys[2]),
230
- vaultId: toBigInt(event.keys[3]),
231
- recipient: toHex(event.keys[4]),
232
- claimer: toHex(event.keys[6]),
233
- fronter: toHex(event.data[2])
234
- };
235
- break;
236
- case "spv_swap_vault::events::Closed":
237
- result = {
238
- type: SpvWithdrawalStateType.CLOSED,
239
- txId: event.txHash,
240
- owner: toHex(event.keys[2]),
241
- vaultId: toBigInt(event.keys[3]),
242
- error: bigNumberishToBuffer(event.data[0]).toString()
243
- }
244
- break;
245
- }
246
- }
247
- );
248
- return result;
249
- }
250
-
251
- getWithdrawalData(btcTx: BtcTx): Promise<StarknetSpvWithdrawalData> {
252
- return Promise.resolve(new StarknetSpvWithdrawalData(btcTx));
253
- }
254
-
255
- //OP_RETURN data encoding/decoding
256
- fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
257
- return StarknetSpvVaultContract.fromOpReturnData(data);
258
- }
259
- static fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
260
- let rawAmount0: bigint = 0n;
261
- let rawAmount1: bigint = 0n;
262
- let executionHash: string = null;
263
- if(data.length===40) {
264
- rawAmount0 = data.readBigInt64LE(32).valueOf();
265
- } else if(data.length===48) {
266
- rawAmount0 = data.readBigInt64LE(32).valueOf();
267
- rawAmount1 = data.readBigInt64LE(40).valueOf();
268
- } else if(data.length===72) {
269
- rawAmount0 = data.readBigInt64LE(32).valueOf();
270
- executionHash = data.slice(40, 72).toString("hex");
271
- } else if(data.length===80) {
272
- rawAmount0 = data.readBigInt64LE(32).valueOf();
273
- rawAmount1 = data.readBigInt64LE(40).valueOf();
274
- executionHash = data.slice(48, 80).toString("hex");
275
- } else {
276
- throw new Error("Invalid OP_RETURN data length!");
277
- }
278
-
279
- if(executionHash!=null) {
280
- const executionHashValue = BigInt("0x"+executionHash);
281
- if(executionHashValue >= STARK_PRIME_MOD) throw new Error("Execution hash not in range of starknet prime");
282
- }
283
-
284
- const recipient = "0x"+data.slice(0, 32).toString("hex");
285
- if(!StarknetAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
286
-
287
- return {executionHash, rawAmounts: [rawAmount0, rawAmount1], recipient};
288
- }
289
-
290
- toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
291
- return StarknetSpvVaultContract.toOpReturnData(recipient, rawAmounts, executionHash);
292
- }
293
- static toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
294
- if(!StarknetAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
295
- if(rawAmounts.length < 1) throw new Error("At least 1 amount needs to be specified");
296
- if(rawAmounts.length > 2) throw new Error("At most 2 amounts need to be specified");
297
- rawAmounts.forEach(val => {
298
- if(val < 0n) throw new Error("Negative raw amount specified");
299
- if(val >= 2n**64n) throw new Error("Raw amount overflow");
300
- });
301
- if(executionHash!=null) {
302
- const executionHashValue = toBigInt(executionHash);
303
- if(executionHashValue < 0n) throw new Error("Execution hash negative");
304
- if(executionHashValue >= STARK_PRIME_MOD) throw new Error("Execution hash not in range of starknet prime");
305
- }
306
- const recipientBuffer = Buffer.from(recipient.substring(2).padStart(64, "0"), "hex");
307
- const amount0Buffer = Buffer.from(rawAmounts[0].toString(16).padStart(16, "0"), "hex");
308
- const amount1Buffer = rawAmounts[1]==null || rawAmounts[1]===0n ? Buffer.alloc(0) : Buffer.from(rawAmounts[1].toString(16).padStart(16, "0"), "hex");
309
- const executionHashBuffer = executionHash==null ? Buffer.alloc(0) : Buffer.from(executionHash.substring(2).padStart(64, "0"), "hex");
310
-
311
- return Buffer.concat([
312
- recipientBuffer,
313
- amount0Buffer.reverse(),
314
- amount1Buffer.reverse(),
315
- executionHashBuffer
316
- ]);
317
- }
318
-
319
- //Actions
320
- async claim(signer: StarknetSigner, vault: StarknetSpvVaultData, txs: {tx: StarknetSpvWithdrawalData, storedHeader?: StarknetBtcStoredHeader}[], synchronizer?: RelaySynchronizer<any, any, any>, initAta?: boolean, txOptions?: TransactionConfirmationOptions): Promise<string> {
321
- const result = await this.txsClaim(signer.getAddress(), vault, txs, synchronizer, initAta, txOptions?.feeRate);
322
- const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
323
- return signature;
324
- }
325
-
326
- async deposit(signer: StarknetSigner, vault: StarknetSpvVaultData, rawAmounts: bigint[], txOptions?: TransactionConfirmationOptions): Promise<string> {
327
- const result = await this.txsDeposit(signer.getAddress(), vault, rawAmounts, txOptions?.feeRate);
328
- const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
329
- return signature;
330
- }
331
-
332
- async frontLiquidity(signer: StarknetSigner, vault: StarknetSpvVaultData, realWithdrawalTx: StarknetSpvWithdrawalData, withdrawSequence: number, txOptions?: TransactionConfirmationOptions): Promise<string> {
333
- const result = await this.txsFrontLiquidity(signer.getAddress(), vault, realWithdrawalTx, withdrawSequence, txOptions?.feeRate);
334
- const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
335
- return signature;
336
- }
337
-
338
- async open(signer: StarknetSigner, vault: StarknetSpvVaultData, txOptions?: TransactionConfirmationOptions): Promise<string> {
339
- const result = await this.txsOpen(signer.getAddress(), vault, txOptions?.feeRate);
340
- const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
341
- return signature;
342
- }
343
-
344
- //Transactions
345
- async txsClaim(
346
- signer: string, vault: StarknetSpvVaultData, txs: {
347
- tx: StarknetSpvWithdrawalData,
348
- storedHeader?: StarknetBtcStoredHeader
349
- }[], synchronizer?: RelaySynchronizer<any, any, any>,
350
- initAta?: boolean, feeRate?: string
351
- ): Promise<StarknetTx[]> {
352
- if(!vault.isOpened()) throw new Error("Cannot claim from a closed vault!");
353
- feeRate ??= await this.Chain.Fees.getFeeRate();
354
-
355
- const txsWithMerkleProofs: {
356
- tx: StarknetSpvWithdrawalData,
357
- reversedTxId: Buffer,
358
- pos: number,
359
- blockheight: number,
360
- merkle: Buffer[],
361
- storedHeader?: StarknetBtcStoredHeader
362
- }[] = [];
363
- for(let tx of txs) {
364
- const merkleProof = await this.bitcoinRpc.getMerkleProof(tx.tx.btcTx.txid, tx.tx.btcTx.blockhash);
365
- this.logger.debug("txsClaim(): merkle proof computed: ", merkleProof);
366
- txsWithMerkleProofs.push({
367
- ...merkleProof,
368
- ...tx
369
- });
370
- }
371
-
372
- const starknetTxs: StarknetTx[] = [];
373
- const storedHeaders: {[blockhash: string]: StarknetBtcStoredHeader} = await StarknetBtcRelay.getCommitedHeadersAndSynchronize(
374
- signer, this.btcRelay, txsWithMerkleProofs.filter(tx => tx.storedHeader==null).map(tx => {
375
- return {
376
- blockhash: tx.tx.btcTx.blockhash,
377
- blockheight: tx.blockheight,
378
- requiredConfirmations: vault.getConfirmations()
379
- }
380
- }), starknetTxs, synchronizer, feeRate
381
- );
382
- if(storedHeaders==null) throw new Error("Cannot fetch committed header!");
383
-
384
- const actions = txsWithMerkleProofs.map(tx => {
385
- return this.Claim(signer, vault, tx.tx, tx.storedHeader ?? storedHeaders[tx.tx.btcTx.blockhash], tx.merkle, tx.pos);
386
- });
387
-
388
- let starknetAction = new StarknetAction(signer, this.Chain);
389
- for(let action of actions) {
390
- starknetAction.add(action);
391
- if(starknetAction.ixsLength() >= this.maxClaimsPerTx) {
392
- await starknetAction.addToTxs(starknetTxs, feeRate);
393
- starknetAction = new StarknetAction(signer, this.Chain);
394
- }
395
- }
396
- if(starknetAction.ixsLength() > 0) {
397
- await starknetAction.addToTxs(starknetTxs, feeRate);
398
- }
399
-
400
- this.logger.debug("txsClaim(): "+starknetTxs.length+" claim TXs created claiming "+txs.length+" txs, owner: "+vault.getOwner()+
401
- " vaultId: "+vault.getVaultId().toString(10));
402
-
403
- return starknetTxs;
404
- }
405
-
406
- async txsDeposit(signer: string, vault: StarknetSpvVaultData, rawAmounts: bigint[], feeRate?: string): Promise<StarknetTx[]> {
407
- if(!vault.isOpened()) throw new Error("Cannot deposit to a closed vault!");
408
- //Approve first
409
- const vaultTokens = vault.getTokenData();
410
- const action = new StarknetAction(signer, this.Chain);
411
- let realAmount0 = 0n;
412
- let realAmount1 = 0n;
413
- if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
414
- realAmount0 = rawAmounts[0] * vaultTokens[0].multiplier;
415
- action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[0].token, realAmount0));
416
- }
417
- if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
418
- realAmount1 = rawAmounts[1] * vaultTokens[1].multiplier;
419
- action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[1].token, realAmount1));
420
- }
421
- action.add(this.Deposit(signer, vault, rawAmounts));
422
-
423
- feeRate ??= await this.Chain.Fees.getFeeRate();
424
-
425
- this.logger.debug("txsDeposit(): deposit TX created,"+
426
- " token0: "+vaultTokens[0].token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
427
- " token1: "+vaultTokens[1].token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
428
-
429
- return [await action.tx(feeRate)];
430
- }
431
-
432
- async txsFrontLiquidity(signer: string, vault: StarknetSpvVaultData, realWithdrawalTx: StarknetSpvWithdrawalData, withdrawSequence: number, feeRate?: string): Promise<StarknetTx[]> {
433
- if(!vault.isOpened()) throw new Error("Cannot front on a closed vault!");
434
-
435
- //Approve first
436
- const vaultTokens = vault.getTokenData();
437
- const action = new StarknetAction(signer, this.Chain);
438
- const rawAmounts = realWithdrawalTx.getFrontingAmount();
439
- let realAmount0 = 0n;
440
- let realAmount1 = 0n;
441
- if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
442
- realAmount0 = rawAmounts[0] * vaultTokens[0].multiplier;
443
- action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[0].token, realAmount0));
444
- }
445
- if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
446
- realAmount1 = rawAmounts[1] * vaultTokens[1].multiplier;
447
- action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[1].token, realAmount1));
448
- }
449
- action.add(this.Front(signer, vault, realWithdrawalTx, withdrawSequence));
450
-
451
- feeRate ??= await this.Chain.Fees.getFeeRate();
452
-
453
- this.logger.debug("txsFrontLiquidity(): front TX created,"+
454
- " token0: "+vaultTokens[0].token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
455
- " token1: "+vaultTokens[1].token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
456
-
457
- return [await action.tx(feeRate)];
458
- }
459
-
460
- async txsOpen(signer: string, vault: StarknetSpvVaultData, feeRate?: string): Promise<StarknetTx[]> {
461
- if(vault.isOpened()) throw new Error("Cannot open an already opened vault!");
462
-
463
- const action = this.Open(signer, vault);
464
-
465
- feeRate ??= await this.Chain.Fees.getFeeRate();
466
-
467
- this.logger.debug("txsOpen(): open TX created, owner: "+vault.getOwner()+
468
- " vaultId: "+vault.getVaultId().toString(10));
469
-
470
- return [await action.tx(feeRate)];
471
- }
472
-
473
- async getClaimFee(signer: string, vault: StarknetSpvVaultData, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
474
- feeRate ??= await this.Chain.Fees.getFeeRate();
475
- return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.CLAIM, feeRate);
476
- }
477
-
478
- async getFrontFee(signer: string, vault: StarknetSpvVaultData, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
479
- feeRate ??= await this.Chain.Fees.getFeeRate();
480
- return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.FRONT, feeRate);
481
- }
482
-
483
- }
1
+ import {
2
+ BitcoinRpc,
3
+ BtcTx,
4
+ RelaySynchronizer,
5
+ SpvVaultContract,
6
+ SpvVaultTokenData,
7
+ SpvWithdrawalState,
8
+ SpvWithdrawalStateType,
9
+ SpvWithdrawalTransactionData, SwapCommitState,
10
+ TransactionConfirmationOptions
11
+ } from "@atomiqlabs/base";
12
+ import {Buffer} from "buffer";
13
+ import {StarknetTx} from "../chain/modules/StarknetTransactions";
14
+ import {StarknetContractBase} from "../contract/StarknetContractBase";
15
+ import {StarknetChainInterface} from "../chain/StarknetChainInterface";
16
+ import {StarknetBtcRelay} from "../btcrelay/StarknetBtcRelay";
17
+ import {cairo, constants, merkle} from "starknet";
18
+ import {StarknetAction} from "../chain/StarknetAction";
19
+ import {SpvVaultContractAbi} from "./SpvVaultContractAbi";
20
+ import {StarknetSigner} from "../wallet/StarknetSigner";
21
+ import {StarknetSpvVaultData} from "./StarknetSpvVaultData";
22
+ import {StarknetSpvWithdrawalData} from "./StarknetSpvWithdrawalData";
23
+ import {bigNumberishToBuffer, bufferToByteArray, bufferToU32Array, getLogger, toBigInt, toHex} from "../../utils/Utils";
24
+ import {StarknetBtcStoredHeader} from "../btcrelay/headers/StarknetBtcStoredHeader";
25
+ import {StarknetAddresses} from "../chain/modules/StarknetAddresses";
26
+ import {StarknetFees} from "../chain/modules/StarknetFees";
27
+ import {toBig} from "@noble/hashes/_u64";
28
+ import {StarknetAbiEvent} from "../contract/modules/StarknetContractEvents";
29
+ import {ExtractAbiEventNames} from "abi-wan-kanabi/dist/kanabi";
30
+
31
+ const spvVaultContractAddreses = {
32
+ [constants.StarknetChainId.SN_SEPOLIA]: "0x02d581ea838cd5ca46ba08660eddd064d50a0392f618e95310432147928d572e",
33
+ [constants.StarknetChainId.SN_MAIN]: "0x01932042992647771f3d0aa6ee526e65359c891fe05a285faaf4d3ffa373e132"
34
+ };
35
+
36
+ const STARK_PRIME_MOD: bigint = 2n**251n + 17n * 2n**192n + 1n;
37
+
38
+ function decodeUtxo(utxo: string): {txHash: bigint, vout: bigint} {
39
+ const [txId, vout] = utxo.split(":");
40
+ return {
41
+ txHash: BigInt("0x"+Buffer.from(txId, "hex").reverse().toString("hex")),
42
+ vout: BigInt(vout)
43
+ }
44
+ }
45
+
46
+ export class StarknetSpvVaultContract
47
+ extends StarknetContractBase<typeof SpvVaultContractAbi>
48
+ implements SpvVaultContract<
49
+ StarknetTx,
50
+ StarknetSigner,
51
+ "STARKNET",
52
+ StarknetSpvVaultData,
53
+ StarknetSpvWithdrawalData
54
+ >
55
+ {
56
+ private static readonly GasCosts = {
57
+ DEPOSIT: {l1DataGas: 400, l2Gas: 4_000_000, l1Gas: 0},
58
+ OPEN: {l1DataGas: 1200, l2Gas: 3_200_000, l1Gas: 0},
59
+ FRONT: {l1DataGas: 800, l2Gas: 12_000_000, l1Gas: 0},
60
+ CLAIM: {l1DataGas: 1000, l2Gas: 400_000_000, l1Gas: 0}
61
+ };
62
+
63
+ readonly chainId = "STARKNET";
64
+
65
+ readonly btcRelay: StarknetBtcRelay<any>;
66
+ readonly bitcoinRpc: BitcoinRpc<any>;
67
+ readonly claimTimeout: number = 180;
68
+ readonly maxClaimsPerTx: number = 10;
69
+
70
+ readonly logger = getLogger("StarknetSpvVaultContract: ");
71
+
72
+ constructor(
73
+ chainInterface: StarknetChainInterface,
74
+ btcRelay: StarknetBtcRelay<any>,
75
+ bitcoinRpc: BitcoinRpc<any>,
76
+ contractAddress: string = spvVaultContractAddreses[chainInterface.starknetChainId]
77
+ ) {
78
+ super(chainInterface, contractAddress, SpvVaultContractAbi);
79
+ this.btcRelay = btcRelay;
80
+ this.bitcoinRpc = bitcoinRpc;
81
+ }
82
+
83
+ //StarknetActions
84
+ protected Open(signer: string, vault: StarknetSpvVaultData): StarknetAction {
85
+ const {txHash, vout} = decodeUtxo(vault.getUtxo());
86
+
87
+ const tokens = vault.getTokenData();
88
+ if(tokens.length!==2) throw new Error("Must specify exactly 2 tokens for vault!");
89
+
90
+ return new StarknetAction(signer, this.Chain,
91
+ this.contract.populateTransaction.open(
92
+ vault.getVaultId(), this.btcRelay.contract.address,
93
+ cairo.tuple(cairo.uint256(txHash), vout), vault.getConfirmations(),
94
+ tokens[0].token, tokens[1].token, tokens[0].multiplier, tokens[1].multiplier
95
+ ),
96
+ StarknetSpvVaultContract.GasCosts.OPEN
97
+ );
98
+ }
99
+
100
+ protected Deposit(signer: string, vault: StarknetSpvVaultData, rawAmounts: bigint[]): StarknetAction {
101
+ return new StarknetAction(signer, this.Chain,
102
+ this.contract.populateTransaction.deposit(vault.getOwner(), vault.getVaultId(), rawAmounts[0], rawAmounts[1] ?? 0n),
103
+ StarknetSpvVaultContract.GasCosts.DEPOSIT
104
+ );
105
+ }
106
+
107
+ protected Front(signer: string, vault: StarknetSpvVaultData, data: StarknetSpvWithdrawalData, withdrawalSequence: number) {
108
+ return new StarknetAction(signer, this.Chain,
109
+ this.contract.populateTransaction.front(
110
+ vault.getOwner(), vault.getVaultId(), BigInt(withdrawalSequence),
111
+ data.getTxHash(), data.serializeToStruct()
112
+ ),
113
+ StarknetSpvVaultContract.GasCosts.FRONT
114
+ );
115
+ }
116
+
117
+ protected Claim(
118
+ signer: string, vault: StarknetSpvVaultData, data: StarknetSpvWithdrawalData,
119
+ blockheader: StarknetBtcStoredHeader, merkle: Buffer[], position: number
120
+ ) {
121
+ return new StarknetAction(signer, this.Chain,
122
+ {
123
+ contractAddress: this.contract.address,
124
+ entrypoint: "claim",
125
+ calldata: [
126
+ vault.getOwner(),
127
+ vault.getVaultId(),
128
+ ...bufferToByteArray(Buffer.from(data.btcTx.hex, "hex")),
129
+ ...blockheader.serialize(),
130
+ merkle.length,
131
+ ...merkle.map(bufferToU32Array).flat(),
132
+ position,
133
+ ].map(val => toHex(val, 0))
134
+ },
135
+ StarknetSpvVaultContract.GasCosts.CLAIM
136
+ );
137
+ }
138
+
139
+ async checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void> {
140
+ const result = await this.Chain.provider.callContract({
141
+ contractAddress: this.contract.address,
142
+ entrypoint: "parse_bitcoin_tx",
143
+ calldata: bufferToByteArray(Buffer.from(tx.btcTx.hex, "hex"))
144
+ });
145
+ if(result==null) throw new Error("Failed to parse transaction!");
146
+ }
147
+
148
+ createVaultData(owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]): Promise<StarknetSpvVaultData> {
149
+ if(tokenData.length!==2) throw new Error("Must specify 2 tokens in tokenData!");
150
+ return Promise.resolve(new StarknetSpvVaultData(owner, vaultId, {
151
+ relay_contract: this.btcRelay.contract.address,
152
+ token_0: tokenData[0].token,
153
+ token_1: tokenData[1].token,
154
+ token_0_multiplier: tokenData[0].multiplier,
155
+ token_1_multiplier: tokenData[1].multiplier,
156
+ utxo: cairo.tuple(cairo.uint256(0), 0),
157
+ confirmations: confirmations,
158
+ withdraw_count: 0,
159
+ deposit_count: 0,
160
+ token_0_amount: 0n,
161
+ token_1_amount: 0n
162
+ }, utxo));
163
+ }
164
+
165
+ //Getters
166
+ async getVaultData(owner: string, vaultId: bigint): Promise<StarknetSpvVaultData> {
167
+ const struct = await this.contract.get_vault(owner, vaultId);
168
+ if(toHex(struct.relay_contract)!==toHex(this.btcRelay.contract.address)) return null;
169
+ return new StarknetSpvVaultData(owner, vaultId, struct);
170
+ }
171
+
172
+ async getMultipleVaultData(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: StarknetSpvVaultData}}> {
173
+ const result: {[owner: string]: {[vaultId: string]: StarknetSpvVaultData}} = {};
174
+ let promises: Promise<void>[] = [];
175
+ //TODO: We can upgrade this to use multicall
176
+ for(let {owner, vaultId} of vaults) {
177
+ promises.push(this.getVaultData(owner, vaultId).then(val => {
178
+ result[owner] ??= {};
179
+ result[owner][vaultId.toString(10)] = val;
180
+ }));
181
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
182
+ await Promise.all(promises);
183
+ promises = [];
184
+ }
185
+ }
186
+ await Promise.all(promises);
187
+ return result;
188
+ }
189
+
190
+ async getVaultLatestUtxo(owner: string, vaultId: bigint): Promise<string | null> {
191
+ const vault = await this.getVaultData(owner, vaultId);
192
+ if(vault==null) return null;
193
+ if(!vault.isOpened()) return null;
194
+ return vault.getUtxo();
195
+ }
196
+
197
+ async getVaultLatestUtxos(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: string | null}}> {
198
+ const result: {[owner: string]: {[vaultId: string]: string | null}} = {};
199
+ let promises: Promise<void>[] = [];
200
+ //TODO: We can upgrade this to use multicall
201
+ for(let {owner, vaultId} of vaults) {
202
+ promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
203
+ result[owner] ??= {};
204
+ result[owner][vaultId.toString(10)] = val;
205
+ }));
206
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
207
+ await Promise.all(promises);
208
+ promises = [];
209
+ }
210
+ }
211
+ await Promise.all(promises);
212
+ return result;
213
+ }
214
+
215
+ async getAllVaults(owner?: string): Promise<StarknetSpvVaultData[]> {
216
+ const openedVaults = new Set<string>();
217
+ await this.Events.findInContractEventsForward(
218
+ ["spv_swap_vault::events::Opened", "spv_swap_vault::events::Closed"],
219
+ owner==null ? null : [null, owner],
220
+ (event) => {
221
+ const owner = toHex(event.params.owner);
222
+ const vaultId = toBigInt(event.params.vault_id);
223
+ const vaultIdentifier = owner+":"+vaultId.toString(10);
224
+ if(event.name==="spv_swap_vault::events::Opened") {
225
+ openedVaults.add(vaultIdentifier);
226
+ } else {
227
+ openedVaults.delete(vaultIdentifier);
228
+ }
229
+ return null;
230
+ }
231
+ );
232
+ const vaults: StarknetSpvVaultData[] = [];
233
+ for(let identifier of openedVaults.keys()) {
234
+ const [owner, vaultIdStr] = identifier.split(":");
235
+ const vaultData = await this.getVaultData(owner, BigInt(vaultIdStr));
236
+ if(vaultData!=null) vaults.push(vaultData);
237
+ }
238
+ return vaults;
239
+ }
240
+
241
+ async getFronterAddress(owner: string, vaultId: bigint, withdrawal: StarknetSpvWithdrawalData): Promise<string | null> {
242
+ const fronterAddress = await this.contract.get_fronter_address_by_id(owner, vaultId, "0x"+withdrawal.getFrontingId());
243
+ if(toHex(fronterAddress, 64)==="0x0000000000000000000000000000000000000000000000000000000000000000") return null;
244
+ return fronterAddress;
245
+ }
246
+
247
+ async getFronterAddresses(withdrawals: {owner: string, vaultId: bigint, withdrawal: StarknetSpvWithdrawalData}[]): Promise<{[btcTxId: string]: string | null}> {
248
+ const result: {
249
+ [btcTxId: string]: string | null
250
+ } = {};
251
+ let promises: Promise<void>[] = [];
252
+ //TODO: We can upgrade this to use multicall
253
+ for(let {owner, vaultId, withdrawal} of withdrawals) {
254
+ promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
255
+ result[withdrawal.getTxId()] = val;
256
+ }));
257
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
258
+ await Promise.all(promises);
259
+ promises = [];
260
+ }
261
+ }
262
+ await Promise.all(promises);
263
+ return result;
264
+ }
265
+
266
+ private parseWithdrawalEvent(event: StarknetAbiEvent<typeof SpvVaultContractAbi, "spv_swap_vault::events::Claimed" | "spv_swap_vault::events::Fronted" | "spv_swap_vault::events::Closed">): SpvWithdrawalState | null {
267
+ switch(event.name) {
268
+ case "spv_swap_vault::events::Fronted":
269
+ return {
270
+ type: SpvWithdrawalStateType.FRONTED,
271
+ txId: event.txHash,
272
+ owner: toHex(event.params.owner),
273
+ vaultId: toBigInt(event.params.vault_id),
274
+ recipient: toHex(event.params.recipient),
275
+ fronter: toHex(event.params.fronting_address)
276
+ };
277
+ case "spv_swap_vault::events::Claimed":
278
+ return {
279
+ type: SpvWithdrawalStateType.CLAIMED,
280
+ txId: event.txHash,
281
+ owner: toHex(event.params.owner),
282
+ vaultId: toBigInt(event.params.vault_id),
283
+ recipient: toHex(event.params.recipient),
284
+ claimer: toHex(event.params.caller),
285
+ fronter: toHex(event.params.fronting_address)
286
+ };
287
+ case "spv_swap_vault::events::Closed":
288
+ return {
289
+ type: SpvWithdrawalStateType.CLOSED,
290
+ txId: event.txHash,
291
+ owner: toHex(event.params.owner),
292
+ vaultId: toBigInt(event.params.vault_id),
293
+ error: bigNumberishToBuffer(event.params.error).toString()
294
+ };
295
+ default:
296
+ return null;
297
+ }
298
+ }
299
+
300
+ async getWithdrawalStates(btcTxIds: string[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
301
+ const result: {[btcTxId: string]: SpvWithdrawalState} = {};
302
+ btcTxIds.forEach(txId => {
303
+ result[txId] = {
304
+ type: SpvWithdrawalStateType.NOT_FOUND
305
+ };
306
+ });
307
+
308
+ for(let i=0;i<btcTxIds.length;i+=this.Chain.config.maxGetLogKeys) {
309
+ const checkBtcTxIds = btcTxIds.slice(i, i+this.Chain.config.maxGetLogKeys);
310
+ const lows: string[] = [];
311
+ const highs: string[] = [];
312
+ checkBtcTxIds.forEach(btcTxId => {
313
+ const txHash = Buffer.from(btcTxId, "hex").reverse();
314
+ const txHashU256 = cairo.uint256("0x"+txHash.toString("hex"));
315
+ lows.push(toHex(txHashU256.low));
316
+ highs.push(toHex(txHashU256.high));
317
+ });
318
+ await this.Events.findInContractEventsForward(
319
+ ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"],
320
+ [
321
+ lows,
322
+ highs
323
+ ],
324
+ async (event) => {
325
+ const txId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex");
326
+ if(result[txId]==null) {
327
+ this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${txId}, but transaction not found in input params!`)
328
+ return;
329
+ }
330
+ const eventResult = this.parseWithdrawalEvent(event);
331
+ if(eventResult!=null) result[txId] = eventResult;
332
+ }
333
+ );
334
+ }
335
+
336
+ return result;
337
+ }
338
+
339
+ async getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState> {
340
+ const txHash = Buffer.from(btcTxId, "hex").reverse();
341
+ const txHashU256 = cairo.uint256("0x"+txHash.toString("hex"));
342
+ let result: SpvWithdrawalState = {
343
+ type: SpvWithdrawalStateType.NOT_FOUND
344
+ };
345
+ await this.Events.findInContractEventsForward(
346
+ ["spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed", "spv_swap_vault::events::Closed"],
347
+ [
348
+ toHex(txHashU256.low),
349
+ toHex(txHashU256.high)
350
+ ],
351
+ async (event) => {
352
+ const eventResult = this.parseWithdrawalEvent(event);
353
+ if(eventResult!=null) result = eventResult;
354
+ }
355
+ );
356
+ return result;
357
+ }
358
+
359
+ getWithdrawalData(btcTx: BtcTx): Promise<StarknetSpvWithdrawalData> {
360
+ return Promise.resolve(new StarknetSpvWithdrawalData(btcTx));
361
+ }
362
+
363
+ //OP_RETURN data encoding/decoding
364
+ fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
365
+ return StarknetSpvVaultContract.fromOpReturnData(data);
366
+ }
367
+ static fromOpReturnData(data: Buffer): { recipient: string; rawAmounts: bigint[]; executionHash: string } {
368
+ let rawAmount0: bigint = 0n;
369
+ let rawAmount1: bigint = 0n;
370
+ let executionHash: string = null;
371
+ if(data.length===40) {
372
+ rawAmount0 = data.readBigInt64LE(32).valueOf();
373
+ } else if(data.length===48) {
374
+ rawAmount0 = data.readBigInt64LE(32).valueOf();
375
+ rawAmount1 = data.readBigInt64LE(40).valueOf();
376
+ } else if(data.length===72) {
377
+ rawAmount0 = data.readBigInt64LE(32).valueOf();
378
+ executionHash = data.slice(40, 72).toString("hex");
379
+ } else if(data.length===80) {
380
+ rawAmount0 = data.readBigInt64LE(32).valueOf();
381
+ rawAmount1 = data.readBigInt64LE(40).valueOf();
382
+ executionHash = data.slice(48, 80).toString("hex");
383
+ } else {
384
+ throw new Error("Invalid OP_RETURN data length!");
385
+ }
386
+
387
+ if(executionHash!=null) {
388
+ const executionHashValue = BigInt("0x"+executionHash);
389
+ if(executionHashValue >= STARK_PRIME_MOD) throw new Error("Execution hash not in range of starknet prime");
390
+ }
391
+
392
+ const recipient = "0x"+data.slice(0, 32).toString("hex");
393
+ if(!StarknetAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
394
+
395
+ return {executionHash, rawAmounts: [rawAmount0, rawAmount1], recipient};
396
+ }
397
+
398
+ toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
399
+ return StarknetSpvVaultContract.toOpReturnData(recipient, rawAmounts, executionHash);
400
+ }
401
+ static toOpReturnData(recipient: string, rawAmounts: bigint[], executionHash?: string): Buffer {
402
+ if(!StarknetAddresses.isValidAddress(recipient)) throw new Error("Invalid recipient specified");
403
+ if(rawAmounts.length < 1) throw new Error("At least 1 amount needs to be specified");
404
+ if(rawAmounts.length > 2) throw new Error("At most 2 amounts need to be specified");
405
+ rawAmounts.forEach(val => {
406
+ if(val < 0n) throw new Error("Negative raw amount specified");
407
+ if(val >= 2n**64n) throw new Error("Raw amount overflow");
408
+ });
409
+ if(executionHash!=null) {
410
+ const executionHashValue = toBigInt(executionHash);
411
+ if(executionHashValue < 0n) throw new Error("Execution hash negative");
412
+ if(executionHashValue >= STARK_PRIME_MOD) throw new Error("Execution hash not in range of starknet prime");
413
+ }
414
+ const recipientBuffer = Buffer.from(recipient.substring(2).padStart(64, "0"), "hex");
415
+ const amount0Buffer = Buffer.from(rawAmounts[0].toString(16).padStart(16, "0"), "hex");
416
+ const amount1Buffer = rawAmounts[1]==null || rawAmounts[1]===0n ? Buffer.alloc(0) : Buffer.from(rawAmounts[1].toString(16).padStart(16, "0"), "hex");
417
+ const executionHashBuffer = executionHash==null ? Buffer.alloc(0) : Buffer.from(executionHash.substring(2).padStart(64, "0"), "hex");
418
+
419
+ return Buffer.concat([
420
+ recipientBuffer,
421
+ amount0Buffer.reverse(),
422
+ amount1Buffer.reverse(),
423
+ executionHashBuffer
424
+ ]);
425
+ }
426
+
427
+ //Actions
428
+ async claim(signer: StarknetSigner, vault: StarknetSpvVaultData, txs: {tx: StarknetSpvWithdrawalData, storedHeader?: StarknetBtcStoredHeader}[], synchronizer?: RelaySynchronizer<any, any, any>, initAta?: boolean, txOptions?: TransactionConfirmationOptions): Promise<string> {
429
+ const result = await this.txsClaim(signer.getAddress(), vault, txs, synchronizer, initAta, txOptions?.feeRate);
430
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
431
+ return signature;
432
+ }
433
+
434
+ async deposit(signer: StarknetSigner, vault: StarknetSpvVaultData, rawAmounts: bigint[], txOptions?: TransactionConfirmationOptions): Promise<string> {
435
+ const result = await this.txsDeposit(signer.getAddress(), vault, rawAmounts, txOptions?.feeRate);
436
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
437
+ return signature;
438
+ }
439
+
440
+ async frontLiquidity(signer: StarknetSigner, vault: StarknetSpvVaultData, realWithdrawalTx: StarknetSpvWithdrawalData, withdrawSequence: number, txOptions?: TransactionConfirmationOptions): Promise<string> {
441
+ const result = await this.txsFrontLiquidity(signer.getAddress(), vault, realWithdrawalTx, withdrawSequence, txOptions?.feeRate);
442
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
443
+ return signature;
444
+ }
445
+
446
+ async open(signer: StarknetSigner, vault: StarknetSpvVaultData, txOptions?: TransactionConfirmationOptions): Promise<string> {
447
+ const result = await this.txsOpen(signer.getAddress(), vault, txOptions?.feeRate);
448
+ const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
449
+ return signature;
450
+ }
451
+
452
+ //Transactions
453
+ async txsClaim(
454
+ signer: string, vault: StarknetSpvVaultData, txs: {
455
+ tx: StarknetSpvWithdrawalData,
456
+ storedHeader?: StarknetBtcStoredHeader
457
+ }[], synchronizer?: RelaySynchronizer<any, any, any>,
458
+ initAta?: boolean, feeRate?: string
459
+ ): Promise<StarknetTx[]> {
460
+ if(!vault.isOpened()) throw new Error("Cannot claim from a closed vault!");
461
+ feeRate ??= await this.Chain.Fees.getFeeRate();
462
+
463
+ const txsWithMerkleProofs: {
464
+ tx: StarknetSpvWithdrawalData,
465
+ reversedTxId: Buffer,
466
+ pos: number,
467
+ blockheight: number,
468
+ merkle: Buffer[],
469
+ storedHeader?: StarknetBtcStoredHeader
470
+ }[] = [];
471
+ for(let tx of txs) {
472
+ const merkleProof = await this.bitcoinRpc.getMerkleProof(tx.tx.btcTx.txid, tx.tx.btcTx.blockhash);
473
+ this.logger.debug("txsClaim(): merkle proof computed: ", merkleProof);
474
+ txsWithMerkleProofs.push({
475
+ ...merkleProof,
476
+ ...tx
477
+ });
478
+ }
479
+
480
+ const starknetTxs: StarknetTx[] = [];
481
+ const storedHeaders: {[blockhash: string]: StarknetBtcStoredHeader} = await StarknetBtcRelay.getCommitedHeadersAndSynchronize(
482
+ signer, this.btcRelay, txsWithMerkleProofs.filter(tx => tx.storedHeader==null).map(tx => {
483
+ return {
484
+ blockhash: tx.tx.btcTx.blockhash,
485
+ blockheight: tx.blockheight,
486
+ requiredConfirmations: vault.getConfirmations()
487
+ }
488
+ }), starknetTxs, synchronizer, feeRate
489
+ );
490
+ if(storedHeaders==null) throw new Error("Cannot fetch committed header!");
491
+
492
+ const actions = txsWithMerkleProofs.map(tx => {
493
+ return this.Claim(signer, vault, tx.tx, tx.storedHeader ?? storedHeaders[tx.tx.btcTx.blockhash], tx.merkle, tx.pos);
494
+ });
495
+
496
+ let starknetAction = new StarknetAction(signer, this.Chain);
497
+ for(let action of actions) {
498
+ starknetAction.add(action);
499
+ if(starknetAction.ixsLength() >= this.maxClaimsPerTx) {
500
+ await starknetAction.addToTxs(starknetTxs, feeRate);
501
+ starknetAction = new StarknetAction(signer, this.Chain);
502
+ }
503
+ }
504
+ if(starknetAction.ixsLength() > 0) {
505
+ await starknetAction.addToTxs(starknetTxs, feeRate);
506
+ }
507
+
508
+ this.logger.debug("txsClaim(): "+starknetTxs.length+" claim TXs created claiming "+txs.length+" txs, owner: "+vault.getOwner()+
509
+ " vaultId: "+vault.getVaultId().toString(10));
510
+
511
+ return starknetTxs;
512
+ }
513
+
514
+ async txsDeposit(signer: string, vault: StarknetSpvVaultData, rawAmounts: bigint[], feeRate?: string): Promise<StarknetTx[]> {
515
+ if(!vault.isOpened()) throw new Error("Cannot deposit to a closed vault!");
516
+ //Approve first
517
+ const vaultTokens = vault.getTokenData();
518
+ const action = new StarknetAction(signer, this.Chain);
519
+ let realAmount0 = 0n;
520
+ let realAmount1 = 0n;
521
+ if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
522
+ realAmount0 = rawAmounts[0] * vaultTokens[0].multiplier;
523
+ action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[0].token, realAmount0));
524
+ }
525
+ if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
526
+ realAmount1 = rawAmounts[1] * vaultTokens[1].multiplier;
527
+ action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[1].token, realAmount1));
528
+ }
529
+ action.add(this.Deposit(signer, vault, rawAmounts));
530
+
531
+ feeRate ??= await this.Chain.Fees.getFeeRate();
532
+
533
+ this.logger.debug("txsDeposit(): deposit TX created,"+
534
+ " token0: "+vaultTokens[0].token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
535
+ " token1: "+vaultTokens[1].token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
536
+
537
+ return [await action.tx(feeRate)];
538
+ }
539
+
540
+ async txsFrontLiquidity(signer: string, vault: StarknetSpvVaultData, realWithdrawalTx: StarknetSpvWithdrawalData, withdrawSequence: number, feeRate?: string): Promise<StarknetTx[]> {
541
+ if(!vault.isOpened()) throw new Error("Cannot front on a closed vault!");
542
+
543
+ //Approve first
544
+ const vaultTokens = vault.getTokenData();
545
+ const action = new StarknetAction(signer, this.Chain);
546
+ const rawAmounts = realWithdrawalTx.getFrontingAmount();
547
+ let realAmount0 = 0n;
548
+ let realAmount1 = 0n;
549
+ if(rawAmounts[0]!=null && rawAmounts[0]!==0n) {
550
+ realAmount0 = rawAmounts[0] * vaultTokens[0].multiplier;
551
+ action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[0].token, realAmount0));
552
+ }
553
+ if(rawAmounts[1]!=null && rawAmounts[1]!==0n) {
554
+ realAmount1 = rawAmounts[1] * vaultTokens[1].multiplier;
555
+ action.add(this.Chain.Tokens.Approve(signer, this.contract.address, vaultTokens[1].token, realAmount1));
556
+ }
557
+ action.add(this.Front(signer, vault, realWithdrawalTx, withdrawSequence));
558
+
559
+ feeRate ??= await this.Chain.Fees.getFeeRate();
560
+
561
+ this.logger.debug("txsFrontLiquidity(): front TX created,"+
562
+ " token0: "+vaultTokens[0].token+" rawAmount0: "+rawAmounts[0].toString(10)+" amount0: "+realAmount0.toString(10)+
563
+ " token1: "+vaultTokens[1].token+" rawAmount1: "+(rawAmounts[1] ?? 0n).toString(10)+" amount1: "+realAmount1.toString(10));
564
+
565
+ return [await action.tx(feeRate)];
566
+ }
567
+
568
+ async txsOpen(signer: string, vault: StarknetSpvVaultData, feeRate?: string): Promise<StarknetTx[]> {
569
+ if(vault.isOpened()) throw new Error("Cannot open an already opened vault!");
570
+
571
+ const action = this.Open(signer, vault);
572
+
573
+ feeRate ??= await this.Chain.Fees.getFeeRate();
574
+
575
+ this.logger.debug("txsOpen(): open TX created, owner: "+vault.getOwner()+
576
+ " vaultId: "+vault.getVaultId().toString(10));
577
+
578
+ return [await action.tx(feeRate)];
579
+ }
580
+
581
+ async getClaimFee(signer: string, vault: StarknetSpvVaultData, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
582
+ feeRate ??= await this.Chain.Fees.getFeeRate();
583
+ return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.CLAIM, feeRate);
584
+ }
585
+
586
+ async getFrontFee(signer: string, vault: StarknetSpvVaultData, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
587
+ feeRate ??= await this.Chain.Fees.getFeeRate();
588
+ return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.FRONT, feeRate);
589
+ }
590
+
591
+ }