@atomiqlabs/chain-solana 10.0.0-dev.3 → 11.0.0

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 +10 -10
  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 +58 -58
  20. package/dist/solana/chain/SolanaChainInterface.js +112 -112
  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 +25 -25
  28. package/dist/solana/chain/modules/SolanaEvents.js +58 -58
  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 +332 -332
  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 +85 -85
  42. package/dist/solana/events/SolanaChainEventsBrowser.js +194 -194
  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 +59 -59
  48. package/dist/solana/program/modules/SolanaProgramEvents.js +103 -103
  49. package/dist/solana/swaps/SolanaSwapData.d.ts +59 -59
  50. package/dist/solana/swaps/SolanaSwapData.js +267 -267
  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 +202 -202
  54. package/dist/solana/swaps/SolanaSwapProgram.js +470 -463
  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 +252 -252
  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 +10 -10
  73. package/dist/solana/wallet/SolanaSigner.js +16 -16
  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 +25 -25
  79. package/src/solana/SolanaChains.ts +23 -23
  80. package/src/solana/SolanaInitializer.ts +102 -102
  81. package/src/solana/btcrelay/SolanaBtcRelay.ts +589 -588
  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 +174 -174
  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 +56 -56
  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 +370 -370
  96. package/src/solana/events/SolanaChainEvents.ts +299 -299
  97. package/src/solana/events/SolanaChainEventsBrowser.ts +256 -256
  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 +140 -140
  101. package/src/solana/swaps/SolanaSwapData.ts +379 -379
  102. package/src/solana/swaps/SolanaSwapModule.ts +16 -16
  103. package/src/solana/swaps/SolanaSwapProgram.ts +697 -692
  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 +312 -312
  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 +23 -23
  114. package/src/utils/Utils.ts +180 -180
