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