@atomiqlabs/chain-solana 11.0.0 → 12.0.6

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.
@@ -1,10 +1,14 @@
1
1
  import {SolanaModule} from "../SolanaModule";
2
- import {ConfirmedSignatureInfo, PublicKey} from "@solana/web3.js";
2
+ import {ConfirmedSignatureInfo, ParsedTransactionWithMeta, PublicKey} from "@solana/web3.js";
3
+ import {sign} from "tweetnacl";
4
+ import {ProgramEvent} from "../../program/modules/SolanaProgramEvents";
3
5
 
4
6
  export class SolanaEvents extends SolanaModule {
5
7
 
6
8
  public readonly LOG_FETCH_LIMIT = 500;
7
9
 
10
+ private usingHeliusTFA: "yes" | "no" | "auto" = "auto";
11
+
8
12
  /**
9
13
  * Gets the signatures for a given topicKey public key, if lastProcessedSignature is specified, it fetches only
10
14
  * the signatures before this signature
@@ -27,6 +31,169 @@ export class SolanaEvents extends SolanaModule {
27
31
  }
28
32
  }
29
33
 
34
+ /**
35
+ * Implements Helius getTransactionsForAddress RPC API
36
+ *
37
+ * @param account
38
+ * @param options
39
+ * @param commitment
40
+ */
41
+ async getTransactionsForAddress(
42
+ account: PublicKey,
43
+ options?: {
44
+ paginationToken?: string,
45
+ filters?: {
46
+ slot?: {gte?: number, lte?: number, gt?: number, lt?: number},
47
+ blockTime?: {gte?: number, lte?: number, gt?: number, lt?: number, eq?: number},
48
+ signature?: {gte?: number, lte?: number, gt?: number, lt?: number, eq?: number},
49
+ status?: "succeeded" | "failed" | "any"
50
+ }
51
+ },
52
+ commitment?: "finalized" | "confirmed" | "processed"
53
+ ): Promise<{
54
+ data: ParsedTransactionWithMeta[],
55
+ paginationToken?: string
56
+ }> {
57
+ //Try to use getPriorityFeeEstimate api of Helius
58
+ const response = await (this.connection as any)._rpcRequest("getTransactionsForAddress", [
59
+ account.toString(),
60
+ {
61
+ ...options,
62
+ transactionDetails: "full",
63
+ sortOrder: "desc",
64
+ limit: 100,
65
+ commitment: commitment ?? "confirmed",
66
+ encoding: "jsonParsed",
67
+ maxSupportedTransactionVersion: 0
68
+ }
69
+ ]).catch(e => {
70
+ //Catching not supported errors
71
+ if(e.message!=null && (e.message.includes("-32601") || e.message.includes("-32600"))) {
72
+ return {
73
+ error: {
74
+ code: -32601,
75
+ message: e.message
76
+ }
77
+ };
78
+ }
79
+ throw e;
80
+ });
81
+
82
+ if(response.error!=null) {
83
+ //Catching not supported errors
84
+ if(response.error.code!==-32601 && response.error.code!==-32600) throw new Error(response.error.message);
85
+ return null;
86
+ }
87
+
88
+ return {
89
+ data: response.result.data.map(val => {
90
+ return {
91
+ ...val, //slot, blockTime, version
92
+ meta: val.meta==null ? undefined : {
93
+ //ParsedTransactionMeta
94
+ ...val.meta,
95
+ innerInstructions: val.meta.innerInstructions==null ? undefined : val.meta.innerInstructions.map(innerIx => ({
96
+ //ParsedInnerInstruction
97
+ ...innerIx, //index
98
+ instructions: innerIx.instructions.map(ix => {
99
+ if(ix.program!=null && ix.programId!=null) {
100
+ return {
101
+ //ParsedInstruction
102
+ ...ix,
103
+ programId: new PublicKey(ix.programId)
104
+ }
105
+ } else {
106
+ return {
107
+ //PartiallyDecodedInstruction
108
+ data: ix.data,
109
+ programId: new PublicKey(ix.programId),
110
+ accounts: ix.accounts.map(pubkey => new PublicKey(pubkey))
111
+ }
112
+ }
113
+ })
114
+ })),
115
+ loadedAddresses: val.meta.loadedAddresses==null ? undefined : {
116
+ writable: val.meta.loadedAddresses.writable.map(pubkey => new PublicKey(pubkey)),
117
+ readonly: val.meta.loadedAddresses.readonly.map(pubkey => new PublicKey(pubkey)),
118
+ }
119
+ },
120
+ transaction: {
121
+ //ParsedTransaction
122
+ ...val.transaction, //signatures
123
+ message: {
124
+ //ParsedMessage
125
+ ...val.transaction.message, //recentBlockhash
126
+ accountKeys: val.transaction.message.accountKeys.map(accountKey => ({
127
+ //ParsedMessageAccount
128
+ ...accountKey,
129
+ pubkey: new PublicKey(accountKey.pubkey)
130
+ })),
131
+ instructions: val.transaction.message.instructions.map(ix => {
132
+ if(ix.program!=null && ix.programId!=null) {
133
+ return {
134
+ //ParsedInstruction
135
+ ...ix,
136
+ programId: new PublicKey(ix.programId)
137
+ }
138
+ } else {
139
+ return {
140
+ //PartiallyDecodedInstruction
141
+ data: ix.data,
142
+ programId: new PublicKey(ix.programId),
143
+ accounts: ix.accounts.map(pubkey => new PublicKey(pubkey))
144
+ }
145
+ }
146
+ }),
147
+ addressTableLookups: val.transaction.message.addressTableLookups==null ? undefined : val.transaction.message.addressTableLookups.map(addressTableLookup => ({
148
+ //ParsedAddressTableLookup
149
+ ...addressTableLookup,
150
+ accountKey: new PublicKey(addressTableLookup.accountKey)
151
+ }))
152
+ }
153
+ }
154
+ }
155
+ }),
156
+ paginationToken: response.result.paginationToken
157
+ };
158
+ }
159
+
160
+ private async _findInTxsTFA<T>(
161
+ topicKey: PublicKey,
162
+ processor: (data: {signatures?: ConfirmedSignatureInfo[], txs?: ParsedTransactionWithMeta[]}) => Promise<T>,
163
+ abortSignal?: AbortSignal,
164
+ startBlockheight?: number
165
+ ): Promise<T> {
166
+ let paginationToken: string;
167
+ let txs: ParsedTransactionWithMeta[] = null;
168
+ while(txs==null || txs.length>0) {
169
+ let filters = startBlockheight!=null ? {
170
+ slot: {gte: startBlockheight}
171
+ } : {};
172
+ const tfaResult = await this.getTransactionsForAddress(topicKey, {
173
+ paginationToken,
174
+ filters: {
175
+ ...filters,
176
+ status: "succeeded"
177
+ }
178
+ }, "confirmed");
179
+
180
+ if(tfaResult==null) {
181
+ //Not supported
182
+ return undefined;
183
+ }
184
+
185
+ txs = tfaResult.data;
186
+ paginationToken = tfaResult.paginationToken;
187
+
188
+ if(abortSignal!=null) abortSignal.throwIfAborted();
189
+ const result: T = await processor({txs});
190
+ if(result!=null) return result;
191
+ if(paginationToken==null) break;
192
+ }
193
+ return null;
194
+
195
+ }
196
+
30
197
  /**
31
198
  * Runs a search backwards in time, processing transaction signatures for a specific topic public key
32
199
  *
@@ -35,23 +202,50 @@ export class SolanaEvents extends SolanaModule {
35
202
  * was found, or null if the search should continue
36
203
  * @param abortSignal
37
204
  * @param logFetchLimit
205
+ * @param startBlockheight
38
206
  */
39
- public async findInSignatures<T>(
207
+ private async _findInSignatures<T>(
40
208
  topicKey: PublicKey,
41
- processor: (signatures: ConfirmedSignatureInfo[]) => Promise<T>,
209
+ processor: (data: {signatures?: ConfirmedSignatureInfo[], txs?: ParsedTransactionWithMeta[]}) => Promise<T>,
42
210
  abortSignal?: AbortSignal,
43
- logFetchLimit?: number
211
+ logFetchLimit?: number,
212
+ startBlockheight?: number
44
213
  ): Promise<T> {
45
214
  if(logFetchLimit==null || logFetchLimit>this.LOG_FETCH_LIMIT) logFetchLimit = this.LOG_FETCH_LIMIT;
46
215
  let signatures: ConfirmedSignatureInfo[] = null;
47
216
  while(signatures==null || signatures.length>0) {
48
217
  signatures = await this.getSignatures(topicKey, logFetchLimit, signatures!=null ? signatures[signatures.length-1].signature : null);
218
+ if(startBlockheight!=null) {
219
+ const endIndex = signatures.findIndex(val => val.slot < startBlockheight);
220
+ if(endIndex===0) return null;
221
+ if(endIndex!==-1) signatures = signatures.slice(0, endIndex - 1);
222
+ }
49
223
  if(abortSignal!=null) abortSignal.throwIfAborted();
50
- const result: T = await processor(signatures);
224
+ const result: T = await processor({signatures});
51
225
  if(result!=null) return result;
52
226
  if(signatures.length<logFetchLimit) break;
53
227
  }
54
228
  return null;
55
229
  }
56
230
 
231
+ public async findInSignatures<T>(
232
+ topicKey: PublicKey,
233
+ processor: (data: {signatures?: ConfirmedSignatureInfo[], txs?: ParsedTransactionWithMeta[]}) => Promise<T>,
234
+ abortSignal?: AbortSignal,
235
+ logFetchLimit?: number,
236
+ startBlockheight?: number
237
+ ) {
238
+ if(this.usingHeliusTFA!=="no") {
239
+ //Attempt to use Helius's gTFA
240
+ const result = await this._findInTxsTFA(topicKey, processor, abortSignal, startBlockheight);
241
+ if(result!==undefined) return result;
242
+
243
+ //Not supported
244
+ if(this.usingHeliusTFA==="yes") throw new Error("Helius gTFA is not supported with current provider!");
245
+ //If set to auto, we can manually set to "no"
246
+ this.usingHeliusTFA = "no";
247
+ }
248
+ return await this._findInSignatures(topicKey, processor, abortSignal, logFetchLimit, startBlockheight);
249
+ }
250
+
57
251
  }
@@ -9,6 +9,7 @@ import * as bs58 from "bs58";
9
9
  import {tryWithRetries} from "../../../utils/Utils";
10
10
  import {Buffer} from "buffer";
11
11
  import {SolanaSigner} from "../../wallet/SolanaSigner";
12
+ import {TransactionRevertedError} from "@atomiqlabs/base";
12
13
 
13
14
  export type SolanaTx = {tx: Transaction, signers: Signer[]};
14
15
 
@@ -74,7 +75,7 @@ export class SolanaTransactions extends SolanaModule {
74
75
  this.logger.info("txConfirmationAndResendWatchdog(): transaction confirmed from HTTP polling, signature: "+signature);
75
76
  resolve(signature);
76
77
  }
77
- if(status==="reverted") reject(new Error("Transaction reverted!"));
78
+ if(status==="reverted") reject(new TransactionRevertedError("Transaction reverted!"));
78
79
  clearInterval(watchdogInterval);
79
80
  }, this.retryPolicy?.transactionResendInterval || 3000);
