@atomiqlabs/chain-solana 12.0.0 → 12.0.7

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.
@@ -195,7 +195,7 @@ class SolanaSwapProgram extends SolanaProgramBase_1.SolanaProgramBase {
195
195
  return { type: base_1.SwapCommitStateType.NOT_COMMITED };
196
196
  }
197
197
  //Check if paid or what
198
- const status = await this.Events.findInEvents(escrowStateKey, async (event, info) => {
198
+ const status = await this.Events.findInEvents(escrowStateKey, async (event, tx) => {
199
199
  if (event.name === "ClaimEvent") {
200
200
  const paymentHash = buffer_1.Buffer.from(event.data.hash).toString("hex");
201
201
  if (paymentHash !== data.paymentHash)
@@ -204,14 +204,12 @@ class SolanaSwapProgram extends SolanaProgramBase_1.SolanaProgramBase {
204
204
  return null;
205
205
  return {
206
206
  type: base_1.SwapCommitStateType.PAID,
207
- getClaimTxId: () => Promise.resolve(info.signature),
207
+ getClaimTxId: () => Promise.resolve(tx.transaction.signatures[0]),
208
208
  getClaimResult: () => Promise.resolve(buffer_1.Buffer.from(event.data.secret).toString("hex")),
209
- getTxBlock: async () => {
210
- return {
211
- blockHeight: (await this.Chain.Blocks.getParsedBlock(info.slot)).blockHeight,
212
- blockTime: info.blockTime
213
- };
214
- }
209
+ getTxBlock: () => Promise.resolve({
210
+ blockHeight: tx.slot,
211
+ blockTime: tx.blockTime
212
+ })
215
213
  };
216
214
  }
217
215
  if (event.name === "RefundEvent") {
@@ -222,13 +220,11 @@ class SolanaSwapProgram extends SolanaProgramBase_1.SolanaProgramBase {
222
220
  return null;
223
221
  return {
224
222
  type: isExpired ? base_1.SwapCommitStateType.EXPIRED : base_1.SwapCommitStateType.NOT_COMMITED,
225
- getRefundTxId: () => Promise.resolve(info.signature),
226
- getTxBlock: async () => {
227
- return {
228
- blockHeight: (await this.Chain.Blocks.getParsedBlock(info.slot)).blockHeight,
229
- blockTime: info.blockTime
230
- };
231
- }
223
+ getRefundTxId: () => Promise.resolve(tx.transaction.signatures[0]),
224
+ getTxBlock: () => Promise.resolve({
225
+ blockHeight: tx.slot,
226
+ blockTime: tx.blockTime
227
+ })
232
228
  };
233
229
  }
234
230
  });
@@ -299,6 +295,91 @@ class SolanaSwapProgram extends SolanaProgramBase_1.SolanaProgramBase {
299
295
  return null;
300
296
  return SolanaSwapData_1.SolanaSwapData.fromEscrowState(account);
301
297
  }
298
+ async getHistoricalSwaps(signer, startBlockheight) {
299
+ let latestBlockheight;
300
+ const events = [];
301
+ await this.Events.findInEvents(new web3_js_1.PublicKey(signer), async (event, tx) => {
302
+ if (latestBlockheight == null)
303
+ latestBlockheight = tx.slot;
304
+ events.push({ event, tx });
305
+ }, undefined, undefined, startBlockheight);
306
+ const swapsOpened = {};
307
+ const resultingSwaps = {};
308
+ events.reverse();
309
+ for (let { event, tx } of events) {
310
+ const txSignature = tx.transaction.signatures[0];
311
+ const paymentHash = buffer_1.Buffer.from(event.data.hash).toString("hex");
312
+ const escrowHash = (0, Utils_1.toEscrowHash)(paymentHash, event.data.sequence);
313
+ if (event.name === "InitializeEvent") {
314
+ //Parse swap data from initialize event
315
+ const txoHash = buffer_1.Buffer.from(event.data.txoHash).toString("hex");
316
+ const instructions = this.Events.decodeInstructions(tx.transaction.message);
317
+ if (instructions == null) {
318
+ this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot parse instructions!`);
319
+ continue;
320
+ }
321
+ const initIx = instructions.find(ix => ix != null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize"));
322
+ if (initIx == null) {
323
+ this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot init instruction not found!`);
324
+ continue;
325
+ }
326
+ swapsOpened[escrowHash] = {
327
+ data: SolanaSwapData_1.SolanaSwapData.fromInstruction(initIx, txoHash),
328
+ getInitTxId: () => Promise.resolve(txSignature),
329
+ getTxBlock: () => Promise.resolve({
330
+ blockHeight: tx.slot,
331
+ blockTime: tx.blockTime
332
+ })
333
+ };
334
+ }
335
+ if (event.name === "ClaimEvent") {
336
+ const foundSwapData = swapsOpened[escrowHash];
337
+ delete swapsOpened[escrowHash];
338
+ resultingSwaps[escrowHash] = {
339
+ init: foundSwapData,
340
+ state: {
341
+ type: base_1.SwapCommitStateType.PAID,
342
+ getClaimTxId: () => Promise.resolve(txSignature),
343
+ getClaimResult: () => Promise.resolve(buffer_1.Buffer.from(event.data.secret).toString("hex")),
344
+ getTxBlock: () => Promise.resolve({
345
+ blockHeight: tx.slot,
346
+ blockTime: tx.blockTime
347
+ })
348
+ }
349
+ };
350
+ }
351
+ if (event.name === "RefundEvent") {
352
+ const foundSwapData = swapsOpened[escrowHash];
353
+ delete swapsOpened[escrowHash];
354
+ const isExpired = foundSwapData != null && await this.isExpired(signer, foundSwapData.data);
355
+ resultingSwaps[escrowHash] = {
356
+ init: foundSwapData,
357
+ state: {
358
+ type: isExpired ? base_1.SwapCommitStateType.EXPIRED : base_1.SwapCommitStateType.NOT_COMMITED,
359
+ getRefundTxId: () => Promise.resolve(txSignature),
360
+ getTxBlock: () => Promise.resolve({
361
+ blockHeight: tx.slot,
362
+ blockTime: tx.blockTime
363
+ })
364
+ }
365
+ };
366
+ }
367
+ }
368
+ for (let escrowHash in swapsOpened) {
369
+ const foundSwapData = swapsOpened[escrowHash];
370
+ const isExpired = await this.isExpired(signer, foundSwapData.data);
371
+ resultingSwaps[escrowHash] = {
372
+ init: foundSwapData,
373
+ state: foundSwapData.data.isOfferer(signer) && isExpired
374
+ ? { type: base_1.SwapCommitStateType.REFUNDABLE }
375
+ : { type: base_1.SwapCommitStateType.COMMITED }
376
+ };
377
+ }
378
+ return {
379
+ swaps: resultingSwaps,
380
+ latestBlockheight
381
+ };
382
+ }
302
383
  ////////////////////////////////////////////
303
384
  //// Swap data initializer
304
385
  createSwapData(type, offerer, claimer, token, amount, claimHash, sequence, expiry, payIn, payOut, securityDeposit, claimerBounty, depositToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-solana",
3
- "version": "12.0.0",
3
+ "version": "12.0.7",
4
4
  "description": "Solana specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "author": "adambor",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "@atomiqlabs/base": "^12.0.0",
25
+ "@atomiqlabs/base": "^12.0.3",
26
26
  "@noble/hashes": "^1.7.1",
27
27
  "bn.js": "5.2.1",
28
28
  "bs58": "4.0.1",
@@ -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") || e.message.includes("-32403"))) {
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 && response.error.code!==-32403) 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
  }
@@ -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