@drift-labs/sdk
Version:
SDK for Drift Protocol
349 lines (348 loc) • 14.5 kB
JavaScript
"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;