80
81
 
@@ -119,14 +120,14 @@ export class SolanaTransactions extends SolanaModule {
119
120
  );
120
121
  this.logger.info("txConfirmFromWebsocket(): transaction status: "+status+" signature: "+signature);
121
122
  if(status==="success") return signature;
122
- if(status==="reverted") throw new Error("Transaction reverted!");
123
+ if(status==="reverted") throw new TransactionRevertedError("Transaction reverted!");
123
124
  if(err instanceof TransactionExpiredBlockheightExceededError || err.toString().startsWith("TransactionExpiredBlockheightExceededError")) {
124
125
  throw new Error("Transaction expired before confirmation, please try again!");
125
126
  } else {
126
127
  throw err;
127
128
  }
128
129
  }
129
- if(result.value.err!=null) throw new Error("Transaction reverted!");
130
+ if(result.value.err!=null) throw new TransactionRevertedError("Transaction reverted!");
130
131
  return signature;
131
132
  }
132
133
 
@@ -239,9 +240,11 @@ export class SolanaTransactions extends SolanaModule {
239
240
  this.logger.debug("sendAndConfirm(): sending transactions, count: "+_txs.length+
240
241
  " waitForConfirmation: "+waitForConfirmation+" parallel: "+parallel);
241
242
 
243
+ const BATCH_SIZE = 50;
244
+
242
245
  const signatures: string[] = [];
243
- for(let e=0;e<_txs.length;e+=50) {
244
- const txs = _txs.slice(e, e+50);
246
+ for(let e=0;e<_txs.length;e+=BATCH_SIZE) {
247
+ const txs = _txs.slice(e, e+BATCH_SIZE);
245
248
 
246
249
  await this.prepareTransactions(signer, txs)
247
250
  const signedTxs = await signer.wallet.signAllTransactions(txs.map(e => e.tx));
@@ -252,23 +255,15 @@ export class SolanaTransactions extends SolanaModule {
252
255
  });
253
256
  this.logger.debug("sendAndConfirm(): sending transaction batch ("+e+".."+(e+50)+"), count: "+txs.length);
254
257
 
255
- if(parallel) {
256
- const promises: Promise<void>[] = [];
257
- for(let solTx of txs) {
258
- const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
259
- if(waitForConfirmation) promises.push(this.confirmTransaction(solTx, abortSignal, "confirmed"));
260
- signatures.push(signature);
261
- }
262
- if(promises.length>0) await Promise.all(promises);
263
- } else {
264
- for(let i=0;i<txs.length;i++) {
265
- const solTx = txs[i];
266
- const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
267
- const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
268
- //Don't await the last promise when !waitForConfirmation
269
- if(i<txs.length-1 || e+50<_txs.length || waitForConfirmation) await confirmPromise;
270
- signatures.push(signature);
271
- }
258
+ //For solana we are forced to send txs one-by-one even with parallel, as we cannot determine their order upfront,
259
+ // however e.g. Jito could possibly handle sending a single package of up to 5 txns in order.
260
+ for(let i=0;i<txs.length;i++) {
261
+ const solTx = txs[i];
262
+ const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
263
+ const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
264
+ //Don't await the last promise when !waitForConfirmation
265
+ if(i<txs.length-1 || e+50<_txs.length || waitForConfirmation) await confirmPromise;
266
+ signatures.push(signature);
272
267
  }
273
268
  }
274
269
 
@@ -1,18 +1,16 @@
1
1
  import {ChainEvents, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SwapEvent} from "@atomiqlabs/base";
2
- import {SolanaSwapData} from "../swaps/SolanaSwapData";
2
+ import {InitInstruction, SolanaSwapData} from "../swaps/SolanaSwapData";
3
3
  import {IdlEvents} from "@coral-xyz/anchor";
4
4
  import {SolanaSwapProgram} from "../swaps/SolanaSwapProgram";
5
5
  import {
6
6
  getLogger,
7
7
  onceAsync, toEscrowHash, tryWithRetries
8
8
  } from "../../utils/Utils";
9
- import {Connection, ParsedTransactionWithMeta, PublicKey} from "@solana/web3.js";
10
- import * as BN from "bn.js";
9
+ import {Connection, ParsedTransactionWithMeta} from "@solana/web3.js";
11
10
  import {SwapTypeEnum} from "../swaps/SwapTypeEnum";
12
11
  import {
13
12
  InstructionWithAccounts,
14
- ProgramEvent,
15
- SingleInstructionWithAccounts
13
+ ProgramEvent
16
14
  } from "../program/modules/SolanaProgramEvents";
17
15
  import {SwapProgram} from "../swaps/programTypes";
18
16
  import {Buffer} from "buffer";
@@ -24,8 +22,6 @@ export type EventObject = {
24
22
  signature: string
25
23
  };
26
24
 
27
- export type InitInstruction = SingleInstructionWithAccounts<SwapProgram["instructions"][2 | 3], SwapProgram>;
28
-
29
25
  /**
30
26
  * Solana on-chain event handler for front-end systems without access to fs, uses pure WS to subscribe, might lose
31
27
  * out on some events if the network is unreliable, front-end systems should take this into consideration and not
@@ -64,49 +60,6 @@ export class SolanaChainEventsBrowser implements ChainEvents<SolanaSwapData> {
64
60
  return this.solanaSwapProgram.Events.decodeInstructions(transaction.transaction.message);
65
61
  }
66
62
 
67
- /**
68
- * Converts initialize instruction data into {SolanaSwapData}
69
- *
70
- * @param initIx
71
- * @param txoHash
72
- * @private
73
- * @returns {SolanaSwapData} converted and parsed swap data
74
- */
75
- private instructionToSwapData(
76
- initIx: InitInstruction,
77
- txoHash: string
78
- ): SolanaSwapData {
79
- const paymentHash: Buffer = Buffer.from(initIx.data.swapData.hash);
80
- let securityDeposit: BN = new BN(0);
81
- let claimerBounty: BN = new BN(0);
82
- let payIn: boolean = true;
83
- if(initIx.name === "offererInitialize") {
84
- payIn = false;
85
- securityDeposit = initIx.data.securityDeposit;
86
- claimerBounty = initIx.data.claimerBounty;
87
- }
88
-
89
- return new SolanaSwapData(
90
- initIx.accounts.offerer,
91
- initIx.accounts.claimer,
92
- initIx.accounts.mint,
93
- initIx.data.swapData.amount,
94
- paymentHash.toString("hex"),
95
- initIx.data.swapData.sequence,
96
- initIx.data.swapData.expiry,
97
- initIx.data.swapData.nonce,
98
- initIx.data.swapData.confirmations,
99
- initIx.data.swapData.payOut,
100
- SwapTypeEnum.toNumber(initIx.data.swapData.kind),
101
- payIn,
102
- initIx.name === "offererInitializePayIn" ? initIx.accounts.offererAta : PublicKey.default,
103
- initIx.data.swapData.payOut ? initIx.accounts.claimerAta : PublicKey.default,
104
- securityDeposit,
105
- claimerBounty,
106
- txoHash
107
- );
108
- }
109
-
110
63
  /**
111
64
  * Returns async getter for fetching on-demand initialize event swap data
112
65
  *
@@ -125,7 +78,7 @@ export class SolanaChainEventsBrowser implements ChainEvents<SolanaSwapData> {
125
78
  ) as InitInstruction;
126
79
  if(initIx == null) return null;
127
80
 
128
- return this.instructionToSwapData(initIx, txoHash);
81
+ return SolanaSwapData.fromInstruction(initIx, txoHash);
129
82
  }
130
83
  }
131
84
 
@@ -1,7 +1,13 @@
1
1
  import {SolanaEvents} from "../../chain/modules/SolanaEvents";
2
2
  import {BorshCoder, DecodeType, Event, EventParser, Idl, IdlTypes, Instruction} from "@coral-xyz/anchor";
3
3
  import {IdlField, IdlInstruction} from "@coral-xyz/anchor/dist/cjs/idl";
4
- import {ConfirmedSignatureInfo, ParsedMessage, PartiallyDecodedInstruction, PublicKey} from "@solana/web3.js";
4
+ import {
5
+ ConfirmedSignatureInfo,
6
+ ParsedMessage, ParsedTransactionWithMeta,
7
+ PartiallyDecodedInstruction,
8
+ PublicKey,
9
+ VersionedTransaction, VersionedTransactionResponse
10
+ } from "@solana/web3.js";
5
11
  import {SolanaProgramBase} from "../SolanaProgramBase";
6
12
  import {SolanaChainInterface} from "../../chain/SolanaChainInterface";
7
13
 
@@ -40,25 +46,6 @@ export class SolanaProgramEvents<IDL extends Idl> extends SolanaEvents {
40
46
  }
41
47
  }
42
48
 
43
- /**
44
- * Gets events from specific transaction as specified by signature, events are ordered from newest to oldest
45
- *
46
- * @param signature
47
- * @private
48
- */
49
- private async getEvents(signature: string): Promise<ProgramEvent<IDL>[]> {
50
- const tx = await this.connection.getTransaction(signature, {
51
- commitment: "confirmed",
52
- maxSupportedTransactionVersion: 0
53
- });
54
- if(tx.meta.err) return [];
55
-
56
- const events = this.parseLogs(tx.meta.logMessages);
57
- events.reverse();
58
-
59
- return events;
60
- }
61
-
62
49
  /**
63
50
  * Runs a search backwards in time, processing the events for a specific topic public key
64
51
  *
@@ -67,22 +54,50 @@ export class SolanaProgramEvents<IDL extends Idl> extends SolanaEvents {
67
54
  * if the search should continue
68
55
  * @param abortSignal
69
56
  * @param logBatchSize how many signatures should be fetched in one getSignaturesForAddress call
57
+ * @param startBlockheight
70
58
  */
71
59
  public findInEvents<T>(
72
60
  topicKey: PublicKey,
73
- processor: (event: ProgramEvent<IDL>, info: ConfirmedSignatureInfo) => Promise<T>,
61
+ processor: (event: ProgramEvent<IDL>, tx: ParsedTransactionWithMeta) => Promise<T>,
74
62
  abortSignal?: AbortSignal,
75
- logBatchSize?: number
63
+ logBatchSize?: number,
64
+ startBlockheight?: number
76
65
  ): Promise<T> {
77
- return this.findInSignatures<T>(topicKey, async (signatures: ConfirmedSignatureInfo[]) => {
78
- for(let data of signatures) {
79
- for(let event of await this.getEvents(data.signature)) {
80
- if(abortSignal!=null) abortSignal.throwIfAborted();
81
- const result: T = await processor(event, data);
82
- if(result!=null) return result;
66
+ return this.findInSignatures<T>(topicKey, async (data: {signatures?: ConfirmedSignatureInfo[], txs?: ParsedTransactionWithMeta[]}) => {
67
+ if(data.signatures) {
68
+ for(let info of data.signatures) {
69
+ if(info.err==null) continue;
70
+
71
+ const tx = await this.connection.getParsedTransaction(info.signature, {
72
+ commitment: "confirmed",
73
+ maxSupportedTransactionVersion: 0
74
+ });
75
+ if(tx.meta.err) continue;
76
+
77
+ const events = this.parseLogs(tx.meta.logMessages);
78
+ events.reverse();
79
+
80
+ for(let event of events) {
81
+ if(abortSignal!=null) abortSignal.throwIfAborted();
82
+ const result: T = await processor(event, tx);
83
+ if(result!=null) return result;
84
+ }
85
+ }
86
+ } else {
87
+ for(let tx of data.txs) {
88
+ if(tx.meta.err) continue;
89
+
90
+ const events = this.parseLogs(tx.meta.logMessages);
91
+ events.reverse();
92
+
93
+ for(let event of events) {
94
+ if(abortSignal!=null) abortSignal.throwIfAborted();
95
+ const result: T = await processor(event, tx);
96
+ if(result!=null) return result;
97
+ }
83
98
  }
84
99
  }
85
- }, abortSignal, logBatchSize);
100
+ }, abortSignal, logBatchSize, startBlockheight);
86
101
  }
87
102
 
88
103
  /**
@@ -1,6 +1,6 @@
1
1
  import {PublicKey} from "@solana/web3.js";
2
2
  import * as BN from "bn.js";
3
- import {SwapData, ChainSwapType} from "@atomiqlabs/base";
3
+ import {ChainSwapType, SwapData} from "@atomiqlabs/base";
4
4
  import {SwapProgram} from "./programTypes";
5
5
  import {IdlAccounts, IdlTypes} from "@coral-xyz/anchor";
6
6
  import {SwapTypeEnum} from "./SwapTypeEnum";
@@ -8,6 +8,9 @@ import {Buffer} from "buffer";
8
8
  import {getAssociatedTokenAddressSync} from "@solana/spl-token";
9
9
  import {toBigInt, toClaimHash, toEscrowHash} from "../../utils/Utils";
10
10
  import {SolanaTokens} from "../chain/modules/SolanaTokens";
11
+ import {SingleInstructionWithAccounts} from "../program/modules/SolanaProgramEvents";
12
+
13
+ export type InitInstruction = SingleInstructionWithAccounts<SwapProgram["instructions"][2 | 3], SwapProgram>;
11
14
 
12
15
  const EXPIRY_BLOCKHEIGHT_THRESHOLD = new BN("1000000000");
13
16
 
@@ -217,6 +220,11 @@ export class SolanaSwapData extends SwapData {
217
220
  return this.txoHash;
218
221
  }
219
222
 
223
+ getHTLCHashHint(): string {
224
+ if(this.getType()===ChainSwapType.HTLC) return this.paymentHash;
225
+ return null;
226
+ }
227
+
220
228
  getExtraData(): string {
221
229
  return this.txoHash;
222
230
  }
@@ -300,6 +308,49 @@ export class SolanaSwapData extends SwapData {
300
308
  other.token.equals(this.token)
301
309
  }
302
310
 
311
+ /**
312
+ * Converts initialize instruction data into {SolanaSwapData}
313
+ *
314
+ * @param initIx
315
+ * @param txoHash
316
+ * @private
317
+ * @returns {SolanaSwapData} converted and parsed swap data
318
+ */
319
+ static fromInstruction(
320
+ initIx: InitInstruction,
321
+ txoHash: string
322
+ ): SolanaSwapData {
323
+ const paymentHash: Buffer = Buffer.from(initIx.data.swapData.hash);
324
+ let securityDeposit: BN = new BN(0);
325
+ let claimerBounty: BN = new BN(0);
326
+ let payIn: boolean = true;
327
+ if(initIx.name === "offererInitialize") {
328
+ payIn = false;
329
+ securityDeposit = initIx.data.securityDeposit;
330
+ claimerBounty = initIx.data.claimerBounty;
331
+ }
332
+
333
+ return new SolanaSwapData(
334
+ initIx.accounts.offerer,
335
+ initIx.accounts.claimer,
336
+ initIx.accounts.mint,
337
+ initIx.data.swapData.amount,
338
+ paymentHash.toString("hex"),
339
+ initIx.data.swapData.sequence,
340
+ initIx.data.swapData.expiry,
341
+ initIx.data.swapData.nonce,
342
+ initIx.data.swapData.confirmations,
343
+ initIx.data.swapData.payOut,
344
+ SwapTypeEnum.toNumber(initIx.data.swapData.kind),
345
+ payIn,
346
+ initIx.name === "offererInitializePayIn" ? initIx.accounts.offererAta : PublicKey.default,
347
+ initIx.data.swapData.payOut ? initIx.accounts.claimerAta : PublicKey.default,
348
+ securityDeposit,
349
+ claimerBounty,
350
+ txoHash
351
+ );
352
+ }
353
+
303
354
  static fromEscrowState(account: IdlAccounts<SwapProgram>["escrowState"]) {
304
355
  const data: IdlTypes<SwapProgram>["SwapData"] = account.data;
305
356