@drift-labs/sdk 2.96.0-beta.25 → 2.96.0-beta.26
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/config.d.ts +1 -0
- package/lib/config.js +2 -1
- package/lib/constants/perpMarkets.js +1 -1
- package/lib/constants/txConstants.d.ts +1 -0
- package/lib/constants/txConstants.js +4 -0
- package/lib/driftClient.d.ts +1 -0
- package/lib/driftClient.js +26 -25
- package/lib/driftClientConfig.d.ts +1 -0
- package/lib/tx/baseTxSender.d.ts +0 -1
- package/lib/tx/baseTxSender.js +6 -24
- package/lib/tx/reportTransactionError.d.ts +20 -0
- package/lib/tx/reportTransactionError.js +103 -0
- package/lib/tx/whileValidTxSender.d.ts +3 -2
- package/lib/tx/whileValidTxSender.js +3 -3
- package/lib/types.d.ts +1 -0
- package/lib/util/TransactionConfirmationManager.d.ts +4 -2
- package/lib/util/TransactionConfirmationManager.js +83 -5
- package/package.json +1 -1
- package/src/config.ts +2 -0
- package/src/constants/perpMarkets.ts +1 -1
- package/src/constants/txConstants.ts +1 -0
- package/src/driftClient.ts +3 -1
- package/src/driftClientConfig.ts +1 -0
- package/src/tx/baseTxSender.ts +8 -30
- package/src/tx/reportTransactionError.ts +159 -0
- package/src/tx/whileValidTxSender.ts +4 -10
- package/src/types.ts +1 -0
- package/src/util/TransactionConfirmationManager.ts +143 -6
- package/tests/tx/TransactionConfirmationManager.test.ts +35 -16
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.96.0-beta.
|
|
1
|
+
2.96.0-beta.26
|
package/lib/config.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type DriftEnv = 'devnet' | 'mainnet-beta';
|
|
|
26
26
|
export declare const DRIFT_PROGRAM_ID = "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH";
|
|
27
27
|
export declare const DRIFT_ORACLE_RECEIVER_ID = "G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha";
|
|
28
28
|
export declare const SWIFT_ID = "SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS";
|
|
29
|
+
export declare const ANCHOR_TEST_SWIFT_ID = "DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv";
|
|
29
30
|
export declare const DEFAULT_CONFIRMATION_OPTS: ConfirmOptions;
|
|
30
31
|
export declare const configs: {
|
|
31
32
|
[key in DriftEnv]: DriftConfig;
|
package/lib/config.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findAllMarketAndOracles = exports.getMarketsAndOraclesForSubscription = exports.initialize = exports.getConfig = exports.configs = exports.DEFAULT_CONFIRMATION_OPTS = exports.SWIFT_ID = exports.DRIFT_ORACLE_RECEIVER_ID = exports.DRIFT_PROGRAM_ID = void 0;
|
|
3
|
+
exports.findAllMarketAndOracles = exports.getMarketsAndOraclesForSubscription = exports.initialize = exports.getConfig = exports.configs = exports.DEFAULT_CONFIRMATION_OPTS = exports.ANCHOR_TEST_SWIFT_ID = exports.SWIFT_ID = exports.DRIFT_ORACLE_RECEIVER_ID = exports.DRIFT_PROGRAM_ID = void 0;
|
|
4
4
|
const perpMarkets_1 = require("./constants/perpMarkets");
|
|
5
5
|
const spotMarkets_1 = require("./constants/spotMarkets");
|
|
6
6
|
const on_demand_1 = require("@switchboard-xyz/on-demand");
|
|
7
7
|
exports.DRIFT_PROGRAM_ID = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
|
|
8
8
|
exports.DRIFT_ORACLE_RECEIVER_ID = 'G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha';
|
|
9
9
|
exports.SWIFT_ID = 'SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS';
|
|
10
|
+
exports.ANCHOR_TEST_SWIFT_ID = 'DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv';
|
|
10
11
|
exports.DEFAULT_CONFIRMATION_OPTS = {
|
|
11
12
|
preflightCommitment: 'confirmed',
|
|
12
13
|
commitment: 'confirmed',
|
|
@@ -263,7 +263,7 @@ exports.DevnetPerpMarkets = [
|
|
|
263
263
|
symbol: 'W-PERP',
|
|
264
264
|
baseAssetSymbol: 'W',
|
|
265
265
|
marketIndex: 23,
|
|
266
|
-
oracle: new web3_js_1.PublicKey('
|
|
266
|
+
oracle: new web3_js_1.PublicKey('J9nrFWjDUeDVZ4BhhxsbQXWgLcLEgQyNBrCbwSADmJdr'),
|
|
267
267
|
launchTs: 1709852537000,
|
|
268
268
|
oracleSource: __1.OracleSource.SWITCHBOARD_ON_DEMAND,
|
|
269
269
|
pythFeedId: '0xeff7446475e218517566ea99e72a4abec2e1bd8498b43b7d8331e29dcb059389',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const NOT_CONFIRMED_ERROR_CODE = -1001;
|
package/lib/driftClient.d.ts
CHANGED
package/lib/driftClient.js
CHANGED
|
@@ -80,7 +80,7 @@ class DriftClient {
|
|
|
80
80
|
this._isSubscribed = val;
|
|
81
81
|
}
|
|
82
82
|
constructor(config) {
|
|
83
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
|
|
83
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8;
|
|
84
84
|
this.users = new Map();
|
|
85
85
|
this._isSubscribed = false;
|
|
86
86
|
this.perpMarketLastSlotCache = new Map();
|
|
@@ -98,16 +98,17 @@ class DriftClient {
|
|
|
98
98
|
// @ts-ignore
|
|
99
99
|
config.wallet, this.opts);
|
|
100
100
|
this.program = new anchor_1.Program(drift_json_1.default, (_c = config.programID) !== null && _c !== void 0 ? _c : new web3_js_1.PublicKey(config_1.DRIFT_PROGRAM_ID), this.provider);
|
|
101
|
-
this.
|
|
102
|
-
this.
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
101
|
+
this.swiftID = (_d = config.swiftID) !== null && _d !== void 0 ? _d : new web3_js_1.PublicKey(config_1.SWIFT_ID);
|
|
102
|
+
this.authority = (_e = config.authority) !== null && _e !== void 0 ? _e : this.wallet.publicKey;
|
|
103
|
+
this.activeSubAccountId = (_f = config.activeSubAccountId) !== null && _f !== void 0 ? _f : 0;
|
|
104
|
+
this.skipLoadUsers = (_g = config.skipLoadUsers) !== null && _g !== void 0 ? _g : false;
|
|
105
|
+
this.txVersion = (_h = config.txVersion) !== null && _h !== void 0 ? _h : 'legacy';
|
|
105
106
|
this.txParams = {
|
|
106
|
-
computeUnits: (
|
|
107
|
-
computeUnitsPrice: (
|
|
107
|
+
computeUnits: (_k = (_j = config.txParams) === null || _j === void 0 ? void 0 : _j.computeUnits) !== null && _k !== void 0 ? _k : 600000,
|
|
108
|
+
computeUnitsPrice: (_m = (_l = config.txParams) === null || _l === void 0 ? void 0 : _l.computeUnitsPrice) !== null && _m !== void 0 ? _m : 0,
|
|
108
109
|
};
|
|
109
110
|
this.txHandler =
|
|
110
|
-
(
|
|
111
|
+
(_o = config === null || config === void 0 ? void 0 : config.txHandler) !== null && _o !== void 0 ? _o : new txHandler_1.TxHandler({
|
|
111
112
|
connection: this.connection,
|
|
112
113
|
// @ts-ignore
|
|
113
114
|
wallet: this.provider.wallet,
|
|
@@ -133,8 +134,8 @@ class DriftClient {
|
|
|
133
134
|
: config.subAccountIds
|
|
134
135
|
? new Map([[this.authority.toString(), config.subAccountIds]])
|
|
135
136
|
: new Map();
|
|
136
|
-
this.includeDelegates = (
|
|
137
|
-
if (((
|
|
137
|
+
this.includeDelegates = (_p = config.includeDelegates) !== null && _p !== void 0 ? _p : false;
|
|
138
|
+
if (((_q = config.accountSubscription) === null || _q === void 0 ? void 0 : _q.type) === 'polling') {
|
|
138
139
|
this.userAccountSubscriptionConfig = {
|
|
139
140
|
type: 'polling',
|
|
140
141
|
accountLoader: config.accountSubscription.accountLoader,
|
|
@@ -147,15 +148,15 @@ class DriftClient {
|
|
|
147
148
|
else {
|
|
148
149
|
this.userAccountSubscriptionConfig = {
|
|
149
150
|
type: 'websocket',
|
|
150
|
-
resubTimeoutMs: (
|
|
151
|
-
logResubMessages: (
|
|
152
|
-
commitment: (
|
|
151
|
+
resubTimeoutMs: (_r = config.accountSubscription) === null || _r === void 0 ? void 0 : _r.resubTimeoutMs,
|
|
152
|
+
logResubMessages: (_s = config.accountSubscription) === null || _s === void 0 ? void 0 : _s.logResubMessages,
|
|
153
|
+
commitment: (_t = config.accountSubscription) === null || _t === void 0 ? void 0 : _t.commitment,
|
|
153
154
|
};
|
|
154
155
|
this.userStatsAccountSubscriptionConfig = {
|
|
155
156
|
type: 'websocket',
|
|
156
|
-
resubTimeoutMs: (
|
|
157
|
-
logResubMessages: (
|
|
158
|
-
commitment: (
|
|
157
|
+
resubTimeoutMs: (_u = config.accountSubscription) === null || _u === void 0 ? void 0 : _u.resubTimeoutMs,
|
|
158
|
+
logResubMessages: (_v = config.accountSubscription) === null || _v === void 0 ? void 0 : _v.logResubMessages,
|
|
159
|
+
commitment: (_w = config.accountSubscription) === null || _w === void 0 ? void 0 : _w.commitment,
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
162
|
if (config.userStats) {
|
|
@@ -173,14 +174,14 @@ class DriftClient {
|
|
|
173
174
|
const noMarketsAndOraclesSpecified = config.perpMarketIndexes === undefined &&
|
|
174
175
|
config.spotMarketIndexes === undefined &&
|
|
175
176
|
config.oracleInfos === undefined;
|
|
176
|
-
if (((
|
|
177
|
-
this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (
|
|
177
|
+
if (((_x = config.accountSubscription) === null || _x === void 0 ? void 0 : _x.type) === 'polling') {
|
|
178
|
+
this.accountSubscriber = new pollingDriftClientAccountSubscriber_1.PollingDriftClientAccountSubscriber(this.program, config.accountSubscription.accountLoader, (_y = config.perpMarketIndexes) !== null && _y !== void 0 ? _y : [], (_z = config.spotMarketIndexes) !== null && _z !== void 0 ? _z : [], (_0 = config.oracleInfos) !== null && _0 !== void 0 ? _0 : [], noMarketsAndOraclesSpecified, delistedMarketSetting);
|
|
178
179
|
}
|
|
179
180
|
else {
|
|
180
|
-
this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (
|
|
181
|
-
resubTimeoutMs: (
|
|
182
|
-
logResubMessages: (
|
|
183
|
-
}, (
|
|
181
|
+
this.accountSubscriber = new webSocketDriftClientAccountSubscriber_1.WebSocketDriftClientAccountSubscriber(this.program, (_1 = config.perpMarketIndexes) !== null && _1 !== void 0 ? _1 : [], (_2 = config.spotMarketIndexes) !== null && _2 !== void 0 ? _2 : [], (_3 = config.oracleInfos) !== null && _3 !== void 0 ? _3 : [], noMarketsAndOraclesSpecified, delistedMarketSetting, {
|
|
182
|
+
resubTimeoutMs: (_4 = config.accountSubscription) === null || _4 === void 0 ? void 0 : _4.resubTimeoutMs,
|
|
183
|
+
logResubMessages: (_5 = config.accountSubscription) === null || _5 === void 0 ? void 0 : _5.logResubMessages,
|
|
184
|
+
}, (_6 = config.accountSubscription) === null || _6 === void 0 ? void 0 : _6.commitment);
|
|
184
185
|
}
|
|
185
186
|
this.eventEmitter = this.accountSubscriber.eventEmitter;
|
|
186
187
|
this.metricsEventEmitter = new events_1.EventEmitter();
|
|
@@ -188,14 +189,14 @@ class DriftClient {
|
|
|
188
189
|
this.enableMetricsEvents = true;
|
|
189
190
|
}
|
|
190
191
|
this.txSender =
|
|
191
|
-
(
|
|
192
|
+
(_7 = config.txSender) !== null && _7 !== void 0 ? _7 : new retryTxSender_1.RetryTxSender({
|
|
192
193
|
connection: this.connection,
|
|
193
194
|
wallet: this.wallet,
|
|
194
195
|
opts: this.opts,
|
|
195
196
|
txHandler: this.txHandler,
|
|
196
197
|
});
|
|
197
198
|
this.sbOnDemandProgramdId =
|
|
198
|
-
config_1.configs[(
|
|
199
|
+
config_1.configs[(_8 = config.env) !== null && _8 !== void 0 ? _8 : 'mainnet-beta'].SB_ON_DEMAND_PID;
|
|
199
200
|
}
|
|
200
201
|
getUserMapKey(subAccountId, authority) {
|
|
201
202
|
return `${subAccountId}_${authority.toString()}`;
|
|
@@ -2956,7 +2957,7 @@ class DriftClient {
|
|
|
2956
2957
|
readablePerpMarketIndex: marketIndex,
|
|
2957
2958
|
});
|
|
2958
2959
|
const swiftServerSignatureIx = web3_js_1.Ed25519Program.createInstructionWithPublicKey({
|
|
2959
|
-
publicKey: new web3_js_1.PublicKey(
|
|
2960
|
+
publicKey: new web3_js_1.PublicKey(this.swiftID).toBytes(),
|
|
2960
2961
|
signature: Uint8Array.from(swiftSignature),
|
|
2961
2962
|
message: Uint8Array.from(encodedSwiftServerMessage),
|
|
2962
2963
|
});
|
package/lib/tx/baseTxSender.d.ts
CHANGED
|
@@ -48,7 +48,6 @@ export declare abstract class BaseTxSender implements TxSender {
|
|
|
48
48
|
addAdditionalConnection(newConnection: Connection): void;
|
|
49
49
|
getTimeoutCount(): number;
|
|
50
50
|
checkConfirmationResultForError(txSig: string, result: SignatureResult): Promise<void>;
|
|
51
|
-
reportTransactionError(txSig: string): Promise<any>;
|
|
52
51
|
getTxLandRate(): number;
|
|
53
52
|
private defaultLandRateToFeeFunc;
|
|
54
53
|
getSuggestedPriorityFeeMultiplier(): number;
|
package/lib/tx/baseTxSender.js
CHANGED
|
@@ -5,16 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BaseTxSender = void 0;
|
|
7
7
|
const types_1 = require("./types");
|
|
8
|
-
const web3_js_1 = require("@solana/web3.js");
|
|
9
8
|
const assert_1 = __importDefault(require("assert"));
|
|
10
9
|
const bs58_1 = __importDefault(require("bs58"));
|
|
11
10
|
const txHandler_1 = require("./txHandler");
|
|
12
11
|
const node_cache_1 = __importDefault(require("node-cache"));
|
|
13
12
|
const config_1 = require("../config");
|
|
13
|
+
const txConstants_1 = require("../constants/txConstants");
|
|
14
|
+
const reportTransactionError_1 = require("./reportTransactionError");
|
|
14
15
|
const BASELINE_TX_LAND_RATE = 0.9;
|
|
15
16
|
const DEFAULT_TIMEOUT = 35000;
|
|
16
17
|
const DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES = 10;
|
|
17
|
-
const NOT_CONFIRMED_ERROR_CODE = -1001;
|
|
18
18
|
class BaseTxSender {
|
|
19
19
|
constructor({ connection, wallet, opts = config_1.DEFAULT_CONFIRMATION_OPTS, timeout = DEFAULT_TIMEOUT, additionalConnections = new Array(), confirmationStrategy = types_1.ConfirmationStrategy.Combo, additionalTxSenderCallbacks, trackTxLandRate, txHandler, txLandRateLookbackWindowMinutes = DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES, landRateToFeeFunc, }) {
|
|
20
20
|
this.timeoutCount = 0;
|
|
@@ -166,7 +166,7 @@ class BaseTxSender {
|
|
|
166
166
|
}
|
|
167
167
|
this.timeoutCount += 1;
|
|
168
168
|
const duration = (Date.now() - start) / 1000;
|
|
169
|
-
throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, NOT_CONFIRMED_ERROR_CODE);
|
|
169
|
+
throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
|
|
170
170
|
}
|
|
171
171
|
return response;
|
|
172
172
|
}
|
|
@@ -192,7 +192,7 @@ class BaseTxSender {
|
|
|
192
192
|
// Transaction not confirmed within 30 seconds
|
|
193
193
|
this.timeoutCount += 1;
|
|
194
194
|
const duration = (Date.now() - start) / 1000;
|
|
195
|
-
throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, NOT_CONFIRMED_ERROR_CODE);
|
|
195
|
+
throw new types_1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
|
|
196
196
|
}
|
|
197
197
|
async confirmTransaction(signature, commitment) {
|
|
198
198
|
if (this.confirmationStrategy === types_1.ConfirmationStrategy.WebSocket ||
|
|
@@ -243,30 +243,12 @@ class BaseTxSender {
|
|
|
243
243
|
return this.timeoutCount;
|
|
244
244
|
}
|
|
245
245
|
async checkConfirmationResultForError(txSig, result) {
|
|
246
|
+
var _a;
|
|
246
247
|
if (result.err) {
|
|
247
|
-
await this.
|
|
248
|
+
await (0, reportTransactionError_1.throwTransactionError)(txSig, this.connection, (_a = this.opts) === null || _a === void 0 ? void 0 : _a.commitment);
|
|
248
249
|
}
|
|
249
250
|
return;
|
|
250
251
|
}
|
|
251
|
-
async reportTransactionError(txSig) {
|
|
252
|
-
var _a, _b;
|
|
253
|
-
const transactionResult = await this.connection.getTransaction(txSig, {
|
|
254
|
-
maxSupportedTransactionVersion: 0,
|
|
255
|
-
});
|
|
256
|
-
if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
const logs = transactionResult.meta.logMessages;
|
|
260
|
-
const lastLog = logs[logs.length - 1];
|
|
261
|
-
const friendlyMessage = (_b = lastLog === null || lastLog === void 0 ? void 0 : lastLog.match(/(failed:) (.+)/)) === null || _b === void 0 ? void 0 : _b[2];
|
|
262
|
-
// @ts-ignore
|
|
263
|
-
throw new web3_js_1.SendTransactionError({
|
|
264
|
-
action: 'send',
|
|
265
|
-
signature: txSig,
|
|
266
|
-
transactionMessage: `Transaction Failed${friendlyMessage ? `: ${friendlyMessage}` : ''}`,
|
|
267
|
-
logs,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
252
|
getTxLandRate() {
|
|
271
253
|
if (!this.trackTxLandRate) {
|
|
272
254
|
console.warn('trackTxLandRate is false, returning default land rate of 0');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Commitment, Connection, SendTransactionError, VersionedTransactionResponse } from '@solana/web3.js';
|
|
2
|
+
/**
|
|
3
|
+
* THROWS if there is an error
|
|
4
|
+
*
|
|
5
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
6
|
+
* @param txSig
|
|
7
|
+
* @param connection
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export declare const throwTransactionError: (txSig: string, connection: Connection, commitment?: Commitment) => Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* RETURNS an error if there is one
|
|
13
|
+
*
|
|
14
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
15
|
+
* @param txSig
|
|
16
|
+
* @param connection
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
export declare const getTransactionErrorFromTxSig: (txSig: string, connection: Connection, commitment?: Commitment) => Promise<SendTransactionError>;
|
|
20
|
+
export declare const getTransactionError: (transactionResult: VersionedTransactionResponse) => SendTransactionError;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTransactionError = exports.getTransactionErrorFromTxSig = exports.throwTransactionError = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const config_1 = require("../config");
|
|
6
|
+
/**
|
|
7
|
+
* The new getTransaction method expects a Finality type instead of a Commitment type. The only options for Finality are 'confirmed' and 'finalized'.
|
|
8
|
+
* @param commitment
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
const commitmentToFinality = (commitment) => {
|
|
12
|
+
switch (commitment) {
|
|
13
|
+
case 'confirmed':
|
|
14
|
+
return 'confirmed';
|
|
15
|
+
case 'finalized':
|
|
16
|
+
return 'finalized';
|
|
17
|
+
default:
|
|
18
|
+
throw new Error(`Invalid commitment when reporting transaction error. The commitment must be 'confirmed' or 'finalized' but was given '${commitment}'. If you're using this commitment for a specific reason, you may need to roll your own logic here.`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const getTransactionResult = async (txSig, connection, commitment) => {
|
|
22
|
+
const finality = commitmentToFinality(commitment || connection.commitment || config_1.DEFAULT_CONFIRMATION_OPTS.commitment);
|
|
23
|
+
return await connection.getTransaction(txSig, {
|
|
24
|
+
maxSupportedTransactionVersion: 0,
|
|
25
|
+
commitment: finality,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const getTransactionResultWithRetry = async (txSig, connection, commitment) => {
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
const retryTimeout = 3000; // Timeout after 3 seconds
|
|
31
|
+
const retryInterval = 800; // Retry with 800ms interval
|
|
32
|
+
const retryCount = 3; // Retry 3 times
|
|
33
|
+
let currentCount = 0;
|
|
34
|
+
let transactionResult = await getTransactionResult(txSig, connection, commitment);
|
|
35
|
+
// Retry 3 times or until timeout as long as we don't have a result yet
|
|
36
|
+
while (!transactionResult &&
|
|
37
|
+
Date.now() - start < retryTimeout &&
|
|
38
|
+
currentCount < retryCount) {
|
|
39
|
+
// Sleep for 1 second :: Do this first so that we don't run the first loop immediately after the initial fetch above
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, retryInterval));
|
|
41
|
+
transactionResult = await getTransactionResult(txSig, connection, commitment);
|
|
42
|
+
currentCount++;
|
|
43
|
+
}
|
|
44
|
+
return transactionResult;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* THROWS if there is an error
|
|
48
|
+
*
|
|
49
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
50
|
+
* @param txSig
|
|
51
|
+
* @param connection
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
const throwTransactionError = async (txSig, connection, commitment) => {
|
|
55
|
+
const err = await (0, exports.getTransactionErrorFromTxSig)(txSig, connection, commitment);
|
|
56
|
+
if (err) {
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
};
|
|
61
|
+
exports.throwTransactionError = throwTransactionError;
|
|
62
|
+
/**
|
|
63
|
+
* RETURNS an error if there is one
|
|
64
|
+
*
|
|
65
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
66
|
+
* @param txSig
|
|
67
|
+
* @param connection
|
|
68
|
+
* @returns
|
|
69
|
+
*/
|
|
70
|
+
const getTransactionErrorFromTxSig = async (txSig, connection, commitment) => {
|
|
71
|
+
var _a;
|
|
72
|
+
const transactionResult = await getTransactionResultWithRetry(txSig, connection, commitment);
|
|
73
|
+
if (!transactionResult) {
|
|
74
|
+
// Throw a generic error because we couldn't get the transaction result for the given txSig
|
|
75
|
+
return new web3_js_1.SendTransactionError({
|
|
76
|
+
action: 'send',
|
|
77
|
+
signature: txSig,
|
|
78
|
+
transactionMessage: `Transaction Failed`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
|
|
82
|
+
// Assume that the transaction was successful and we are here erroneously because we have a result with no error
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return (0, exports.getTransactionError)(transactionResult);
|
|
86
|
+
};
|
|
87
|
+
exports.getTransactionErrorFromTxSig = getTransactionErrorFromTxSig;
|
|
88
|
+
const getTransactionError = (transactionResult) => {
|
|
89
|
+
var _a, _b, _c, _d, _e, _f;
|
|
90
|
+
if (!((_a = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _a === void 0 ? void 0 : _a.err)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const logs = (_c = (_b = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.meta) === null || _b === void 0 ? void 0 : _b.logMessages) !== null && _c !== void 0 ? _c : ['No logs'];
|
|
94
|
+
const lastLog = logs[logs.length - 1];
|
|
95
|
+
const friendlyMessage = (_d = lastLog === null || lastLog === void 0 ? void 0 : lastLog.match(/(failed:) (.+)/)) === null || _d === void 0 ? void 0 : _d[2];
|
|
96
|
+
return new web3_js_1.SendTransactionError({
|
|
97
|
+
action: 'send',
|
|
98
|
+
signature: (_f = (_e = transactionResult === null || transactionResult === void 0 ? void 0 : transactionResult.transaction) === null || _e === void 0 ? void 0 : _e.signatures) === null || _f === void 0 ? void 0 : _f[0],
|
|
99
|
+
transactionMessage: `Transaction Failed${friendlyMessage ? `: ${friendlyMessage}` : ''}`,
|
|
100
|
+
logs,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
exports.getTransactionError = getTransactionError;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { TxSigAndSlot } from './types';
|
|
2
|
+
import { ConfirmationStrategy, TxSigAndSlot } from './types';
|
|
3
3
|
import { ConfirmOptions, Connection, Signer, Transaction, VersionedTransaction } from '@solana/web3.js';
|
|
4
4
|
import { BaseTxSender } from './baseTxSender';
|
|
5
5
|
import { TxHandler } from './txHandler';
|
|
@@ -21,13 +21,14 @@ export declare class WhileValidTxSender extends BaseTxSender {
|
|
|
21
21
|
}>;
|
|
22
22
|
useBlockHeightOffset: boolean;
|
|
23
23
|
private checkAndSetUseBlockHeightOffset;
|
|
24
|
-
constructor({ connection, wallet, opts, retrySleep, additionalConnections, additionalTxSenderCallbacks, txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }: {
|
|
24
|
+
constructor({ connection, wallet, opts, retrySleep, additionalConnections, confirmationStrategy, additionalTxSenderCallbacks, txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }: {
|
|
25
25
|
connection: Connection;
|
|
26
26
|
wallet: IWallet;
|
|
27
27
|
opts?: ConfirmOptions;
|
|
28
28
|
retrySleep?: number;
|
|
29
29
|
additionalConnections?: any;
|
|
30
30
|
additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
|
|
31
|
+
confirmationStrategy: ConfirmationStrategy;
|
|
31
32
|
txHandler?: TxHandler;
|
|
32
33
|
trackTxLandRate?: boolean;
|
|
33
34
|
txLandRateLookbackWindowMinutes?: number;
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.WhileValidTxSender = void 0;
|
|
7
|
+
const types_1 = require("./types");
|
|
7
8
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
9
|
const baseTxSender_1 = require("./baseTxSender");
|
|
9
10
|
const bs58_1 = __importDefault(require("bs58"));
|
|
@@ -29,7 +30,7 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
|
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
|
-
constructor({ connection, wallet, opts = { ...config_1.DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 }, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), additionalTxSenderCallbacks = [], txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }) {
|
|
33
|
+
constructor({ connection, wallet, opts = { ...config_1.DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 }, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), confirmationStrategy = types_1.ConfirmationStrategy.Combo, additionalTxSenderCallbacks = [], txHandler, trackTxLandRate, txLandRateLookbackWindowMinutes, landRateToFeeFunc, }) {
|
|
33
34
|
super({
|
|
34
35
|
connection,
|
|
35
36
|
wallet,
|
|
@@ -39,6 +40,7 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
|
|
|
39
40
|
txHandler,
|
|
40
41
|
trackTxLandRate,
|
|
41
42
|
txLandRateLookbackWindowMinutes,
|
|
43
|
+
confirmationStrategy,
|
|
42
44
|
landRateToFeeFunc,
|
|
43
45
|
});
|
|
44
46
|
this.timoutCount = 0;
|
|
@@ -103,13 +105,11 @@ class WhileValidTxSender extends baseTxSender_1.BaseTxSender {
|
|
|
103
105
|
}
|
|
104
106
|
const txSig = bs58_1.default.encode(signedTx.signatures[0]);
|
|
105
107
|
this.untilValid.set(txSig, latestBlockhash);
|
|
106
|
-
console.debug(`preflight_commitment`, `sending_tx_with_preflight_commitment::${opts === null || opts === void 0 ? void 0 : opts.preflightCommitment}`);
|
|
107
108
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
108
109
|
}
|
|
109
110
|
async sendRawTransaction(rawTransaction, opts) {
|
|
110
111
|
var _a, _b, _c;
|
|
111
112
|
const startTime = this.getTimestamp();
|
|
112
|
-
console.debug(`preflight_commitment`, `sending_tx_with_preflight_commitment::${opts === null || opts === void 0 ? void 0 : opts.preflightCommitment}`);
|
|
113
113
|
const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
114
114
|
(_a = this.txSigCache) === null || _a === void 0 ? void 0 : _a.set(txid, false);
|
|
115
115
|
this.sendToAdditionalConnections(rawTransaction, opts);
|
package/lib/types.d.ts
CHANGED
|
@@ -1103,6 +1103,7 @@ export type SwiftServerMessage = {
|
|
|
1103
1103
|
export type SwiftOrderParamsMessage = {
|
|
1104
1104
|
swiftOrderParams: OptionalOrderParams;
|
|
1105
1105
|
expectedOrderId: number;
|
|
1106
|
+
subAccountId: number;
|
|
1106
1107
|
takeProfitOrderParams: SwiftTriggerOrderParams | null;
|
|
1107
1108
|
stopLossOrderParams: SwiftTriggerOrderParams | null;
|
|
1108
1109
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Connection, SignatureStatus, TransactionConfirmationStatus } from '@solana/web3.js';
|
|
1
|
+
import { Connection, RpcResponseAndContext, SignatureResult, SignatureStatus, TransactionConfirmationStatus } from '@solana/web3.js';
|
|
2
2
|
/**
|
|
3
3
|
* Class to await for transaction confirmations in an optimised manner. It tracks a shared list of all pending transactions and fetches them in bulk in a shared RPC request whenever they have an "overlapping" polling interval. E.g. tx1 with an interval of 200ms and tx2 with an interval of 300ms (if sent at the same time) will be fetched together at at 600ms, 1200ms, 1800ms, etc.
|
|
4
4
|
*/
|
|
@@ -7,8 +7,10 @@ export declare class TransactionConfirmationManager {
|
|
|
7
7
|
private pendingConfirmations;
|
|
8
8
|
private intervalId;
|
|
9
9
|
constructor(connection: Connection);
|
|
10
|
-
|
|
10
|
+
confirmTransactionWebSocket(txSig: string, timeout?: number, desiredConfirmationStatus?: TransactionConfirmationStatus): Promise<RpcResponseAndContext<SignatureResult>>;
|
|
11
|
+
confirmTransactionPolling(txSig: string, desiredConfirmationStatus?: TransactionConfirmationStatus, timeout?: number, pollInterval?: number, searchTransactionHistory?: boolean): Promise<SignatureStatus>;
|
|
11
12
|
private startConfirmationLoop;
|
|
12
13
|
private checkPendingConfirmations;
|
|
14
|
+
private checkStatusMatchesDesiredConfirmationStatus;
|
|
13
15
|
private checkTransactionStatuses;
|
|
14
16
|
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TransactionConfirmationManager = void 0;
|
|
4
4
|
const config_1 = require("../config");
|
|
5
|
+
const __1 = require("..");
|
|
6
|
+
const txConstants_1 = require("../constants/txConstants");
|
|
7
|
+
const reportTransactionError_1 = require("../tx/reportTransactionError");
|
|
8
|
+
const promiseTimeout_1 = require("./promiseTimeout");
|
|
5
9
|
const confirmationStatusValues = {
|
|
6
10
|
processed: 0,
|
|
7
11
|
confirmed: 1,
|
|
@@ -16,7 +20,75 @@ class TransactionConfirmationManager {
|
|
|
16
20
|
this.intervalId = null;
|
|
17
21
|
this.connection = connection;
|
|
18
22
|
}
|
|
19
|
-
async
|
|
23
|
+
async confirmTransactionWebSocket(txSig, timeout = 30000, desiredConfirmationStatus = config_1.DEFAULT_CONFIRMATION_OPTS.commitment) {
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
const subscriptionCommitment = desiredConfirmationStatus || config_1.DEFAULT_CONFIRMATION_OPTS.commitment;
|
|
26
|
+
let response = null;
|
|
27
|
+
let subscriptionId;
|
|
28
|
+
const confirmationPromise = new Promise((resolve, reject) => {
|
|
29
|
+
try {
|
|
30
|
+
subscriptionId = this.connection.onSignature(txSig, (result, context) => {
|
|
31
|
+
response = {
|
|
32
|
+
context,
|
|
33
|
+
value: result,
|
|
34
|
+
};
|
|
35
|
+
resolve(null);
|
|
36
|
+
}, subscriptionCommitment);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
reject(err);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// We do a one-shot confirmation check just in case the transaction is ALREADY confirmed when we create the websocket confirmation .. We want to run this concurrently with the onSignature subscription. If this returns true then we can return early as the transaction has already been confirmed.
|
|
43
|
+
const oneShotConfirmationPromise = this.connection.getSignatureStatuses([
|
|
44
|
+
txSig,
|
|
45
|
+
]);
|
|
46
|
+
const resolveReference = {};
|
|
47
|
+
// This is the promise we are waiting on to resolve the overall confirmation. It will resolve the faster of a positive oneShot confirmation, or the websocket confirmation, or the timeout.
|
|
48
|
+
const overallWaitingForConfirmationPromise = new Promise((resolve) => {
|
|
49
|
+
resolveReference.resolve = resolve;
|
|
50
|
+
});
|
|
51
|
+
// Await for the one shot confirmation and resolve the waiting promise if we get a positive confirmation result
|
|
52
|
+
oneShotConfirmationPromise.then(async (oneShotResponse) => {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
if (!oneShotResponse || !((_a = oneShotResponse === null || oneShotResponse === void 0 ? void 0 : oneShotResponse.value) === null || _a === void 0 ? void 0 : _a[0]))
|
|
55
|
+
return;
|
|
56
|
+
const resultValue = oneShotResponse.value[0];
|
|
57
|
+
if (resultValue.err) {
|
|
58
|
+
await (0, reportTransactionError_1.throwTransactionError)(txSig, this.connection);
|
|
59
|
+
}
|
|
60
|
+
if (this.checkStatusMatchesDesiredConfirmationStatus(resultValue, desiredConfirmationStatus)) {
|
|
61
|
+
response = {
|
|
62
|
+
context: oneShotResponse.context,
|
|
63
|
+
value: oneShotResponse.value[0],
|
|
64
|
+
};
|
|
65
|
+
(_b = resolveReference.resolve) === null || _b === void 0 ? void 0 : _b.call(resolveReference);
|
|
66
|
+
}
|
|
67
|
+
}, (onRejected) => {
|
|
68
|
+
throw onRejected;
|
|
69
|
+
});
|
|
70
|
+
// Await for the websocket confirmation with the configured timeout
|
|
71
|
+
(0, promiseTimeout_1.promiseTimeout)(confirmationPromise, timeout).then(() => {
|
|
72
|
+
var _a;
|
|
73
|
+
(_a = resolveReference.resolve) === null || _a === void 0 ? void 0 : _a.call(resolveReference);
|
|
74
|
+
}, (onRejected) => {
|
|
75
|
+
throw onRejected;
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
await overallWaitingForConfirmationPromise;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (subscriptionId !== undefined) {
|
|
82
|
+
this.connection.removeSignatureListener(subscriptionId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const duration = (Date.now() - start) / 1000;
|
|
86
|
+
if (response === null) {
|
|
87
|
+
throw new __1.TxSendError(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${txSig} using the Solana Explorer or CLI tools.`, txConstants_1.NOT_CONFIRMED_ERROR_CODE);
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
async confirmTransactionPolling(txSig, desiredConfirmationStatus = config_1.DEFAULT_CONFIRMATION_OPTS.commitment, timeout = 30000, pollInterval = 1000, searchTransactionHistory = false) {
|
|
20
92
|
// Interval must be > 400ms and a multiple of 100ms
|
|
21
93
|
if (pollInterval < 400 || pollInterval % 100 !== 0) {
|
|
22
94
|
throw new Error('Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms');
|
|
@@ -60,6 +132,14 @@ class TransactionConfirmationManager {
|
|
|
60
132
|
this.intervalId = null;
|
|
61
133
|
}
|
|
62
134
|
}
|
|
135
|
+
checkStatusMatchesDesiredConfirmationStatus(status, desiredConfirmationStatus) {
|
|
136
|
+
if (status.confirmationStatus &&
|
|
137
|
+
confirmationStatusValues[status.confirmationStatus] >=
|
|
138
|
+
confirmationStatusValues[desiredConfirmationStatus]) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
63
143
|
async checkTransactionStatuses(requests) {
|
|
64
144
|
const txSigs = requests.map((request) => request.txSig);
|
|
65
145
|
const { value: statuses } = await this.connection.getSignatureStatuses(txSigs, {
|
|
@@ -75,8 +155,8 @@ class TransactionConfirmationManager {
|
|
|
75
155
|
continue;
|
|
76
156
|
}
|
|
77
157
|
if (status.err) {
|
|
78
|
-
request.reject(new Error(`Transaction failed: ${JSON.stringify(status.err)}`));
|
|
79
158
|
this.pendingConfirmations.delete(request.txSig);
|
|
159
|
+
request.reject(await (0, reportTransactionError_1.getTransactionErrorFromTxSig)(request.txSig, this.connection));
|
|
80
160
|
continue;
|
|
81
161
|
}
|
|
82
162
|
if (confirmationStatusValues[status.confirmationStatus] === undefined ||
|
|
@@ -84,9 +164,7 @@ class TransactionConfirmationManager {
|
|
|
84
164
|
undefined) {
|
|
85
165
|
throw new Error(`Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`);
|
|
86
166
|
}
|
|
87
|
-
if (status.
|
|
88
|
-
confirmationStatusValues[status.confirmationStatus] >=
|
|
89
|
-
confirmationStatusValues[request.desiredConfirmationStatus]) {
|
|
167
|
+
if (this.checkStatusMatchesDesiredConfirmationStatus(status, request.desiredConfirmationStatus)) {
|
|
90
168
|
request.resolve(status);
|
|
91
169
|
this.pendingConfirmations.delete(request.txSig);
|
|
92
170
|
}
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -44,6 +44,8 @@ export const DRIFT_PROGRAM_ID = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
|
|
|
44
44
|
export const DRIFT_ORACLE_RECEIVER_ID =
|
|
45
45
|
'G6EoTTTgpkNBtVXo96EQp2m6uwwVh2Kt6YidjkmQqoha';
|
|
46
46
|
export const SWIFT_ID = 'SW1fThqrxLzVprnCMpiybiqYQfoNCdduC5uWsSUKChS';
|
|
47
|
+
export const ANCHOR_TEST_SWIFT_ID =
|
|
48
|
+
'DpaEdAPW3ZX67fnczT14AoX12Lx9VMkxvtT81nCHy3Nv';
|
|
47
49
|
|
|
48
50
|
export const DEFAULT_CONFIRMATION_OPTS: ConfirmOptions = {
|
|
49
51
|
preflightCommitment: 'confirmed',
|
|
@@ -297,7 +297,7 @@ export const DevnetPerpMarkets: PerpMarketConfig[] = [
|
|
|
297
297
|
symbol: 'W-PERP',
|
|
298
298
|
baseAssetSymbol: 'W',
|
|
299
299
|
marketIndex: 23,
|
|
300
|
-
oracle: new PublicKey('
|
|
300
|
+
oracle: new PublicKey('J9nrFWjDUeDVZ4BhhxsbQXWgLcLEgQyNBrCbwSADmJdr'),
|
|
301
301
|
launchTs: 1709852537000,
|
|
302
302
|
oracleSource: OracleSource.SWITCHBOARD_ON_DEMAND,
|
|
303
303
|
pythFeedId:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NOT_CONFIRMED_ERROR_CODE = -1001;
|
package/src/driftClient.ts
CHANGED
|
@@ -181,6 +181,7 @@ export class DriftClient {
|
|
|
181
181
|
connection: Connection;
|
|
182
182
|
wallet: IWallet;
|
|
183
183
|
public program: Program;
|
|
184
|
+
public swiftID: PublicKey;
|
|
184
185
|
provider: AnchorProvider;
|
|
185
186
|
opts?: ConfirmOptions;
|
|
186
187
|
users = new Map<string, User>();
|
|
@@ -245,6 +246,7 @@ export class DriftClient {
|
|
|
245
246
|
config.programID ?? new PublicKey(DRIFT_PROGRAM_ID),
|
|
246
247
|
this.provider
|
|
247
248
|
);
|
|
249
|
+
this.swiftID = config.swiftID ?? new PublicKey(SWIFT_ID);
|
|
248
250
|
|
|
249
251
|
this.authority = config.authority ?? this.wallet.publicKey;
|
|
250
252
|
this.activeSubAccountId = config.activeSubAccountId ?? 0;
|
|
@@ -5483,7 +5485,7 @@ export class DriftClient {
|
|
|
5483
5485
|
|
|
5484
5486
|
const swiftServerSignatureIx =
|
|
5485
5487
|
Ed25519Program.createInstructionWithPublicKey({
|
|
5486
|
-
publicKey: new PublicKey(
|
|
5488
|
+
publicKey: new PublicKey(this.swiftID).toBytes(),
|
|
5487
5489
|
signature: Uint8Array.from(swiftSignature),
|
|
5488
5490
|
message: Uint8Array.from(encodedSwiftServerMessage),
|
|
5489
5491
|
});
|
package/src/driftClientConfig.ts
CHANGED
package/src/tx/baseTxSender.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
TransactionSignature,
|
|
16
16
|
Connection,
|
|
17
17
|
VersionedTransaction,
|
|
18
|
-
SendTransactionError,
|
|
19
18
|
TransactionInstruction,
|
|
20
19
|
AddressLookupTableAccount,
|
|
21
20
|
BlockhashWithExpiryBlockHeight,
|
|
@@ -26,11 +25,12 @@ import { TxHandler } from './txHandler';
|
|
|
26
25
|
import { IWallet } from '../types';
|
|
27
26
|
import NodeCache from 'node-cache';
|
|
28
27
|
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
28
|
+
import { NOT_CONFIRMED_ERROR_CODE } from '../constants/txConstants';
|
|
29
|
+
import { throwTransactionError } from './reportTransactionError';
|
|
29
30
|
|
|
30
31
|
const BASELINE_TX_LAND_RATE = 0.9;
|
|
31
32
|
const DEFAULT_TIMEOUT = 35000;
|
|
32
33
|
const DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES = 10;
|
|
33
|
-
const NOT_CONFIRMED_ERROR_CODE = -1001;
|
|
34
34
|
|
|
35
35
|
export abstract class BaseTxSender implements TxSender {
|
|
36
36
|
connection: Connection;
|
|
@@ -406,40 +406,18 @@ export abstract class BaseTxSender implements TxSender {
|
|
|
406
406
|
public async checkConfirmationResultForError(
|
|
407
407
|
txSig: string,
|
|
408
408
|
result: SignatureResult
|
|
409
|
-
) {
|
|
409
|
+
): Promise<void> {
|
|
410
410
|
if (result.err) {
|
|
411
|
-
await
|
|
411
|
+
await throwTransactionError(
|
|
412
|
+
txSig,
|
|
413
|
+
this.connection,
|
|
414
|
+
this.opts?.commitment
|
|
415
|
+
);
|
|
412
416
|
}
|
|
413
417
|
|
|
414
418
|
return;
|
|
415
419
|
}
|
|
416
420
|
|
|
417
|
-
public async reportTransactionError(txSig: string) {
|
|
418
|
-
const transactionResult = await this.connection.getTransaction(txSig, {
|
|
419
|
-
maxSupportedTransactionVersion: 0,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
if (!transactionResult?.meta?.err) {
|
|
423
|
-
return undefined;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const logs = transactionResult.meta.logMessages;
|
|
427
|
-
|
|
428
|
-
const lastLog = logs[logs.length - 1];
|
|
429
|
-
|
|
430
|
-
const friendlyMessage = lastLog?.match(/(failed:) (.+)/)?.[2];
|
|
431
|
-
|
|
432
|
-
// @ts-ignore
|
|
433
|
-
throw new SendTransactionError({
|
|
434
|
-
action: 'send',
|
|
435
|
-
signature: txSig,
|
|
436
|
-
transactionMessage: `Transaction Failed${
|
|
437
|
-
friendlyMessage ? `: ${friendlyMessage}` : ''
|
|
438
|
-
}`,
|
|
439
|
-
logs,
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
|
|
443
421
|
public getTxLandRate(): number {
|
|
444
422
|
if (!this.trackTxLandRate) {
|
|
445
423
|
console.warn(
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Commitment,
|
|
3
|
+
Connection,
|
|
4
|
+
Finality,
|
|
5
|
+
SendTransactionError,
|
|
6
|
+
VersionedTransactionResponse,
|
|
7
|
+
} from '@solana/web3.js';
|
|
8
|
+
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The new getTransaction method expects a Finality type instead of a Commitment type. The only options for Finality are 'confirmed' and 'finalized'.
|
|
12
|
+
* @param commitment
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
const commitmentToFinality = (commitment: Commitment): Finality => {
|
|
16
|
+
switch (commitment) {
|
|
17
|
+
case 'confirmed':
|
|
18
|
+
return 'confirmed';
|
|
19
|
+
case 'finalized':
|
|
20
|
+
return 'finalized';
|
|
21
|
+
default:
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Invalid commitment when reporting transaction error. The commitment must be 'confirmed' or 'finalized' but was given '${commitment}'. If you're using this commitment for a specific reason, you may need to roll your own logic here.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getTransactionResult = async (
|
|
29
|
+
txSig: string,
|
|
30
|
+
connection: Connection,
|
|
31
|
+
commitment?: Commitment
|
|
32
|
+
): Promise<VersionedTransactionResponse> => {
|
|
33
|
+
const finality = commitmentToFinality(
|
|
34
|
+
commitment || connection.commitment || DEFAULT_CONFIRMATION_OPTS.commitment
|
|
35
|
+
);
|
|
36
|
+
return await connection.getTransaction(txSig, {
|
|
37
|
+
maxSupportedTransactionVersion: 0,
|
|
38
|
+
commitment: finality,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getTransactionResultWithRetry = async (
|
|
43
|
+
txSig: string,
|
|
44
|
+
connection: Connection,
|
|
45
|
+
commitment?: Commitment
|
|
46
|
+
): Promise<VersionedTransactionResponse> => {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
|
|
49
|
+
const retryTimeout = 3_000; // Timeout after 3 seconds
|
|
50
|
+
const retryInterval = 800; // Retry with 800ms interval
|
|
51
|
+
const retryCount = 3; // Retry 3 times
|
|
52
|
+
|
|
53
|
+
let currentCount = 0;
|
|
54
|
+
let transactionResult = await getTransactionResult(
|
|
55
|
+
txSig,
|
|
56
|
+
connection,
|
|
57
|
+
commitment
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Retry 3 times or until timeout as long as we don't have a result yet
|
|
61
|
+
while (
|
|
62
|
+
!transactionResult &&
|
|
63
|
+
Date.now() - start < retryTimeout &&
|
|
64
|
+
currentCount < retryCount
|
|
65
|
+
) {
|
|
66
|
+
// Sleep for 1 second :: Do this first so that we don't run the first loop immediately after the initial fetch above
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, retryInterval));
|
|
68
|
+
|
|
69
|
+
transactionResult = await getTransactionResult(
|
|
70
|
+
txSig,
|
|
71
|
+
connection,
|
|
72
|
+
commitment
|
|
73
|
+
);
|
|
74
|
+
currentCount++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return transactionResult;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* THROWS if there is an error
|
|
82
|
+
*
|
|
83
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
84
|
+
* @param txSig
|
|
85
|
+
* @param connection
|
|
86
|
+
* @returns
|
|
87
|
+
*/
|
|
88
|
+
export const throwTransactionError = async (
|
|
89
|
+
txSig: string,
|
|
90
|
+
connection: Connection,
|
|
91
|
+
commitment?: Commitment
|
|
92
|
+
): Promise<void> => {
|
|
93
|
+
const err = await getTransactionErrorFromTxSig(txSig, connection, commitment);
|
|
94
|
+
|
|
95
|
+
if (err) {
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* RETURNS an error if there is one
|
|
104
|
+
*
|
|
105
|
+
* Should only be used for a txSig that is confirmed has an error. There is a race-condition where sometimes the transaction is not instantly available to fetch after the confirmation has already failed with an error, so this method has retry logic which we don't want to do wastefully. This method will throw a generic error if it can't get the transaction result after a retry period.
|
|
106
|
+
* @param txSig
|
|
107
|
+
* @param connection
|
|
108
|
+
* @returns
|
|
109
|
+
*/
|
|
110
|
+
export const getTransactionErrorFromTxSig = async (
|
|
111
|
+
txSig: string,
|
|
112
|
+
connection: Connection,
|
|
113
|
+
commitment?: Commitment
|
|
114
|
+
): Promise<SendTransactionError> => {
|
|
115
|
+
const transactionResult = await getTransactionResultWithRetry(
|
|
116
|
+
txSig,
|
|
117
|
+
connection,
|
|
118
|
+
commitment
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!transactionResult) {
|
|
122
|
+
// Throw a generic error because we couldn't get the transaction result for the given txSig
|
|
123
|
+
return new SendTransactionError({
|
|
124
|
+
action: 'send',
|
|
125
|
+
signature: txSig,
|
|
126
|
+
transactionMessage: `Transaction Failed`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!transactionResult?.meta?.err) {
|
|
131
|
+
// Assume that the transaction was successful and we are here erroneously because we have a result with no error
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return getTransactionError(transactionResult);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const getTransactionError = (
|
|
139
|
+
transactionResult: VersionedTransactionResponse
|
|
140
|
+
): SendTransactionError => {
|
|
141
|
+
if (!transactionResult?.meta?.err) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const logs = transactionResult?.meta?.logMessages ?? ['No logs'];
|
|
146
|
+
|
|
147
|
+
const lastLog = logs[logs.length - 1];
|
|
148
|
+
|
|
149
|
+
const friendlyMessage = lastLog?.match(/(failed:) (.+)/)?.[2];
|
|
150
|
+
|
|
151
|
+
return new SendTransactionError({
|
|
152
|
+
action: 'send',
|
|
153
|
+
signature: transactionResult?.transaction?.signatures?.[0],
|
|
154
|
+
transactionMessage: `Transaction Failed${
|
|
155
|
+
friendlyMessage ? `: ${friendlyMessage}` : ''
|
|
156
|
+
}`,
|
|
157
|
+
logs,
|
|
158
|
+
});
|
|
159
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TxSigAndSlot } from './types';
|
|
1
|
+
import { ConfirmationStrategy, TxSigAndSlot } from './types';
|
|
2
2
|
import {
|
|
3
3
|
ConfirmOptions,
|
|
4
4
|
Connection,
|
|
@@ -62,6 +62,7 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
62
62
|
opts = { ...DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 },
|
|
63
63
|
retrySleep = DEFAULT_RETRY,
|
|
64
64
|
additionalConnections = new Array<Connection>(),
|
|
65
|
+
confirmationStrategy = ConfirmationStrategy.Combo,
|
|
65
66
|
additionalTxSenderCallbacks = [],
|
|
66
67
|
txHandler,
|
|
67
68
|
trackTxLandRate,
|
|
@@ -74,6 +75,7 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
74
75
|
retrySleep?: number;
|
|
75
76
|
additionalConnections?;
|
|
76
77
|
additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
|
|
78
|
+
confirmationStrategy: ConfirmationStrategy;
|
|
77
79
|
txHandler?: TxHandler;
|
|
78
80
|
trackTxLandRate?: boolean;
|
|
79
81
|
txLandRateLookbackWindowMinutes?: number;
|
|
@@ -88,6 +90,7 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
88
90
|
txHandler,
|
|
89
91
|
trackTxLandRate,
|
|
90
92
|
txLandRateLookbackWindowMinutes,
|
|
93
|
+
confirmationStrategy,
|
|
91
94
|
landRateToFeeFunc,
|
|
92
95
|
});
|
|
93
96
|
this.retrySleep = retrySleep;
|
|
@@ -187,11 +190,6 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
187
190
|
const txSig = bs58.encode(signedTx.signatures[0]);
|
|
188
191
|
this.untilValid.set(txSig, latestBlockhash);
|
|
189
192
|
|
|
190
|
-
console.debug(
|
|
191
|
-
`preflight_commitment`,
|
|
192
|
-
`sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
|
|
193
|
-
);
|
|
194
|
-
|
|
195
193
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
196
194
|
}
|
|
197
195
|
|
|
@@ -201,10 +199,6 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
201
199
|
): Promise<TxSigAndSlot> {
|
|
202
200
|
const startTime = this.getTimestamp();
|
|
203
201
|
|
|
204
|
-
console.debug(
|
|
205
|
-
`preflight_commitment`,
|
|
206
|
-
`sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
|
|
207
|
-
);
|
|
208
202
|
const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
209
203
|
this.txSigCache?.set(txid, false);
|
|
210
204
|
this.sendToAdditionalConnections(rawTransaction, opts);
|
package/src/types.ts
CHANGED
|
@@ -1064,6 +1064,7 @@ export type SwiftServerMessage = {
|
|
|
1064
1064
|
export type SwiftOrderParamsMessage = {
|
|
1065
1065
|
swiftOrderParams: OptionalOrderParams;
|
|
1066
1066
|
expectedOrderId: number;
|
|
1067
|
+
subAccountId: number;
|
|
1067
1068
|
takeProfitOrderParams: SwiftTriggerOrderParams | null;
|
|
1068
1069
|
stopLossOrderParams: SwiftTriggerOrderParams | null;
|
|
1069
1070
|
};
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ClientSubscriptionId,
|
|
2
3
|
Connection,
|
|
4
|
+
Context,
|
|
5
|
+
RpcResponseAndContext,
|
|
6
|
+
SignatureResult,
|
|
3
7
|
SignatureStatus,
|
|
4
8
|
TransactionConfirmationStatus,
|
|
5
9
|
} from '@solana/web3.js';
|
|
6
10
|
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
11
|
+
import { TxSendError } from '..';
|
|
12
|
+
import { NOT_CONFIRMED_ERROR_CODE } from '../constants/txConstants';
|
|
13
|
+
import {
|
|
14
|
+
getTransactionErrorFromTxSig,
|
|
15
|
+
throwTransactionError,
|
|
16
|
+
} from '../tx/reportTransactionError';
|
|
17
|
+
import { promiseTimeout } from './promiseTimeout';
|
|
18
|
+
|
|
19
|
+
type ResolveReference = {
|
|
20
|
+
resolve?: () => void;
|
|
21
|
+
};
|
|
7
22
|
|
|
8
23
|
const confirmationStatusValues: Record<TransactionConfirmationStatus, number> =
|
|
9
24
|
{
|
|
@@ -36,7 +51,113 @@ export class TransactionConfirmationManager {
|
|
|
36
51
|
this.connection = connection;
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
async
|
|
54
|
+
async confirmTransactionWebSocket(
|
|
55
|
+
txSig: string,
|
|
56
|
+
timeout = 30000,
|
|
57
|
+
desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus
|
|
58
|
+
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
const subscriptionCommitment =
|
|
61
|
+
desiredConfirmationStatus || DEFAULT_CONFIRMATION_OPTS.commitment;
|
|
62
|
+
|
|
63
|
+
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
64
|
+
|
|
65
|
+
let subscriptionId: ClientSubscriptionId;
|
|
66
|
+
|
|
67
|
+
const confirmationPromise = new Promise((resolve, reject) => {
|
|
68
|
+
try {
|
|
69
|
+
subscriptionId = this.connection.onSignature(
|
|
70
|
+
txSig,
|
|
71
|
+
(result: SignatureResult, context: Context) => {
|
|
72
|
+
response = {
|
|
73
|
+
context,
|
|
74
|
+
value: result,
|
|
75
|
+
};
|
|
76
|
+
resolve(null);
|
|
77
|
+
},
|
|
78
|
+
subscriptionCommitment
|
|
79
|
+
);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
reject(err);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// We do a one-shot confirmation check just in case the transaction is ALREADY confirmed when we create the websocket confirmation .. We want to run this concurrently with the onSignature subscription. If this returns true then we can return early as the transaction has already been confirmed.
|
|
86
|
+
const oneShotConfirmationPromise = this.connection.getSignatureStatuses([
|
|
87
|
+
txSig,
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const resolveReference: ResolveReference = {};
|
|
91
|
+
|
|
92
|
+
// This is the promise we are waiting on to resolve the overall confirmation. It will resolve the faster of a positive oneShot confirmation, or the websocket confirmation, or the timeout.
|
|
93
|
+
const overallWaitingForConfirmationPromise = new Promise<void>(
|
|
94
|
+
(resolve) => {
|
|
95
|
+
resolveReference.resolve = resolve;
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Await for the one shot confirmation and resolve the waiting promise if we get a positive confirmation result
|
|
100
|
+
oneShotConfirmationPromise.then(
|
|
101
|
+
async (oneShotResponse) => {
|
|
102
|
+
if (!oneShotResponse || !oneShotResponse?.value?.[0]) return;
|
|
103
|
+
|
|
104
|
+
const resultValue = oneShotResponse.value[0];
|
|
105
|
+
|
|
106
|
+
if (resultValue.err) {
|
|
107
|
+
await throwTransactionError(txSig, this.connection);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
this.checkStatusMatchesDesiredConfirmationStatus(
|
|
112
|
+
resultValue,
|
|
113
|
+
desiredConfirmationStatus
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
response = {
|
|
117
|
+
context: oneShotResponse.context,
|
|
118
|
+
value: oneShotResponse.value[0],
|
|
119
|
+
};
|
|
120
|
+
resolveReference.resolve?.();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
(onRejected) => {
|
|
124
|
+
throw onRejected;
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Await for the websocket confirmation with the configured timeout
|
|
129
|
+
promiseTimeout(confirmationPromise, timeout).then(
|
|
130
|
+
() => {
|
|
131
|
+
resolveReference.resolve?.();
|
|
132
|
+
},
|
|
133
|
+
(onRejected) => {
|
|
134
|
+
throw onRejected;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await overallWaitingForConfirmationPromise;
|
|
140
|
+
} finally {
|
|
141
|
+
if (subscriptionId !== undefined) {
|
|
142
|
+
this.connection.removeSignatureListener(subscriptionId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const duration = (Date.now() - start) / 1000;
|
|
147
|
+
|
|
148
|
+
if (response === null) {
|
|
149
|
+
throw new TxSendError(
|
|
150
|
+
`Transaction was not confirmed in ${duration.toFixed(
|
|
151
|
+
2
|
|
152
|
+
)} seconds. It is unknown if it succeeded or failed. Check signature ${txSig} using the Solana Explorer or CLI tools.`,
|
|
153
|
+
NOT_CONFIRMED_ERROR_CODE
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async confirmTransactionPolling(
|
|
40
161
|
txSig: string,
|
|
41
162
|
desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus,
|
|
42
163
|
timeout = 30000,
|
|
@@ -99,6 +220,21 @@ export class TransactionConfirmationManager {
|
|
|
99
220
|
}
|
|
100
221
|
}
|
|
101
222
|
|
|
223
|
+
private checkStatusMatchesDesiredConfirmationStatus(
|
|
224
|
+
status: SignatureStatus,
|
|
225
|
+
desiredConfirmationStatus: TransactionConfirmationStatus
|
|
226
|
+
): boolean {
|
|
227
|
+
if (
|
|
228
|
+
status.confirmationStatus &&
|
|
229
|
+
confirmationStatusValues[status.confirmationStatus] >=
|
|
230
|
+
confirmationStatusValues[desiredConfirmationStatus]
|
|
231
|
+
) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
102
238
|
private async checkTransactionStatuses(
|
|
103
239
|
requests: TransactionConfirmationRequest[]
|
|
104
240
|
) {
|
|
@@ -125,10 +261,10 @@ export class TransactionConfirmationManager {
|
|
|
125
261
|
}
|
|
126
262
|
|
|
127
263
|
if (status.err) {
|
|
264
|
+
this.pendingConfirmations.delete(request.txSig);
|
|
128
265
|
request.reject(
|
|
129
|
-
|
|
266
|
+
await getTransactionErrorFromTxSig(request.txSig, this.connection)
|
|
130
267
|
);
|
|
131
|
-
this.pendingConfirmations.delete(request.txSig);
|
|
132
268
|
continue;
|
|
133
269
|
}
|
|
134
270
|
|
|
@@ -143,9 +279,10 @@ export class TransactionConfirmationManager {
|
|
|
143
279
|
}
|
|
144
280
|
|
|
145
281
|
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
282
|
+
this.checkStatusMatchesDesiredConfirmationStatus(
|
|
283
|
+
status,
|
|
284
|
+
request.desiredConfirmationStatus
|
|
285
|
+
)
|
|
149
286
|
) {
|
|
150
287
|
request.resolve(status);
|
|
151
288
|
this.pendingConfirmations.delete(request.txSig);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
2
|
import sinon from 'sinon';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Connection,
|
|
5
|
+
SignatureStatus,
|
|
6
|
+
VersionedTransactionResponse,
|
|
7
|
+
} from '@solana/web3.js';
|
|
4
8
|
import { TransactionConfirmationManager } from '../../src/util/TransactionConfirmationManager';
|
|
5
9
|
import assert from 'assert';
|
|
6
10
|
|
|
7
|
-
describe('
|
|
11
|
+
describe('TransactionConfirmationManager_Polling_Tests', () => {
|
|
8
12
|
let manager: TransactionConfirmationManager;
|
|
9
13
|
let mockConnection: sinon.SinonStubbedInstance<Connection>;
|
|
10
14
|
|
|
@@ -21,7 +25,12 @@ describe('TransactionConfirmationManager', () => {
|
|
|
21
25
|
|
|
22
26
|
it('should throw error for invalid poll interval', async () => {
|
|
23
27
|
try {
|
|
24
|
-
await manager.
|
|
28
|
+
await manager.confirmTransactionPolling(
|
|
29
|
+
'fakeTxSig',
|
|
30
|
+
'confirmed',
|
|
31
|
+
30000,
|
|
32
|
+
300
|
|
33
|
+
);
|
|
25
34
|
assert.fail('Expected an error to be thrown');
|
|
26
35
|
} catch (error) {
|
|
27
36
|
assert(error instanceof Error);
|
|
@@ -46,7 +55,7 @@ describe('TransactionConfirmationManager', () => {
|
|
|
46
55
|
value: [fakeStatus],
|
|
47
56
|
});
|
|
48
57
|
|
|
49
|
-
const result = await manager.
|
|
58
|
+
const result = await manager.confirmTransactionPolling(
|
|
50
59
|
fakeTxSig,
|
|
51
60
|
'confirmed',
|
|
52
61
|
30000,
|
|
@@ -61,7 +70,7 @@ describe('TransactionConfirmationManager', () => {
|
|
|
61
70
|
).to.be.true;
|
|
62
71
|
});
|
|
63
72
|
|
|
64
|
-
it('should reject when transaction fails', async ()
|
|
73
|
+
it('should reject when transaction fails', async function () {
|
|
65
74
|
const fakeTxSig = 'fakeTxSig';
|
|
66
75
|
const fakeStatus: SignatureStatus = {
|
|
67
76
|
slot: 100,
|
|
@@ -75,15 +84,25 @@ describe('TransactionConfirmationManager', () => {
|
|
|
75
84
|
value: [fakeStatus],
|
|
76
85
|
});
|
|
77
86
|
|
|
87
|
+
// The transaction manager falls into getTransaction when it detects a transaction failure so we need to mock that as well
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
mockConnection.getTransaction.resolves({
|
|
90
|
+
meta: {
|
|
91
|
+
logMessages: ['Transaction failed: Custom'],
|
|
92
|
+
err: { InstructionError: [0, 'Custom'] },
|
|
93
|
+
},
|
|
94
|
+
} as VersionedTransactionResponse);
|
|
95
|
+
|
|
78
96
|
try {
|
|
79
|
-
await manager.
|
|
97
|
+
await manager.confirmTransactionPolling(
|
|
98
|
+
fakeTxSig,
|
|
99
|
+
'confirmed',
|
|
100
|
+
30000,
|
|
101
|
+
400
|
|
102
|
+
);
|
|
80
103
|
assert.fail('Expected an error to be thrown');
|
|
81
104
|
} catch (error) {
|
|
82
|
-
|
|
83
|
-
assert.strictEqual(
|
|
84
|
-
error.message,
|
|
85
|
-
'Transaction failed: {"InstructionError":[0,"Custom"]}'
|
|
86
|
-
);
|
|
105
|
+
return;
|
|
87
106
|
}
|
|
88
107
|
});
|
|
89
108
|
|
|
@@ -96,7 +115,7 @@ describe('TransactionConfirmationManager', () => {
|
|
|
96
115
|
value: [null],
|
|
97
116
|
});
|
|
98
117
|
|
|
99
|
-
const promise = manager.
|
|
118
|
+
const promise = manager.confirmTransactionPolling(
|
|
100
119
|
fakeTxSig,
|
|
101
120
|
'confirmed',
|
|
102
121
|
5000,
|
|
@@ -140,13 +159,13 @@ describe('TransactionConfirmationManager', () => {
|
|
|
140
159
|
value: [fakeStatus1, fakeStatus2],
|
|
141
160
|
});
|
|
142
161
|
|
|
143
|
-
const promise1 = manager.
|
|
162
|
+
const promise1 = manager.confirmTransactionPolling(
|
|
144
163
|
fakeTxSig1,
|
|
145
164
|
'confirmed',
|
|
146
165
|
30000,
|
|
147
166
|
400
|
|
148
167
|
);
|
|
149
|
-
const promise2 = manager.
|
|
168
|
+
const promise2 = manager.confirmTransactionPolling(
|
|
150
169
|
fakeTxSig2,
|
|
151
170
|
'confirmed',
|
|
152
171
|
30000,
|
|
@@ -206,13 +225,13 @@ describe('TransactionConfirmationManager', () => {
|
|
|
206
225
|
const startTime = Date.now();
|
|
207
226
|
|
|
208
227
|
// Start both confirmation processes
|
|
209
|
-
const promise1 = manager.
|
|
228
|
+
const promise1 = manager.confirmTransactionPolling(
|
|
210
229
|
fakeTxSig1,
|
|
211
230
|
'confirmed',
|
|
212
231
|
5000,
|
|
213
232
|
400
|
|
214
233
|
);
|
|
215
|
-
const promise2 = manager.
|
|
234
|
+
const promise2 = manager.confirmTransactionPolling(
|
|
216
235
|
fakeTxSig2,
|
|
217
236
|
'confirmed',
|
|
218
237
|
5000,
|