@atomiqlabs/chain-solana 13.5.13 → 13.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +73 -73
  3. package/dist/index.d.ts +81 -81
  4. package/dist/index.js +102 -102
  5. package/dist/node/index.d.ts +9 -9
  6. package/dist/node/index.js +13 -13
  7. package/dist/solana/SolanaChainType.d.ts +15 -15
  8. package/dist/solana/SolanaChainType.js +2 -2
  9. package/dist/solana/SolanaChains.d.ts +12 -12
  10. package/dist/solana/SolanaChains.js +45 -45
  11. package/dist/solana/SolanaInitializer.d.ts +94 -94
  12. package/dist/solana/SolanaInitializer.js +174 -174
  13. package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +222 -222
  14. package/dist/solana/btcrelay/SolanaBtcRelay.js +455 -455
  15. package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +84 -84
  16. package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +70 -70
  17. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +92 -92
  18. package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +109 -109
  19. package/dist/solana/btcrelay/program/programIdl.json +671 -671
  20. package/dist/solana/chain/SolanaAction.d.ts +26 -26
  21. package/dist/solana/chain/SolanaAction.js +87 -87
  22. package/dist/solana/chain/SolanaChainInterface.d.ts +224 -224
  23. package/dist/solana/chain/SolanaChainInterface.js +275 -275
  24. package/dist/solana/chain/SolanaModule.d.ts +14 -14
  25. package/dist/solana/chain/SolanaModule.js +13 -13
  26. package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
  27. package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
  28. package/dist/solana/chain/modules/SolanaBlocks.d.ts +32 -32
  29. package/dist/solana/chain/modules/SolanaBlocks.js +78 -78
  30. package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
  31. package/dist/solana/chain/modules/SolanaEvents.js +238 -238
  32. package/dist/solana/chain/modules/SolanaFees.d.ts +189 -189
  33. package/dist/solana/chain/modules/SolanaFees.js +434 -434
  34. package/dist/solana/chain/modules/SolanaSignatures.d.ts +24 -24
  35. package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
  36. package/dist/solana/chain/modules/SolanaSlots.d.ts +33 -33
  37. package/dist/solana/chain/modules/SolanaSlots.js +72 -72
  38. package/dist/solana/chain/modules/SolanaTokens.d.ts +123 -123
  39. package/dist/solana/chain/modules/SolanaTokens.js +242 -242
  40. package/dist/solana/chain/modules/SolanaTransactions.d.ts +149 -149
  41. package/dist/solana/chain/modules/SolanaTransactions.js +445 -445
  42. package/dist/solana/connection/ConnectionWithRetries.d.ts +35 -35
  43. package/dist/solana/connection/ConnectionWithRetries.js +86 -71
  44. package/dist/solana/events/SolanaChainEvents.d.ts +45 -45
  45. package/dist/solana/events/SolanaChainEvents.js +108 -108
  46. package/dist/solana/events/SolanaChainEventsBrowser.d.ts +205 -205
  47. package/dist/solana/events/SolanaChainEventsBrowser.js +404 -404
  48. package/dist/solana/program/SolanaProgramBase.d.ts +73 -73
  49. package/dist/solana/program/SolanaProgramBase.js +54 -54
  50. package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
  51. package/dist/solana/program/SolanaProgramModule.js +11 -11
  52. package/dist/solana/program/modules/SolanaProgramEvents.d.ts +53 -53
  53. package/dist/solana/program/modules/SolanaProgramEvents.js +117 -117
  54. package/dist/solana/swaps/SolanaSwapData.d.ts +333 -333
  55. package/dist/solana/swaps/SolanaSwapData.js +535 -535
  56. package/dist/solana/swaps/SolanaSwapModule.d.ts +11 -11
  57. package/dist/solana/swaps/SolanaSwapModule.js +12 -12
  58. package/dist/solana/swaps/SolanaSwapProgram.d.ts +376 -376
  59. package/dist/solana/swaps/SolanaSwapProgram.js +769 -769
  60. package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
  61. package/dist/solana/swaps/SwapTypeEnum.js +43 -43
  62. package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +95 -95
  63. package/dist/solana/swaps/modules/SolanaDataAccount.js +232 -232
  64. package/dist/solana/swaps/modules/SolanaLpVault.d.ts +69 -69
  65. package/dist/solana/swaps/modules/SolanaLpVault.js +171 -171
  66. package/dist/solana/swaps/modules/SwapClaim.d.ts +126 -126
  67. package/dist/solana/swaps/modules/SwapClaim.js +294 -294
  68. package/dist/solana/swaps/modules/SwapInit.d.ts +213 -213
  69. package/dist/solana/swaps/modules/SwapInit.js +658 -658
  70. package/dist/solana/swaps/modules/SwapRefund.d.ts +87 -87
  71. package/dist/solana/swaps/modules/SwapRefund.js +293 -293
  72. package/dist/solana/swaps/programIdl.json +945 -945
  73. package/dist/solana/swaps/programTypes.d.ts +943 -943
  74. package/dist/solana/swaps/programTypes.js +945 -945
  75. package/dist/solana/swaps/v1/programIdl.json +945 -945
  76. package/dist/solana/swaps/v1/programTypes.d.ts +943 -943
  77. package/dist/solana/swaps/v1/programTypes.js +945 -945
  78. package/dist/solana/swaps/v2/programIdl.json +952 -952
  79. package/dist/solana/swaps/v2/programTypes.d.ts +950 -950
  80. package/dist/solana/swaps/v2/programTypes.js +952 -952
  81. package/dist/solana/wallet/SolanaKeypairWallet.d.ts +29 -29
  82. package/dist/solana/wallet/SolanaKeypairWallet.js +50 -50
  83. package/dist/solana/wallet/SolanaSigner.d.ts +30 -30
  84. package/dist/solana/wallet/SolanaSigner.js +30 -30
  85. package/dist/utils/Utils.d.ts +58 -58
  86. package/dist/utils/Utils.js +170 -170
  87. package/node/index.d.ts +1 -1
  88. package/node/index.js +3 -3
  89. package/package.json +46 -46
  90. package/src/index.ts +87 -87
  91. package/src/node/index.ts +9 -9
  92. package/src/solana/SolanaChainType.ts +32 -32
  93. package/src/solana/SolanaChains.ts +46 -46
  94. package/src/solana/SolanaInitializer.ts +278 -278
  95. package/src/solana/btcrelay/SolanaBtcRelay.ts +615 -615
  96. package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +116 -116
  97. package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +148 -148
  98. package/src/solana/btcrelay/program/programIdl.json +670 -670
  99. package/src/solana/chain/SolanaAction.ts +109 -109
  100. package/src/solana/chain/SolanaChainInterface.ts +404 -404
  101. package/src/solana/chain/SolanaModule.ts +20 -20
  102. package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
  103. package/src/solana/chain/modules/SolanaBlocks.ts +89 -89
  104. package/src/solana/chain/modules/SolanaEvents.ts +271 -271
  105. package/src/solana/chain/modules/SolanaFees.ts +522 -522
  106. package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
  107. package/src/solana/chain/modules/SolanaSlots.ts +85 -85
  108. package/src/solana/chain/modules/SolanaTokens.ts +300 -300
  109. package/src/solana/chain/modules/SolanaTransactions.ts +503 -503
  110. package/src/solana/connection/ConnectionWithRetries.ts +113 -96
  111. package/src/solana/events/SolanaChainEvents.ts +127 -127
  112. package/src/solana/events/SolanaChainEventsBrowser.ts +495 -495
  113. package/src/solana/program/SolanaProgramBase.ts +119 -119
  114. package/src/solana/program/SolanaProgramModule.ts +15 -15
  115. package/src/solana/program/modules/SolanaProgramEvents.ts +157 -157
  116. package/src/solana/swaps/SolanaSwapData.ts +735 -735
  117. package/src/solana/swaps/SolanaSwapModule.ts +19 -19
  118. package/src/solana/swaps/SolanaSwapProgram.ts +1074 -1074
  119. package/src/solana/swaps/SwapTypeEnum.ts +30 -30
  120. package/src/solana/swaps/modules/SolanaDataAccount.ts +302 -302
  121. package/src/solana/swaps/modules/SolanaLpVault.ts +208 -208
  122. package/src/solana/swaps/modules/SwapClaim.ts +387 -387
  123. package/src/solana/swaps/modules/SwapInit.ts +785 -785
  124. package/src/solana/swaps/modules/SwapRefund.ts +353 -353
  125. package/src/solana/swaps/v1/programIdl.json +944 -944
  126. package/src/solana/swaps/v1/programTypes.ts +1885 -1885
  127. package/src/solana/swaps/v2/programIdl.json +951 -951
  128. package/src/solana/swaps/v2/programTypes.ts +1899 -1899
  129. package/src/solana/wallet/SolanaKeypairWallet.ts +56 -56
  130. package/src/solana/wallet/SolanaSigner.ts +43 -43
  131. package/src/utils/Utils.ts +194 -194
