@drift-labs/sdk 2.37.1-beta.1 → 2.37.1-beta.2
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/VERSION +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/tx/baseTxSender.d.ts +30 -0
- package/lib/tx/baseTxSender.js +176 -0
- package/lib/tx/fastSingleTxSender.d.ts +27 -0
- package/lib/tx/fastSingleTxSender.js +83 -0
- package/lib/tx/retryTxSender.d.ts +5 -14
- package/lib/tx/retryTxSender.js +7 -158
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/marinade/types.ts +70 -70
- package/src/tx/baseTxSender.ts +276 -0
- package/src/tx/fastSingleTxSender.ts +142 -0
- package/src/tx/retryTxSender.ts +9 -235
- package/tests/amm/test.ts +83 -39
- package/tests/dlob/test.ts +19 -17
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { TxSender, TxSigAndSlot } from './types';
|
|
2
|
+
import {
|
|
3
|
+
Commitment,
|
|
4
|
+
ConfirmOptions,
|
|
5
|
+
Context,
|
|
6
|
+
RpcResponseAndContext,
|
|
7
|
+
Signer,
|
|
8
|
+
SignatureResult,
|
|
9
|
+
Transaction,
|
|
10
|
+
TransactionSignature,
|
|
11
|
+
Connection,
|
|
12
|
+
VersionedTransaction,
|
|
13
|
+
TransactionMessage,
|
|
14
|
+
TransactionInstruction,
|
|
15
|
+
AddressLookupTableAccount,
|
|
16
|
+
} from '@solana/web3.js';
|
|
17
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
18
|
+
import assert from 'assert';
|
|
19
|
+
import bs58 from 'bs58';
|
|
20
|
+
import { IWallet } from '../types';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TIMEOUT = 35000;
|
|
23
|
+
|
|
24
|
+
export abstract class BaseTxSender implements TxSender {
|
|
25
|
+
connection: Connection;
|
|
26
|
+
wallet: IWallet;
|
|
27
|
+
opts: ConfirmOptions;
|
|
28
|
+
timeout: number;
|
|
29
|
+
additionalConnections: Connection[];
|
|
30
|
+
timeoutCount = 0;
|
|
31
|
+
|
|
32
|
+
public constructor({
|
|
33
|
+
connection,
|
|
34
|
+
wallet,
|
|
35
|
+
opts = AnchorProvider.defaultOptions(),
|
|
36
|
+
timeout = DEFAULT_TIMEOUT,
|
|
37
|
+
additionalConnections = new Array<Connection>(),
|
|
38
|
+
}: {
|
|
39
|
+
connection: Connection;
|
|
40
|
+
wallet: IWallet;
|
|
41
|
+
opts?: ConfirmOptions;
|
|
42
|
+
timeout?: number;
|
|
43
|
+
additionalConnections?;
|
|
44
|
+
}) {
|
|
45
|
+
this.connection = connection;
|
|
46
|
+
this.wallet = wallet;
|
|
47
|
+
this.opts = opts;
|
|
48
|
+
this.timeout = timeout;
|
|
49
|
+
this.additionalConnections = additionalConnections;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async send(
|
|
53
|
+
tx: Transaction,
|
|
54
|
+
additionalSigners?: Array<Signer>,
|
|
55
|
+
opts?: ConfirmOptions,
|
|
56
|
+
preSigned?: boolean
|
|
57
|
+
): Promise<TxSigAndSlot> {
|
|
58
|
+
if (additionalSigners === undefined) {
|
|
59
|
+
additionalSigners = [];
|
|
60
|
+
}
|
|
61
|
+
if (opts === undefined) {
|
|
62
|
+
opts = this.opts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const signedTx = preSigned
|
|
66
|
+
? tx
|
|
67
|
+
: await this.prepareTx(tx, additionalSigners, opts);
|
|
68
|
+
|
|
69
|
+
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async prepareTx(
|
|
73
|
+
tx: Transaction,
|
|
74
|
+
additionalSigners: Array<Signer>,
|
|
75
|
+
opts: ConfirmOptions
|
|
76
|
+
): Promise<Transaction> {
|
|
77
|
+
tx.feePayer = this.wallet.publicKey;
|
|
78
|
+
tx.recentBlockhash = (
|
|
79
|
+
await this.connection.getLatestBlockhash(opts.preflightCommitment)
|
|
80
|
+
).blockhash;
|
|
81
|
+
|
|
82
|
+
additionalSigners
|
|
83
|
+
.filter((s): s is Signer => s !== undefined)
|
|
84
|
+
.forEach((kp) => {
|
|
85
|
+
tx.partialSign(kp);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const signedTx = await this.wallet.signTransaction(tx);
|
|
89
|
+
|
|
90
|
+
return signedTx;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getVersionedTransaction(
|
|
94
|
+
ixs: TransactionInstruction[],
|
|
95
|
+
lookupTableAccounts: AddressLookupTableAccount[],
|
|
96
|
+
additionalSigners?: Array<Signer>,
|
|
97
|
+
opts?: ConfirmOptions
|
|
98
|
+
): Promise<VersionedTransaction> {
|
|
99
|
+
if (additionalSigners === undefined) {
|
|
100
|
+
additionalSigners = [];
|
|
101
|
+
}
|
|
102
|
+
if (opts === undefined) {
|
|
103
|
+
opts = this.opts;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const message = new TransactionMessage({
|
|
107
|
+
payerKey: this.wallet.publicKey,
|
|
108
|
+
recentBlockhash: (
|
|
109
|
+
await this.connection.getLatestBlockhash(opts.preflightCommitment)
|
|
110
|
+
).blockhash,
|
|
111
|
+
instructions: ixs,
|
|
112
|
+
}).compileToV0Message(lookupTableAccounts);
|
|
113
|
+
|
|
114
|
+
const tx = new VersionedTransaction(message);
|
|
115
|
+
|
|
116
|
+
return tx;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async sendVersionedTransaction(
|
|
120
|
+
tx: VersionedTransaction,
|
|
121
|
+
additionalSigners?: Array<Signer>,
|
|
122
|
+
opts?: ConfirmOptions,
|
|
123
|
+
preSigned?: boolean
|
|
124
|
+
): Promise<TxSigAndSlot> {
|
|
125
|
+
let signedTx;
|
|
126
|
+
if (preSigned) {
|
|
127
|
+
signedTx = tx;
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
} else if (this.wallet.payer) {
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
tx.sign((additionalSigners ?? []).concat(this.wallet.payer));
|
|
132
|
+
signedTx = tx;
|
|
133
|
+
} else {
|
|
134
|
+
additionalSigners
|
|
135
|
+
?.filter((s): s is Signer => s !== undefined)
|
|
136
|
+
.forEach((kp) => {
|
|
137
|
+
tx.sign([kp]);
|
|
138
|
+
});
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
signedTx = await this.wallet.signTransaction(tx);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (opts === undefined) {
|
|
144
|
+
opts = this.opts;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async sendRawTransaction(
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
152
|
+
rawTransaction: Buffer | Uint8Array,
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
154
|
+
opts: ConfirmOptions
|
|
155
|
+
): Promise<TxSigAndSlot> {
|
|
156
|
+
throw new Error('Must be implemented by subclass');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async confirmTransaction(
|
|
160
|
+
signature: TransactionSignature,
|
|
161
|
+
commitment?: Commitment
|
|
162
|
+
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
163
|
+
let decodedSignature;
|
|
164
|
+
try {
|
|
165
|
+
decodedSignature = bs58.decode(signature);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
throw new Error('signature must be base58 encoded: ' + signature);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
171
|
+
|
|
172
|
+
const start = Date.now();
|
|
173
|
+
const subscriptionCommitment = commitment || this.opts.commitment;
|
|
174
|
+
|
|
175
|
+
const subscriptionIds = new Array<number>();
|
|
176
|
+
const connections = [this.connection, ...this.additionalConnections];
|
|
177
|
+
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
178
|
+
const promises = connections.map((connection, i) => {
|
|
179
|
+
let subscriptionId;
|
|
180
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
181
|
+
try {
|
|
182
|
+
subscriptionId = connection.onSignature(
|
|
183
|
+
signature,
|
|
184
|
+
(result: SignatureResult, context: Context) => {
|
|
185
|
+
subscriptionIds[i] = undefined;
|
|
186
|
+
response = {
|
|
187
|
+
context,
|
|
188
|
+
value: result,
|
|
189
|
+
};
|
|
190
|
+
resolve(null);
|
|
191
|
+
},
|
|
192
|
+
subscriptionCommitment
|
|
193
|
+
);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
reject(err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
subscriptionIds.push(subscriptionId);
|
|
199
|
+
return confirmPromise;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await this.promiseTimeout(promises, this.timeout);
|
|
204
|
+
} finally {
|
|
205
|
+
for (const [i, subscriptionId] of subscriptionIds.entries()) {
|
|
206
|
+
if (subscriptionId) {
|
|
207
|
+
connections[i].removeSignatureListener(subscriptionId);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (response === null) {
|
|
213
|
+
this.timeoutCount += 1;
|
|
214
|
+
const duration = (Date.now() - start) / 1000;
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Transaction was not confirmed in ${duration.toFixed(
|
|
217
|
+
2
|
|
218
|
+
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return response;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getTimestamp(): number {
|
|
226
|
+
return new Date().getTime();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
promiseTimeout<T>(
|
|
230
|
+
promises: Promise<T>[],
|
|
231
|
+
timeoutMs: number
|
|
232
|
+
): Promise<T | null> {
|
|
233
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
234
|
+
const timeoutPromise: Promise<null> = new Promise((resolve) => {
|
|
235
|
+
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return Promise.race([...promises, timeoutPromise]).then(
|
|
239
|
+
(result: T | null) => {
|
|
240
|
+
clearTimeout(timeoutId);
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
sendToAdditionalConnections(
|
|
247
|
+
rawTx: Buffer | Uint8Array,
|
|
248
|
+
opts: ConfirmOptions
|
|
249
|
+
): void {
|
|
250
|
+
this.additionalConnections.map((connection) => {
|
|
251
|
+
connection.sendRawTransaction(rawTx, opts).catch((e) => {
|
|
252
|
+
console.error(
|
|
253
|
+
// @ts-ignore
|
|
254
|
+
`error sending tx to additional connection ${connection._rpcEndpoint}`
|
|
255
|
+
);
|
|
256
|
+
console.error(e);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public addAdditionalConnection(newConnection: Connection): void {
|
|
262
|
+
const alreadyUsingConnection =
|
|
263
|
+
this.additionalConnections.filter((connection) => {
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
return connection._rpcEndpoint === newConnection.rpcEndpoint;
|
|
266
|
+
}).length > 0;
|
|
267
|
+
|
|
268
|
+
if (!alreadyUsingConnection) {
|
|
269
|
+
this.additionalConnections.push(newConnection);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
public getTimeoutCount(): number {
|
|
274
|
+
return this.timeoutCount;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { TxSigAndSlot } from './types';
|
|
2
|
+
import {
|
|
3
|
+
ConfirmOptions,
|
|
4
|
+
Signer,
|
|
5
|
+
Transaction,
|
|
6
|
+
TransactionSignature,
|
|
7
|
+
Connection,
|
|
8
|
+
VersionedTransaction,
|
|
9
|
+
TransactionMessage,
|
|
10
|
+
TransactionInstruction,
|
|
11
|
+
AddressLookupTableAccount,
|
|
12
|
+
} from '@solana/web3.js';
|
|
13
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
14
|
+
import { IWallet } from '../types';
|
|
15
|
+
import { BaseTxSender } from './baseTxSender';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_TIMEOUT = 35000;
|
|
18
|
+
const DEFAULT_BLOCKHASH_REFRESH = 10000;
|
|
19
|
+
|
|
20
|
+
export class FastSingleTxSender extends BaseTxSender {
|
|
21
|
+
connection: Connection;
|
|
22
|
+
wallet: IWallet;
|
|
23
|
+
opts: ConfirmOptions;
|
|
24
|
+
timeout: number;
|
|
25
|
+
blockhashRefreshInterval: number;
|
|
26
|
+
additionalConnections: Connection[];
|
|
27
|
+
timoutCount = 0;
|
|
28
|
+
recentBlockhash: string;
|
|
29
|
+
|
|
30
|
+
public constructor({
|
|
31
|
+
connection,
|
|
32
|
+
wallet,
|
|
33
|
+
opts = AnchorProvider.defaultOptions(),
|
|
34
|
+
timeout = DEFAULT_TIMEOUT,
|
|
35
|
+
blockhashRefreshInterval = DEFAULT_BLOCKHASH_REFRESH,
|
|
36
|
+
additionalConnections = new Array<Connection>(),
|
|
37
|
+
}: {
|
|
38
|
+
connection: Connection;
|
|
39
|
+
wallet: IWallet;
|
|
40
|
+
opts?: ConfirmOptions;
|
|
41
|
+
timeout?: number;
|
|
42
|
+
blockhashRefreshInterval?: number;
|
|
43
|
+
additionalConnections?;
|
|
44
|
+
}) {
|
|
45
|
+
super({ connection, wallet, opts, timeout, additionalConnections });
|
|
46
|
+
this.connection = connection;
|
|
47
|
+
this.wallet = wallet;
|
|
48
|
+
this.opts = opts;
|
|
49
|
+
this.timeout = timeout;
|
|
50
|
+
this.blockhashRefreshInterval = blockhashRefreshInterval;
|
|
51
|
+
this.additionalConnections = additionalConnections;
|
|
52
|
+
this.startBlockhashRefreshLoop();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
startBlockhashRefreshLoop(): void {
|
|
56
|
+
setInterval(async () => {
|
|
57
|
+
this.recentBlockhash = (
|
|
58
|
+
await this.connection.getLatestBlockhash(this.opts)
|
|
59
|
+
).blockhash;
|
|
60
|
+
}, this.blockhashRefreshInterval);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async prepareTx(
|
|
64
|
+
tx: Transaction,
|
|
65
|
+
additionalSigners: Array<Signer>,
|
|
66
|
+
opts: ConfirmOptions
|
|
67
|
+
): Promise<Transaction> {
|
|
68
|
+
tx.feePayer = this.wallet.publicKey;
|
|
69
|
+
|
|
70
|
+
tx.recentBlockhash =
|
|
71
|
+
this.recentBlockhash ??
|
|
72
|
+
(await this.connection.getLatestBlockhash(opts.preflightCommitment))
|
|
73
|
+
.blockhash;
|
|
74
|
+
|
|
75
|
+
additionalSigners
|
|
76
|
+
.filter((s): s is Signer => s !== undefined)
|
|
77
|
+
.forEach((kp) => {
|
|
78
|
+
tx.partialSign(kp);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const signedTx = await this.wallet.signTransaction(tx);
|
|
82
|
+
|
|
83
|
+
return signedTx;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getVersionedTransaction(
|
|
87
|
+
ixs: TransactionInstruction[],
|
|
88
|
+
lookupTableAccounts: AddressLookupTableAccount[],
|
|
89
|
+
additionalSigners?: Array<Signer>,
|
|
90
|
+
opts?: ConfirmOptions
|
|
91
|
+
): Promise<VersionedTransaction> {
|
|
92
|
+
if (additionalSigners === undefined) {
|
|
93
|
+
additionalSigners = [];
|
|
94
|
+
}
|
|
95
|
+
if (opts === undefined) {
|
|
96
|
+
opts = this.opts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const message = new TransactionMessage({
|
|
100
|
+
payerKey: this.wallet.publicKey,
|
|
101
|
+
recentBlockhash:
|
|
102
|
+
this.recentBlockhash ??
|
|
103
|
+
(await this.connection.getLatestBlockhash(opts.preflightCommitment))
|
|
104
|
+
.blockhash,
|
|
105
|
+
instructions: ixs,
|
|
106
|
+
}).compileToV0Message(lookupTableAccounts);
|
|
107
|
+
|
|
108
|
+
const tx = new VersionedTransaction(message);
|
|
109
|
+
|
|
110
|
+
return tx;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async sendRawTransaction(
|
|
114
|
+
rawTransaction: Buffer | Uint8Array,
|
|
115
|
+
opts: ConfirmOptions
|
|
116
|
+
): Promise<TxSigAndSlot> {
|
|
117
|
+
let txid: TransactionSignature;
|
|
118
|
+
try {
|
|
119
|
+
txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
120
|
+
this.sendToAdditionalConnections(rawTransaction, opts);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(e);
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.connection.sendRawTransaction(rawTransaction, opts).catch((e) => {
|
|
127
|
+
console.error(e);
|
|
128
|
+
});
|
|
129
|
+
this.sendToAdditionalConnections(rawTransaction, opts);
|
|
130
|
+
|
|
131
|
+
let slot: number;
|
|
132
|
+
try {
|
|
133
|
+
const result = await this.confirmTransaction(txid, opts.commitment);
|
|
134
|
+
slot = result.context.slot;
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.error(e);
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { txSig: txid, slot };
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/tx/retryTxSender.ts
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TxSigAndSlot } from './types';
|
|
2
2
|
import {
|
|
3
|
-
Commitment,
|
|
4
3
|
ConfirmOptions,
|
|
5
|
-
Context,
|
|
6
|
-
RpcResponseAndContext,
|
|
7
|
-
Signer,
|
|
8
|
-
SignatureResult,
|
|
9
|
-
Transaction,
|
|
10
4
|
TransactionSignature,
|
|
11
5
|
Connection,
|
|
12
|
-
VersionedTransaction,
|
|
13
|
-
TransactionMessage,
|
|
14
|
-
TransactionInstruction,
|
|
15
|
-
AddressLookupTableAccount,
|
|
16
6
|
} from '@solana/web3.js';
|
|
17
7
|
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
18
|
-
import assert from 'assert';
|
|
19
|
-
import bs58 from 'bs58';
|
|
20
8
|
import { IWallet } from '../types';
|
|
9
|
+
import { BaseTxSender } from './baseTxSender';
|
|
21
10
|
|
|
22
11
|
const DEFAULT_TIMEOUT = 35000;
|
|
23
12
|
const DEFAULT_RETRY = 8000;
|
|
@@ -26,7 +15,7 @@ type ResolveReference = {
|
|
|
26
15
|
resolve?: () => void;
|
|
27
16
|
};
|
|
28
17
|
|
|
29
|
-
export class RetryTxSender
|
|
18
|
+
export class RetryTxSender extends BaseTxSender {
|
|
30
19
|
connection: Connection;
|
|
31
20
|
wallet: IWallet;
|
|
32
21
|
opts: ConfirmOptions;
|
|
@@ -50,6 +39,7 @@ export class RetryTxSender implements TxSender {
|
|
|
50
39
|
retrySleep?: number;
|
|
51
40
|
additionalConnections?;
|
|
52
41
|
}) {
|
|
42
|
+
super({ connection, wallet, opts, timeout, additionalConnections });
|
|
53
43
|
this.connection = connection;
|
|
54
44
|
this.wallet = wallet;
|
|
55
45
|
this.opts = opts;
|
|
@@ -58,102 +48,11 @@ export class RetryTxSender implements TxSender {
|
|
|
58
48
|
this.additionalConnections = additionalConnections;
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
async
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
): Promise<TxSigAndSlot> {
|
|
67
|
-
if (additionalSigners === undefined) {
|
|
68
|
-
additionalSigners = [];
|
|
69
|
-
}
|
|
70
|
-
if (opts === undefined) {
|
|
71
|
-
opts = this.opts;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const signedTx = preSigned
|
|
75
|
-
? tx
|
|
76
|
-
: await this.prepareTx(tx, additionalSigners, opts);
|
|
77
|
-
|
|
78
|
-
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async prepareTx(
|
|
82
|
-
tx: Transaction,
|
|
83
|
-
additionalSigners: Array<Signer>,
|
|
84
|
-
opts: ConfirmOptions
|
|
85
|
-
): Promise<Transaction> {
|
|
86
|
-
tx.feePayer = this.wallet.publicKey;
|
|
87
|
-
tx.recentBlockhash = (
|
|
88
|
-
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
|
89
|
-
).blockhash;
|
|
90
|
-
|
|
91
|
-
additionalSigners
|
|
92
|
-
.filter((s): s is Signer => s !== undefined)
|
|
93
|
-
.forEach((kp) => {
|
|
94
|
-
tx.partialSign(kp);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const signedTx = await this.wallet.signTransaction(tx);
|
|
98
|
-
|
|
99
|
-
return signedTx;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async getVersionedTransaction(
|
|
103
|
-
ixs: TransactionInstruction[],
|
|
104
|
-
lookupTableAccounts: AddressLookupTableAccount[],
|
|
105
|
-
additionalSigners?: Array<Signer>,
|
|
106
|
-
opts?: ConfirmOptions
|
|
107
|
-
): Promise<VersionedTransaction> {
|
|
108
|
-
if (additionalSigners === undefined) {
|
|
109
|
-
additionalSigners = [];
|
|
110
|
-
}
|
|
111
|
-
if (opts === undefined) {
|
|
112
|
-
opts = this.opts;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const message = new TransactionMessage({
|
|
116
|
-
payerKey: this.wallet.publicKey,
|
|
117
|
-
recentBlockhash: (
|
|
118
|
-
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
|
119
|
-
).blockhash,
|
|
120
|
-
instructions: ixs,
|
|
121
|
-
}).compileToV0Message(lookupTableAccounts);
|
|
122
|
-
|
|
123
|
-
const tx = new VersionedTransaction(message);
|
|
124
|
-
|
|
125
|
-
return tx;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async sendVersionedTransaction(
|
|
129
|
-
tx: VersionedTransaction,
|
|
130
|
-
additionalSigners?: Array<Signer>,
|
|
131
|
-
opts?: ConfirmOptions,
|
|
132
|
-
preSigned?: boolean
|
|
133
|
-
): Promise<TxSigAndSlot> {
|
|
134
|
-
let signedTx;
|
|
135
|
-
if (preSigned) {
|
|
136
|
-
signedTx = tx;
|
|
137
|
-
// @ts-ignore
|
|
138
|
-
} else if (this.wallet.payer) {
|
|
139
|
-
// @ts-ignore
|
|
140
|
-
tx.sign((additionalSigners ?? []).concat(this.wallet.payer));
|
|
141
|
-
signedTx = tx;
|
|
142
|
-
} else {
|
|
143
|
-
additionalSigners
|
|
144
|
-
?.filter((s): s is Signer => s !== undefined)
|
|
145
|
-
.forEach((kp) => {
|
|
146
|
-
tx.sign([kp]);
|
|
147
|
-
});
|
|
148
|
-
// @ts-ignore
|
|
149
|
-
signedTx = await this.wallet.signTransaction(tx);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (opts === undefined) {
|
|
153
|
-
opts = this.opts;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
51
|
+
async sleep(reference: ResolveReference): Promise<void> {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
reference.resolve = resolve;
|
|
54
|
+
setTimeout(resolve, this.retrySleep);
|
|
55
|
+
});
|
|
157
56
|
}
|
|
158
57
|
|
|
159
58
|
async sendRawTransaction(
|
|
@@ -210,129 +109,4 @@ export class RetryTxSender implements TxSender {
|
|
|
210
109
|
|
|
211
110
|
return { txSig: txid, slot };
|
|
212
111
|
}
|
|
213
|
-
|
|
214
|
-
async confirmTransaction(
|
|
215
|
-
signature: TransactionSignature,
|
|
216
|
-
commitment?: Commitment
|
|
217
|
-
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
218
|
-
let decodedSignature;
|
|
219
|
-
try {
|
|
220
|
-
decodedSignature = bs58.decode(signature);
|
|
221
|
-
} catch (err) {
|
|
222
|
-
throw new Error('signature must be base58 encoded: ' + signature);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
226
|
-
|
|
227
|
-
const start = Date.now();
|
|
228
|
-
const subscriptionCommitment = commitment || this.opts.commitment;
|
|
229
|
-
|
|
230
|
-
const subscriptionIds = new Array<number>();
|
|
231
|
-
const connections = [this.connection, ...this.additionalConnections];
|
|
232
|
-
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
233
|
-
const promises = connections.map((connection, i) => {
|
|
234
|
-
let subscriptionId;
|
|
235
|
-
const confirmPromise = new Promise((resolve, reject) => {
|
|
236
|
-
try {
|
|
237
|
-
subscriptionId = connection.onSignature(
|
|
238
|
-
signature,
|
|
239
|
-
(result: SignatureResult, context: Context) => {
|
|
240
|
-
subscriptionIds[i] = undefined;
|
|
241
|
-
response = {
|
|
242
|
-
context,
|
|
243
|
-
value: result,
|
|
244
|
-
};
|
|
245
|
-
resolve(null);
|
|
246
|
-
},
|
|
247
|
-
subscriptionCommitment
|
|
248
|
-
);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
reject(err);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
subscriptionIds.push(subscriptionId);
|
|
254
|
-
return confirmPromise;
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
await this.promiseTimeout(promises, this.timeout);
|
|
259
|
-
} finally {
|
|
260
|
-
for (const [i, subscriptionId] of subscriptionIds.entries()) {
|
|
261
|
-
if (subscriptionId) {
|
|
262
|
-
connections[i].removeSignatureListener(subscriptionId);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (response === null) {
|
|
268
|
-
this.timoutCount += 1;
|
|
269
|
-
const duration = (Date.now() - start) / 1000;
|
|
270
|
-
throw new Error(
|
|
271
|
-
`Transaction was not confirmed in ${duration.toFixed(
|
|
272
|
-
2
|
|
273
|
-
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return response;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
getTimestamp(): number {
|
|
281
|
-
return new Date().getTime();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async sleep(reference: ResolveReference): Promise<void> {
|
|
285
|
-
return new Promise((resolve) => {
|
|
286
|
-
reference.resolve = resolve;
|
|
287
|
-
setTimeout(resolve, this.retrySleep);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
promiseTimeout<T>(
|
|
292
|
-
promises: Promise<T>[],
|
|
293
|
-
timeoutMs: number
|
|
294
|
-
): Promise<T | null> {
|
|
295
|
-
let timeoutId: ReturnType<typeof setTimeout>;
|
|
296
|
-
const timeoutPromise: Promise<null> = new Promise((resolve) => {
|
|
297
|
-
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
return Promise.race([...promises, timeoutPromise]).then(
|
|
301
|
-
(result: T | null) => {
|
|
302
|
-
clearTimeout(timeoutId);
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
sendToAdditionalConnections(
|
|
309
|
-
rawTx: Buffer | Uint8Array,
|
|
310
|
-
opts: ConfirmOptions
|
|
311
|
-
): void {
|
|
312
|
-
this.additionalConnections.map((connection) => {
|
|
313
|
-
connection.sendRawTransaction(rawTx, opts).catch((e) => {
|
|
314
|
-
console.error(
|
|
315
|
-
// @ts-ignore
|
|
316
|
-
`error sending tx to additional connection ${connection._rpcEndpoint}`
|
|
317
|
-
);
|
|
318
|
-
console.error(e);
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
public addAdditionalConnection(newConnection: Connection): void {
|
|
324
|
-
const alreadyUsingConnection =
|
|
325
|
-
this.additionalConnections.filter((connection) => {
|
|
326
|
-
// @ts-ignore
|
|
327
|
-
return connection._rpcEndpoint === newConnection.rpcEndpoint;
|
|
328
|
-
}).length > 0;
|
|
329
|
-
|
|
330
|
-
if (!alreadyUsingConnection) {
|
|
331
|
-
this.additionalConnections.push(newConnection);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public getTimeoutCount(): number {
|
|
336
|
-
return this.timoutCount;
|
|
337
|
-
}
|
|
338
112
|
}
|