@@ -1,332 +1,332 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SolanaTransactions = void 0;
4
- const web3_js_1 = require("@solana/web3.js");
5
- const SolanaModule_1 = require("../SolanaModule");
6
- const bs58 = require("bs58");
7
- const Utils_1 = require("../../../utils/Utils");
8
- const buffer_1 = require("buffer");
9
- class SolanaTransactions extends SolanaModule_1.SolanaModule {
10
- /**
11
- * Sends raw solana transaction, first through the cbkSendTransaction callback (for e.g. sending the transaction
12
- * to a different specific RPC), the through the Fees handler (for e.g. Jito transaction) and last through the
13
- * underlying provider's Connection instance (the usual way). Only sends the transaction through one channel.
14
- *
15
- * @param data
16
- * @param options
17
- * @private
18
- */
19
- async sendRawTransaction(data, options) {
20
- let result = null;
21
- options ?? (options = {});
22
- options.maxRetries = 0;
23
- if (this.cbkSendTransaction != null)
24
- result = await this.cbkSendTransaction(data, options);
25
- if (result == null)
26
- result = await this.root.Fees.submitTx(data, options);
27
- if (result == null)
28
- result = await this.connection.sendRawTransaction(data, options);
29
- // this.logger.debug("sendRawTransaction(): tx sent, signature: "+result);
30
- return result;
31
- }
32
- /**
33
- * Waits for the transaction to confirm by periodically checking the transaction status over HTTP, also
34
- * re-sends the transaction at regular intervals
35
- *
36
- * @param solanaTx solana tx to wait for confirmation for
37
- * @param finality wait for this finality
38
- * @param abortSignal signal to abort waiting for tx confirmation
39
- * @private
40
- */
41
- txConfirmationAndResendWatchdog(solanaTx, finality, abortSignal) {
42
- const rawTx = solanaTx.tx.serialize();
43
- const signature = bs58.encode(solanaTx.tx.signature);
44
- return new Promise((resolve, reject) => {
45
- let watchdogInterval;
46
- watchdogInterval = setInterval(async () => {
47
- const result = await this.sendRawTransaction(rawTx, { skipPreflight: true }).catch(e => this.logger.error("txConfirmationAndResendWatchdog(): transaction re-sent error: ", e));
48
- this.logger.debug("txConfirmationAndResendWatchdog(): transaction re-sent: " + result);
49
- const status = await this.getTxIdStatus(signature, finality).catch(e => this.logger.error("txConfirmationAndResendWatchdog(): get tx id status error: ", e));
50
- if (status == null || status === "not_found")
51
- return;
52
- if (status === "success") {
53
- this.logger.info("txConfirmationAndResendWatchdog(): transaction confirmed from HTTP polling, signature: " + signature);
54
- resolve(signature);
55
- }
56
- if (status === "reverted")
57
- reject(new Error("Transaction reverted!"));
58
- clearInterval(watchdogInterval);
59
- }, this.retryPolicy?.transactionResendInterval || 3000);
60
- if (abortSignal != null)
61
- abortSignal.addEventListener("abort", () => {
62
- clearInterval(watchdogInterval);
63
- reject(abortSignal.reason);
64
- });
65
- });
66
- }
67
- /**
68
- * Waits for the transaction to confirm from WS, sometimes the WS rejects even though the transaction was confirmed
69
- * this therefore also runs an ultimate check on the transaction in case the WS handler rejects, checking if it
70
- * really was expired
71
- *
72
- * @param solanaTx solana tx to wait for confirmation for
73
- * @param finality wait for this finality
74
- * @param abortSignal signal to abort waiting for tx confirmation
75
- * @private
76
- */
77
- async txConfirmFromWebsocket(solanaTx, finality, abortSignal) {
78
- const signature = bs58.encode(solanaTx.tx.signature);
79
- let result;
80
- try {
81
- result = await this.connection.confirmTransaction({
82
- signature: signature,
83
- blockhash: solanaTx.tx.recentBlockhash,
84
- lastValidBlockHeight: solanaTx.tx.lastValidBlockHeight,
85
- abortSignal
86
- }, finality);
87
- this.logger.info("txConfirmFromWebsocket(): transaction confirmed from WS, signature: " + signature);
88
- }
89
- catch (err) {
90
- if (abortSignal != null && abortSignal.aborted)
91
- throw err;
92
- this.logger.debug("txConfirmFromWebsocket(): transaction rejected from WS, running ultimate check, expiry blockheight: " + solanaTx.tx.lastValidBlockHeight + " signature: " + signature + " error: " + err);
93
- const status = await (0, Utils_1.tryWithRetries)(() => this.getTxIdStatus(signature, finality));
94
- this.logger.info("txConfirmFromWebsocket(): transaction status: " + status + " signature: " + signature);
95
- if (status === "success")
96
- return signature;
97
- if (status === "reverted")
98
- throw new Error("Transaction reverted!");
99
- if (err instanceof web3_js_1.TransactionExpiredBlockheightExceededError || err.toString().startsWith("TransactionExpiredBlockheightExceededError")) {
100
- throw new Error("Transaction expired before confirmation, please try again!");
101
- }
102
- else {
103
- throw err;
104
- }
105
- }
106
- if (result.value.err != null)
107
- throw new Error("Transaction reverted!");
108
- return signature;
109
- }
110
- /**
111
- * Waits for transaction confirmation using WS subscription and occasional HTTP polling, also re-sends
112
- * the transaction at regular interval
113
- *
114
- * @param solanaTx solana transaction to wait for confirmation for & keep re-sending until it confirms
115
- * @param abortSignal signal to abort waiting for tx confirmation
116
- * @param finality wait for specific finality
117
- * @private
118
- */
119
- async confirmTransaction(solanaTx, abortSignal, finality) {
120
- const abortController = new AbortController();
121
- if (abortSignal != null)
122
- abortSignal.addEventListener("abort", () => {
123
- abortController.abort();
124
- });
125
- let txSignature;
126
- try {
127
- txSignature = await Promise.race([
128
- this.txConfirmationAndResendWatchdog(solanaTx, finality, abortController.signal),
129
- this.txConfirmFromWebsocket(solanaTx, finality, abortController.signal)
130
- ]);
131
- }
132
- catch (e) {
133
- abortController.abort(e);
134
- throw e;
135
- }
136
- // this.logger.info("confirmTransaction(): transaction confirmed, signature: "+txSignature);
137
- abortController.abort();
138
- }
139
- /**
140
- * Prepares solana transactions, assigns recentBlockhash if needed, applies Phantom hotfix,
141
- * sets feePayer to ourselves, calls beforeTxSigned callback & signs transaction with provided signers array
142
- *
143
- * @param signer
144
- * @param txs
145
- * @private
146
- */
147
- async prepareTransactions(signer, txs) {
148
- let latestBlockData = null;
149
- for (let tx of txs) {
150
- if (tx.tx.recentBlockhash == null) {
151
- if (latestBlockData == null) {
152
- latestBlockData = await (0, Utils_1.tryWithRetries)(() => this.connection.getLatestBlockhash("confirmed"), this.retryPolicy);
153
- this.logger.debug("prepareTransactions(): fetched latest block data for transactions," +
154
- " blockhash: " + latestBlockData.blockhash + " expiry blockheight: " + latestBlockData.lastValidBlockHeight);
155
- }
156
- tx.tx.recentBlockhash = latestBlockData.blockhash;
157
- tx.tx.lastValidBlockHeight = latestBlockData.lastValidBlockHeight;
158
- }
159
- //This is a hotfix for Phantom adding compute unit price instruction on the first position & breaking
160
- // required instructions order (e.g. btc relay verify needs to be 0th instruction in a tx)
161
- if (signer.keypair == null && tx.tx.signatures.length === 0) {
162
- const foundIx = tx.tx.instructions.find(ix => ix.programId.equals(web3_js_1.ComputeBudgetProgram.programId) && web3_js_1.ComputeBudgetInstruction.decodeInstructionType(ix) === "SetComputeUnitPrice");
163
- if (foundIx == null)
164
- tx.tx.instructions.splice(tx.tx.instructions.length - 1, 0, web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }));
165
- }
166
- tx.tx.feePayer = signer.getPublicKey();
167
- if (this.cbkBeforeTxSigned != null)
168
- await this.cbkBeforeTxSigned(tx);
169
- if (tx.signers != null && tx.signers.length > 0)
170
- for (let signer of tx.signers)
171
- tx.tx.sign(signer);
172
- }
173
- }
174
- /**
175
- * Sends out a signed transaction to the RPC
176
- *
177
- * @param solTx solana tx to send
178
- * @param options send options to be passed to the RPC
179
- * @param onBeforePublish a callback called before every transaction is published
180
- * @private
181
- */
182
- async sendSignedTransaction(solTx, options, onBeforePublish) {
183
- if (onBeforePublish != null)
184
- await onBeforePublish(bs58.encode(solTx.tx.signature), await this.serializeTx(solTx));
185
- const serializedTx = solTx.tx.serialize();
186
- this.logger.debug("sendSignedTransaction(): sending transaction: " + serializedTx.toString("hex") +
187
- " signature: " + bs58.encode(solTx.tx.signature));
188
- const txResult = await (0, Utils_1.tryWithRetries)(() => this.sendRawTransaction(serializedTx, options), this.retryPolicy);
189
- this.logger.info("sendSignedTransaction(): tx sent, signature: " + txResult);
190
- return txResult;
191
- }
192
- /**
193
- * Prepares (adds recent blockhash if required, applies Phantom hotfix),
194
- * signs (all together using signAllTransactions() calls), sends (in parallel or sequentially) &
195
- * optionally waits for confirmation of a batch of solana transactions
196
- *
197
- * @param signer
198
- * @param _txs
199
- * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
200
- * are re-sent at regular intervals)
201
- * @param abortSignal abort signal to abort waiting for transaction confirmations
202
- * @param parallel whether the send all the transaction at once in parallel or sequentially (such that transactions
203
- * are executed in order)
204
- * @param onBeforePublish a callback called before every transaction is published
205
- */
206
- async sendAndConfirm(signer, _txs, waitForConfirmation, abortSignal, parallel, onBeforePublish) {
207
- const options = {
208
- skipPreflight: true
209
- };
210
- this.logger.debug("sendAndConfirm(): sending transactions, count: " + _txs.length +
211
- " waitForConfirmation: " + waitForConfirmation + " parallel: " + parallel);
212
- const signatures = [];
213
- for (let e = 0; e < _txs.length; e += 50) {
214
- const txs = _txs.slice(e, e + 50);
215
- await this.prepareTransactions(signer, txs);
216
- const signedTxs = await signer.wallet.signAllTransactions(txs.map(e => e.tx));
217
- signedTxs.forEach((tx, index) => {
218
- const solTx = txs[index];
219
- tx.lastValidBlockHeight = solTx.tx.lastValidBlockHeight;
220
- solTx.tx = tx;
221
- });
222
- this.logger.debug("sendAndConfirm(): sending transaction batch (" + e + ".." + (e + 50) + "), count: " + txs.length);
223
- if (parallel) {
224
- const promises = [];
225
- for (let solTx of txs) {
226
- const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
227
- if (waitForConfirmation)
228
- promises.push(this.confirmTransaction(solTx, abortSignal, "confirmed"));
229
- signatures.push(signature);
230
- }
231
- if (promises.length > 0)
232
- await Promise.all(promises);
233
- }
234
- else {
235
- for (let i = 0; i < txs.length; i++) {
236
- const solTx = txs[i];
237
- const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
238
- const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
239
- //Don't await the last promise when !waitForConfirmation
240
- if (i < txs.length - 1 || e + 50 < _txs.length || waitForConfirmation)
241
- await confirmPromise;
242
- signatures.push(signature);
243
- }
244
- }
245
- }
246
- this.logger.info("sendAndConfirm(): sent transactions, count: " + _txs.length +
247
- " waitForConfirmation: " + waitForConfirmation + " parallel: " + parallel);
248
- return signatures;
249
- }
250
- /**
251
- * Serializes the solana transaction, saves the transaction, signers & last valid blockheight
252
- *
253
- * @param tx
254
- */
255
- serializeTx(tx) {
256
- return Promise.resolve(JSON.stringify({
257
- tx: tx.tx.serialize().toString("hex"),
258
- signers: tx.signers.map(e => buffer_1.Buffer.from(e.secretKey).toString("hex")),
259
- lastValidBlockheight: tx.tx.lastValidBlockHeight
260
- }));
261
- }
262
- /**
263
- * Deserializes saved solana transaction, extracting the transaction, signers & last valid blockheight
264
- *
265
- * @param txData
266
- */
267
- deserializeTx(txData) {
268
- const jsonParsed = JSON.parse(txData);
269
- const transaction = web3_js_1.Transaction.from(buffer_1.Buffer.from(jsonParsed.tx, "hex"));
270
- transaction.lastValidBlockHeight = jsonParsed.lastValidBlockheight;
271
- return Promise.resolve({
272
- tx: transaction,
273
- signers: jsonParsed.signers.map(e => web3_js_1.Keypair.fromSecretKey(buffer_1.Buffer.from(e, "hex"))),
274
- });
275
- }
276
- /**
277
- * Gets the status of the raw solana transaction, this also checks transaction expiry & can therefore report tx
278
- * in "pending" status, however pending status doesn't necessarily mean that the transaction was sent (again,
279
- * no mempool on Solana, cannot check that), this function is preferred against getTxIdStatus
280
- *
281
- * @param tx
282
- */
283
- async getTxStatus(tx) {
284
- const parsedTx = await this.deserializeTx(tx);
285
- const txReceipt = await this.connection.getTransaction(bs58.encode(parsedTx.tx.signature), {
286
- commitment: "confirmed",
287
- maxSupportedTransactionVersion: 0
288
- });
289
- if (txReceipt == null) {
290
- const currentBlockheight = await this.connection.getBlockHeight("processed");
291
- if (currentBlockheight > parsedTx.tx.lastValidBlockHeight)
292
- return "not_found";
293
- return "pending";
294
- }
295
- if (txReceipt.meta.err)
296
- return "reverted";
297
- return "success";
298
- }
299
- /**
300
- * Gets the status of the solana transaction with a specific txId, this cannot report whether the transaction is
301
- * "pending" because Solana has no concept of mempool & only confirmed transactions are accessible
302
- *
303
- * @param txId
304
- * @param finality
305
- */
306
- async getTxIdStatus(txId, finality) {
307
- const txReceipt = await this.connection.getTransaction(txId, {
308
- commitment: finality || "confirmed",
309
- maxSupportedTransactionVersion: 0
310
- });
311
- if (txReceipt == null)
312
- return "not_found";
313
- if (txReceipt.meta.err)
314
- return "reverted";
315
- return "success";
316
- }
317
- onBeforeTxSigned(callback) {
318
- this.cbkBeforeTxSigned = callback;
319
- }
320
- offBeforeTxSigned(callback) {
321
- this.cbkBeforeTxSigned = null;
322
- return true;
323
- }
324
- onSendTransaction(callback) {
325
- this.cbkSendTransaction = callback;
326
- }
327
- offSendTransaction(callback) {
328
- this.cbkSendTransaction = null;
329
- return true;
330
- }
331
- }
332
- exports.SolanaTransactions = SolanaTransactions;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SolanaTransactions = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const SolanaModule_1 = require("../SolanaModule");
6
+ const bs58 = require("bs58");
7
+ const Utils_1 = require("../../../utils/Utils");
8
+ const buffer_1 = require("buffer");
9
+ class SolanaTransactions extends SolanaModule_1.SolanaModule {
10
+ /**
11
+ * Sends raw solana transaction, first through the cbkSendTransaction callback (for e.g. sending the transaction
12
+ * to a different specific RPC), the through the Fees handler (for e.g. Jito transaction) and last through the
13
+ * underlying provider's Connection instance (the usual way). Only sends the transaction through one channel.
14
+ *
15
+ * @param data
16
+ * @param options
17
+ * @private
18
+ */
19
+ async sendRawTransaction(data, options) {
20
+ let result = null;
21
+ options ?? (options = {});
22
+ options.maxRetries = 0;
23
+ if (this.cbkSendTransaction != null)
24
+ result = await this.cbkSendTransaction(data, options);
25
+ if (result == null)
26
+ result = await this.root.Fees.submitTx(data, options);
27
+ if (result == null)
28
+ result = await this.connection.sendRawTransaction(data, options);
29
+ // this.logger.debug("sendRawTransaction(): tx sent, signature: "+result);
30
+ return result;
31
+ }
32
+ /**
33
+ * Waits for the transaction to confirm by periodically checking the transaction status over HTTP, also
34
+ * re-sends the transaction at regular intervals
35
+ *
36
+ * @param solanaTx solana tx to wait for confirmation for
37
+ * @param finality wait for this finality
38
+ * @param abortSignal signal to abort waiting for tx confirmation
39
+ * @private
40
+ */
41
+ txConfirmationAndResendWatchdog(solanaTx, finality, abortSignal) {
42
+ const rawTx = solanaTx.tx.serialize();
43
+ const signature = bs58.encode(solanaTx.tx.signature);
44
+ return new Promise((resolve, reject) => {
45
+ let watchdogInterval;
46
+ watchdogInterval = setInterval(async () => {
47
+ const result = await this.sendRawTransaction(rawTx, { skipPreflight: true }).catch(e => this.logger.error("txConfirmationAndResendWatchdog(): transaction re-sent error: ", e));
48
+ this.logger.debug("txConfirmationAndResendWatchdog(): transaction re-sent: " + result);
49
+ const status = await this.getTxIdStatus(signature, finality).catch(e => this.logger.error("txConfirmationAndResendWatchdog(): get tx id status error: ", e));
50
+ if (status == null || status === "not_found")
51
+ return;
52
+ if (status === "success") {
53
+ this.logger.info("txConfirmationAndResendWatchdog(): transaction confirmed from HTTP polling, signature: " + signature);
54
+ resolve(signature);
55
+ }
56
+ if (status === "reverted")
57
+ reject(new Error("Transaction reverted!"));
58
+ clearInterval(watchdogInterval);
59
+ }, this.retryPolicy?.transactionResendInterval || 3000);
60
+ if (abortSignal != null)
61
+ abortSignal.addEventListener("abort", () => {
62
+ clearInterval(watchdogInterval);
63
+ reject(abortSignal.reason);
64
+ });
65
+ });
66
+ }
67
+ /**
68
+ * Waits for the transaction to confirm from WS, sometimes the WS rejects even though the transaction was confirmed
69
+ * this therefore also runs an ultimate check on the transaction in case the WS handler rejects, checking if it
70
+ * really was expired
71
+ *
72
+ * @param solanaTx solana tx to wait for confirmation for
73
+ * @param finality wait for this finality
74
+ * @param abortSignal signal to abort waiting for tx confirmation
75
+ * @private
76
+ */
77
+ async txConfirmFromWebsocket(solanaTx, finality, abortSignal) {
78
+ const signature = bs58.encode(solanaTx.tx.signature);
79
+ let result;
80
+ try {
81
+ result = await this.connection.confirmTransaction({
82
+ signature: signature,
83
+ blockhash: solanaTx.tx.recentBlockhash,
84
+ lastValidBlockHeight: solanaTx.tx.lastValidBlockHeight,
85
+ abortSignal
86
+ }, finality);
87
+ this.logger.info("txConfirmFromWebsocket(): transaction confirmed from WS, signature: " + signature);
88
+ }
89
+ catch (err) {
90
+ if (abortSignal != null && abortSignal.aborted)
91
+ throw err;
92
+ this.logger.debug("txConfirmFromWebsocket(): transaction rejected from WS, running ultimate check, expiry blockheight: " + solanaTx.tx.lastValidBlockHeight + " signature: " + signature + " error: " + err);
93
+ const status = await (0, Utils_1.tryWithRetries)(() => this.getTxIdStatus(signature, finality));
94
+ this.logger.info("txConfirmFromWebsocket(): transaction status: " + status + " signature: " + signature);
95
+ if (status === "success")
96
+ return signature;
97
+ if (status === "reverted")
98
+ throw new Error("Transaction reverted!");
99
+ if (err instanceof web3_js_1.TransactionExpiredBlockheightExceededError || err.toString().startsWith("TransactionExpiredBlockheightExceededError")) {
100
+ throw new Error("Transaction expired before confirmation, please try again!");
101
+ }
102
+ else {
103
+ throw err;
104
+ }
105
+ }
106
+ if (result.value.err != null)
107
+ throw new Error("Transaction reverted!");
108
+ return signature;
109
+ }
110
+ /**
111
+ * Waits for transaction confirmation using WS subscription and occasional HTTP polling, also re-sends
112
+ * the transaction at regular interval
113
+ *
114
+ * @param solanaTx solana transaction to wait for confirmation for & keep re-sending until it confirms
115
+ * @param abortSignal signal to abort waiting for tx confirmation
116
+ * @param finality wait for specific finality
117
+ * @private
118
+ */
119
+ async confirmTransaction(solanaTx, abortSignal, finality) {
120
+ const abortController = new AbortController();
121
+ if (abortSignal != null)
122
+ abortSignal.addEventListener("abort", () => {
123
+ abortController.abort();
124
+ });
125
+ let txSignature;
126
+ try {
127
+ txSignature = await Promise.race([
128
+ this.txConfirmationAndResendWatchdog(solanaTx, finality, abortController.signal),
129
+ this.txConfirmFromWebsocket(solanaTx, finality, abortController.signal)
130
+ ]);
131
+ }
132
+ catch (e) {
133
+ abortController.abort(e);
134
+ throw e;
135
+ }
136
+ // this.logger.info("confirmTransaction(): transaction confirmed, signature: "+txSignature);
137
+ abortController.abort();
138
+ }
139
+ /**
140
+ * Prepares solana transactions, assigns recentBlockhash if needed, applies Phantom hotfix,
141
+ * sets feePayer to ourselves, calls beforeTxSigned callback & signs transaction with provided signers array
142
+ *
143
+ * @param signer
144
+ * @param txs
145
+ * @private
146
+ */
147
+ async prepareTransactions(signer, txs) {
148
+ let latestBlockData = null;
149
+ for (let tx of txs) {
150
+ if (tx.tx.recentBlockhash == null) {
151
+ if (latestBlockData == null) {
152
+ latestBlockData = await (0, Utils_1.tryWithRetries)(() => this.connection.getLatestBlockhash("confirmed"), this.retryPolicy);
153
+ this.logger.debug("prepareTransactions(): fetched latest block data for transactions," +
154
+ " blockhash: " + latestBlockData.blockhash + " expiry blockheight: " + latestBlockData.lastValidBlockHeight);
155
+ }
156
+ tx.tx.recentBlockhash = latestBlockData.blockhash;
157
+ tx.tx.lastValidBlockHeight = latestBlockData.lastValidBlockHeight;
158
+ }
159
+ //This is a hotfix for Phantom adding compute unit price instruction on the first position & breaking
160
+ // required instructions order (e.g. btc relay verify needs to be 0th instruction in a tx)
161
+ if (signer.keypair == null && tx.tx.signatures.length === 0) {
162
+ const foundIx = tx.tx.instructions.find(ix => ix.programId.equals(web3_js_1.ComputeBudgetProgram.programId) && web3_js_1.ComputeBudgetInstruction.decodeInstructionType(ix) === "SetComputeUnitPrice");
163
+ if (foundIx == null)
164
+ tx.tx.instructions.splice(tx.tx.instructions.length - 1, 0, web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }));
165
+ }
166
+ tx.tx.feePayer = signer.getPublicKey();
167
+ if (this.cbkBeforeTxSigned != null)
168
+ await this.cbkBeforeTxSigned(tx);
169
+ if (tx.signers != null && tx.signers.length > 0)
170
+ for (let signer of tx.signers)
171
+ tx.tx.sign(signer);
172
+ }
173
+ }
174
+ /**
175
+ * Sends out a signed transaction to the RPC
176
+ *
177
+ * @param solTx solana tx to send
178
+ * @param options send options to be passed to the RPC
179
+ * @param onBeforePublish a callback called before every transaction is published
180
+ * @private
181
+ */
182
+ async sendSignedTransaction(solTx, options, onBeforePublish) {
183
+ if (onBeforePublish != null)
184
+ await onBeforePublish(bs58.encode(solTx.tx.signature), await this.serializeTx(solTx));
185
+ const serializedTx = solTx.tx.serialize();
186
+ this.logger.debug("sendSignedTransaction(): sending transaction: " + serializedTx.toString("hex") +
187
+ " signature: " + bs58.encode(solTx.tx.signature));
188
+ const txResult = await (0, Utils_1.tryWithRetries)(() => this.sendRawTransaction(serializedTx, options), this.retryPolicy);
189
+ this.logger.info("sendSignedTransaction(): tx sent, signature: " + txResult);
190
+ return txResult;
191
+ }
192
+ /**
193
+ * Prepares (adds recent blockhash if required, applies Phantom hotfix),
194
+ * signs (all together using signAllTransactions() calls), sends (in parallel or sequentially) &
195
+ * optionally waits for confirmation of a batch of solana transactions
196
+ *
197
+ * @param signer
198
+ * @param _txs
199
+ * @param waitForConfirmation whether to wait for transaction confirmations (this also makes sure the transactions
200
+ * are re-sent at regular intervals)
201
+ * @param abortSignal abort signal to abort waiting for transaction confirmations
202
+ * @param parallel whether the send all the transaction at once in parallel or sequentially (such that transactions
203
+ * are executed in order)
204
+ * @param onBeforePublish a callback called before every transaction is published
205
+ */
206
+ async sendAndConfirm(signer, _txs, waitForConfirmation, abortSignal, parallel, onBeforePublish) {
207
+ const options = {
208
+ skipPreflight: true
209
+ };
210
+ this.logger.debug("sendAndConfirm(): sending transactions, count: " + _txs.length +
211
+ " waitForConfirmation: " + waitForConfirmation + " parallel: " + parallel);
212
+ const signatures = [];
213
+ for (let e = 0; e < _txs.length; e += 50) {
214
+ const txs = _txs.slice(e, e + 50);
215
+ await this.prepareTransactions(signer, txs);
216
+ const signedTxs = await signer.wallet.signAllTransactions(txs.map(e => e.tx));
217
+ signedTxs.forEach((tx, index) => {
218
+ const solTx = txs[index];
219
+ tx.lastValidBlockHeight = solTx.tx.lastValidBlockHeight;
220
+ solTx.tx = tx;
221
+ });
222
+ this.logger.debug("sendAndConfirm(): sending transaction batch (" + e + ".." + (e + 50) + "), count: " + txs.length);
223
+ if (parallel) {
224
+ const promises = [];
225
+ for (let solTx of txs) {
226
+ const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
227
+ if (waitForConfirmation)
228
+ promises.push(this.confirmTransaction(solTx, abortSignal, "confirmed"));
229
+ signatures.push(signature);
230
+ }
231
+ if (promises.length > 0)
232
+ await Promise.all(promises);
233
+ }
234
+ else {
235
+ for (let i = 0; i < txs.length; i++) {
236
+ const solTx = txs[i];
237
+ const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
238
+ const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
239
+ //Don't await the last promise when !waitForConfirmation
240
+ if (i < txs.length - 1 || e + 50 < _txs.length || waitForConfirmation)
241
+ await confirmPromise;
242
+ signatures.push(signature);
243
+ }
244
+ }
245
+ }
246
+ this.logger.info("sendAndConfirm(): sent transactions, count: " + _txs.length +
247
+ " waitForConfirmation: " + waitForConfirmation + " parallel: " + parallel);
248
+ return signatures;
249
+ }
250
+ /**
251
+ * Serializes the solana transaction, saves the transaction, signers & last valid blockheight
252
+ *
253
+ * @param tx
254
+ */
255
+ serializeTx(tx) {
256
+ return Promise.resolve(JSON.stringify({
257
+ tx: tx.tx.serialize().toString("hex"),
258
+ signers: tx.signers.map(e => buffer_1.Buffer.from(e.secretKey).toString("hex")),
259
+ lastValidBlockheight: tx.tx.lastValidBlockHeight
260
+ }));
261
+ }
262
+ /**
263
+ * Deserializes saved solana transaction, extracting the transaction, signers & last valid blockheight
264
+ *
265
+ * @param txData
266
+ */
267
+ deserializeTx(txData) {
268
+ const jsonParsed = JSON.parse(txData);
269
+ const transaction = web3_js_1.Transaction.from(buffer_1.Buffer.from(jsonParsed.tx, "hex"));
270
+ transaction.lastValidBlockHeight = jsonParsed.lastValidBlockheight;
271
+ return Promise.resolve({
272
+ tx: transaction,
273
+ signers: jsonParsed.signers.map(e => web3_js_1.Keypair.fromSecretKey(buffer_1.Buffer.from(e, "hex"))),
274
+ });
275
+ }
276
+ /**
277
+ * Gets the status of the raw solana transaction, this also checks transaction expiry & can therefore report tx
278
+ * in "pending" status, however pending status doesn't necessarily mean that the transaction was sent (again,
279
+ * no mempool on Solana, cannot check that), this function is preferred against getTxIdStatus
280
+ *
281
+ * @param tx
282
+ */
283
+ async getTxStatus(tx) {
284
+ const parsedTx = await this.deserializeTx(tx);
285
+ const txReceipt = await this.connection.getTransaction(bs58.encode(parsedTx.tx.signature), {
286
+ commitment: "confirmed",
287
+ maxSupportedTransactionVersion: 0
288
+ });
289
+ if (txReceipt == null) {
290
+ const currentBlockheight = await this.connection.getBlockHeight("processed");
291
+ if (currentBlockheight > parsedTx.tx.lastValidBlockHeight)
292
+ return "not_found";
293
+ return "pending";
294
+ }
295
+ if (txReceipt.meta.err)
296
+ return "reverted";
297
+ return "success";
298
+ }
299
+ /**
300
+ * Gets the status of the solana transaction with a specific txId, this cannot report whether the transaction is
301
+ * "pending" because Solana has no concept of mempool & only confirmed transactions are accessible
302
+ *
303
+ * @param txId
304
+ * @param finality
305
+ */
306
+ async getTxIdStatus(txId, finality) {
307
+ const txReceipt = await this.connection.getTransaction(txId, {
308
+ commitment: finality || "confirmed",
309
+ maxSupportedTransactionVersion: 0
310
+ });
311
+ if (txReceipt == null)
312
+ return "not_found";
313
+ if (txReceipt.meta.err)
314
+ return "reverted";
315
+ return "success";
316
+ }
317
+ onBeforeTxSigned(callback) {
318
+ this.cbkBeforeTxSigned = callback;
319
+ }
320
+ offBeforeTxSigned(callback) {
321
+ this.cbkBeforeTxSigned = null;
322
+ return true;
323
+ }
324
+ onSendTransaction(callback) {
325
+ this.cbkSendTransaction = callback;
326
+ }
327
+ offSendTransaction(callback) {
328
+ this.cbkSendTransaction = null;
329
+ return true;
330
+ }
331
+ }
332
+ exports.SolanaTransactions = SolanaTransactions;