@atomiqlabs/chain-solana 12.0.14 → 12.0.15

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 -238
  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 -270
  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,324 +1,324 @@
1
- import {SolanaSwapModule} from "../SolanaSwapModule";
2
- import {SolanaSwapData} from "../SolanaSwapData";
3
- import {sha256} from "@noble/hashes/sha2";
4
- import {sign} from "tweetnacl";
5
- import {SignatureVerificationError, SwapDataVerificationError} from "@atomiqlabs/base";
6
- import {SolanaTx} from "../../chain/modules/SolanaTransactions";
7
- import {
8
- Ed25519Program, Keypair,
9
- PublicKey,
10
- SYSVAR_INSTRUCTIONS_PUBKEY
11
- } from "@solana/web3.js";
12
- import {
13
- getAssociatedTokenAddressSync,
14
- TOKEN_PROGRAM_ID
15
- } from "@solana/spl-token";
16
- import {SolanaAction} from "../../chain/SolanaAction";
17
- import {toBN, tryWithRetries} from "../../../utils/Utils";
18
- import {Buffer} from "buffer";
19
- import {SolanaSigner} from "../../wallet/SolanaSigner";
20
- import {SolanaTokens} from "../../chain/modules/SolanaTokens";
21
- import * as BN from "bn.js";
22
-
23
- export class SwapRefund extends SolanaSwapModule {
24
-
25
- private static readonly CUCosts = {
26
- REFUND: 15000,
27
- REFUND_PAY_OUT: 50000
28
- };
29
-
30
- /**
31
- * Action for generic Refund instruction
32
- *
33
- * @param swapData
34
- * @param refundAuthTimeout optional refund authorization timeout (should be 0 for refunding expired swaps)
35
- * @constructor
36
- * @private
37
- */
38
- private async Refund(swapData: SolanaSwapData, refundAuthTimeout?: bigint): Promise<SolanaAction> {
39
- const accounts = {
40
- offerer: swapData.offerer,
41
- claimer: swapData.claimer,
42
- escrowState: this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
43
- claimerUserData: !swapData.payOut ? this.program.SwapUserVault(swapData.claimer, swapData.token) : null,
44
- ixSysvar: refundAuthTimeout!=null ? SYSVAR_INSTRUCTIONS_PUBKEY : null
45
- };
46
-
47
- const useTimeout = refundAuthTimeout!=null ? refundAuthTimeout : 0n;
48
- if(swapData.isPayIn()) {
49
- const ata = getAssociatedTokenAddressSync(swapData.token, swapData.offerer);
50
-
51
- return new SolanaAction(swapData.offerer, this.root,
52
- await this.swapProgram.methods
53
- .offererRefundPayIn(toBN(useTimeout))
54
- .accounts({
55
- ...accounts,
56
- offererAta: ata,
57
- vault: this.program.SwapVault(swapData.token),
58
- vaultAuthority: this.program.SwapVaultAuthority,
59
- tokenProgram: TOKEN_PROGRAM_ID
60
- })
61
- .instruction(),
62
- SwapRefund.CUCosts.REFUND_PAY_OUT
63
- );
64
- } else {
65
- return new SolanaAction(swapData.offerer, this.root,
66
- await this.swapProgram.methods
67
- .offererRefund(toBN(useTimeout))
68
- .accounts({
69
- ...accounts,
70
- offererUserData: this.program.SwapUserVault(swapData.offerer, swapData.token)
71
- })
72
- .instruction(),
73
- SwapRefund.CUCosts.REFUND
74
- );
75
- }
76
- }
77
-
78
- /**
79
- * Action for refunding with signature, adds the Ed25519 verify instruction
80
- *
81
- * @param swapData
82
- * @param timeout
83
- * @param prefix
84
- * @param signature
85
- * @constructor
86
- * @private
87
- */
88
- private async RefundWithSignature(
89
- swapData: SolanaSwapData,
90
- timeout: string,
91
- prefix: string,
92
- signature: Buffer
93
- ): Promise<SolanaAction> {
94
- const action = new SolanaAction(swapData.offerer, this.root,
95
- Ed25519Program.createInstructionWithPublicKey({
96
- message: this.getRefundMessage(swapData, prefix, timeout),
97
- publicKey: swapData.claimer.toBuffer(),
98
- signature: signature
99
- }),
100
- 0,
101
- null,
102
- null,
103
- true
104
- );
105
- action.addAction(await this.Refund(swapData, BigInt(timeout)));
106
- return action;
107
- }
108
-
109
- /**
110
- * Gets the message to be signed as a refund authorization
111
- *
112
- * @param swapData
113
- * @param prefix
114
- * @param timeout
115
- * @private
116
- */
117
- private getRefundMessage(swapData: SolanaSwapData, prefix: string, timeout: string): Buffer {
118
- const messageBuffers = [
119
- Buffer.from(prefix, "ascii"),
120
- swapData.amount.toArrayLike(Buffer, "le", 8),
121
- swapData.expiry.toArrayLike(Buffer, "le", 8),
122
- swapData.sequence.toArrayLike(Buffer, "le", 8),
123
- Buffer.from(swapData.paymentHash, "hex"),
124
- new BN(timeout).toArrayLike(Buffer, "le", 8)
125
- ];
126
-
127
- return Buffer.from(sha256(Buffer.concat(messageBuffers)));
128
- }
129
-
130
- /**
131
- * Checks whether we should unwrap the WSOL to SOL when refunding the swap
132
- *
133
- * @param swapData
134
- * @private
135
- */
136
- private shouldUnwrap(swapData: SolanaSwapData): boolean {
137
- return swapData.isPayIn() &&
138
- swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
139
- swapData.offerer.equals(swapData.offerer);
140
- }
141
-
142
- public signSwapRefund(
143
- signer: SolanaSigner,
144
- swapData: SolanaSwapData,
145
- authorizationTimeout: number
146
- ): Promise<{ prefix: string; timeout: string; signature: string }> {
147
- if(signer.keypair==null) throw new Error("Unsupported");
148
- if(!signer.getPublicKey().equals(swapData.claimer)) throw new Error("Invalid signer, public key mismatch!");
149
-
150
- const authPrefix = "refund";
151
- const authTimeout = Math.floor(Date.now()/1000)+authorizationTimeout;
152
-
153
- const messageBuffer = this.getRefundMessage(swapData, authPrefix, authTimeout.toString(10));
154
- const signature = sign.detached(messageBuffer, signer.keypair.secretKey);
155
-
156
- return Promise.resolve({
157
- prefix: authPrefix,
158
- timeout: authTimeout.toString(10),
159
- signature: Buffer.from(signature).toString("hex")
160
- });
161
- }
162
-
163
- public isSignatureValid(swapData: SolanaSwapData, timeout: string, prefix: string, signature: string): Promise<Buffer> {
164
- if(prefix!=="refund") throw new SignatureVerificationError("Invalid prefix");
165
-
166
- const expiryTimestamp = BigInt(timeout);
167
- const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
168
-
169
- const isExpired = (expiryTimestamp - currentTimestamp) < BigInt(this.program.authGracePeriod);
170
- if(isExpired) throw new SignatureVerificationError("Authorization expired!");
171
-
172
- const signatureBuffer = Buffer.from(signature, "hex");
173
- const messageBuffer = this.getRefundMessage(swapData, prefix, timeout);
174
-
175
- if(!sign.detached.verify(messageBuffer, signatureBuffer, swapData.claimer.toBuffer())) {
176
- throw new SignatureVerificationError("Invalid signature!");
177
- }
178
-
179
- return Promise.resolve(messageBuffer);
180
- }
181
-
182
- /**
183
- * Creates transactions required for refunding timed out swap, also unwraps WSOL to SOL
184
- *
185
- * @param swapData swap data to refund
186
- * @param check whether to check if swap is already expired and refundable
187
- * @param initAta should initialize ATA if it doesn't exist
188
- * @param feeRate fee rate to be used for the transactions
189
- */
190
- public async txsRefund(
191
- swapData: SolanaSwapData,
192
- check?: boolean,
193
- initAta?: boolean,
194
- feeRate?: string
195
- ): Promise<SolanaTx[]> {
196
- if(check && !await tryWithRetries(() => this.program.isRequestRefundable(swapData.offerer.toString(), swapData), this.retryPolicy)) {
197
- throw new SwapDataVerificationError("Not refundable yet!");
198
- }
199
- const shouldInitAta = swapData.isPayIn() && !await this.root.Tokens.ataExists(swapData.offererAta);
200
- if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
201
-
202
- if(feeRate==null) feeRate = await this.program.getRefundFeeRate(swapData)
203
-
204
- const shouldUnwrap = this.shouldUnwrap(swapData);
205
- const action = new SolanaAction(swapData.offerer, this.root);
206
- if(shouldInitAta) {
207
- const initAction = this.root.Tokens.InitAta(swapData.offerer, swapData.offerer, swapData.token, swapData.offererAta);
208
- if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address")
209
- action.addAction(initAction);
210
- }
211
- action.add(await this.Refund(swapData));
212
- if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(swapData.offerer));
213
-
214
- this.logger.debug("txsRefund(): creating claim transaction, swap: "+swapData.getClaimHash()+
215
- " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
216
-
217
- return [await action.tx(feeRate)];
218
- }
219
-
220
- /**
221
- * Creates transactions required for refunding the swap with authorization signature, also unwraps WSOL to SOL
222
- *
223
- * @param swapData swap data to refund
224
- * @param timeout signature timeout
225
- * @param prefix signature prefix of the counterparty
226
- * @param signature signature of the counterparty
227
- * @param check whether to check if swap is committed before attempting refund
228
- * @param initAta should initialize ATA if it doesn't exist
229
- * @param feeRate fee rate to be used for the transactions
230
- */
231
- public async txsRefundWithAuthorization(
232
- swapData: SolanaSwapData,
233
- timeout: string,
234
- prefix: string,
235
- signature: string,
236
- check?: boolean,
237
- initAta?: boolean,
238
- feeRate?: string
239
- ): Promise<SolanaTx[]> {
240
- if(check && !await tryWithRetries(() => this.program.isCommited(swapData), this.retryPolicy)) {
241
- throw new SwapDataVerificationError("Not correctly committed");
242
- }
243
- await tryWithRetries(
244
- () => this.isSignatureValid(swapData, timeout, prefix, signature),
245
- this.retryPolicy,
246
- (e) => e instanceof SignatureVerificationError
247
- );
248
- const shouldInitAta = swapData.isPayIn() && !await this.root.Tokens.ataExists(swapData.offererAta);
249
- if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
250
-
251
- if(feeRate==null) feeRate = await this.program.getRefundFeeRate(swapData);
252
-
253
- const signatureBuffer = Buffer.from(signature, "hex");
254
-
255
- const shouldUnwrap = this.shouldUnwrap(swapData);
256
- const action = await this.RefundWithSignature(swapData, timeout, prefix, signatureBuffer);
257
- if(shouldInitAta) {
258
- const initAction = this.root.Tokens.InitAta(swapData.offerer, swapData.offerer, swapData.token, swapData.offererAta);
259
- if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
260
- action.addAction(initAction, 1); //Need to add it after the Ed25519 verify IX, but before the actual refund IX
261
- }
262
- if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(swapData.offerer));
263
-
264
- this.logger.debug("txsRefundWithAuthorization(): creating claim transaction, swap: "+swapData.getClaimHash()+
265
- " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+
266
- " auth expiry: "+timeout+" signature: "+signature);
267
-
268
- //Push a random keypair to the TX signer such that pesky Phantom
269
- // doesn't fuck up the instructions order!
270
- const tx = await action.tx(feeRate);
271
- const signer = Keypair.generate();
272
- tx.tx.instructions.find(val => val.programId.equals(this.program.program.programId)).keys.push({
273
- pubkey: signer.publicKey,
274
- isSigner: true,
275
- isWritable: false
276
- });
277
- (tx.signers ??= []).push(signer);
278
-
279
- return [tx];
280
- }
281
-
282
- public getRefundFeeRate(swapData: SolanaSwapData): Promise<string> {
283
- const accounts: PublicKey[] = [];
284
- if(swapData.payIn) {
285
- if(swapData.token!=null) accounts.push(this.program.SwapVault(swapData.token));
286
- if(swapData.offerer!=null) accounts.push(swapData.offerer);
287
- if(swapData.claimer!=null) accounts.push(swapData.claimer);
288
- if(swapData.offererAta!=null && !swapData.offererAta.equals(PublicKey.default)) accounts.push(swapData.offererAta);
289
- } else {
290
- if(swapData.offerer!=null) {
291
- accounts.push(swapData.offerer);
292
- if(swapData.token!=null) accounts.push(this.program.SwapUserVault(swapData.offerer, swapData.token));
293
- }
294
- if(swapData.claimer!=null) accounts.push(swapData.claimer);
295
- }
296
-
297
- if(swapData.paymentHash!=null) accounts.push(this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
298
-
299
- return this.root.Fees.getFeeRate(accounts);
300
- }
301
-
302
- /**
303
- * Get the estimated solana transaction fee of the refund transaction, in the worst case scenario in case where the
304
- * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
305
- */
306
- async getRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
307
- return BigInt(swapData==null || swapData.payIn ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
308
- await this.getRawRefundFee(swapData, feeRate);
309
- }
310
-
311
- /**
312
- * Get the estimated solana transaction fee of the refund transaction
313
- */
314
- async getRawRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
315
- if(swapData==null) return 15000n;
316
-
317
- feeRate = feeRate || await this.getRefundFeeRate(swapData);
318
-
319
- const computeBudget = swapData.payIn ? SwapRefund.CUCosts.REFUND_PAY_OUT : SwapRefund.CUCosts.REFUND;
320
-
321
- return 15000n + this.root.Fees.getPriorityFee(computeBudget, feeRate);
322
- }
323
-
1
+ import {SolanaSwapModule} from "../SolanaSwapModule";
2
+ import {SolanaSwapData} from "../SolanaSwapData";
3
+ import {sha256} from "@noble/hashes/sha2";
4
+ import {sign} from "tweetnacl";
5
+ import {SignatureVerificationError, SwapDataVerificationError} from "@atomiqlabs/base";
6
+ import {SolanaTx} from "../../chain/modules/SolanaTransactions";
7
+ import {
8
+ Ed25519Program, Keypair,
9
+ PublicKey,
10
+ SYSVAR_INSTRUCTIONS_PUBKEY
11
+ } from "@solana/web3.js";
12
+ import {
13
+ getAssociatedTokenAddressSync,
14
+ TOKEN_PROGRAM_ID
15
+ } from "@solana/spl-token";
16
+ import {SolanaAction} from "../../chain/SolanaAction";
17
+ import {toBN, tryWithRetries} from "../../../utils/Utils";
18
+ import {Buffer} from "buffer";
19
+ import {SolanaSigner} from "../../wallet/SolanaSigner";
20
+ import {SolanaTokens} from "../../chain/modules/SolanaTokens";
21
+ import * as BN from "bn.js";
22
+
23
+ export class SwapRefund extends SolanaSwapModule {
24
+
25
+ private static readonly CUCosts = {
26
+ REFUND: 15000,
27
+ REFUND_PAY_OUT: 50000
28
+ };
29
+
30
+ /**
31
+ * Action for generic Refund instruction
32
+ *
33
+ * @param swapData
34
+ * @param refundAuthTimeout optional refund authorization timeout (should be 0 for refunding expired swaps)
35
+ * @constructor
36
+ * @private
37
+ */
38
+ private async Refund(swapData: SolanaSwapData, refundAuthTimeout?: bigint): Promise<SolanaAction> {
39
+ const accounts = {
40
+ offerer: swapData.offerer,
41
+ claimer: swapData.claimer,
42
+ escrowState: this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
43
+ claimerUserData: !swapData.payOut ? this.program.SwapUserVault(swapData.claimer, swapData.token) : null,
44
+ ixSysvar: refundAuthTimeout!=null ? SYSVAR_INSTRUCTIONS_PUBKEY : null
45
+ };
46
+
47
+ const useTimeout = refundAuthTimeout!=null ? refundAuthTimeout : 0n;
48
+ if(swapData.isPayIn()) {
49
+ const ata = getAssociatedTokenAddressSync(swapData.token, swapData.offerer);
50
+
51
+ return new SolanaAction(swapData.offerer, this.root,
52
+ await this.swapProgram.methods
53
+ .offererRefundPayIn(toBN(useTimeout))
54
+ .accounts({
55
+ ...accounts,
56
+ offererAta: ata,
57
+ vault: this.program.SwapVault(swapData.token),
58
+ vaultAuthority: this.program.SwapVaultAuthority,
59
+ tokenProgram: TOKEN_PROGRAM_ID
60
+ })
61
+ .instruction(),
62
+ SwapRefund.CUCosts.REFUND_PAY_OUT
63
+ );
64
+ } else {
65
+ return new SolanaAction(swapData.offerer, this.root,
66
+ await this.swapProgram.methods
67
+ .offererRefund(toBN(useTimeout))
68
+ .accounts({
69
+ ...accounts,
70
+ offererUserData: this.program.SwapUserVault(swapData.offerer, swapData.token)
71
+ })
72
+ .instruction(),
73
+ SwapRefund.CUCosts.REFUND
74
+ );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Action for refunding with signature, adds the Ed25519 verify instruction
80
+ *
81
+ * @param swapData
82
+ * @param timeout
83
+ * @param prefix
84
+ * @param signature
85
+ * @constructor
86
+ * @private
87
+ */
88
+ private async RefundWithSignature(
89
+ swapData: SolanaSwapData,
90
+ timeout: string,
91
+ prefix: string,
92
+ signature: Buffer
93
+ ): Promise<SolanaAction> {
94
+ const action = new SolanaAction(swapData.offerer, this.root,
95
+ Ed25519Program.createInstructionWithPublicKey({
96
+ message: this.getRefundMessage(swapData, prefix, timeout),
97
+ publicKey: swapData.claimer.toBuffer(),
98
+ signature: signature
99
+ }),
100
+ 0,
101
+ null,
102
+ null,
103
+ true
104
+ );
105
+ action.addAction(await this.Refund(swapData, BigInt(timeout)));
106
+ return action;
107
+ }
108
+
109
+ /**
110
+ * Gets the message to be signed as a refund authorization
111
+ *
112
+ * @param swapData
113
+ * @param prefix
114
+ * @param timeout
115
+ * @private
116
+ */
117
+ private getRefundMessage(swapData: SolanaSwapData, prefix: string, timeout: string): Buffer {
118
+ const messageBuffers = [
119
+ Buffer.from(prefix, "ascii"),
120
+ swapData.amount.toArrayLike(Buffer, "le", 8),
121
+ swapData.expiry.toArrayLike(Buffer, "le", 8),
122
+ swapData.sequence.toArrayLike(Buffer, "le", 8),
123
+ Buffer.from(swapData.paymentHash, "hex"),
124
+ new BN(timeout).toArrayLike(Buffer, "le", 8)
125
+ ];
126
+
127
+ return Buffer.from(sha256(Buffer.concat(messageBuffers)));
128
+ }
129
+
130
+ /**
131
+ * Checks whether we should unwrap the WSOL to SOL when refunding the swap
132
+ *
133
+ * @param swapData
134
+ * @private
135
+ */
136
+ private shouldUnwrap(swapData: SolanaSwapData): boolean {
137
+ return swapData.isPayIn() &&
138
+ swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
139
+ swapData.offerer.equals(swapData.offerer);
140
+ }
141
+
142
+ public signSwapRefund(
143
+ signer: SolanaSigner,
144
+ swapData: SolanaSwapData,
145
+ authorizationTimeout: number
146
+ ): Promise<{ prefix: string; timeout: string; signature: string }> {
147
+ if(signer.keypair==null) throw new Error("Unsupported");
148
+ if(!signer.getPublicKey().equals(swapData.claimer)) throw new Error("Invalid signer, public key mismatch!");
149
+
150
+ const authPrefix = "refund";
151
+ const authTimeout = Math.floor(Date.now()/1000)+authorizationTimeout;
152
+
153
+ const messageBuffer = this.getRefundMessage(swapData, authPrefix, authTimeout.toString(10));
154
+ const signature = sign.detached(messageBuffer, signer.keypair.secretKey);
155
+
156
+ return Promise.resolve({
157
+ prefix: authPrefix,
158
+ timeout: authTimeout.toString(10),
159
+ signature: Buffer.from(signature).toString("hex")
160
+ });
161
+ }
162
+
163
+ public isSignatureValid(swapData: SolanaSwapData, timeout: string, prefix: string, signature: string): Promise<Buffer> {
164
+ if(prefix!=="refund") throw new SignatureVerificationError("Invalid prefix");
165
+
166
+ const expiryTimestamp = BigInt(timeout);
167
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
168
+
169
+ const isExpired = (expiryTimestamp - currentTimestamp) < BigInt(this.program.authGracePeriod);
170
+ if(isExpired) throw new SignatureVerificationError("Authorization expired!");
171
+
172
+ const signatureBuffer = Buffer.from(signature, "hex");
173
+ const messageBuffer = this.getRefundMessage(swapData, prefix, timeout);
174
+
175
+ if(!sign.detached.verify(messageBuffer, signatureBuffer, swapData.claimer.toBuffer())) {
176
+ throw new SignatureVerificationError("Invalid signature!");
177
+ }
178
+
179
+ return Promise.resolve(messageBuffer);
180
+ }
181
+
182
+ /**
183
+ * Creates transactions required for refunding timed out swap, also unwraps WSOL to SOL
184
+ *
185
+ * @param swapData swap data to refund
186
+ * @param check whether to check if swap is already expired and refundable
187
+ * @param initAta should initialize ATA if it doesn't exist
188
+ * @param feeRate fee rate to be used for the transactions
189
+ */
190
+ public async txsRefund(
191
+ swapData: SolanaSwapData,
192
+ check?: boolean,
193
+ initAta?: boolean,
194
+ feeRate?: string
195
+ ): Promise<SolanaTx[]> {
196
+ if(check && !await tryWithRetries(() => this.program.isRequestRefundable(swapData.offerer.toString(), swapData), this.retryPolicy)) {
197
+ throw new SwapDataVerificationError("Not refundable yet!");
198
+ }
199
+ const shouldInitAta = swapData.isPayIn() && !await this.root.Tokens.ataExists(swapData.offererAta);
200
+ if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
201
+
202
+ if(feeRate==null) feeRate = await this.program.getRefundFeeRate(swapData)
203
+
204
+ const shouldUnwrap = this.shouldUnwrap(swapData);
205
+ const action = new SolanaAction(swapData.offerer, this.root);
206
+ if(shouldInitAta) {
207
+ const initAction = this.root.Tokens.InitAta(swapData.offerer, swapData.offerer, swapData.token, swapData.offererAta);
208
+ if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address")
209
+ action.addAction(initAction);
210
+ }
211
+ action.add(await this.Refund(swapData));
212
+ if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(swapData.offerer));
213
+
214
+ this.logger.debug("txsRefund(): creating claim transaction, swap: "+swapData.getClaimHash()+
215
+ " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
216
+
217
+ return [await action.tx(feeRate)];
218
+ }
219
+
220
+ /**
221
+ * Creates transactions required for refunding the swap with authorization signature, also unwraps WSOL to SOL
222
+ *
223
+ * @param swapData swap data to refund
224
+ * @param timeout signature timeout
225
+ * @param prefix signature prefix of the counterparty
226
+ * @param signature signature of the counterparty
227
+ * @param check whether to check if swap is committed before attempting refund
228
+ * @param initAta should initialize ATA if it doesn't exist
229
+ * @param feeRate fee rate to be used for the transactions
230
+ */
231
+ public async txsRefundWithAuthorization(
232
+ swapData: SolanaSwapData,
233
+ timeout: string,
234
+ prefix: string,
235
+ signature: string,
236
+ check?: boolean,
237
+ initAta?: boolean,
238
+ feeRate?: string
239
+ ): Promise<SolanaTx[]> {
240
+ if(check && !await tryWithRetries(() => this.program.isCommited(swapData), this.retryPolicy)) {
241
+ throw new SwapDataVerificationError("Not correctly committed");
242
+ }
243
+ await tryWithRetries(
244
+ () => this.isSignatureValid(swapData, timeout, prefix, signature),
245
+ this.retryPolicy,
246
+ (e) => e instanceof SignatureVerificationError
247
+ );
248
+ const shouldInitAta = swapData.isPayIn() && !await this.root.Tokens.ataExists(swapData.offererAta);
249
+ if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
250
+
251
+ if(feeRate==null) feeRate = await this.program.getRefundFeeRate(swapData);
252
+
253
+ const signatureBuffer = Buffer.from(signature, "hex");
254
+
255
+ const shouldUnwrap = this.shouldUnwrap(swapData);
256
+ const action = await this.RefundWithSignature(swapData, timeout, prefix, signatureBuffer);
257
+ if(shouldInitAta) {
258
+ const initAction = this.root.Tokens.InitAta(swapData.offerer, swapData.offerer, swapData.token, swapData.offererAta);
259
+ if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
260
+ action.addAction(initAction, 1); //Need to add it after the Ed25519 verify IX, but before the actual refund IX
261
+ }
262
+ if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(swapData.offerer));
263
+
264
+ this.logger.debug("txsRefundWithAuthorization(): creating claim transaction, swap: "+swapData.getClaimHash()+
265
+ " initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+
266
+ " auth expiry: "+timeout+" signature: "+signature);
267
+
268
+ //Push a random keypair to the TX signer such that pesky Phantom
269
+ // doesn't fuck up the instructions order!
270
+ const tx = await action.tx(feeRate);
271
+ const signer = Keypair.generate();
272
+ tx.tx.instructions.find(val => val.programId.equals(this.program.program.programId)).keys.push({
273
+ pubkey: signer.publicKey,
274
+ isSigner: true,
275
+ isWritable: false
276
+ });
277
+ (tx.signers ??= []).push(signer);
278
+
279
+ return [tx];
280
+ }
281
+
282
+ public getRefundFeeRate(swapData: SolanaSwapData): Promise<string> {
283
+ const accounts: PublicKey[] = [];
284
+ if(swapData.payIn) {
285
+ if(swapData.token!=null) accounts.push(this.program.SwapVault(swapData.token));
286
+ if(swapData.offerer!=null) accounts.push(swapData.offerer);
287
+ if(swapData.claimer!=null) accounts.push(swapData.claimer);
288
+ if(swapData.offererAta!=null && !swapData.offererAta.equals(PublicKey.default)) accounts.push(swapData.offererAta);
289
+ } else {
290
+ if(swapData.offerer!=null) {
291
+ accounts.push(swapData.offerer);
292
+ if(swapData.token!=null) accounts.push(this.program.SwapUserVault(swapData.offerer, swapData.token));
293
+ }
294
+ if(swapData.claimer!=null) accounts.push(swapData.claimer);
295
+ }
296
+
297
+ if(swapData.paymentHash!=null) accounts.push(this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
298
+
299
+ return this.root.Fees.getFeeRate(accounts);
300
+ }
301
+
302
+ /**
303
+ * Get the estimated solana transaction fee of the refund transaction, in the worst case scenario in case where the
304
+ * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
305
+ */
306
+ async getRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
307
+ return BigInt(swapData==null || swapData.payIn ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
308
+ await this.getRawRefundFee(swapData, feeRate);
309
+ }
310
+
311
+ /**
312
+ * Get the estimated solana transaction fee of the refund transaction
313
+ */
314
+ async getRawRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
315
+ if(swapData==null) return 15000n;
316
+
317
+ feeRate = feeRate || await this.getRefundFeeRate(swapData);
318
+
319
+ const computeBudget = swapData.payIn ? SwapRefund.CUCosts.REFUND_PAY_OUT : SwapRefund.CUCosts.REFUND;
320
+
321
+ return 15000n + this.root.Fees.getPriorityFee(computeBudget, feeRate);
322
+ }
323
+
324
324
  }