UNPKG

@drift-labs/sdk

Version:
349 lines (348 loc) • 14.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.asBN = exports.BankrunConnection = exports.BankrunContextWrapper = void 0; const web3_js_1 = require("@solana/web3.js"); const solana_bankrun_1 = require("solana-bankrun"); const anchor_bankrun_1 = require("anchor-bankrun"); const bs58_1 = __importDefault(require("bs58")); const anchor_1 = require("@coral-xyz/anchor"); const spl_token_1 = require("@solana/spl-token"); const utils_1 = require("../tx/utils"); class BankrunContextWrapper { constructor(context, verifySignatures = true) { this.commitment = 'confirmed'; this.context = context; this.provider = new anchor_bankrun_1.BankrunProvider(context); this.connection = new BankrunConnection(this.context.banksClient, this.context, verifySignatures); } async sendTransaction(tx, additionalSigners) { const isVersioned = (0, utils_1.isVersionedTransaction)(tx); if (!additionalSigners) { additionalSigners = []; } if (isVersioned) { tx = tx; tx.message.recentBlockhash = await this.getLatestBlockhash(); if (!additionalSigners) { additionalSigners = []; } tx.sign([this.context.payer, ...additionalSigners]); } else { tx = tx; tx.recentBlockhash = await this.getLatestBlockhash(); tx.feePayer = this.context.payer.publicKey; tx.sign(this.context.payer, ...additionalSigners); } return await this.connection.sendTransaction(tx); } async getMinimumBalanceForRentExemption(_) { return 10 * web3_js_1.LAMPORTS_PER_SOL; } async fundKeypair(keypair, lamports) { const ixs = [ web3_js_1.SystemProgram.transfer({ fromPubkey: this.context.payer.publicKey, toPubkey: keypair.publicKey, lamports, }), ]; const tx = new web3_js_1.Transaction().add(...ixs); return await this.sendTransaction(tx); } async getLatestBlockhash() { const blockhash = await this.connection.getLatestBlockhash('finalized'); return blockhash.blockhash; } printTxLogs(signature) { this.connection.printTxLogs(signature); } async moveTimeForward(increment) { const approxSlots = increment / 0.4; const slot = await this.connection.getSlot(); this.context.warpToSlot(BigInt(Math.floor(Number(slot) + approxSlots))); const currentClock = await this.context.banksClient.getClock(); const newUnixTimestamp = currentClock.unixTimestamp + BigInt(increment); const newClock = new solana_bankrun_1.Clock(currentClock.slot, currentClock.epochStartTimestamp, currentClock.epoch, currentClock.leaderScheduleEpoch, newUnixTimestamp); await this.context.setClock(newClock); } async setTimestamp(unix_timestamp) { const currentClock = await this.context.banksClient.getClock(); const newUnixTimestamp = BigInt(unix_timestamp); const newClock = new solana_bankrun_1.Clock(currentClock.slot, currentClock.epochStartTimestamp, currentClock.epoch, currentClock.leaderScheduleEpoch, newUnixTimestamp); await this.context.setClock(newClock); } } exports.BankrunContextWrapper = BankrunContextWrapper; class BankrunConnection { constructor(banksClient, context, verifySignatures = true) { this.transactionToMeta = new Map(); this.nextClientSubscriptionId = 0; this.onLogCallbacks = new Map(); this.onAccountChangeCallbacks = new Map(); this._banksClient = banksClient; this.context = context; this.verifySignatures = verifySignatures; } getSlot() { return this._banksClient.getSlot(); } toConnection() { return this; } async getTokenAccount(publicKey) { const info = await this.getAccountInfo(publicKey); return (0, spl_token_1.unpackAccount)(publicKey, info, info.owner); } async getMultipleAccountsInfo(publicKeys, _commitmentOrConfig) { const accountInfos = []; for (const publicKey of publicKeys) { const accountInfo = await this.getAccountInfo(publicKey); accountInfos.push(accountInfo); } return accountInfos; } async getAccountInfo(publicKey) { const parsedAccountInfo = await this.getParsedAccountInfo(publicKey); return parsedAccountInfo ? parsedAccountInfo.value : null; } async getAccountInfoAndContext(publicKey, _commitment) { return await this.getParsedAccountInfo(publicKey); } async sendRawTransaction(rawTransaction, // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types _options) { const tx = web3_js_1.Transaction.from(rawTransaction); const signature = await this.sendTransaction(tx); return signature; } async sendTransaction(tx) { const isVersioned = (0, utils_1.isVersionedTransaction)(tx); const serialized = isVersioned ? tx.serialize() : tx.serialize({ verifySignatures: this.verifySignatures, }); // @ts-ignore const internal = this._banksClient.inner; const inner = isVersioned ? await internal.tryProcessVersionedTransaction(serialized) : await internal.tryProcessLegacyTransaction(serialized); const banksTransactionMeta = new solana_bankrun_1.BanksTransactionResultWithMeta(inner); const signature = isVersioned ? bs58_1.default.encode(tx.signatures[0]) : bs58_1.default.encode(tx.signatures[0].signature); if (banksTransactionMeta.result) { if (!banksTransactionMeta.result .toString() .includes('This transaction has already been processed')) { throw new Error(banksTransactionMeta.result); } else { console.log(`Tx already processed (sig: ${signature}): ${JSON.stringify(banksTransactionMeta)}`, banksTransactionMeta, banksTransactionMeta.result); console.log(tx); } } if (!this.transactionToMeta.has(signature)) { this.transactionToMeta.set(signature, banksTransactionMeta); } let finalizedCount = 0; while (finalizedCount < 10) { const signatureStatus = (await this.getSignatureStatus(signature)).value .confirmationStatus; if (signatureStatus.toString() == '"finalized"') { finalizedCount += 1; } } // update the clock slot/timestamp // sometimes race condition causes failures so we retry try { await this.updateSlotAndClock(); } catch (e) { await this.updateSlotAndClock(); } if (this.onLogCallbacks.size > 0) { const transaction = await this.getTransaction(signature); const context = { slot: transaction.slot }; const logs = { logs: transaction.meta.logMessages, err: transaction.meta.err, signature, }; for (const logCallback of this.onLogCallbacks.values()) { logCallback(logs, context); } } for (const [publicKey, callback,] of this.onAccountChangeCallbacks.values()) { const accountInfo = await this.getParsedAccountInfo(publicKey); callback(accountInfo.value, accountInfo.context); } return signature; } async updateSlotAndClock() { const currentSlot = await this.getSlot(); const nextSlot = currentSlot + BigInt(1); this.context.warpToSlot(nextSlot); const currentClock = await this._banksClient.getClock(); const newClock = new solana_bankrun_1.Clock(nextSlot, currentClock.epochStartTimestamp, currentClock.epoch, currentClock.leaderScheduleEpoch, currentClock.unixTimestamp + BigInt(1)); this.context.setClock(newClock); this.clock = newClock; } getTime() { return Number(this.clock.unixTimestamp); } async getParsedAccountInfo(publicKey) { const accountInfoBytes = await this._banksClient.getAccount(publicKey); if (accountInfoBytes === null) { return { context: { slot: Number(await this._banksClient.getSlot()) }, value: null, }; } accountInfoBytes.data = Buffer.from(accountInfoBytes.data); const accountInfoBuffer = accountInfoBytes; return { context: { slot: Number(await this._banksClient.getSlot()) }, value: accountInfoBuffer, }; } async getLatestBlockhash(commitment) { const blockhashAndBlockheight = await this._banksClient.getLatestBlockhash(commitment); return { blockhash: blockhashAndBlockheight[0], lastValidBlockHeight: Number(blockhashAndBlockheight[1]), }; } async getAddressLookupTable(accountKey) { const { context, value: accountInfo } = await this.getParsedAccountInfo(accountKey); let value = null; if (accountInfo !== null) { value = new web3_js_1.AddressLookupTableAccount({ key: accountKey, state: web3_js_1.AddressLookupTableAccount.deserialize(accountInfo.data), }); } return { context, value, }; } async getSignatureStatus(signature, _config) { const transactionStatus = await this._banksClient.getTransactionStatus(signature); if (transactionStatus === null) { return { context: { slot: Number(await this._banksClient.getSlot()) }, value: null, }; } return { context: { slot: Number(await this._banksClient.getSlot()) }, value: { slot: Number(transactionStatus.slot), confirmations: Number(transactionStatus.confirmations), err: transactionStatus.err, confirmationStatus: transactionStatus.confirmationStatus, }, }; } /** * There's really no direct equivalent to getTransaction exposed by SolanaProgramTest, so we do the best that we can here - it's a little hacky. */ async getTransaction(signature, _rawConfig) { const txMeta = this.transactionToMeta.get(signature); if (txMeta === undefined) { return null; } const transactionStatus = await this._banksClient.getTransactionStatus(signature); if (txMeta.meta === null) { throw new Error(`tx has no meta: ${JSON.stringify(txMeta)}`); } const meta = { logMessages: txMeta.meta.logMessages, err: txMeta.result, }; return { slot: Number(transactionStatus.slot), meta, }; } findComputeUnitConsumption(signature) { const txMeta = this.transactionToMeta.get(signature); if (txMeta === undefined) { throw new Error('Transaction not found'); } return txMeta.meta.computeUnitsConsumed; } printTxLogs(signature) { const txMeta = this.transactionToMeta.get(signature); if (txMeta === undefined) { throw new Error('Transaction not found'); } console.log(txMeta.meta.logMessages); } async simulateTransaction(transaction, _config) { var _a, _b, _c, _d; const simulationResult = await this._banksClient.simulateTransaction(transaction); const returnDataProgramId = (_b = (_a = simulationResult.meta) === null || _a === void 0 ? void 0 : _a.returnData) === null || _b === void 0 ? void 0 : _b.programId.toBase58(); const returnDataNormalized = Buffer.from((_d = (_c = simulationResult.meta) === null || _c === void 0 ? void 0 : _c.returnData) === null || _d === void 0 ? void 0 : _d.data).toString('base64'); const returnData = { programId: returnDataProgramId, data: [returnDataNormalized, 'base64'], }; return { context: { slot: Number(await this._banksClient.getSlot()) }, value: { err: simulationResult.result, logs: simulationResult.meta.logMessages, accounts: undefined, unitsConsumed: Number(simulationResult.meta.computeUnitsConsumed), returnData, }, }; } onSignature(signature, callback, commitment) { const txMeta = this.transactionToMeta.get(signature); this._banksClient.getSlot(commitment).then((slot) => { if (txMeta) { callback({ err: txMeta.result }, { slot: Number(slot) }); } }); return 0; } async removeSignatureListener(_clientSubscriptionId) { // Nothing actually has to happen here! Pretty cool, huh? // This function signature only exists to match the web3js interface } onLogs(filter, callback, _commitment) { const subscriptId = this.nextClientSubscriptionId; this.onLogCallbacks.set(subscriptId, callback); this.nextClientSubscriptionId += 1; return subscriptId; } async removeOnLogsListener(clientSubscriptionId) { this.onLogCallbacks.delete(clientSubscriptionId); } onAccountChange(publicKey, callback, // @ts-ignore _commitment) { const subscriptId = this.nextClientSubscriptionId; this.onAccountChangeCallbacks.set(subscriptId, [publicKey, callback]); this.nextClientSubscriptionId += 1; return subscriptId; } async removeAccountChangeListener(clientSubscriptionId) { this.onAccountChangeCallbacks.delete(clientSubscriptionId); } async getMinimumBalanceForRentExemption(_) { return 10 * web3_js_1.LAMPORTS_PER_SOL; } } exports.BankrunConnection = BankrunConnection; function asBN(value) { return new anchor_1.BN(Number(value)); } exports.asBN = asBN;