@debridge-finance/solana-utils
Version:
Common utils package to power communication with Solana contracts at deBridge
143 lines • 6.95 kB
JavaScript
import { __awaiter } from "tslib";
import { Keypair, PublicKey, Transaction, VersionedTransaction, } from "@solana/web3.js";
import { parseLogs, } from "@debridge-finance/solana-transaction-parser";
import { FAKE_BLOCKHASH } from "./constants";
import { TOKEN_PROGRAM_ID } from "./accounts";
import { parseSplAccount } from "./spl";
import { ContractErrors, RpcErrors } from "./interfaces";
export function simulateTransaction(connection, tx, simulateConfig) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h;
const addressesToGetDiffFor = ((_b = (_a = simulateConfig.accounts) === null || _a === void 0 ? void 0 : _a.addresses) !== null && _b !== void 0 ? _b : []).map((a) => new PublicKey(a));
const accountsToFetch = addressesToGetDiffFor.length > 0
? yield connection.getMultipleAccountsInfo(addressesToGetDiffFor)
: [];
const result = yield connection.simulateTransaction(tx, simulateConfig);
const diffMap = new Map();
for (const [idx, simulatedAccountInfo] of ((_c = result.value.accounts) !== null && _c !== void 0 ? _c : []).entries()) {
const accountAddress = addressesToGetDiffFor[idx];
const oldAccountData = (_d = accountsToFetch.at(idx)) !== null && _d !== void 0 ? _d : null;
const oldParsedMaybeSpl = (oldAccountData === null || oldAccountData === void 0 ? void 0 : oldAccountData.owner.equals(TOKEN_PROGRAM_ID))
? parseSplAccount(oldAccountData)
: null;
const oldSolValue = (_e = oldAccountData === null || oldAccountData === void 0 ? void 0 : oldAccountData.lamports) !== null && _e !== void 0 ? _e : 0;
const newSolValue = (_f = simulatedAccountInfo === null || simulatedAccountInfo === void 0 ? void 0 : simulatedAccountInfo.lamports) !== null && _f !== void 0 ? _f : 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 = Object.assign(Object.assign({}, simulatedAccountInfo), { owner: new PublicKey(simulatedAccountInfo.owner), data: Buffer.from(rawData, "base64") });
newParsedMaybeSpl = artificialAccountInfo.owner.equals(TOKEN_PROGRAM_ID)
? 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 === null || newParsedMaybeSpl === void 0 ? void 0 : newParsedMaybeSpl.mint) || (oldParsedMaybeSpl === null || oldParsedMaybeSpl === void 0 ? void 0 : oldParsedMaybeSpl.mint);
if (token === undefined)
throw new Error("Failed to get token from the spl token account");
const owner = (newParsedMaybeSpl === null || newParsedMaybeSpl === void 0 ? void 0 : newParsedMaybeSpl.owner) || (oldParsedMaybeSpl === null || oldParsedMaybeSpl === void 0 ? void 0 : oldParsedMaybeSpl.owner);
if (owner === undefined)
throw new Error("Failed to get owner from the spl token account");
splDiff = {
before: (_g = oldParsedMaybeSpl === null || oldParsedMaybeSpl === void 0 ? void 0 : oldParsedMaybeSpl.amount) !== null && _g !== void 0 ? _g : BigInt(0),
after: (_h = newParsedMaybeSpl === null || newParsedMaybeSpl === void 0 ? void 0 : newParsedMaybeSpl.amount) !== null && _h !== void 0 ? _h : BigInt(0),
token,
owner,
};
}
}
if (splDiff !== null || solDiff !== null) {
diffMap.set(accountAddress.toBase58(), {
native: solDiff,
spl: splDiff,
});
}
}
return Object.assign(Object.assign({}, result.value), { diff: diffMap });
});
}
/**
* Serializes transaction and returns serialized size
* @param transaction tx to get length
* @param feePayer optional fee payer
* @returns transaction size
*/
export function getTransactionSize(tx) {
try {
if ("version" in tx) {
const txCopy = new VersionedTransaction(tx.message);
txCopy.message.recentBlockhash = FAKE_BLOCKHASH;
return txCopy.serialize().length;
}
else {
const txCopy = new Transaction(tx);
txCopy.feePayer = Keypair.generate().publicKey;
txCopy.recentBlockhash = FAKE_BLOCKHASH;
return tx.serialize({ verifySignatures: false }).length;
}
}
catch (e) {
return null;
}
}
function getParsedError(log) {
if (/insufficient lamports/.test(log)) {
return ContractErrors.InsufficientLamports;
}
else if (/insufficient funds/.test(log)) {
return ContractErrors.InsufficientFunds;
}
else {
return undefined;
}
}
/**
* Parse logs
* @param logs array of strings
* @param err object or string
* @returns parsed logs
*/
export function getErrorData(logs, err) {
var _a, _b;
let parsedError;
const parsed = 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 = (_a = RpcErrors[err]) !== null && _a !== void 0 ? _a : err;
}
else if (typeof err === "object" && Object.keys(err).length) {
const key = Object.keys(err)[0];
parsedError = (_b = RpcErrors[key]) !== null && _b !== void 0 ? _b : key;
}
}
return { message, parsedError };
}
//# sourceMappingURL=transaction.js.map