@@ -1,495 +1,495 @@
1
- import {ChainEvents, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SwapEvent} from "@atomiqlabs/base";
2
- import {InitInstruction, SolanaSwapData} from "../swaps/SolanaSwapData";
3
- import {IdlEvents} from "@coral-xyz/anchor";
4
- import {SolanaSwapProgram} from "../swaps/SolanaSwapProgram";
5
- import {
6
- getLogger,
7
- onceAsync, toEscrowHash, tryWithRetries
8
- } from "../../utils/Utils";
9
- import {ConfirmedSignatureInfo, Connection, ParsedTransactionWithMeta} from "@solana/web3.js";
10
- import {SwapTypeEnum} from "../swaps/SwapTypeEnum";
11
- import {
12
- InstructionWithAccounts,
13
- ProgramEvent
14
- } from "../program/modules/SolanaProgramEvents";
15
- import {SwapProgram} from "../swaps/v1/programTypes";
16
- import {Buffer} from "buffer";
17
-
18
- /**
19
- * Parsed event payload grouped by originating transaction metadata.
20
- *
21
- * @category Events
22
- */
23
- export type EventObject = {
24
- events: ProgramEvent<SwapProgram>[],
25
- instructions?: (InstructionWithAccounts<SwapProgram> | null)[],
26
- blockTime: number,
27
- signature: string
28
- };
29
-
30
- const LOG_FETCH_LIMIT = 500;
31
- const PROCESSED_SIGNATURES_BACKLOG = 500;
32
-
33
- /**
34
- * Legacy current cursor of Solana event listener state.
35
- *
36
- * @category Events
37
- */
38
- export type SolanaLegacyEventListenerState = {
39
- /**
40
- * Last processed transaction's signature
41
- */
42
- signature: string,
43
- /**
44
- * Last processed transaction's slot
45
- */
46
- slot: number
47
- };
48
-
49
- /**
50
- * Current cursor of Solana event listener state.
51
- *
52
- * @category Events
53
- */
54
- export type SolanaEventListenerState = {
55
- [version: string]: SolanaLegacyEventListenerState | null
56
- };
57
-
58
- function toNewEventListenerState(obj: SolanaLegacyEventListenerState | SolanaEventListenerState | undefined): SolanaEventListenerState | undefined {
59
- if(obj==null) return undefined;
60
- if(obj.slot!=null || obj.signature!=null) return {"v1": obj} as SolanaEventListenerState;
61
- return obj as SolanaEventListenerState;
62
- }
63
-
64
- /**
65
- * Solana on-chain event handler for front-end systems without access to fs, uses pure WS to subscribe, might lose
66
- * out on some events if the network is unreliable, front-end systems should take this into consideration and not
67
- * rely purely on events
68
- *
69
- * @category Events
70
- */
71
- export class SolanaChainEventsBrowser implements ChainEvents<SolanaSwapData, SolanaLegacyEventListenerState | SolanaEventListenerState> {
72
-
73
- /**
74
- * @internal
75
- */
76
- protected readonly listeners: EventListener<SolanaSwapData>[] = [];
77
- /**
78
- * @internal
79
- */
80
- protected readonly connection: Connection;
81
- /**
82
- * @internal
83
- */
84
- protected readonly contractVersions: {[version: string]: {swapContract: SolanaSwapProgram}};
85
- /**
86
- * @internal
87
- */
88
- protected eventListeners: {[version: string]: number[]} = {};
89
- /**
90
- * @internal
91
- */
92
- protected readonly logger = getLogger("SolanaChainEventsBrowser: ");
93
-
94
- private readonly logFetchLimit: number;
95
- private signaturesProcessing: {
96
- [signature: string]: Promise<boolean>
97
- } = {};
98
- private processedSignatures: string[] = [];
99
- private processedSignaturesIndex: number = 0;
100
-
101
- constructor(
102
- connection: Connection,
103
- contractVersions: SolanaSwapProgram | {[version: string]: {swapContract: SolanaSwapProgram}},
104
- logFetchLimit?: number
105
- ) {
106
- this.connection = connection;
107
- if(contractVersions instanceof SolanaSwapProgram) {
108
- this.contractVersions = {[contractVersions.version]: {swapContract: contractVersions}};
109
- } else {
110
- this.contractVersions = contractVersions;
111
- }
112
- this.logFetchLimit = logFetchLimit ?? LOG_FETCH_LIMIT;
113
- }
114
-
115
- private addProcessedSignature(signature: string, version: string) {
116
- this.processedSignatures[this.processedSignaturesIndex] = signature+"-"+version;
117
- this.processedSignaturesIndex += 1;
118
- if(this.processedSignaturesIndex >= PROCESSED_SIGNATURES_BACKLOG) this.processedSignaturesIndex = 0;
119
- }
120
-
121
- private isSignatureProcessed(signature: string, version: string): boolean {
122
- return this.processedSignatures.includes(signature+"-"+version);
123
- }
124
-
125
- /**
126
- * Parses EventObject from the transaction
127
- *
128
- * @param transaction
129
- * @param version
130
- * @private
131
- * @returns {EventObject} parsed event object
132
- */
133
- private getEventObjectFromTransaction(transaction: ParsedTransactionWithMeta, version: string): EventObject | null {
134
- const signature = transaction.transaction.signatures[0];
135
- if(transaction.meta==null) throw new Error(`Transaction 'meta' not found for Solana tx: ${signature}`);
136
- if(transaction.meta.err!=null || transaction.meta.logMessages==null) return null;
137
-
138
- const instructions = this.contractVersions[version].swapContract._Events.decodeInstructions(transaction.transaction.message);
139
- const events = this.contractVersions[version].swapContract._Events.parseLogs(transaction.meta.logMessages);
140
-
141
- return {
142
- instructions,
143
- events,
144
- blockTime: transaction.blockTime!,
145
- signature: signature
146
- };
147
- }
148
-
149
- /**
150
- * Fetches transaction from the RPC, parses it to even object & processes it through event handler
151
- *
152
- * @param signature
153
- * @param version
154
- * @private
155
- * @returns {boolean} whether the operation was successful
156
- */
157
- private async fetchTxAndProcessEvent(signature: string, version: string): Promise<boolean> {
158
- try {
159
- const transaction = await this.connection.getParsedTransaction(signature, {
160
- commitment: "confirmed",
161
- maxSupportedTransactionVersion: 1
162
- });
163
- if(transaction==null) return false;
164
-
165
- const eventObject = this.getEventObjectFromTransaction(transaction, version);
166
- if(eventObject==null) return true;
167
-
168
- await this.processEvent(eventObject, version);
169
- return true;
170
- } catch (e) {
171
- this.logger.error("fetchTxAndProcessEvent(): Error fetching transaction and processing event, signature: "+signature, e);
172
- return false;
173
- }
174
- }
175
-
176
- /**
177
- * Fetches and parses transaction instructions
178
- *
179
- * @private
180
- * @returns {Promise<(InstructionWithAccounts<SwapProgram> | null)[] | null>} array of parsed instructions
181
- */
182
- private async getTransactionInstructions(signature: string, version: string): Promise<(InstructionWithAccounts<SwapProgram> | null)[] | null> {
183
- const transaction = await tryWithRetries<ParsedTransactionWithMeta>(async () => {
184
- const res = await this.connection.getParsedTransaction(signature, {
185
- commitment: "confirmed",
186
- maxSupportedTransactionVersion: 0
187
- });
188
- if(res==null) throw new Error("Transaction not found!");
189
- return res;
190
- });
191
- if(transaction.meta==null) throw new Error("Transaction 'meta' not found!");
192
- if(transaction.meta.err!=null) return null;
193
- return this.contractVersions[version].swapContract._Events.decodeInstructions(transaction.transaction.message);
194
- }
195
-
196
- /**
197
- * Returns async getter for fetching on-demand initialize event swap data
198
- *
199
- * @param eventObject
200
- * @param txoHash
201
- * @param version
202
- * @private
203
- * @returns {() => Promise<SolanaSwapData>} getter to be passed to InitializeEvent constructor
204
- */
205
- private getSwapDataGetter(eventObject: EventObject, txoHash: string, version: string): () => Promise<SolanaSwapData | null> {
206
- return async () => {
207
- if(eventObject.instructions==null) {
208
- const ixs = await this.getTransactionInstructions(eventObject.signature, version);
209
- if(ixs==null) return null;
210
- eventObject.instructions = ixs;
211
- }
212
-
213
- const initIx = eventObject.instructions.find(
214
- ix => ix!=null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize")
215
- ) as InitInstruction;
216
- if(initIx == null) return null;
217
-
218
- return SolanaSwapData.fromInstruction(this.contractVersions[version].swapContract.program.programId, version as "v1" | "v2", initIx, txoHash);
219
- }
220
- }
221
-
222
- /**
223
- * @internal
224
- */
225
- protected parseInitializeEvent(data: IdlEvents<SwapProgram>["InitializeEvent"], eventObject: EventObject, version: string): InitializeEvent<SolanaSwapData> {
226
- const paymentHash: string = Buffer.from(data.hash).toString("hex");
227
- const txoHash: string = Buffer.from(data.txoHash).toString("hex");
228
- const escrowHash = toEscrowHash(paymentHash, data.sequence);
229
- this.logger.debug("InitializeEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
230
- " txoHash: "+txoHash+" escrowHash: "+escrowHash);
231
- return new InitializeEvent<SolanaSwapData>(
232
- escrowHash,
233
- SwapTypeEnum.toChainSwapType(data.kind),
234
- onceAsync<SolanaSwapData | null>(this.getSwapDataGetter(eventObject, txoHash, version)),
235
- version
236
- );
237
- }
238
-
239
- /**
240
- * @internal
241
- */
242
- protected parseRefundEvent(data: IdlEvents<SwapProgram>["RefundEvent"], version: string): RefundEvent<SolanaSwapData> {
243
- const paymentHash: string = Buffer.from(data.hash).toString("hex");
244
- const escrowHash = toEscrowHash(paymentHash, data.sequence);
245
- this.logger.debug("RefundEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
246
- " escrowHash: "+escrowHash);
247
- return new RefundEvent<SolanaSwapData>(escrowHash, version);
248
- }
249
-
250
- /**
251
- * @internal
252
- */
253
- protected parseClaimEvent(data: IdlEvents<SwapProgram>["ClaimEvent"], version: string): ClaimEvent<SolanaSwapData> {
254
- const secret: string = Buffer.from(data.secret).toString("hex");
255
- const paymentHash: string = Buffer.from(data.hash).toString("hex");
256
- const escrowHash = toEscrowHash(paymentHash, data.sequence);
257
- this.logger.debug("ClaimEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
258
- " secret: "+secret+" escrowHash: "+escrowHash);
259
- return new ClaimEvent<SolanaSwapData>(escrowHash, secret, version);
260
- }
261
-
262
- /**
263
- * Processes event as received from the chain, parses it & calls event listeners
264
- *
265
- * @param eventObject
266
- * @param version
267
- * @internal
268
- */
269
- protected async processEvent(eventObject : EventObject, version: string) {
270
- let parsedEvents: SwapEvent<SolanaSwapData>[] = eventObject.events.map(event => {
271
- let parsedEvent: SwapEvent<SolanaSwapData> | undefined;
272
- switch(event.name) {
273
- case "ClaimEvent":
274
- parsedEvent = this.parseClaimEvent(event.data, version);
275
- break;
276
- case "RefundEvent":
277
- parsedEvent = this.parseRefundEvent(event.data, version);
278
- break;
279
- case "InitializeEvent":
280
- parsedEvent = this.parseInitializeEvent(event.data, eventObject, version);
281
- break;
282
- }
283
- if(parsedEvent==null) return null;
284
- (parsedEvent as any).meta = {
285
- blockTime: eventObject.blockTime,
286
- timestamp: eventObject.blockTime,
287
- txId: eventObject.signature
288
- };
289
- return parsedEvent;
290
- }).filter(parsedEvent => parsedEvent!=null);
291
-
292
- for(let listener of this.listeners) {
293
- await listener(parsedEvents);
294
- }
295
- }
296
-
297
- /**
298
- * Returns websocket event handler for specific event type
299
- *
300
- * @param name
301
- * @param version
302
- * @internal
303
- * @returns event handler to be passed to program's addEventListener function
304
- */
305
- protected getWsEventHandler<E extends "InitializeEvent" | "RefundEvent" | "ClaimEvent">(
306
- name: E,
307
- version: string
308
- ): (data: IdlEvents<SwapProgram>[E], slotNumber: number, signature: string) => void {
309
- return (data: IdlEvents<SwapProgram>[E], slotNumber: number, signature: string) => {
310
- if(this.signaturesProcessing[signature+"-"+version]!=null) return;
311
- if(this.isSignatureProcessed(signature, version)) return;
312
-
313
- this.logger.debug("getWsEventHandler("+name+"): Process signature: ", signature);
314
-
315
- this.signaturesProcessing[signature+"-"+version] = this.processEvent({
316
- events: [{name, data: data as any}],
317
- blockTime: Math.floor(Date.now()/1000),
318
- signature
319
- }, version).then(() => true).catch(e => {
320
- this.logger.error("getWsEventHandler("+name+"): Error processing signature: "+signature, e);
321
- return false;
322
- });
323
- };
324
- }
325
-
326
- /**
327
- * Sets up event handlers listening for swap events over websocket
328
- *
329
- * @internal
330
- */
331
- protected setupWebsocket() {
332
- for(let version in this.contractVersions) {
333
- const program = this.contractVersions[version].swapContract.program;
334
- const eventListeners = this.eventListeners[version] ??= [];
335
- eventListeners.push(program.addEventListener<"InitializeEvent">("InitializeEvent", this.getWsEventHandler("InitializeEvent", version)));
336
- eventListeners.push(program.addEventListener<"ClaimEvent">("ClaimEvent", this.getWsEventHandler("ClaimEvent", version)));
337
- eventListeners.push(program.addEventListener<"RefundEvent">("RefundEvent", this.getWsEventHandler("RefundEvent", version)));
338
- }
339
- }
340
-
341
- /**
342
- * Gets all the new signatures from the last processed signature
343
- *
344
- * @param lastProcessedSignature
345
- * @param version
346
- * @private
347
- */
348
- private async getNewSignatures(lastProcessedSignature: {signature: string, slot: number}, version: string): Promise<ConfirmedSignatureInfo[] | null> {
349
- let signatures: ConfirmedSignatureInfo[] = [];
350
-
351
- let fetched = null;
352
- while(fetched==null || fetched.length===this.logFetchLimit) {
353
- if(signatures.length===0) {
354
- fetched = await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
355
- until: lastProcessedSignature.signature,
356
- limit: this.logFetchLimit
357
- }, "confirmed");
358
- //Check if newest returned signature (index 0) is older than the latest signature's slot, this is a sanity check
359
- if(fetched.length>0 && fetched[0].slot<lastProcessedSignature.slot) {
360
- this.logger.debug("getNewSignatures(): Sanity check triggered, returned signature slot height is older than latest!");
361
- return null;
362
- }
363
- } else {
364
- fetched = await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
365
- before: signatures[signatures.length-1].signature,
366
- until: lastProcessedSignature.signature,
367
- limit: this.logFetchLimit
368
- }, "confirmed");
369
- }
370
-
371
- signatures = signatures.concat(fetched);
372
- }
373
-
374
- return signatures;
375
- }
376
-
377
- /**
378
- * Gets single latest known signature
379
- *
380
- * @private
381
- */
382
- private async getFirstSignature(version: string): Promise<ConfirmedSignatureInfo[]> {
383
- return await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
384
- limit: 1
385
- }, "confirmed");
386
- }
387
-
388
- /**
389
- * Processes signatures, fetches transactions & processes event through event handlers
390
- *
391
- * @param signatures
392
- * @param version
393
- * @private
394
- * @returns {Promise<{signature: string, slot: number}>} latest processed transaction signature and slot height
395
- */
396
- private async processSignatures(signatures: ConfirmedSignatureInfo[], version: string): Promise<{signature: string, slot: number} | null> {
397
- let lastSuccessfulSignature: {signature: string, slot: number} | null = null;
398
-
399
- try {
400
- for(let i=signatures.length-1;i>=0;i--) {
401
- const txSignature = signatures[i];
402
-
403
- //Check if signature is already being processed by the
404
- const signaturePromise = this.signaturesProcessing[txSignature.signature+"-"+version];
405
- if(signaturePromise!=null) {
406
- const result = await signaturePromise;
407
- delete this.signaturesProcessing[txSignature.signature+"-"+version];
408
- if(result) {
409
- lastSuccessfulSignature = txSignature;
410
- this.addProcessedSignature(txSignature.signature, version);
411
- continue;
412
- }
413
- }
414
-
415
- this.logger.debug("processSignatures(): Process signature: ", txSignature);
416
-
417
- const processPromise: Promise<boolean> = this.fetchTxAndProcessEvent(txSignature.signature, version);
418
- this.signaturesProcessing[txSignature.signature+"-"+version] = processPromise;
419
-
420
- const result = await processPromise;
421
- if(!result) throw new Error("Failed to process signature: "+txSignature);
422
- lastSuccessfulSignature = txSignature;
423
- this.addProcessedSignature(txSignature.signature, version);
424
- delete this.signaturesProcessing[txSignature.signature+"-"+version];
425
- }
426
- } catch (e) {
427
- this.logger.error("processSignatures(): Failed processing signatures: ", e);
428
- }
429
- return lastSuccessfulSignature;
430
- }
431
-
432
- /**
433
- * @inheritDoc
434
- */
435
- async poll(lastSignature?: SolanaLegacyEventListenerState | SolanaEventListenerState): Promise<SolanaEventListenerState> {
436
- const _lastSignature = toNewEventListenerState(lastSignature);
437
-
438
- const result: SolanaEventListenerState = {};
439
- for(let version in this.contractVersions) {
440
- const lastSignature = _lastSignature?.[version];
441
-
442
- let signatures = lastSignature==null
443
- ? await this.getFirstSignature(version)
444
- : await this.getNewSignatures(lastSignature, version);
445
- if(signatures==null) {
446
- result[version] = lastSignature ?? null;
447
- } else {
448
- let lastSuccessfulSignature = await this.processSignatures(signatures, version);
449
- result[version] = lastSuccessfulSignature ?? lastSignature ?? null;
450
- }
451
- }
452
-
453
- return result;
454
- }
455
-
456
- /**
457
- * @inheritDoc
458
- */
459
- init(noAutomaticPoll?: boolean): Promise<void> {
460
- if(noAutomaticPoll) return Promise.resolve();
461
- this.setupWebsocket();
462
- return Promise.resolve();
463
- }
464
-
465
- /**
466
- * @inheritDoc
467
- */
468
- async stop(): Promise<void> {
469
- for(let version in this.eventListeners) {
470
- for(let num of this.eventListeners[version]) {
471
- await this.contractVersions[version].swapContract.program.removeEventListener(num);
472
- }
473
- }
474
- this.eventListeners = {};
475
- }
476
-
477
- /**
478
- * @inheritDoc
479
- */
480
- registerListener(cbk: EventListener<SolanaSwapData>): void {
481
- this.listeners.push(cbk);
482
- }
483
-
484
- /**
485
- * @inheritDoc
486
- */
487
- unregisterListener(cbk: EventListener<SolanaSwapData>): boolean {
488
- const index = this.listeners.indexOf(cbk);
489
- if(index>=0) {
490
- this.listeners.splice(index, 1);
491
- return true;
492
- }
493
- return false;
494
- }
495
- }
1
+ import {ChainEvents, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SwapEvent} from "@atomiqlabs/base";
2
+ import {InitInstruction, SolanaSwapData} from "../swaps/SolanaSwapData";
3
+ import {IdlEvents} from "@coral-xyz/anchor";
4
+ import {SolanaSwapProgram} from "../swaps/SolanaSwapProgram";
5
+ import {
6
+ getLogger,
7
+ onceAsync, toEscrowHash, tryWithRetries
8
+ } from "../../utils/Utils";
9
+ import {ConfirmedSignatureInfo, Connection, ParsedTransactionWithMeta} from "@solana/web3.js";
10
+ import {SwapTypeEnum} from "../swaps/SwapTypeEnum";
11
+ import {
12
+ InstructionWithAccounts,
13
+ ProgramEvent
14
+ } from "../program/modules/SolanaProgramEvents";
15
+ import {SwapProgram} from "../swaps/v1/programTypes";
16
+ import {Buffer} from "buffer";
17
+
18
+ /**
19
+ * Parsed event payload grouped by originating transaction metadata.
20
+ *
21
+ * @category Events
22
+ */
23
+ export type EventObject = {
24
+ events: ProgramEvent<SwapProgram>[],
25
+ instructions?: (InstructionWithAccounts<SwapProgram> | null)[],
26
+ blockTime: number,
27
+ signature: string
28
+ };
29
+
30
+ const LOG_FETCH_LIMIT = 500;
31
+ const PROCESSED_SIGNATURES_BACKLOG = 500;
32
+
33
+ /**
34
+ * Legacy current cursor of Solana event listener state.
35
+ *
36
+ * @category Events
37
+ */
38
+ export type SolanaLegacyEventListenerState = {
39
+ /**
40
+ * Last processed transaction's signature
41
+ */
42
+ signature: string,
43
+ /**
44
+ * Last processed transaction's slot
45
+ */
46
+ slot: number
47
+ };
48
+
49
+ /**
50
+ * Current cursor of Solana event listener state.
51
+ *
52
+ * @category Events
53
+ */
54
+ export type SolanaEventListenerState = {
55
+ [version: string]: SolanaLegacyEventListenerState | null
56
+ };
57
+
58
+ function toNewEventListenerState(obj: SolanaLegacyEventListenerState | SolanaEventListenerState | undefined): SolanaEventListenerState | undefined {
59
+ if(obj==null) return undefined;
60
+ if(obj.slot!=null || obj.signature!=null) return {"v1": obj} as SolanaEventListenerState;
61
+ return obj as SolanaEventListenerState;
62
+ }
63
+
64
+ /**
65
+ * Solana on-chain event handler for front-end systems without access to fs, uses pure WS to subscribe, might lose
66
+ * out on some events if the network is unreliable, front-end systems should take this into consideration and not
67
+ * rely purely on events
68
+ *
69
+ * @category Events
70
+ */
71
+ export class SolanaChainEventsBrowser implements ChainEvents<SolanaSwapData, SolanaLegacyEventListenerState | SolanaEventListenerState> {
72
+
73
+ /**
74
+ * @internal
75
+ */
76
+ protected readonly listeners: EventListener<SolanaSwapData>[] = [];
77
+ /**
78
+ * @internal
79
+ */
80
+ protected readonly connection: Connection;
81
+ /**
82
+ * @internal
83
+ */
84
+ protected readonly contractVersions: {[version: string]: {swapContract: SolanaSwapProgram}};
85
+ /**
86
+ * @internal
87
+ */
88
+ protected eventListeners: {[version: string]: number[]} = {};
89
+ /**
90
+ * @internal
91
+ */
92
+ protected readonly logger = getLogger("SolanaChainEventsBrowser: ");
93
+
94
+ private readonly logFetchLimit: number;
95
+ private signaturesProcessing: {
96
+ [signature: string]: Promise<boolean>
97
+ } = {};
98
+ private processedSignatures: string[] = [];
99
+ private processedSignaturesIndex: number = 0;
100
+
101
+ constructor(
102
+ connection: Connection,
103
+ contractVersions: SolanaSwapProgram | {[version: string]: {swapContract: SolanaSwapProgram}},
104
+ logFetchLimit?: number
105
+ ) {
106
+ this.connection = connection;
107
+ if(contractVersions instanceof SolanaSwapProgram) {
108
+ this.contractVersions = {[contractVersions.version]: {swapContract: contractVersions}};
109
+ } else {
110
+ this.contractVersions = contractVersions;
111
+ }
112
+ this.logFetchLimit = logFetchLimit ?? LOG_FETCH_LIMIT;
113
+ }
114
+
115
+ private addProcessedSignature(signature: string, version: string) {
116
+ this.processedSignatures[this.processedSignaturesIndex] = signature+"-"+version;
117
+ this.processedSignaturesIndex += 1;
118
+ if(this.processedSignaturesIndex >= PROCESSED_SIGNATURES_BACKLOG) this.processedSignaturesIndex = 0;
119
+ }
120
+
121
+ private isSignatureProcessed(signature: string, version: string): boolean {
122
+ return this.processedSignatures.includes(signature+"-"+version);
123
+ }
124
+
125
+ /**
126
+ * Parses EventObject from the transaction
127
+ *
128
+ * @param transaction
129
+ * @param version
130
+ * @private
131
+ * @returns {EventObject} parsed event object
132
+ */
133
+ private getEventObjectFromTransaction(transaction: ParsedTransactionWithMeta, version: string): EventObject | null {
134
+ const signature = transaction.transaction.signatures[0];
135
+ if(transaction.meta==null) throw new Error(`Transaction 'meta' not found for Solana tx: ${signature}`);
136
+ if(transaction.meta.err!=null || transaction.meta.logMessages==null) return null;
137
+
138
+ const instructions = this.contractVersions[version].swapContract._Events.decodeInstructions(transaction.transaction.message);
139
+ const events = this.contractVersions[version].swapContract._Events.parseLogs(transaction.meta.logMessages);
140
+
141
+ return {
142
+ instructions,
143
+ events,
144
+ blockTime: transaction.blockTime!,
145
+ signature: signature
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Fetches transaction from the RPC, parses it to even object & processes it through event handler
151
+ *
152
+ * @param signature
153
+ * @param version
154
+ * @private
155
+ * @returns {boolean} whether the operation was successful
156
+ */
157
+ private async fetchTxAndProcessEvent(signature: string, version: string): Promise<boolean> {
158
+ try {
159
+ const transaction = await this.connection.getParsedTransaction(signature, {
160
+ commitment: "confirmed",
161
+ maxSupportedTransactionVersion: 1
162
+ });
163
+ if(transaction==null) return false;
164
+
165
+ const eventObject = this.getEventObjectFromTransaction(transaction, version);
166
+ if(eventObject==null) return true;
167
+
168
+ await this.processEvent(eventObject, version);
169
+ return true;
170
+ } catch (e) {
171
+ this.logger.error("fetchTxAndProcessEvent(): Error fetching transaction and processing event, signature: "+signature, e);
172
+ return false;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Fetches and parses transaction instructions
178
+ *
179
+ * @private
180
+ * @returns {Promise<(InstructionWithAccounts<SwapProgram> | null)[] | null>} array of parsed instructions
181
+ */
182
+ private async getTransactionInstructions(signature: string, version: string): Promise<(InstructionWithAccounts<SwapProgram> | null)[] | null> {
183
+ const transaction = await tryWithRetries<ParsedTransactionWithMeta>(async () => {
184
+ const res = await this.connection.getParsedTransaction(signature, {
185
+ commitment: "confirmed",
186
+ maxSupportedTransactionVersion: 0
187
+ });
188
+ if(res==null) throw new Error("Transaction not found!");
189
+ return res;
190
+ });
191
+ if(transaction.meta==null) throw new Error("Transaction 'meta' not found!");
192
+ if(transaction.meta.err!=null) return null;
193
+ return this.contractVersions[version].swapContract._Events.decodeInstructions(transaction.transaction.message);
194
+ }
195
+
196
+ /**
197
+ * Returns async getter for fetching on-demand initialize event swap data
198
+ *
199
+ * @param eventObject
200
+ * @param txoHash
201
+ * @param version
202
+ * @private
203
+ * @returns {() => Promise<SolanaSwapData>} getter to be passed to InitializeEvent constructor
204
+ */
205
+ private getSwapDataGetter(eventObject: EventObject, txoHash: string, version: string): () => Promise<SolanaSwapData | null> {
206
+ return async () => {
207
+ if(eventObject.instructions==null) {
208
+ const ixs = await this.getTransactionInstructions(eventObject.signature, version);
209
+ if(ixs==null) return null;
210
+ eventObject.instructions = ixs;
211
+ }
212
+
213
+ const initIx = eventObject.instructions.find(
214
+ ix => ix!=null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize")
215
+ ) as InitInstruction;
216
+ if(initIx == null) return null;
217
+
218
+ return SolanaSwapData.fromInstruction(this.contractVersions[version].swapContract.program.programId, version as "v1" | "v2", initIx, txoHash);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * @internal
224
+ */
225
+ protected parseInitializeEvent(data: IdlEvents<SwapProgram>["InitializeEvent"], eventObject: EventObject, version: string): InitializeEvent<SolanaSwapData> {
226
+ const paymentHash: string = Buffer.from(data.hash).toString("hex");
227
+ const txoHash: string = Buffer.from(data.txoHash).toString("hex");
228
+ const escrowHash = toEscrowHash(paymentHash, data.sequence);
229
+ this.logger.debug("InitializeEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
230
+ " txoHash: "+txoHash+" escrowHash: "+escrowHash);
231
+ return new InitializeEvent<SolanaSwapData>(
232
+ escrowHash,
233
+ SwapTypeEnum.toChainSwapType(data.kind),
234
+ onceAsync<SolanaSwapData | null>(this.getSwapDataGetter(eventObject, txoHash, version)),
235
+ version
236
+ );
237
+ }
238
+
239
+ /**
240
+ * @internal
241
+ */
242
+ protected parseRefundEvent(data: IdlEvents<SwapProgram>["RefundEvent"], version: string): RefundEvent<SolanaSwapData> {
243
+ const paymentHash: string = Buffer.from(data.hash).toString("hex");
244
+ const escrowHash = toEscrowHash(paymentHash, data.sequence);
245
+ this.logger.debug("RefundEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
246
+ " escrowHash: "+escrowHash);
247
+ return new RefundEvent<SolanaSwapData>(escrowHash, version);
248
+ }
249
+
250
+ /**
251
+ * @internal
252
+ */
253
+ protected parseClaimEvent(data: IdlEvents<SwapProgram>["ClaimEvent"], version: string): ClaimEvent<SolanaSwapData> {
254
+ const secret: string = Buffer.from(data.secret).toString("hex");
255
+ const paymentHash: string = Buffer.from(data.hash).toString("hex");
256
+ const escrowHash = toEscrowHash(paymentHash, data.sequence);
257
+ this.logger.debug("ClaimEvent paymentHash: "+paymentHash+" sequence: "+data.sequence.toString(10)+
258
+ " secret: "+secret+" escrowHash: "+escrowHash);
259
+ return new ClaimEvent<SolanaSwapData>(escrowHash, secret, version);
260
+ }
261
+
262
+ /**
263
+ * Processes event as received from the chain, parses it & calls event listeners
264
+ *
265
+ * @param eventObject
266
+ * @param version
267
+ * @internal
268
+ */
269
+ protected async processEvent(eventObject : EventObject, version: string) {
270
+ let parsedEvents: SwapEvent<SolanaSwapData>[] = eventObject.events.map(event => {
271
+ let parsedEvent: SwapEvent<SolanaSwapData> | undefined;
272
+ switch(event.name) {
273
+ case "ClaimEvent":
274
+ parsedEvent = this.parseClaimEvent(event.data, version);
275
+ break;
276
+ case "RefundEvent":
277
+ parsedEvent = this.parseRefundEvent(event.data, version);
278
+ break;
279
+ case "InitializeEvent":
280
+ parsedEvent = this.parseInitializeEvent(event.data, eventObject, version);
281
+ break;
282
+ }
283
+ if(parsedEvent==null) return null;
284
+ (parsedEvent as any).meta = {
285
+ blockTime: eventObject.blockTime,
286
+ timestamp: eventObject.blockTime,
287
+ txId: eventObject.signature
288
+ };
289
+ return parsedEvent;
290
+ }).filter(parsedEvent => parsedEvent!=null);
291
+
292
+ for(let listener of this.listeners) {
293
+ await listener(parsedEvents);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Returns websocket event handler for specific event type
299
+ *
300
+ * @param name
301
+ * @param version
302
+ * @internal
303
+ * @returns event handler to be passed to program's addEventListener function
304
+ */
305
+ protected getWsEventHandler<E extends "InitializeEvent" | "RefundEvent" | "ClaimEvent">(
306
+ name: E,
307
+ version: string
308
+ ): (data: IdlEvents<SwapProgram>[E], slotNumber: number, signature: string) => void {
309
+ return (data: IdlEvents<SwapProgram>[E], slotNumber: number, signature: string) => {
310
+ if(this.signaturesProcessing[signature+"-"+version]!=null) return;
311
+ if(this.isSignatureProcessed(signature, version)) return;
312
+
313
+ this.logger.debug("getWsEventHandler("+name+"): Process signature: ", signature);
314
+
315
+ this.signaturesProcessing[signature+"-"+version] = this.processEvent({
316
+ events: [{name, data: data as any}],
317
+ blockTime: Math.floor(Date.now()/1000),
318
+ signature
319
+ }, version).then(() => true).catch(e => {
320
+ this.logger.error("getWsEventHandler("+name+"): Error processing signature: "+signature, e);
321
+ return false;
322
+ });
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Sets up event handlers listening for swap events over websocket
328
+ *
329
+ * @internal
330
+ */
331
+ protected setupWebsocket() {
332
+ for(let version in this.contractVersions) {
333
+ const program = this.contractVersions[version].swapContract.program;
334
+ const eventListeners = this.eventListeners[version] ??= [];
335
+ eventListeners.push(program.addEventListener<"InitializeEvent">("InitializeEvent", this.getWsEventHandler("InitializeEvent", version)));
336
+ eventListeners.push(program.addEventListener<"ClaimEvent">("ClaimEvent", this.getWsEventHandler("ClaimEvent", version)));
337
+ eventListeners.push(program.addEventListener<"RefundEvent">("RefundEvent", this.getWsEventHandler("RefundEvent", version)));
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Gets all the new signatures from the last processed signature
343
+ *
344
+ * @param lastProcessedSignature
345
+ * @param version
346
+ * @private
347
+ */
348
+ private async getNewSignatures(lastProcessedSignature: {signature: string, slot: number}, version: string): Promise<ConfirmedSignatureInfo[] | null> {
349
+ let signatures: ConfirmedSignatureInfo[] = [];
350
+
351
+ let fetched = null;
352
+ while(fetched==null || fetched.length===this.logFetchLimit) {
353
+ if(signatures.length===0) {
354
+ fetched = await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
355
+ until: lastProcessedSignature.signature,
356
+ limit: this.logFetchLimit
357
+ }, "confirmed");
358
+ //Check if newest returned signature (index 0) is older than the latest signature's slot, this is a sanity check
359
+ if(fetched.length>0 && fetched[0].slot<lastProcessedSignature.slot) {
360
+ this.logger.debug("getNewSignatures(): Sanity check triggered, returned signature slot height is older than latest!");
361
+ return null;
362
+ }
363
+ } else {
364
+ fetched = await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
365
+ before: signatures[signatures.length-1].signature,
366
+ until: lastProcessedSignature.signature,
367
+ limit: this.logFetchLimit
368
+ }, "confirmed");
369
+ }
370
+
371
+ signatures = signatures.concat(fetched);
372
+ }
373
+
374
+ return signatures;
375
+ }
376
+
377
+ /**
378
+ * Gets single latest known signature
379
+ *
380
+ * @private
381
+ */
382
+ private async getFirstSignature(version: string): Promise<ConfirmedSignatureInfo[]> {
383
+ return await this.connection.getSignaturesForAddress(this.contractVersions[version].swapContract.program.programId, {
384
+ limit: 1
385
+ }, "confirmed");
386
+ }
387
+
388
+ /**
389
+ * Processes signatures, fetches transactions & processes event through event handlers
390
+ *
391
+ * @param signatures
392
+ * @param version
393
+ * @private
394
+ * @returns {Promise<{signature: string, slot: number}>} latest processed transaction signature and slot height
395
+ */
396
+ private async processSignatures(signatures: ConfirmedSignatureInfo[], version: string): Promise<{signature: string, slot: number} | null> {
397
+ let lastSuccessfulSignature: {signature: string, slot: number} | null = null;
398
+
399
+ try {
400
+ for(let i=signatures.length-1;i>=0;i--) {
401
+ const txSignature = signatures[i];
402
+
403
+ //Check if signature is already being processed by the
404
+ const signaturePromise = this.signaturesProcessing[txSignature.signature+"-"+version];
405
+ if(signaturePromise!=null) {
406
+ const result = await signaturePromise;
407
+ delete this.signaturesProcessing[txSignature.signature+"-"+version];
408
+ if(result) {
409
+ lastSuccessfulSignature = txSignature;
410
+ this.addProcessedSignature(txSignature.signature, version);
411
+ continue;
412
+ }
413
+ }
414
+
415
+ this.logger.debug("processSignatures(): Process signature: ", txSignature);
416
+
417
+ const processPromise: Promise<boolean> = this.fetchTxAndProcessEvent(txSignature.signature, version);
418
+ this.signaturesProcessing[txSignature.signature+"-"+version] = processPromise;
419
+
420
+ const result = await processPromise;
421
+ if(!result) throw new Error("Failed to process signature: "+txSignature);
422
+ lastSuccessfulSignature = txSignature;
423
+ this.addProcessedSignature(txSignature.signature, version);
424
+ delete this.signaturesProcessing[txSignature.signature+"-"+version];
425
+ }
426
+ } catch (e) {
427
+ this.logger.error("processSignatures(): Failed processing signatures: ", e);
428
+ }
429
+ return lastSuccessfulSignature;
430
+ }
431
+
432
+ /**
433
+ * @inheritDoc
434
+ */
435
+ async poll(lastSignature?: SolanaLegacyEventListenerState | SolanaEventListenerState): Promise<SolanaEventListenerState> {
436
+ const _lastSignature = toNewEventListenerState(lastSignature);
437
+
438
+ const result: SolanaEventListenerState = {};
439
+ for(let version in this.contractVersions) {
440
+ const lastSignature = _lastSignature?.[version];
441
+
442
+ let signatures = lastSignature==null
443
+ ? await this.getFirstSignature(version)
444
+ : await this.getNewSignatures(lastSignature, version);
445
+ if(signatures==null) {
446
+ result[version] = lastSignature ?? null;
447
+ } else {
448
+ let lastSuccessfulSignature = await this.processSignatures(signatures, version);
449
+ result[version] = lastSuccessfulSignature ?? lastSignature ?? null;
450
+ }
451
+ }
452
+
453
+ return result;
454
+ }
455
+
456
+ /**
457
+ * @inheritDoc
458
+ */
459
+ init(noAutomaticPoll?: boolean): Promise<void> {
460
+ if(noAutomaticPoll) return Promise.resolve();
461
+ this.setupWebsocket();
462
+ return Promise.resolve();
463
+ }
464
+
465
+ /**
466
+ * @inheritDoc
467
+ */
468
+ async stop(): Promise<void> {
469
+ for(let version in this.eventListeners) {
470
+ for(let num of this.eventListeners[version]) {
471
+ await this.contractVersions[version].swapContract.program.removeEventListener(num);
472
+ }
473
+ }
474
+ this.eventListeners = {};
475
+ }
476
+
477
+ /**
478
+ * @inheritDoc
479
+ */
480
+ registerListener(cbk: EventListener<SolanaSwapData>): void {
481
+ this.listeners.push(cbk);
482
+ }
483
+
484
+ /**
485
+ * @inheritDoc
486
+ */
487
+ unregisterListener(cbk: EventListener<SolanaSwapData>): boolean {
488
+ const index = this.listeners.indexOf(cbk);
489
+ if(index>=0) {
490
+ this.listeners.splice(index, 1);
491
+ return true;
492
+ }
493
+ return false;
494
+ }
495
+ }