@atomiqlabs/chain-solana 11.0.0 → 12.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/solana/SolanaChainType.d.ts +2 -1
- package/dist/solana/chain/SolanaChainInterface.d.ts +8 -1
- package/dist/solana/chain/SolanaChainInterface.js +13 -0
- package/dist/solana/chain/modules/SolanaEvents.d.ts +45 -2
- package/dist/solana/chain/modules/SolanaEvents.js +167 -2
- package/dist/solana/chain/modules/SolanaTransactions.js +17 -26
- 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 +26 -4
- package/dist/solana/swaps/SolanaSwapProgram.js +116 -19
- package/dist/solana/wallet/SolanaSigner.d.ts +1 -0
- package/dist/solana/wallet/SolanaSigner.js +1 -0
- package/package.json +2 -2
- package/src/solana/SolanaChainType.ts +2 -0
- package/src/solana/chain/SolanaChainInterface.ts +19 -1
- package/src/solana/chain/modules/SolanaEvents.ts +199 -5
- package/src/solana/chain/modules/SolanaTransactions.ts +17 -22
- 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 +172 -20
- package/src/solana/wallet/SolanaSigner.ts +1 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {SolanaModule} from "../SolanaModule";
|
|
2
|
-
import {ConfirmedSignatureInfo, PublicKey} from "@solana/web3.js";
|
|
2
|
+
import {ConfirmedSignatureInfo, ParsedTransactionWithMeta, PublicKey} from "@solana/web3.js";
|
|
3
|
+
import {sign} from "tweetnacl";
|
|
4
|
+
import {ProgramEvent} from "../../program/modules/SolanaProgramEvents";
|
|
3
5
|
|
|
4
6
|
export class SolanaEvents extends SolanaModule {
|
|
5
7
|
|
|
6
8
|
public readonly LOG_FETCH_LIMIT = 500;
|
|
7
9
|
|
|
10
|
+
private usingHeliusTFA: "yes" | "no" | "auto" = "auto";
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Gets the signatures for a given topicKey public key, if lastProcessedSignature is specified, it fetches only
|
|
10
14
|
* the signatures before this signature
|
|
@@ -27,6 +31,169 @@ export class SolanaEvents extends SolanaModule {
|
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Implements Helius getTransactionsForAddress RPC API
|
|
36
|
+
*
|
|
37
|
+
* @param account
|
|
38
|
+
* @param options
|
|
39
|
+
* @param commitment
|
|
40
|
+
*/
|
|
41
|
+
async getTransactionsForAddress(
|
|
42
|
+
account: PublicKey,
|
|
43
|
+
options?: {
|
|
44
|
+
paginationToken?: string,
|
|
45
|
+
filters?: {
|
|
46
|
+
slot?: {gte?: number, lte?: number, gt?: number, lt?: number},
|
|
47
|
+
blockTime?: {gte?: number, lte?: number, gt?: number, lt?: number, eq?: number},
|
|
48
|
+
signature?: {gte?: number, lte?: number, gt?: number, lt?: number, eq?: number},
|
|
49
|
+
status?: "succeeded" | "failed" | "any"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
commitment?: "finalized" | "confirmed" | "processed"
|
|
53
|
+
): Promise<{
|
|
54
|
+
data: ParsedTransactionWithMeta[],
|
|
55
|
+
paginationToken?: string
|
|
56
|
+
}> {
|
|
57
|
+
//Try to use getPriorityFeeEstimate api of Helius
|
|
58
|
+
const response = await (this.connection as any)._rpcRequest("getTransactionsForAddress", [
|
|
59
|
+
account.toString(),
|
|
60
|
+
{
|
|
61
|
+
...options,
|
|
62
|
+
transactionDetails: "full",
|
|
63
|
+
sortOrder: "desc",
|
|
64
|
+
limit: 100,
|
|
65
|
+
commitment: commitment ?? "confirmed",
|
|
66
|
+
encoding: "jsonParsed",
|
|
67
|
+
maxSupportedTransactionVersion: 0
|
|
68
|
+
}
|
|
69
|
+
]).catch(e => {
|
|
70
|
+
//Catching not supported errors
|
|
71
|
+
if(e.message!=null && (e.message.includes("-32601") || e.message.includes("-32600"))) {
|
|
72
|
+
return {
|
|
73
|
+
error: {
|
|
74
|
+
code: -32601,
|
|
75
|
+
message: e.message
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
throw e;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if(response.error!=null) {
|
|
83
|
+
//Catching not supported errors
|
|
84
|
+
if(response.error.code!==-32601 && response.error.code!==-32600) throw new Error(response.error.message);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
data: response.result.data.map(val => {
|
|
90
|
+
return {
|
|
91
|
+
...val, //slot, blockTime, version
|
|
92
|
+
meta: val.meta==null ? undefined : {
|
|
93
|
+
//ParsedTransactionMeta
|
|
94
|
+
...val.meta,
|
|
95
|
+
innerInstructions: val.meta.innerInstructions==null ? undefined : val.meta.innerInstructions.map(innerIx => ({
|
|
96
|
+
//ParsedInnerInstruction
|
|
97
|
+
...innerIx, //index
|
|
98
|
+
instructions: innerIx.instructions.map(ix => {
|
|
99
|
+
if(ix.program!=null && ix.programId!=null) {
|
|
100
|
+
return {
|
|
101
|
+
//ParsedInstruction
|
|
102
|
+
...ix,
|
|
103
|
+
programId: new PublicKey(ix.programId)
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
return {
|
|
107
|
+
//PartiallyDecodedInstruction
|
|
108
|
+
data: ix.data,
|
|
109
|
+
programId: new PublicKey(ix.programId),
|
|
110
|
+
accounts: ix.accounts.map(pubkey => new PublicKey(pubkey))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})),
|
|
115
|
+
loadedAddresses: val.meta.loadedAddresses==null ? undefined : {
|
|
116
|
+
writable: val.meta.loadedAddresses.writable.map(pubkey => new PublicKey(pubkey)),
|
|
117
|
+
readonly: val.meta.loadedAddresses.readonly.map(pubkey => new PublicKey(pubkey)),
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
transaction: {
|
|
121
|
+
//ParsedTransaction
|
|
122
|
+
...val.transaction, //signatures
|
|
123
|
+
message: {
|
|
124
|
+
//ParsedMessage
|
|
125
|
+
...val.transaction.message, //recentBlockhash
|
|
126
|
+
accountKeys: val.transaction.message.accountKeys.map(accountKey => ({
|
|
127
|
+
//ParsedMessageAccount
|
|
128
|
+
...accountKey,
|
|
129
|
+
pubkey: new PublicKey(accountKey.pubkey)
|
|
130
|
+
})),
|
|
131
|
+
instructions: val.transaction.message.instructions.map(ix => {
|
|
132
|
+
if(ix.program!=null && ix.programId!=null) {
|
|
133
|
+
return {
|
|
134
|
+
//ParsedInstruction
|
|
135
|
+
...ix,
|
|
136
|
+
programId: new PublicKey(ix.programId)
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
return {
|
|
140
|
+
//PartiallyDecodedInstruction
|
|
141
|
+
data: ix.data,
|
|
142
|
+
programId: new PublicKey(ix.programId),
|
|
143
|
+
accounts: ix.accounts.map(pubkey => new PublicKey(pubkey))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
addressTableLookups: val.transaction.message.addressTableLookups==null ? undefined : val.transaction.message.addressTableLookups.map(addressTableLookup => ({
|
|
148
|
+
//ParsedAddressTableLookup
|
|
149
|
+
...addressTableLookup,
|
|
150
|
+
accountKey: new PublicKey(addressTableLookup.accountKey)
|
|
151
|
+
}))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
paginationToken: response.result.paginationToken
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async _findInTxsTFA<T>(
|
|
161
|
+
topicKey: PublicKey,
|
|
162
|
+
processor: (data: {signatures?: ConfirmedSignatureInfo[], txs?: ParsedTransactionWithMeta[]}) => Promise<T>,
|
|
163
|
+
abortSignal?: AbortSignal,
|
|
164
|
+
startBlockheight?: number
|
|
165
|
+
): Promise<T> {
|
|
166
|
+
let paginationToken: string;
|
|
167
|
+
let txs: ParsedTransactionWithMeta[] = null;
|
|
168
|
+
while(txs==null || txs.length>0) {
|
|
169
|
+
let filters = startBlockheight!=null ? {
|
|
170
|
+
slot: {gte: startBlockheight}
|
|
171
|
+
} : {};
|
|
172
|
+
const tfaResult = await this.getTransactionsForAddress(topicKey, {
|
|
173
|
+
paginationToken,
|
|
174
|
+
filters: {
|
|
175
|
+
...filters,
|
|
176
|
+
status: "succeeded"
|
|
177
|
+
}
|
|
178
|
+
}, "confirmed");
|
|
179
|
+
|
|
180
|
+
if(tfaResult==null) {
|
|
181
|
+
//Not supported
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
txs = tfaResult.data;
|
|
186
|
+
paginationToken = tfaResult.paginationToken;
|
|
187
|
+
|
|
188
|
+
if(abortSignal!=null) abortSignal.throwIfAborted();
|
|
189
|
+
const result: T = await processor({txs});
|
|
190
|
+
if(result!=null) return result;
|
|
191
|
+
if(paginationToken==null) break;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
|
|
30
197
|
/**
|
|
31
198
|
* Runs a search backwards in time, processing transaction signatures for a specific topic public key
|
|
32
199
|
*
|
|
@@ -35,23 +202,50 @@ export class SolanaEvents extends SolanaModule {
|
|
|
35
202
|
* was found, or null if the search should continue
|
|
36
203
|
* @param abortSignal
|
|
37
204
|
* @param logFetchLimit
|
|
205
|
+
* @param startBlockheight
|
|
38
206
|
*/
|
|
39
|
-
|
|
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
|
}
|
|
@@ -9,6 +9,7 @@ import * as bs58 from "bs58";
|
|
|
9
9
|
import {tryWithRetries} from "../../../utils/Utils";
|
|
10
10
|
import {Buffer} from "buffer";
|
|
11
11
|
import {SolanaSigner} from "../../wallet/SolanaSigner";
|
|
12
|
+
import {TransactionRevertedError} from "@atomiqlabs/base";
|
|
12
13
|
|
|
13
14
|
export type SolanaTx = {tx: Transaction, signers: Signer[]};
|
|
14
15
|
|
|
@@ -74,7 +75,7 @@ export class SolanaTransactions extends SolanaModule {
|
|
|
74
75
|
this.logger.info("txConfirmationAndResendWatchdog(): transaction confirmed from HTTP polling, signature: "+signature);
|
|
75
76
|
resolve(signature);
|
|
76
77
|
}
|
|
77
|
-
if(status==="reverted") reject(new
|
|
78
|
+
if(status==="reverted") reject(new TransactionRevertedError("Transaction reverted!"));
|
|
78
79
|
clearInterval(watchdogInterval);
|
|
79
80
|
}, this.retryPolicy?.transactionResendInterval || 3000);
|
|
80
81
|
|
|
@@ -119,14 +120,14 @@ export class SolanaTransactions extends SolanaModule {
|
|
|
119
120
|
);
|
|
120
121
|
this.logger.info("txConfirmFromWebsocket(): transaction status: "+status+" signature: "+signature);
|
|
121
122
|
if(status==="success") return signature;
|
|
122
|
-
if(status==="reverted") throw new
|
|
123
|
+
if(status==="reverted") throw new TransactionRevertedError("Transaction reverted!");
|
|
123
124
|
if(err instanceof TransactionExpiredBlockheightExceededError || err.toString().startsWith("TransactionExpiredBlockheightExceededError")) {
|
|
124
125
|
throw new Error("Transaction expired before confirmation, please try again!");
|
|
125
126
|
} else {
|
|
126
127
|
throw err;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
|
-
if(result.value.err!=null) throw new
|
|
130
|
+
if(result.value.err!=null) throw new TransactionRevertedError("Transaction reverted!");
|
|
130
131
|
return signature;
|
|
131
132
|
}
|
|
132
133
|
|
|
@@ -239,9 +240,11 @@ export class SolanaTransactions extends SolanaModule {
|
|
|
239
240
|
this.logger.debug("sendAndConfirm(): sending transactions, count: "+_txs.length+
|
|
240
241
|
" waitForConfirmation: "+waitForConfirmation+" parallel: "+parallel);
|
|
241
242
|
|
|
243
|
+
const BATCH_SIZE = 50;
|
|
244
|
+
|
|
242
245
|
const signatures: string[] = [];
|
|
243
|
-
for(let e=0;e<_txs.length;e+=
|
|
244
|
-
const txs = _txs.slice(e, e+
|
|
246
|
+
for(let e=0;e<_txs.length;e+=BATCH_SIZE) {
|
|
247
|
+
const txs = _txs.slice(e, e+BATCH_SIZE);
|
|
245
248
|
|
|
246
249
|
await this.prepareTransactions(signer, txs)
|
|
247
250
|
const signedTxs = await signer.wallet.signAllTransactions(txs.map(e => e.tx));
|
|
@@ -252,23 +255,15 @@ export class SolanaTransactions extends SolanaModule {
|
|
|
252
255
|
});
|
|
253
256
|
this.logger.debug("sendAndConfirm(): sending transaction batch ("+e+".."+(e+50)+"), count: "+txs.length);
|
|
254
257
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if(
|
|
263
|
-
|
|
264
|
-
for(let i=0;i<txs.length;i++) {
|
|
265
|
-
const solTx = txs[i];
|
|
266
|
-
const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
|
|
267
|
-
const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
|
|
268
|
-
//Don't await the last promise when !waitForConfirmation
|
|
269
|
-
if(i<txs.length-1 || e+50<_txs.length || waitForConfirmation) await confirmPromise;
|
|
270
|
-
signatures.push(signature);
|
|
271
|
-
}
|
|
258
|
+
//For solana we are forced to send txs one-by-one even with parallel, as we cannot determine their order upfront,
|
|
259
|
+
// however e.g. Jito could possibly handle sending a single package of up to 5 txns in order.
|
|
260
|
+
for(let i=0;i<txs.length;i++) {
|
|
261
|
+
const solTx = txs[i];
|
|
262
|
+
const signature = await this.sendSignedTransaction(solTx, options, onBeforePublish);
|
|
263
|
+
const confirmPromise = this.confirmTransaction(solTx, abortSignal, "confirmed");
|
|
264
|
+
//Don't await the last promise when !waitForConfirmation
|
|
265
|
+
if(i<txs.length-1 || e+50<_txs.length || waitForConfirmation) await confirmPromise;
|
|
266
|
+
signatures.push(signature);
|
|
272
267
|
}
|
|
273
268
|
}
|
|
274
269
|
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import {ChainEvents, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SwapEvent} from "@atomiqlabs/base";
|
|
2
|
-
import {SolanaSwapData} from "../swaps/SolanaSwapData";
|
|
2
|
+
import {InitInstruction, SolanaSwapData} from "../swaps/SolanaSwapData";
|
|
3
3
|
import {IdlEvents} from "@coral-xyz/anchor";
|
|
4
4
|
import {SolanaSwapProgram} from "../swaps/SolanaSwapProgram";
|
|
5
5
|
import {
|
|
6
6
|
getLogger,
|
|
7
7
|
onceAsync, toEscrowHash, tryWithRetries
|
|
8
8
|
} from "../../utils/Utils";
|
|
9
|
-
import {Connection, ParsedTransactionWithMeta
|
|
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
|
|