@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.
- package/dist/solana/chain/modules/SolanaEvents.d.ts +45 -2
- package/dist/solana/chain/modules/SolanaEvents.js +167 -2
- package/dist/solana/events/SolanaChainEventsBrowser.d.ts +1 -11
- package/dist/solana/events/SolanaChainEventsBrowser.js +1 -23
- package/dist/solana/program/modules/SolanaProgramEvents.d.ts +3 -9
- package/dist/solana/program/modules/SolanaProgramEvents.js +38 -27
- package/dist/solana/swaps/SolanaSwapData.d.ts +13 -1
- package/dist/solana/swaps/SolanaSwapData.js +25 -0
- package/dist/solana/swaps/SolanaSwapProgram.d.ts +16 -0
- package/dist/solana/swaps/SolanaSwapProgram.js +96 -15
- package/package.json +2 -2
- package/src/solana/chain/modules/SolanaEvents.ts +199 -5
- package/src/solana/events/SolanaChainEventsBrowser.ts +4 -51
- package/src/solana/program/modules/SolanaProgramEvents.ts +44 -29
- package/src/solana/swaps/SolanaSwapData.ts +52 -1
- package/src/solana/swaps/SolanaSwapProgram.ts +146 -16
|
@@ -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,
|
|
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(
|
|
207
|
+
getClaimTxId: () => Promise.resolve(tx.transaction.signatures[0]),
|
|
208
208
|
getClaimResult: () => Promise.resolve(buffer_1.Buffer.from(event.data.secret).toString("hex")),
|
|
209
|
-
getTxBlock:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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(
|
|
226
|
-
getTxBlock:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
207
|
+
private async _findInSignatures<T>(
|
|
40
208
|
topicKey: PublicKey,
|
|
41
|
-
processor: (
|
|
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
|
|
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
|
|
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 {
|
|
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>,
|
|
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 (
|
|
78
|
-
|
|
79
|
-
for(let
|
|
80
|
-
if(
|
|
81
|
-
|
|
82
|
-
|
|
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 {
|
|
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
|
|