UNPKG

@drift-labs/sdk-browser

Version:
293 lines (292 loc) 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseTxSender = void 0; const types_1 = require("./types"); const assert_1 = __importDefault(require("assert")); const bs58_1 = __importDefault(require("bs58")); const txHandler_1 = require("./txHandler"); const node_cache_1 = __importDefault(require("node-cache")); const config_1 = require("../config"); const txConstants_1 = require("../constants/txConstants"); const reportTransactionError_1 = require("./reportTransactionError"); const BASELINE_TX_LAND_RATE = 0.9; const DEFAULT_TIMEOUT = 35000; const DEFAULT_TX_LAND_RATE_LOOKBACK_WINDOW_MINUTES = 10; class BaseTxSender { 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, throwOnTimeoutError = true, throwOnTransactionError = true, }) { this.timeoutCount = 0; this.txLandRate = 0; this.lastPriorityFeeSuggestion = 1; this.connection = connection; this.wallet = wallet; this.opts = opts; this.timeout = timeout; this.additionalConnections = additionalConnections; this.confirmationStrategy = confirmationStrategy; this.additionalTxSenderCallbacks = additionalTxSenderCallbacks; this.txHandler = txHandler !== null && txHandler !== void 0 ? txHandler : new txHandler_1.TxHandler({ connection: this.connection, wallet: this.wallet, confirmationOptions: this.opts, }); this.trackTxLandRate = trackTxLandRate; this.lookbackWindowMinutes = txLandRateLookbackWindowMinutes * 60; if (this.trackTxLandRate) { this.txSigCache = new node_cache_1.default({ stdTTL: this.lookbackWindowMinutes, checkperiod: 120, }); } this.landRateToFeeFunc = landRateToFeeFunc !== null && landRateToFeeFunc !== void 0 ? landRateToFeeFunc : this.defaultLandRateToFeeFunc.bind(this); this.throwOnTimeoutError = throwOnTimeoutError; this.throwOnTransactionError = throwOnTransactionError; } async send(tx, additionalSigners, opts, preSigned) { if (additionalSigners === undefined) { additionalSigners = []; } if (opts === undefined) { opts = this.opts; } const signedTx = await this.prepareTx(tx, additionalSigners, opts, preSigned); return this.sendRawTransaction(signedTx.serialize(), opts); } async prepareTx(tx, additionalSigners, opts, preSigned) { return this.txHandler.prepareTx(tx, additionalSigners, undefined, opts, preSigned); } async getVersionedTransaction(ixs, lookupTableAccounts, _additionalSigners, opts, blockhash) { return this.txHandler.generateVersionedTransaction(blockhash !== null && blockhash !== void 0 ? blockhash : (await this.connection.getLatestBlockhash()), ixs, lookupTableAccounts, this.wallet); } async sendVersionedTransaction(tx, additionalSigners, opts, preSigned) { let signedTx; if (preSigned) { signedTx = tx; // @ts-ignore } else if (this.wallet.payer) { // @ts-ignore tx.sign((additionalSigners !== null && additionalSigners !== void 0 ? additionalSigners : []).concat(this.wallet.payer)); signedTx = tx; } else { signedTx = await this.txHandler.signVersionedTx(tx, additionalSigners, undefined, this.wallet); } if (opts === undefined) { opts = this.opts; } return this.sendRawTransaction(signedTx.serialize(), opts); } async sendRawTransaction( // eslint-disable-next-line @typescript-eslint/no-unused-vars rawTransaction, // eslint-disable-next-line @typescript-eslint/no-unused-vars opts) { throw new Error('Must be implemented by subclass'); } /* Simulate the tx and return a boolean for success value */ async simulateTransaction(tx) { try { const result = await this.connection.simulateTransaction(tx); if (result.value.err != null) { console.error('Error in transaction simulation: ', result.value.err); return false; } return true; } catch (e) { console.error('Error calling simulateTransaction: ', e); return false; } } async confirmTransactionWebSocket(signature, commitment) { var _a, _b; let decodedSignature; try { decodedSignature = bs58_1.default.decode(signature); } catch (err) { throw new Error('signature must be base58 encoded: ' + signature); } (0, assert_1.default)(decodedSignature.length === 64, 'signature has invalid length'); const start = Date.now(); const subscriptionCommitment = commitment || this.opts.commitment; const subscriptionIds = new Array(); const connections = [this.connection, ...this.additionalConnections]; let response = null; const promises = connections.map((connection, i) => { let subscriptionId; const confirmPromise = new Promise((resolve, reject) => { try { subscriptionId = connection.onSignature(signature, (result, context) => { subscriptionIds[i] = undefined; response = { context, value: result, }; resolve(null); }, subscriptionCommitment); } catch (err) { reject(err); } }); subscriptionIds.push(subscriptionId); return confirmPromise; }); try { await this.promiseTimeout(promises, this.timeout); } finally { for (const [i, subscriptionId] of subscriptionIds.entries()) { if (subscriptionId) { connections[i].removeSignatureListener(subscriptionId); } } } if (response === null) { if (this.confirmationStrategy === types_1.ConfirmationStrategy.Combo) { try { const rpcResponse = await this.connection.getSignatureStatuses([ signature, ]); if ((_b = (_a = rpcResponse === null || rpcResponse === void 0 ? void 0 : rpcResponse.value) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.confirmationStatus) { response = { context: rpcResponse.context, value: { err: rpcResponse.value[0].err }, }; return response; } } catch (error) { // Ignore error to pass through to timeout error } } this.timeoutCount += 1; const duration = (Date.now() - start) / 1000; if (this.throwOnTimeoutError) { 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); } } return response; } async confirmTransactionPolling(signature, commitment = 'finalized') { var _a; let totalTime = 0; let backoffTime = 400; // approx block time const start = Date.now(); while (totalTime < this.timeout) { await new Promise((resolve) => setTimeout(resolve, backoffTime)); const rpcResponse = await this.connection.getSignatureStatuses([ signature, ]); const signatureResult = rpcResponse && ((_a = rpcResponse.value) === null || _a === void 0 ? void 0 : _a[0]); if (rpcResponse && signatureResult && signatureResult.confirmationStatus === commitment) { return { context: rpcResponse.context, value: { err: null } }; } totalTime += backoffTime; backoffTime = Math.min(backoffTime * 2, 5000); } // Transaction not confirmed within 30 seconds this.timeoutCount += 1; const duration = (Date.now() - start) / 1000; if (this.throwOnTimeoutError) { 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); } } async confirmTransaction(signature, commitment) { if (this.confirmationStrategy === types_1.ConfirmationStrategy.WebSocket || this.confirmationStrategy === types_1.ConfirmationStrategy.Combo) { return await this.confirmTransactionWebSocket(signature, commitment); } else if (this.confirmationStrategy === types_1.ConfirmationStrategy.Polling) { return await this.confirmTransactionPolling(signature, commitment); } } getTimestamp() { return new Date().getTime(); } promiseTimeout(promises, timeoutMs) { let timeoutId; const timeoutPromise = new Promise((resolve) => { timeoutId = setTimeout(() => resolve(null), timeoutMs); }); return Promise.race([...promises, timeoutPromise]).then((result) => { clearTimeout(timeoutId); return result; }); } sendToAdditionalConnections(rawTx, opts) { var _a; this.additionalConnections.map((connection) => { connection.sendRawTransaction(rawTx, opts).catch((e) => { console.error( // @ts-ignore `error sending tx to additional connection ${connection._rpcEndpoint}`); console.error(e); }); }); (_a = this.additionalTxSenderCallbacks) === null || _a === void 0 ? void 0 : _a.map((callback) => { callback(bs58_1.default.encode(rawTx)); }); } addAdditionalConnection(newConnection) { const alreadyUsingConnection = this.additionalConnections.filter((connection) => { // @ts-ignore return connection._rpcEndpoint === newConnection.rpcEndpoint; }).length > 0; if (!alreadyUsingConnection) { this.additionalConnections.push(newConnection); } } getTimeoutCount() { return this.timeoutCount; } async checkConfirmationResultForError(txSig, result) { var _a; if (result === null || result === void 0 ? void 0 : result.err) { await (0, reportTransactionError_1.throwTransactionError)(txSig, this.connection, (_a = this.opts) === null || _a === void 0 ? void 0 : _a.commitment); } return; } getTxLandRate() { if (!this.trackTxLandRate) { return this.txLandRate; } const keys = this.txSigCache.keys(); const denominator = keys.length; if (denominator === 0) { return this.txLandRate; } let numerator = 0; for (const key of keys) { const value = this.txSigCache.get(key); if (value) { numerator += 1; } } this.txLandRate = numerator / denominator; return this.txLandRate; } defaultLandRateToFeeFunc(txLandRate) { if (txLandRate >= BASELINE_TX_LAND_RATE || this.txSigCache.keys().length < 3) { return 1; } const multiplier = 10 * Math.log10(1 + (BASELINE_TX_LAND_RATE - txLandRate) * 5); return Math.min(multiplier, 10); } getSuggestedPriorityFeeMultiplier() { if (!this.trackTxLandRate) { return 1; } return this.landRateToFeeFunc(this.getTxLandRate()); } } exports.BaseTxSender = BaseTxSender;