UNPKG

@debridge-finance/solana-utils

Version:

Common utils package to power communication with Solana contracts at deBridge

210 lines 8.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Wallet = void 0; exports.getAccountInfo = getAccountInfo; exports.hexToBuffer = hexToBuffer; exports.bufferToHex = bufferToHex; exports.sleep = sleep; exports.sendAll = sendAll; const buffer_1 = require("buffer"); const web3_js_1 = require("@solana/web3.js"); const constants_1 = require("./constants"); async function getAccountInfo(connection, account, commitment = "confirmed") { const info = await connection.getAccountInfo(account, commitment); if (info) { // alchemy bug fix if (info.lamports === 0) return null; return info; } else { return null; } } function hexToBuffer(data, align) { if (!data.startsWith("0x")) throw new Error("[hexToBuffer]: string must start with 0x"); data = data.slice(2); // remove 0x if (!data) return buffer_1.Buffer.from([]); const bufferLen = data.length / 2; data = data.length % 2 ? "0" + data : data; // append leading zero if data is not aligned, eg "fff" => "0f ff" align = align || 0; if (align && bufferLen < align) { return buffer_1.Buffer.concat([ buffer_1.Buffer.from(Uint8Array.from({ length: align - bufferLen }).fill(0)), buffer_1.Buffer.from(data, "hex"), ]); } return buffer_1.Buffer.from(data, "hex"); } function bufferToHex(data) { return `0x${buffer_1.Buffer.from(data).toString("hex")}`; } async function sleep(milliSeconds) { return new Promise((resolve, reject) => { setTimeout(resolve, milliSeconds); }); } function isVersionedTx(arg) { return "version" in arg; } class Wallet { constructor(keypair) { this.keypair = keypair; this.publicKey = keypair.publicKey; } async signTransaction(tx, additionalSigners) { if (!isVersionedTx(tx)) { tx.sign(this.keypair, ...(additionalSigners || [])); } else { tx.sign([this.keypair, ...(additionalSigners || [])]); } return Promise.resolve(tx); } async signAllTransactions(txs, additionalSigners) { const promises = []; for (const [id, tx] of txs.entries()) { promises.push(this.signTransaction(tx, additionalSigners && additionalSigners.length > id ? additionalSigners[id] : [])); } return Promise.all(promises); } } exports.Wallet = Wallet; async function sendWithRetries(connection, serialized, lastValidBlockHeight, timesToSend = 5, blockhashCommitment = "finalized") { const txId = await connection.sendRawTransaction(serialized, { skipPreflight: false, // never skip preflight preflightCommitment: blockhashCommitment, // use specific preflight commitment }); } function txToV0(transaction, payer, blockhash) { if (isVersionedTx(transaction)) { return transaction; } else { const v0Message = web3_js_1.MessageV0.compile({ instructions: transaction.instructions, payerKey: transaction.feePayer || payer, recentBlockhash: transaction.recentBlockhash || blockhash, }); return new web3_js_1.VersionedTransaction(v0Message); } } function setBlockhash(transaction, blockhash) { if (isVersionedTx(transaction)) { transaction.message.recentBlockhash = blockhash; } else { transaction.recentBlockhash = blockhash; } } async function simulateTx(connection, tx, commitment, feePayer, convertToV0, alreadySigned) { let convert = convertToV0 && !alreadySigned; let txCopy = isVersionedTx(tx) ? new web3_js_1.VersionedTransaction(tx.message, tx.signatures) : new web3_js_1.Transaction(tx); if (convert) { if (isVersionedTx(txCopy)) { txCopy.message.recentBlockhash = constants_1.FAKE_BLOCKHASH; txCopy = tx; } else { txCopy = new web3_js_1.VersionedTransaction(web3_js_1.MessageV0.compile({ addressLookupTableAccounts: [], instructions: txCopy.instructions, recentBlockhash: constants_1.FAKE_BLOCKHASH, payerKey: feePayer, })); } } let result; if (isVersionedTx(txCopy)) { result = await connection.simulateTransaction(txCopy, { commitment, sigVerify: alreadySigned, replaceRecentBlockhash: !alreadySigned, }); } else { txCopy.recentBlockhash = constants_1.FAKE_BLOCKHASH; result = await connection.simulateTransaction(txCopy.compileMessage()); } if (result.value.err !== null) { throw new Error(`Tx simulation Error! Transaction message dump: ${buffer_1.Buffer.from(txCopy.serialize()).toString("base64")}\n Error: ${JSON.stringify(result.value.err)}, logs:\n ${result.value.logs?.join("\n")}`); } } /** * * @param connection rpc connection * @param wallet wallet interface to sign transaction * @param transactions transaction(s) to send * @param options additional {@link SendAllOptions|options} * @returns */ async function sendAll(connection, wallet, transactions, options) { const skipSign = options?.skipSign ?? false; const blockhashCommitment = options?.blockhashCommitment === undefined ? (connection.commitment ?? "confirmed") : options.blockhashCommitment; const simulationCommitment = options?.simulationCommtiment ?? blockhashCommitment; const useMinContextSlot = options?.useMinContextSlot ?? false; const convertIntoTxV0 = options?.convertIntoTxV0 ?? true; const skipPreflight = options?.skipPreflight ?? false; if (!Array.isArray(transactions)) transactions = [transactions]; if (convertIntoTxV0) { transactions = transactions.map((tx) => txToV0(tx, wallet.publicKey, constants_1.FAKE_BLOCKHASH)); } let txIds = []; if (options?.waitBetweenSend === undefined || options.waitBetweenSend === 0) { if (!skipPreflight) { await Promise.all(transactions.map((tx) => simulateTx(connection, tx, simulationCommitment, wallet.publicKey, convertIntoTxV0, skipSign))); } const blockhashResponse = await connection.getLatestBlockhashAndContext({ commitment: blockhashCommitment, }); let signed = transactions; if (!skipSign) { // set real blockhash transactions.map((tx) => setBlockhash(tx, blockhashResponse.value.blockhash)); // sign after setting correct blockhashes signed = await wallet.signAllTransactions(transactions, options?.signers); } txIds = await Promise.all(signed.map((signedTx) => { const serialized = buffer_1.Buffer.from(signedTx.serialize()).toString("base64"); options?.logger?.(`Sending tx: ${serialized}`); return connection.sendEncodedTransaction(serialized, { preflightCommitment: simulationCommitment, skipPreflight: false, minContextSlot: useMinContextSlot ? blockhashResponse.context.slot + 1 : undefined, }); })); } else { for (let i = 0; i < transactions.length; i++) { const tx = transactions[i]; const signed = skipSign ? tx : await wallet.signTransaction(tx, options?.signers && options?.signers.length > i ? options.signers[i] : undefined); if (!skipPreflight) { await simulateTx(connection, tx, simulationCommitment, wallet.publicKey, convertIntoTxV0, skipSign); } const serialized = buffer_1.Buffer.from(signed.serialize()).toString("base64"); options?.logger?.(`Sending tx: ${serialized}`); txIds.push(await connection.sendEncodedTransaction(serialized, { preflightCommitment: simulationCommitment, skipPreflight: false, })); await sleep(options.waitBetweenSend); } } return txIds; } //# sourceMappingURL=helpers.js.map