@debridge-finance/solana-utils
Version:
Common utils package to power communication with Solana contracts at deBridge
210 lines • 8.24 kB
JavaScript
;
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