@debridge-finance/solana-utils
Version:
Common utils package to power communication with Solana contracts at deBridge
150 lines • 5.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.simulateTransaction = simulateTransaction;
exports.getTransactionSize = getTransactionSize;
exports.getErrorData = getErrorData;
const web3_js_1 = require("@solana/web3.js");
const solana_transaction_parser_1 = require("@debridge-finance/solana-transaction-parser");
const constants_1 = require("./constants");
const accounts_1 = require("./accounts");
const spl_1 = require("./spl");
const interfaces_1 = require("./interfaces");
async function simulateTransaction(connection, tx, simulateConfig) {
const addressesToGetDiffFor = (simulateConfig.accounts?.addresses ?? []).map((a) => new web3_js_1.PublicKey(a));
const accountsToFetch = addressesToGetDiffFor.length > 0
? await connection.getMultipleAccountsInfo(addressesToGetDiffFor)
: [];
const result = await connection.simulateTransaction(tx, simulateConfig);
const diffMap = new Map();
for (const [idx, simulatedAccountInfo] of (result.value.accounts ?? []).entries()) {
const accountAddress = addressesToGetDiffFor[idx];
const oldAccountData = accountsToFetch.at(idx) ?? null;
const oldParsedMaybeSpl = oldAccountData?.owner.equals(accounts_1.TOKEN_PROGRAM_ID)
? (0, spl_1.parseSplAccount)(oldAccountData)
: null;
const oldSolValue = oldAccountData?.lamports ?? 0;
const newSolValue = simulatedAccountInfo?.lamports ?? 0;
const solDiff = oldSolValue === newSolValue
? null
: {
before: oldSolValue,
after: newSolValue,
};
let newParsedMaybeSpl = null;
if (simulatedAccountInfo) {
const [rawData, encoding] = simulatedAccountInfo.data;
if (encoding !== "base64")
throw new Error("Expected bs64 account data");
const artificialAccountInfo = {
...simulatedAccountInfo,
owner: new web3_js_1.PublicKey(simulatedAccountInfo.owner),
data: Buffer.from(rawData, "base64"),
};
newParsedMaybeSpl = artificialAccountInfo.owner.equals(accounts_1.TOKEN_PROGRAM_ID)
? (0, spl_1.parseSplAccount)(artificialAccountInfo)
: null;
}
let splDiff = null;
// old state is spl token account OR new state is spl token account
if (oldParsedMaybeSpl !== null || newParsedMaybeSpl !== null) {
if (oldParsedMaybeSpl !== newParsedMaybeSpl) {
const token = newParsedMaybeSpl?.mint || oldParsedMaybeSpl?.mint;
if (token === undefined)
throw new Error("Failed to get token from the spl token account");
const owner = newParsedMaybeSpl?.owner || oldParsedMaybeSpl?.owner;
if (owner === undefined)
throw new Error("Failed to get owner from the spl token account");
splDiff = {
before: oldParsedMaybeSpl?.amount ?? BigInt(0),
after: newParsedMaybeSpl?.amount ?? BigInt(0),
token,
owner,
};
}
}
if (splDiff !== null || solDiff !== null) {
diffMap.set(accountAddress.toBase58(), {
native: solDiff,
spl: splDiff,
});
}
}
return {
...result.value,
diff: diffMap,
};
}
/**
* Serializes transaction and returns serialized size
* @param transaction tx to get length
* @param feePayer optional fee payer
* @returns transaction size
*/
function getTransactionSize(tx) {
try {
if ("version" in tx) {
const txCopy = new web3_js_1.VersionedTransaction(tx.message);
txCopy.message.recentBlockhash = constants_1.FAKE_BLOCKHASH;
return txCopy.serialize().length;
}
else {
const txCopy = new web3_js_1.Transaction(tx);
txCopy.feePayer = web3_js_1.Keypair.generate().publicKey;
txCopy.recentBlockhash = constants_1.FAKE_BLOCKHASH;
return tx.serialize({ verifySignatures: false }).length;
}
}
catch (e) {
return null;
}
}
function getParsedError(log) {
if (/insufficient lamports/.test(log)) {
return interfaces_1.ContractErrors.InsufficientLamports;
}
else if (/insufficient funds/.test(log)) {
return interfaces_1.ContractErrors.InsufficientFunds;
}
else {
return undefined;
}
}
/**
* Parse logs
* @param logs array of strings
* @param err object or string
* @returns parsed logs
*/
function getErrorData(logs, err) {
let parsedError;
const parsed = (0, solana_transaction_parser_1.parseLogs)(logs).filter((context) => context.errors.length);
const errorData = parsed.map((context) => ({
logMessages: context.logMessages,
errors: context.errors,
instructionIndex: context.instructionIndex,
}));
const message = errorData.reduce((string, context) => {
if (context.errors.length) {
string += `errors: ${JSON.stringify(context.errors)}, `;
}
if (context.logMessages.length) {
string += `messages: ${JSON.stringify(context.logMessages)}, `;
}
string += `instruction index: ${context.instructionIndex}\n`;
return string;
}, "");
if (message) {
parsedError = getParsedError(message);
}
else {
if (typeof err === "string") {
parsedError = interfaces_1.RpcErrors[err] ?? err;
}
else if (typeof err === "object" && Object.keys(err).length) {
const key = Object.keys(err)[0];
parsedError = interfaces_1.RpcErrors[key] ?? key;
}
}
return { message, parsedError };
}
//# sourceMappingURL=transaction.js.map