@atomiqlabs/chain-solana 13.5.13 → 13.5.14

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 (131) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +73 -73
  3. package/dist/index.d.ts +81 -81
  4. package/dist/index.js +102 -102
  5. package/dist/node/index.d.ts +9 -9
  6. package/dist/node/index.js +13 -13
  7. package/dist/solana/SolanaChainType.d.ts +15 -15
  8. package/dist/solana/SolanaChainType.js +2 -2
  9. package/dist/solana/SolanaChains.d.ts +12 -12
  10. package/dist/solana/SolanaChains.js +45 -45
  11. package/dist/solana/SolanaInitializer.d.ts +94 -94
  12. package/dist/solana/SolanaInitializer.js +174 -174
  13. package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +222 -222
  14. package/dist/solana/btcrelay/SolanaBtcRelay.js +455 -455
  15. package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +84 -84
  16. package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +70 -70
  17. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +92 -92
  18. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +109 -109
  19. package/dist/solana/btcrelay/program/programIdl.json +671 -671
  20. package/dist/solana/chain/SolanaAction.d.ts +26 -26
  21. package/dist/solana/chain/SolanaAction.js +87 -87
  22. package/dist/solana/chain/SolanaChainInterface.d.ts +224 -224
  23. package/dist/solana/chain/SolanaChainInterface.js +275 -275
  24. package/dist/solana/chain/SolanaModule.d.ts +14 -14
  25. package/dist/solana/chain/SolanaModule.js +13 -13
  26. package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
  27. package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
  28. package/dist/solana/chain/modules/SolanaBlocks.d.ts +32 -32
  29. package/dist/solana/chain/modules/SolanaBlocks.js +78 -78
  30. package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
  31. package/dist/solana/chain/modules/SolanaEvents.js +238 -238
  32. package/dist/solana/chain/modules/SolanaFees.d.ts +189 -189
  33. package/dist/solana/chain/modules/SolanaFees.js +434 -434
  34. package/dist/solana/chain/modules/SolanaSignatures.d.ts +24 -24
  35. package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
  36. package/dist/solana/chain/modules/SolanaSlots.d.ts +33 -33
  37. package/dist/solana/chain/modules/SolanaSlots.js +72 -72
  38. package/dist/solana/chain/modules/SolanaTokens.d.ts +123 -123
  39. package/dist/solana/chain/modules/SolanaTokens.js +242 -242
  40. package/dist/solana/chain/modules/SolanaTransactions.d.ts +149 -149
  41. package/dist/solana/chain/modules/SolanaTransactions.js +445 -445
  42. package/dist/solana/connection/ConnectionWithRetries.d.ts +35 -35
  43. package/dist/solana/connection/ConnectionWithRetries.js +86 -71
  44. package/dist/solana/events/SolanaChainEvents.d.ts +45 -45
  45. package/dist/solana/events/SolanaChainEvents.js +108 -108
  46. package/dist/solana/events/SolanaChainEventsBrowser.d.ts +205 -205
  47. package/dist/solana/events/SolanaChainEventsBrowser.js +404 -404
  48. package/dist/solana/program/SolanaProgramBase.d.ts +73 -73
  49. package/dist/solana/program/SolanaProgramBase.js +54 -54
  50. package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
  51. package/dist/solana/program/SolanaProgramModule.js +11 -11
  52. package/dist/solana/program/modules/SolanaProgramEvents.d.ts +53 -53
  53. package/dist/solana/program/modules/SolanaProgramEvents.js +117 -117
  54. package/dist/solana/swaps/SolanaSwapData.d.ts +333 -333
  55. package/dist/solana/swaps/SolanaSwapData.js +535 -535
  56. package/dist/solana/swaps/SolanaSwapModule.d.ts +11 -11
  57. package/dist/solana/swaps/SolanaSwapModule.js +12 -12
  58. package/dist/solana/swaps/SolanaSwapProgram.d.ts +376 -376
  59. package/dist/solana/swaps/SolanaSwapProgram.js +769 -769
  60. package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
  61. package/dist/solana/swaps/SwapTypeEnum.js +43 -43
  62. package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +95 -95
  63. package/dist/solana/swaps/modules/SolanaDataAccount.js +232 -232
  64. package/dist/solana/swaps/modules/SolanaLpVault.d.ts +69 -69
  65. package/dist/solana/swaps/modules/SolanaLpVault.js +171 -171
  66. package/dist/solana/swaps/modules/SwapClaim.d.ts +126 -126
  67. package/dist/solana/swaps/modules/SwapClaim.js +294 -294
  68. package/dist/solana/swaps/modules/SwapInit.d.ts +213 -213
  69. package/dist/solana/swaps/modules/SwapInit.js +658 -658
  70. package/dist/solana/swaps/modules/SwapRefund.d.ts +87 -87
  71. package/dist/solana/swaps/modules/SwapRefund.js +293 -293
  72. package/dist/solana/swaps/programIdl.json +945 -945
  73. package/dist/solana/swaps/programTypes.d.ts +943 -943
  74. package/dist/solana/swaps/programTypes.js +945 -945
  75. package/dist/solana/swaps/v1/programIdl.json +945 -945
  76. package/dist/solana/swaps/v1/programTypes.d.ts +943 -943
  77. package/dist/solana/swaps/v1/programTypes.js +945 -945
  78. package/dist/solana/swaps/v2/programIdl.json +952 -952
  79. package/dist/solana/swaps/v2/programTypes.d.ts +950 -950
  80. package/dist/solana/swaps/v2/programTypes.js +952 -952
  81. package/dist/solana/wallet/SolanaKeypairWallet.d.ts +29 -29
  82. package/dist/solana/wallet/SolanaKeypairWallet.js +50 -50
  83. package/dist/solana/wallet/SolanaSigner.d.ts +30 -30
  84. package/dist/solana/wallet/SolanaSigner.js +30 -30
  85. package/dist/utils/Utils.d.ts +58 -58
  86. package/dist/utils/Utils.js +170 -170
  87. package/node/index.d.ts +1 -1
  88. package/node/index.js +3 -3
  89. package/package.json +46 -46
  90. package/src/index.ts +87 -87
  91. package/src/node/index.ts +9 -9
  92. package/src/solana/SolanaChainType.ts +32 -32
  93. package/src/solana/SolanaChains.ts +46 -46
  94. package/src/solana/SolanaInitializer.ts +278 -278
  95. package/src/solana/btcrelay/SolanaBtcRelay.ts +615 -615
  96. package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +116 -116
  97. package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +148 -148
  98. package/src/solana/btcrelay/program/programIdl.json +670 -670
  99. package/src/solana/chain/SolanaAction.ts +109 -109
  100. package/src/solana/chain/SolanaChainInterface.ts +404 -404
  101. package/src/solana/chain/SolanaModule.ts +20 -20
  102. package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
  103. package/src/solana/chain/modules/SolanaBlocks.ts +89 -89
  104. package/src/solana/chain/modules/SolanaEvents.ts +271 -271
  105. package/src/solana/chain/modules/SolanaFees.ts +522 -522
  106. package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
  107. package/src/solana/chain/modules/SolanaSlots.ts +85 -85
  108. package/src/solana/chain/modules/SolanaTokens.ts +300 -300
  109. package/src/solana/chain/modules/SolanaTransactions.ts +503 -503
  110. package/src/solana/connection/ConnectionWithRetries.ts +113 -96
  111. package/src/solana/events/SolanaChainEvents.ts +127 -127
  112. package/src/solana/events/SolanaChainEventsBrowser.ts +495 -495
  113. package/src/solana/program/SolanaProgramBase.ts +119 -119
  114. package/src/solana/program/SolanaProgramModule.ts +15 -15
  115. package/src/solana/program/modules/SolanaProgramEvents.ts +157 -157
  116. package/src/solana/swaps/SolanaSwapData.ts +735 -735
  117. package/src/solana/swaps/SolanaSwapModule.ts +19 -19
  118. package/src/solana/swaps/SolanaSwapProgram.ts +1074 -1074
  119. package/src/solana/swaps/SwapTypeEnum.ts +30 -30
  120. package/src/solana/swaps/modules/SolanaDataAccount.ts +302 -302
  121. package/src/solana/swaps/modules/SolanaLpVault.ts +208 -208
  122. package/src/solana/swaps/modules/SwapClaim.ts +387 -387
  123. package/src/solana/swaps/modules/SwapInit.ts +785 -785
  124. package/src/solana/swaps/modules/SwapRefund.ts +353 -353
  125. package/src/solana/swaps/v1/programIdl.json +944 -944
  126. package/src/solana/swaps/v1/programTypes.ts +1885 -1885
  127. package/src/solana/swaps/v2/programIdl.json +951 -951
  128. package/src/solana/swaps/v2/programTypes.ts +1899 -1899
  129. package/src/solana/wallet/SolanaKeypairWallet.ts +56 -56
  130. package/src/solana/wallet/SolanaSigner.ts +43 -43
  131. package/src/utils/Utils.ts +194 -194
@@ -1,388 +1,388 @@
1
- import {SolanaSwapModule} from "../SolanaSwapModule";
2
- import {SolanaSwapData} from "../SolanaSwapData";
3
- import {SolanaAction} from "../../chain/SolanaAction";
4
- import {getAssociatedTokenAddress, TOKEN_PROGRAM_ID} from "@solana/spl-token";
5
- import {ChainSwapType, RelaySynchronizer, SwapDataVerificationError} from "@atomiqlabs/base";
6
- import {PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY} from "@solana/web3.js";
7
- import {SolanaTx} from "../../chain/modules/SolanaTransactions";
8
- import {SolanaBtcStoredHeader} from "../../btcrelay/headers/SolanaBtcStoredHeader";
9
- import {SolanaBtcRelay} from "../../btcrelay/SolanaBtcRelay";
10
- import {SolanaSwapProgram} from "../SolanaSwapProgram";
11
- import {SolanaSigner} from "../../wallet/SolanaSigner";
12
- import {SolanaTokens} from "../../chain/modules/SolanaTokens";
13
- import * as BN from "bn.js";
14
- import {SolanaChainInterface} from "../../chain/SolanaChainInterface";
15
-
16
- export class SwapClaim extends SolanaSwapModule {
17
-
18
- private static readonly CUCosts = {
19
- CLAIM: 25000,
20
- CLAIM_PAY_OUT: 50000,
21
- CLAIM_ONCHAIN: 600000,
22
- CLAIM_ONCHAIN_PAY_OUT: 600000
23
- };
24
-
25
- readonly btcRelay: SolanaBtcRelay<any>;
26
-
27
- /**
28
- * Claim action which uses the provided hex encoded secret for claiming the swap
29
- *
30
- * @param signer
31
- * @param swapData
32
- * @param secret
33
- * @private
34
- */
35
- private Claim(signer: PublicKey, swapData: SolanaSwapData, secret: string): Promise<SolanaAction>;
36
- /**
37
- * Claim action which uses data in the provided data ccount with dataKey to claim the swap
38
- *
39
- * @param signer
40
- * @param swapData
41
- * @param dataKey
42
- * @private
43
- */
44
- private Claim(signer: PublicKey, swapData: SolanaSwapData, dataKey: PublicKey): Promise<SolanaAction>;
45
- private async Claim(
46
- signer: PublicKey,
47
- swapData: SolanaSwapData,
48
- secretOrDataKey: string | PublicKey
49
- ): Promise<SolanaAction> {
50
- const isDataKey = typeof(secretOrDataKey)!=="string";
51
-
52
- const accounts = {
53
- signer,
54
- offerer: swapData.offerer,
55
- claimer: swapData.claimer,
56
- initializer: swapData.isPayIn() ? swapData.offerer : swapData.claimer,
57
- escrowState: this.program._SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
58
- ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
59
- data: isDataKey ? secretOrDataKey : null,
60
- };
61
- let secretBuffer = isDataKey ?
62
- Buffer.alloc(0) :
63
- Buffer.from(secretOrDataKey, "hex");
64
-
65
- if(swapData.isPayOut()) {
66
- return new SolanaAction(signer, this.root,
67
- await this.swapProgram.methods
68
- .claimerClaimPayOut(secretBuffer)
69
- .accounts({
70
- ...accounts,
71
- claimerAta: swapData.claimerAta,
72
- vault: this.program._SwapVault(swapData.token),
73
- vaultAuthority: this.program._SwapVaultAuthority, // Only necessary for V1 program
74
- tokenProgram: TOKEN_PROGRAM_ID
75
- })
76
- .instruction(),
77
- this.getComputeBudget(swapData)
78
- );
79
- } else {
80
- return new SolanaAction(signer, this.root,
81
- await this.swapProgram.methods
82
- .claimerClaim(secretBuffer)
83
- .accounts({
84
- ...accounts,
85
- claimerUserData: this.program._SwapUserVault(swapData.claimer, swapData.token)
86
- })
87
- .instruction(),
88
- this.getComputeBudget(swapData)
89
- );
90
- }
91
- }
92
-
93
- /**
94
- * Verify and claim action required for BTC on-chain swaps verified through btc relay, adds the btc relay verify
95
- * instruction to the 0th index in the transaction, also intentionally sets compute budget to null such that no
96
- * compute budget instruction is added, since that takes up too much space and txs are limited to 1232 bytes
97
- *
98
- * @param signer
99
- * @param swapData
100
- * @param storeDataKey
101
- * @param merkleProof
102
- * @param commitedHeader
103
- * @private
104
- */
105
- private async VerifyAndClaim(
106
- signer: PublicKey,
107
- swapData: SolanaSwapData,
108
- storeDataKey: PublicKey,
109
- merkleProof: {reversedTxId: Buffer, pos: number, merkle: Buffer[]},
110
- commitedHeader: SolanaBtcStoredHeader
111
- ): Promise<SolanaAction> {
112
- const action = await this.btcRelay.Verify(
113
- signer,
114
- merkleProof.reversedTxId,
115
- swapData.confirmations,
116
- merkleProof.pos,
117
- merkleProof.merkle,
118
- commitedHeader
119
- );
120
- action.addAction(await this.Claim(signer, swapData, storeDataKey));
121
- action.computeBudget = null;
122
- return action;
123
- }
124
-
125
- constructor(chainInterface: SolanaChainInterface, program: SolanaSwapProgram, btcRelay: SolanaBtcRelay<any>) {
126
- super(chainInterface, program);
127
- this.btcRelay = btcRelay;
128
- }
129
-
130
- /**
131
- * Gets the compute budget required for claiming the swap
132
- *
133
- * @param swapData
134
- * @private
135
- */
136
- private getComputeBudget(swapData: SolanaSwapData) {
137
- if(swapData.isPayOut()) {
138
- return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM_PAY_OUT" : "CLAIM_ONCHAIN_PAY_OUT"]
139
- } else {
140
- return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM" : "CLAIM_ONCHAIN"];
141
- }
142
- }
143
-
144
- /**
145
- * Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
146
- * requiredConfirmations
147
- * If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
148
- * current chain tip & adds them to the txs array
149
- *
150
- * @param signer
151
- * @param txBlockheight transaction blockheight
152
- * @param requiredConfirmations required confirmation for the swap to be claimable with that TX
153
- * @param blockhash blockhash of the block which includes the transaction
154
- * @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
155
- * txns are added here
156
- * @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
157
- * to the required blockheight
158
- * @private
159
- */
160
- private async getCommitedHeaderAndSynchronize(
161
- signer: PublicKey,
162
- txBlockheight: number,
163
- requiredConfirmations: number,
164
- blockhash: string,
165
- txs: SolanaTx[],
166
- synchronizer?: RelaySynchronizer<SolanaBtcStoredHeader, SolanaTx, any>,
167
- ): Promise<SolanaBtcStoredHeader | null> {
168
- const requiredBlockheight = txBlockheight+requiredConfirmations-1;
169
-
170
- const result = await this.btcRelay.retrieveLogAndBlockheight({
171
- blockhash: blockhash
172
- }, requiredBlockheight);
173
-
174
- if(result!=null) return result.header;
175
-
176
- //Need to synchronize
177
- if(synchronizer==null) return null;
178
-
179
- //TODO: We don't have to synchronize to tip, only to our required blockheight
180
- const resp = await synchronizer.syncToLatestTxs(signer.toString());
181
- this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
182
- "synchronizing ourselves in "+resp.txs.length+" txs");
183
- this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
184
- resp.txs.forEach(tx => txs.push(tx));
185
-
186
- //Retrieve computed header
187
- return resp.computedHeaderMap[txBlockheight];
188
- }
189
-
190
- /**
191
- * Adds the transactions required for initialization and writing of transaction data to the data account
192
- *
193
- * @param signer
194
- * @param tx transaction to be written
195
- * @param vout vout of the transaction to use to satisfy swap conditions
196
- * @param feeRate fee rate for the transactions
197
- * @param txs solana transaction array, init & write transactions are added here
198
- * @private
199
- * @returns {Promise<PublicKey>} publicKey/address of the data account
200
- */
201
- private addTxsWriteTransactionData(
202
- signer: PublicKey | SolanaSigner,
203
- tx: {hex: string, txid: string},
204
- vout: number,
205
- feeRate: string,
206
- txs: SolanaTx[]
207
- ): Promise<PublicKey> {
208
- const reversedTxId = Buffer.from(tx.txid, "hex").reverse();
209
- const writeData: Buffer = Buffer.concat([
210
- Buffer.from(new BN(vout).toArray("le", 4)),
211
- Buffer.from(tx.hex, "hex")
212
- ]);
213
- this.logger.debug("addTxsWriteTransactionData(): writing transaction data: ", writeData.toString("hex"));
214
-
215
- return this.program._DataAccount.addTxsWriteData(signer, reversedTxId, writeData, txs, feeRate);
216
- }
217
-
218
- /**
219
- * Checks whether we should unwrap the WSOL to SOL when claiming the swap
220
- *
221
- * @param signer
222
- * @param swapData
223
- * @private
224
- */
225
- private shouldUnwrap(signer: PublicKey, swapData: SolanaSwapData): boolean {
226
- return swapData.isPayOut() &&
227
- swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
228
- swapData.claimer.equals(signer);
229
- }
230
-
231
- /**
232
- * Creates transactions claiming the swap using a secret (for HTLC swaps)
233
- *
234
- * @param signer
235
- * @param swapData swap to claim
236
- * @param secret hex encoded secret pre-image to the HTLC hash
237
- * @param checkExpiry whether to check if the swap is already expired (trying to claim an expired swap with a secret
238
- * is dangerous because we might end up revealing the secret to the counterparty without being able to claim the swap)
239
- * @param initAta whether to init the claimer's ATA if it doesn't exist
240
- * @param feeRate fee rate to use for the transaction
241
- * @param skipAtaCheck whether to check if ATA exists
242
- */
243
- async txsClaimWithSecret(
244
- signer: PublicKey,
245
- swapData: SolanaSwapData,
246
- secret: string,
247
- checkExpiry?: boolean,
248
- initAta?: boolean,
249
- feeRate?: string,
250
- skipAtaCheck?: boolean
251
- ): Promise<SolanaTx[]> {
252
- //We need to be sure that this transaction confirms in time, otherwise we reveal the secret to the counterparty
253
- // and won't claim the funds
254
- if(checkExpiry && await this.program.isExpired(swapData.claimer.toString(), swapData)) {
255
- throw new SwapDataVerificationError("Not enough time to reliably pay the invoice");
256
- }
257
-
258
- const claimerAta = swapData.claimerAta ?? await getAssociatedTokenAddress(swapData.token, swapData.claimer);
259
- const shouldInitAta = !skipAtaCheck && swapData.isPayOut() && !await this.root.Tokens.ataExists(claimerAta);
260
- if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
261
-
262
- if(feeRate==null) feeRate = await this.getClaimFeeRate(signer, swapData);
263
-
264
- const action = new SolanaAction(signer, this.root);
265
-
266
- const shouldUnwrap = this.shouldUnwrap(signer, swapData);
267
- if(shouldInitAta) {
268
- const initAction = this.root.Tokens.InitAta(signer, swapData.claimer, swapData.token, claimerAta);
269
- if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
270
- action.add(initAction);
271
- }
272
- action.add(await this.Claim(signer, swapData, secret));
273
- if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(signer));
274
-
275
- this.logger.debug("txsClaimWithSecret(): creating claim transaction, swap: "+swapData.getClaimHash()+
276
- " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
277
-
278
- return [await action.tx(feeRate)];
279
- }
280
-
281
- /**
282
- * Creates transaction claiming the swap using a confirmed transaction data (for BTC on-chain swaps)
283
- *
284
- * @param signer
285
- * @param swapData swap to claim
286
- * @param tx bitcoin transaction that satisfies the swap condition
287
- * @param vout vout of the bitcoin transaction that satisfies the swap condition
288
- * @param commitedHeader commited header data from btc relay (fetched internally if null)
289
- * @param synchronizer optional synchronizer to use in case we need to sync up the btc relay ourselves
290
- * @param initAta whether to initialize claimer's ATA
291
- * @param feeRate fee rate to be used for the transactions
292
- */
293
- async txsClaimWithTxData(
294
- signer: PublicKey | SolanaSigner,
295
- swapData: SolanaSwapData,
296
- tx: { blockhash: string, txid: string, hex: string, height: number },
297
- vout: number,
298
- commitedHeader?: SolanaBtcStoredHeader | null,
299
- synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
300
- initAta?: boolean,
301
- feeRate?: string
302
- ): Promise<{txs: SolanaTx[], claimTxIndex: number, storageAcc: PublicKey}> {
303
- const claimerAta = swapData.claimerAta ?? await getAssociatedTokenAddress(swapData.token, swapData.claimer);
304
-
305
- const shouldInitAta = swapData.isPayOut() && !await this.root.Tokens.ataExists(claimerAta);
306
- if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
307
-
308
- const signerKey = signer instanceof SolanaSigner ? signer.getPublicKey() : signer;
309
-
310
- if(feeRate==null) feeRate = await this.getClaimFeeRate(signerKey, swapData);
311
-
312
- const merkleProof = await this.btcRelay._bitcoinRpc.getMerkleProof(tx.txid, tx.blockhash);
313
- if(merkleProof==null) throw new Error(`Failed to generate merkle proof for tx: ${tx.txid}`);
314
- this.logger.debug("txsClaimWithTxData(): merkle proof computed: ", merkleProof);
315
-
316
- const txs: SolanaTx[] = [];
317
- if(commitedHeader==null) commitedHeader = await this.getCommitedHeaderAndSynchronize(
318
- signerKey, tx.height, swapData.confirmations,
319
- tx.blockhash, txs, synchronizer
320
- );
321
- if(commitedHeader==null) throw new Error("Cannot get committed header, did you pass synchronizer?");
322
-
323
- const storageAcc = await this.addTxsWriteTransactionData(signer, tx, vout, feeRate, txs);
324
-
325
- const shouldUnwrap = this.shouldUnwrap(signerKey, swapData);
326
- if(shouldInitAta) {
327
- const initAction = this.root.Tokens.InitAta(signerKey, swapData.claimer, swapData.token, claimerAta);
328
- if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
329
- await initAction.addToTxs(txs, feeRate);
330
- }
331
- const claimTxIndex = txs.length;
332
- const claimAction = await this.VerifyAndClaim(signerKey, swapData, storageAcc, merkleProof, commitedHeader);
333
- await claimAction.addToTxs(txs, feeRate);
334
- if(shouldUnwrap) await this.root.Tokens.Unwrap(signerKey).addToTxs(txs, feeRate);
335
-
336
- this.logger.debug("txsClaimWithTxData(): creating claim transaction, swap: "+swapData.getClaimHash()+
337
- " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+" num txns: "+txs.length);
338
-
339
- return {txs, claimTxIndex, storageAcc};
340
- }
341
-
342
- public getClaimFeeRate(signer: PublicKey, swapData: SolanaSwapData): Promise<string> {
343
- const accounts: PublicKey[] = [signer];
344
- if(swapData.payOut) {
345
- if(swapData.token!=null) accounts.push(this.program._SwapVault(swapData.token));
346
- if(swapData.payIn) {
347
- if(swapData.offerer!=null) accounts.push(swapData.offerer);
348
- } else {
349
- if(swapData.claimer!=null) accounts.push(swapData.claimer);
350
- }
351
- if(swapData.claimerAta!=null && !swapData.claimerAta.equals(PublicKey.default)) accounts.push(swapData.claimerAta);
352
- } else {
353
- if(swapData.claimer!=null && swapData.token!=null) accounts.push(this.program._SwapUserVault(swapData.claimer, swapData.token));
354
-
355
- if(swapData.payIn) {
356
- if(swapData.offerer!=null) accounts.push(swapData.offerer);
357
- } else {
358
- if(swapData.claimer!=null) accounts.push(swapData.claimer);
359
- }
360
- }
361
-
362
- if(swapData.paymentHash!=null) accounts.push(this.program._SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
363
-
364
- return this.root.Fees.getFeeRate(accounts);
365
- }
366
-
367
- /**
368
- * Get the estimated solana transaction fee of the claim transaction in the worst case scenario in case where the
369
- * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
370
- */
371
- public async getClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
372
- return BigInt(swapData==null || swapData.payOut ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
373
- await this.getRawClaimFee(signer, swapData, feeRate);
374
- }
375
-
376
- /**
377
- * Get the estimated solana transaction fee of the claim transaction, without
378
- */
379
- public async getRawClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
380
- if(swapData==null) return 5000n;
381
-
382
- feeRate = feeRate || await this.getClaimFeeRate(signer, swapData);
383
-
384
- //Include rent exempt in claim fee, to take into consideration worst case cost when user destroys ATA
385
- return 5000n + this.root.Fees.getPriorityFee(this.getComputeBudget(swapData), feeRate);
386
- }
387
-
1
+ import {SolanaSwapModule} from "../SolanaSwapModule";
2
+ import {SolanaSwapData} from "../SolanaSwapData";
3
+ import {SolanaAction} from "../../chain/SolanaAction";
4
+ import {getAssociatedTokenAddress, TOKEN_PROGRAM_ID} from "@solana/spl-token";
5
+ import {ChainSwapType, RelaySynchronizer, SwapDataVerificationError} from "@atomiqlabs/base";
6
+ import {PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY} from "@solana/web3.js";
7
+ import {SolanaTx} from "../../chain/modules/SolanaTransactions";
8
+ import {SolanaBtcStoredHeader} from "../../btcrelay/headers/SolanaBtcStoredHeader";
9
+ import {SolanaBtcRelay} from "../../btcrelay/SolanaBtcRelay";
10
+ import {SolanaSwapProgram} from "../SolanaSwapProgram";
11
+ import {SolanaSigner} from "../../wallet/SolanaSigner";
12
+ import {SolanaTokens} from "../../chain/modules/SolanaTokens";
13
+ import * as BN from "bn.js";
14
+ import {SolanaChainInterface} from "../../chain/SolanaChainInterface";
15
+
16
+ export class SwapClaim extends SolanaSwapModule {
17
+
18
+ private static readonly CUCosts = {
19
+ CLAIM: 25000,
20
+ CLAIM_PAY_OUT: 50000,
21
+ CLAIM_ONCHAIN: 600000,
22
+ CLAIM_ONCHAIN_PAY_OUT: 600000
23
+ };
24
+
25
+ readonly btcRelay: SolanaBtcRelay<any>;
26
+
27
+ /**
28
+ * Claim action which uses the provided hex encoded secret for claiming the swap
29
+ *
30
+ * @param signer
31
+ * @param swapData
32
+ * @param secret
33
+ * @private
34
+ */
35
+ private Claim(signer: PublicKey, swapData: SolanaSwapData, secret: string): Promise<SolanaAction>;
36
+ /**
37
+ * Claim action which uses data in the provided data ccount with dataKey to claim the swap
38
+ *
39
+ * @param signer
40
+ * @param swapData
41
+ * @param dataKey
42
+ * @private
43
+ */
44
+ private Claim(signer: PublicKey, swapData: SolanaSwapData, dataKey: PublicKey): Promise<SolanaAction>;
45
+ private async Claim(
46
+ signer: PublicKey,
47
+ swapData: SolanaSwapData,
48
+ secretOrDataKey: string | PublicKey
49
+ ): Promise<SolanaAction> {
50
+ const isDataKey = typeof(secretOrDataKey)!=="string";
51
+
52
+ const accounts = {
53
+ signer,
54
+ offerer: swapData.offerer,
55
+ claimer: swapData.claimer,
56
+ initializer: swapData.isPayIn() ? swapData.offerer : swapData.claimer,
57
+ escrowState: this.program._SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
58
+ ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
59
+ data: isDataKey ? secretOrDataKey : null,
60
+ };
61
+ let secretBuffer = isDataKey ?
62
+ Buffer.alloc(0) :
63
+ Buffer.from(secretOrDataKey, "hex");
64
+
65
+ if(swapData.isPayOut()) {
66
+ return new SolanaAction(signer, this.root,
67
+ await this.swapProgram.methods
68
+ .claimerClaimPayOut(secretBuffer)
69
+ .accounts({
70
+ ...accounts,
71
+ claimerAta: swapData.claimerAta,
72
+ vault: this.program._SwapVault(swapData.token),
73
+ vaultAuthority: this.program._SwapVaultAuthority, // Only necessary for V1 program
74
+ tokenProgram: TOKEN_PROGRAM_ID
75
+ })
76
+ .instruction(),
77
+ this.getComputeBudget(swapData)
78
+ );
79
+ } else {
80
+ return new SolanaAction(signer, this.root,
81
+ await this.swapProgram.methods
82
+ .claimerClaim(secretBuffer)
83
+ .accounts({
84
+ ...accounts,
85
+ claimerUserData: this.program._SwapUserVault(swapData.claimer, swapData.token)
86
+ })
87
+ .instruction(),
88
+ this.getComputeBudget(swapData)
89
+ );
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Verify and claim action required for BTC on-chain swaps verified through btc relay, adds the btc relay verify
95
+ * instruction to the 0th index in the transaction, also intentionally sets compute budget to null such that no
96
+ * compute budget instruction is added, since that takes up too much space and txs are limited to 1232 bytes
97
+ *
98
+ * @param signer
99
+ * @param swapData
100
+ * @param storeDataKey
101
+ * @param merkleProof
102
+ * @param commitedHeader
103
+ * @private
104
+ */
105
+ private async VerifyAndClaim(
106
+ signer: PublicKey,
107
+ swapData: SolanaSwapData,
108
+ storeDataKey: PublicKey,
109
+ merkleProof: {reversedTxId: Buffer, pos: number, merkle: Buffer[]},
110
+ commitedHeader: SolanaBtcStoredHeader
111
+ ): Promise<SolanaAction> {
112
+ const action = await this.btcRelay.Verify(
113
+ signer,
114
+ merkleProof.reversedTxId,
115
+ swapData.confirmations,
116
+ merkleProof.pos,
117
+ merkleProof.merkle,
118
+ commitedHeader
119
+ );
120
+ action.addAction(await this.Claim(signer, swapData, storeDataKey));
121
+ action.computeBudget = null;
122
+ return action;
123
+ }
124
+
125
+ constructor(chainInterface: SolanaChainInterface, program: SolanaSwapProgram, btcRelay: SolanaBtcRelay<any>) {
126
+ super(chainInterface, program);
127
+ this.btcRelay = btcRelay;
128
+ }
129
+
130
+ /**
131
+ * Gets the compute budget required for claiming the swap
132
+ *
133
+ * @param swapData
134
+ * @private
135
+ */
136
+ private getComputeBudget(swapData: SolanaSwapData) {
137
+ if(swapData.isPayOut()) {
138
+ return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM_PAY_OUT" : "CLAIM_ONCHAIN_PAY_OUT"]
139
+ } else {
140
+ return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM" : "CLAIM_ONCHAIN"];
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
146
+ * requiredConfirmations
147
+ * If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
148
+ * current chain tip & adds them to the txs array
149
+ *
150
+ * @param signer
151
+ * @param txBlockheight transaction blockheight
152
+ * @param requiredConfirmations required confirmation for the swap to be claimable with that TX
153
+ * @param blockhash blockhash of the block which includes the transaction
154
+ * @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
155
+ * txns are added here
156
+ * @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
157
+ * to the required blockheight
158
+ * @private
159
+ */
160
+ private async getCommitedHeaderAndSynchronize(
161
+ signer: PublicKey,
162
+ txBlockheight: number,
163
+ requiredConfirmations: number,
164
+ blockhash: string,
165
+ txs: SolanaTx[],
166
+ synchronizer?: RelaySynchronizer<SolanaBtcStoredHeader, SolanaTx, any>,
167
+ ): Promise<SolanaBtcStoredHeader | null> {
168
+ const requiredBlockheight = txBlockheight+requiredConfirmations-1;
169
+
170
+ const result = await this.btcRelay.retrieveLogAndBlockheight({
171
+ blockhash: blockhash
172
+ }, requiredBlockheight);
173
+
174
+ if(result!=null) return result.header;
175
+
176
+ //Need to synchronize
177
+ if(synchronizer==null) return null;
178
+
179
+ //TODO: We don't have to synchronize to tip, only to our required blockheight
180
+ const resp = await synchronizer.syncToLatestTxs(signer.toString());
181
+ this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
182
+ "synchronizing ourselves in "+resp.txs.length+" txs");
183
+ this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
184
+ resp.txs.forEach(tx => txs.push(tx));
185
+
186
+ //Retrieve computed header
187
+ return resp.computedHeaderMap[txBlockheight];
188
+ }
189
+
190
+ /**
191
+ * Adds the transactions required for initialization and writing of transaction data to the data account
192
+ *
193
+ * @param signer
194
+ * @param tx transaction to be written
195
+ * @param vout vout of the transaction to use to satisfy swap conditions
196
+ * @param feeRate fee rate for the transactions
197
+ * @param txs solana transaction array, init & write transactions are added here
198
+ * @private
199
+ * @returns {Promise<PublicKey>} publicKey/address of the data account
200
+ */
201
+ private addTxsWriteTransactionData(
202
+ signer: PublicKey | SolanaSigner,
203
+ tx: {hex: string, txid: string},
204
+ vout: number,
205
+ feeRate: string,
206
+ txs: SolanaTx[]
207
+ ): Promise<PublicKey> {
208
+ const reversedTxId = Buffer.from(tx.txid, "hex").reverse();
209
+ const writeData: Buffer = Buffer.concat([
210
+ Buffer.from(new BN(vout).toArray("le", 4)),
211
+ Buffer.from(tx.hex, "hex")
212
+ ]);
213
+ this.logger.debug("addTxsWriteTransactionData(): writing transaction data: ", writeData.toString("hex"));
214
+
215
+ return this.program._DataAccount.addTxsWriteData(signer, reversedTxId, writeData, txs, feeRate);
216
+ }
217
+
218
+ /**
219
+ * Checks whether we should unwrap the WSOL to SOL when claiming the swap
220
+ *
221
+ * @param signer
222
+ * @param swapData
223
+ * @private
224
+ */
225
+ private shouldUnwrap(signer: PublicKey, swapData: SolanaSwapData): boolean {
226
+ return swapData.isPayOut() &&
227
+ swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
228
+ swapData.claimer.equals(signer);
229
+ }
230
+
231
+ /**
232
+ * Creates transactions claiming the swap using a secret (for HTLC swaps)
233
+ *
234
+ * @param signer
235
+ * @param swapData swap to claim
236
+ * @param secret hex encoded secret pre-image to the HTLC hash
237
+ * @param checkExpiry whether to check if the swap is already expired (trying to claim an expired swap with a secret
238
+ * is dangerous because we might end up revealing the secret to the counterparty without being able to claim the swap)
239
+ * @param initAta whether to init the claimer's ATA if it doesn't exist
240
+ * @param feeRate fee rate to use for the transaction
241
+ * @param skipAtaCheck whether to check if ATA exists
242
+ */
243
+ async txsClaimWithSecret(
244
+ signer: PublicKey,
245
+ swapData: SolanaSwapData,
246
+ secret: string,
247
+ checkExpiry?: boolean,
248
+ initAta?: boolean,
249
+ feeRate?: string,
250
+ skipAtaCheck?: boolean
251
+ ): Promise<SolanaTx[]> {
252
+ //We need to be sure that this transaction confirms in time, otherwise we reveal the secret to the counterparty
253
+ // and won't claim the funds
254
+ if(checkExpiry && await this.program.isExpired(swapData.claimer.toString(), swapData)) {
255
+ throw new SwapDataVerificationError("Not enough time to reliably pay the invoice");
256
+ }
257
+
258
+ const claimerAta = swapData.claimerAta ?? await getAssociatedTokenAddress(swapData.token, swapData.claimer);
259
+ const shouldInitAta = !skipAtaCheck && swapData.isPayOut() && !await this.root.Tokens.ataExists(claimerAta);
260
+ if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
261
+
262
+ if(feeRate==null) feeRate = await this.getClaimFeeRate(signer, swapData);
263
+
264
+ const action = new SolanaAction(signer, this.root);
265
+
266
+ const shouldUnwrap = this.shouldUnwrap(signer, swapData);
267
+ if(shouldInitAta) {
268
+ const initAction = this.root.Tokens.InitAta(signer, swapData.claimer, swapData.token, claimerAta);
269
+ if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
270
+ action.add(initAction);
271
+ }
272
+ action.add(await this.Claim(signer, swapData, secret));
273
+ if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(signer));
274
+
275
+ this.logger.debug("txsClaimWithSecret(): creating claim transaction, swap: "+swapData.getClaimHash()+
276
+ " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
277
+
278
+ return [await action.tx(feeRate)];
279
+ }
280
+
281
+ /**
282
+ * Creates transaction claiming the swap using a confirmed transaction data (for BTC on-chain swaps)
283
+ *
284
+ * @param signer
285
+ * @param swapData swap to claim
286
+ * @param tx bitcoin transaction that satisfies the swap condition
287
+ * @param vout vout of the bitcoin transaction that satisfies the swap condition
288
+ * @param commitedHeader commited header data from btc relay (fetched internally if null)
289
+ * @param synchronizer optional synchronizer to use in case we need to sync up the btc relay ourselves
290
+ * @param initAta whether to initialize claimer's ATA
291
+ * @param feeRate fee rate to be used for the transactions
292
+ */
293
+ async txsClaimWithTxData(
294
+ signer: PublicKey | SolanaSigner,
295
+ swapData: SolanaSwapData,
296
+ tx: { blockhash: string, txid: string, hex: string, height: number },
297
+ vout: number,
298
+ commitedHeader?: SolanaBtcStoredHeader | null,
299
+ synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
300
+ initAta?: boolean,
301
+ feeRate?: string
302
+ ): Promise<{txs: SolanaTx[], claimTxIndex: number, storageAcc: PublicKey}> {
303
+ const claimerAta = swapData.claimerAta ?? await getAssociatedTokenAddress(swapData.token, swapData.claimer);
304
+
305
+ const shouldInitAta = swapData.isPayOut() && !await this.root.Tokens.ataExists(claimerAta);
306
+ if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
307
+
308
+ const signerKey = signer instanceof SolanaSigner ? signer.getPublicKey() : signer;
309
+
310
+ if(feeRate==null) feeRate = await this.getClaimFeeRate(signerKey, swapData);
311
+
312
+ const merkleProof = await this.btcRelay._bitcoinRpc.getMerkleProof(tx.txid, tx.blockhash);
313
+ if(merkleProof==null) throw new Error(`Failed to generate merkle proof for tx: ${tx.txid}`);
314
+ this.logger.debug("txsClaimWithTxData(): merkle proof computed: ", merkleProof);
315
+
316
+ const txs: SolanaTx[] = [];
317
+ if(commitedHeader==null) commitedHeader = await this.getCommitedHeaderAndSynchronize(
318
+ signerKey, tx.height, swapData.confirmations,
319
+ tx.blockhash, txs, synchronizer
320
+ );
321
+ if(commitedHeader==null) throw new Error("Cannot get committed header, did you pass synchronizer?");
322
+
323
+ const storageAcc = await this.addTxsWriteTransactionData(signer, tx, vout, feeRate, txs);
324
+
325
+ const shouldUnwrap = this.shouldUnwrap(signerKey, swapData);
326
+ if(shouldInitAta) {
327
+ const initAction = this.root.Tokens.InitAta(signerKey, swapData.claimer, swapData.token, claimerAta);
328
+ if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
329
+ await initAction.addToTxs(txs, feeRate);
330
+ }
331
+ const claimTxIndex = txs.length;
332
+ const claimAction = await this.VerifyAndClaim(signerKey, swapData, storageAcc, merkleProof, commitedHeader);
333
+ await claimAction.addToTxs(txs, feeRate);
334
+ if(shouldUnwrap) await this.root.Tokens.Unwrap(signerKey).addToTxs(txs, feeRate);
335
+
336
+ this.logger.debug("txsClaimWithTxData(): creating claim transaction, swap: "+swapData.getClaimHash()+
337
+ " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+" num txns: "+txs.length);
338
+
339
+ return {txs, claimTxIndex, storageAcc};
340
+ }
341
+
342
+ public getClaimFeeRate(signer: PublicKey, swapData: SolanaSwapData): Promise<string> {
343
+ const accounts: PublicKey[] = [signer];
344
+ if(swapData.payOut) {
345
+ if(swapData.token!=null) accounts.push(this.program._SwapVault(swapData.token));
346
+ if(swapData.payIn) {
347
+ if(swapData.offerer!=null) accounts.push(swapData.offerer);
348
+ } else {
349
+ if(swapData.claimer!=null) accounts.push(swapData.claimer);
350
+ }
351
+ if(swapData.claimerAta!=null && !swapData.claimerAta.equals(PublicKey.default)) accounts.push(swapData.claimerAta);
352
+ } else {
353
+ if(swapData.claimer!=null && swapData.token!=null) accounts.push(this.program._SwapUserVault(swapData.claimer, swapData.token));
354
+
355
+ if(swapData.payIn) {
356
+ if(swapData.offerer!=null) accounts.push(swapData.offerer);
357
+ } else {
358
+ if(swapData.claimer!=null) accounts.push(swapData.claimer);
359
+ }
360
+ }
361
+
362
+ if(swapData.paymentHash!=null) accounts.push(this.program._SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
363
+
364
+ return this.root.Fees.getFeeRate(accounts);
365
+ }
366
+
367
+ /**
368
+ * Get the estimated solana transaction fee of the claim transaction in the worst case scenario in case where the
369
+ * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
370
+ */
371
+ public async getClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
372
+ return BigInt(swapData==null || swapData.payOut ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
373
+ await this.getRawClaimFee(signer, swapData, feeRate);
374
+ }
375
+
376
+ /**
377
+ * Get the estimated solana transaction fee of the claim transaction, without
378
+ */
379
+ public async getRawClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
380
+ if(swapData==null) return 5000n;
381
+
382
+ feeRate = feeRate || await this.getClaimFeeRate(signer, swapData);
383
+
384
+ //Include rent exempt in claim fee, to take into consideration worst case cost when user destroys ATA
385
+ return 5000n + this.root.Fees.getPriorityFee(this.getComputeBudget(swapData), feeRate);
386
+ }
387
+
388
388
  }