@solana-developers/helpers
Version:
Solana helper functions
282 lines • 12.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIdlByPath = getIdlByPath;
exports.getIdlByProgramId = getIdlByProgramId;
exports.createProviderForConnection = createProviderForConnection;
exports.getIdlParsedAccountData = getIdlParsedAccountData;
exports.parseAnchorTransactionEvents = parseAnchorTransactionEvents;
exports.decodeAnchorTransaction = decodeAnchorTransaction;
const web3_js_1 = require("@solana/web3.js");
const anchor_1 = require("@coral-xyz/anchor");
const bn_js_1 = __importDefault(require("bn.js"));
const convertLegacyIdl_1 = require("./convertLegacyIdl");
/**
* Loads an Anchor IDL from a local file path
*
* @param idlPath - Path to the IDL JSON file
* @returns The parsed IDL
*
* @example
* ```typescript
* const idl = await getIdlByPath("./idl/program.json");
* ```
*/
async function getIdlByPath(idlPath) {
const fs = await Promise.resolve().then(() => __importStar(require("node:fs")));
const path = await Promise.resolve().then(() => __importStar(require("node:path")));
// Load and parse IDL file
const idlFile = fs.readFileSync(path.resolve(idlPath), "utf8");
const idl = JSON.parse(idlFile);
return idl;
}
/**
* Fetches an Anchor IDL from a program on-chain
*
* @param programId - Public key of the program
* @param provider - Anchor Provider instance (you can use createProviderForConnection to create)
* @returns The fetched IDL
* @throws If IDL cannot be found for the program
*
* @example
* ```typescript
* const idl = await getIdlByProgramId(
* new PublicKey("Foo1111111111111111111111111111111111111"),
* connection
* );
* ```
*/
async function getIdlByProgramId(programId, provider) {
var idl = await anchor_1.Program.fetchIdl(programId, provider);
if (!idl)
throw new Error(`IDL not found for program ${programId.toString()}`);
return idl;
}
/**
* Creates an Anchor provider for a given connection
*
* @param connection - The Solana connection object
* @param keypair - Optional keypair to use for the provider (defaults to a new random keypair)
* @param options - Optional configuration options for the provider
* @returns An Anchor provider instance
*
* @example
* ```typescript
* const provider = createProviderForConnection(connection);
* ```
*/
function createProviderForConnection(connection, keypair = new web3_js_1.Keypair(), options = {
commitment: "confirmed",
}) {
return new anchor_1.AnchorProvider(connection, new anchor_1.Wallet(keypair), options);
}
/**
* Fetches and parses an account's data using an Anchor IDL
*
* @param idl - The Anchor IDL (use getIdlByProgramId or getIdlByPath to obtain)
* @param accountName - The name of the account as defined in the IDL
* @param accountAddress - The public key of the account to fetch
* @param provider - Anchor Provider instance (you can use createProviderForConnection to create)
* @param programId - Optional program ID needed for legacy IDLs
* @returns The decoded account data
*
* @example
* ```typescript
* const idl = await getIdlByProgramId(programId, connection);
* const data = await getIdlParsedAccountData(idl, "counter", accountAddress);
* ```
*/
async function getIdlParsedAccountData(idl, accountName, accountAddress, provider, programId) {
const program = new anchor_1.Program((0, convertLegacyIdl_1.formatIdl)(idl, programId?.toString()), provider);
const accountInfo = await provider.connection.getAccountInfo(accountAddress);
if (!accountInfo) {
throw new Error(`Account ${accountAddress.toString()} not found`);
}
return program.coder.accounts.decode(accountName, accountInfo.data);
}
/**
* Parses Anchor events from a transaction
*
* @param idl - The Anchor IDL (use getIdlByProgramId or getIdlByPath to obtain)
* @param signature - Transaction signature to parse events from
* @param provider - Anchor Provider instance (you can use createProviderForConnection to create)
* @param programId - Optional program ID needed for legacy IDLs
* @returns Array of parsed events with their name and data
*
* @example
* ```typescript
* const idl = await getIdlByPath("./idl/program.json");
* const events = await parseAnchorTransactionEvents(idl, signature);
* ```
*/
async function parseAnchorTransactionEvents(idl, signature, provider, programId) {
const program = new anchor_1.Program((0, convertLegacyIdl_1.formatIdl)(idl, programId?.toString()), provider);
const parser = new anchor_1.EventParser(program.programId, program.coder);
const transaction = await provider.connection.getTransaction(signature, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
if (!transaction?.meta?.logMessages) {
return [];
}
const events = [];
for (const event of parser.parseLogs(transaction.meta.logMessages)) {
events.push({
name: event.name,
data: event.data,
});
}
return events;
}
/**
* Decodes all Anchor instructions and their involved accounts in a transaction
*
* @param idl - The Anchor IDL (use getIdlByProgramId or getIdlByPath to obtain)
* @param signature - Transaction signature to decode
* @param connection - Optional connection object (uses default provider if not specified)
* @param programId - Optional program ID needed for legacy IDLs
* @returns Decoded transaction with instructions and accounts
*
* @example
* ```typescript
* const idl = await getIdlByProgramId(programId, connection);
* const decoded = await decodeAnchorTransaction(idl, signature);
* ```
*/
async function decodeAnchorTransaction(idl, signature, provider, programId) {
const program = new anchor_1.Program((0, convertLegacyIdl_1.formatIdl)(idl, programId?.toString()), provider);
const accountsCoder = new anchor_1.BorshAccountsCoder(program.idl);
const instructionCoder = new anchor_1.BorshInstructionCoder(program.idl);
const transaction = await provider.connection.getTransaction(signature, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
if (!transaction) {
throw new Error(`Transaction ${signature} not found`);
}
const decodedInstructions = [];
const message = transaction.transaction.message;
const instructions = "addressTableLookups" in message
? message.compiledInstructions
: message.instructions;
const accountKeys = message.getAccountKeys();
for (const ix of instructions) {
const programId = accountKeys.get("programIdIndex" in ix
? ix.programIdIndex
: ix.programId);
if (!programId)
continue;
if (programId.equals(program.programId)) {
try {
const decoded = instructionCoder.decode(Buffer.from(ix.data));
if (decoded) {
const ixType = idl.instructions.find((i) => i.name === decoded.name);
const accountIndices = "accounts" in ix ? ix.accounts : ix.accountKeyIndexes;
// Get all accounts involved in this instruction
const accounts = await Promise.all(accountIndices.map(async (index, i) => {
const pubkey = accountKeys.get(index);
if (!pubkey)
return null;
const accountMeta = ixType?.accounts[i];
const accountInfo = await provider.connection.getAccountInfo(pubkey);
let accountData;
if (accountInfo?.owner.equals(program.programId)) {
try {
const accountType = idl.accounts?.find((acc) => accountInfo.data
.slice(0, 8)
.equals(accountsCoder.accountDiscriminator(acc.name.toLowerCase())));
if (accountType) {
accountData = accountsCoder.decode(accountType.name.toLowerCase(), accountInfo.data);
}
}
catch (e) {
console.log(`Failed to decode account data: ${e}`);
}
}
return {
name: accountMeta?.name || `account_${i}`,
pubkey: pubkey.toString(),
isSigner: message.staticAccountKeys.findIndex((k) => k.equals(pubkey)) <
message.header.numRequiredSignatures || false,
isWritable: message.isAccountWritable(index),
...(accountData && { data: accountData }),
};
}));
decodedInstructions.push({
name: decoded.name,
type: ixType ? JSON.stringify(ixType.args) : "unknown",
data: decoded.data,
accounts,
toString: function () {
let output = `\nInstruction: ${this.name}\n`;
output += `├─ Arguments: ${JSON.stringify(formatData(this.data))}\n`;
output += `└─ Accounts:\n`;
this.accounts.forEach((acc) => {
output += ` ├─ ${acc.name}:\n`;
output += ` │ ├─ Address: ${acc.pubkey}\n`;
output += ` │ ├─ Signer: ${acc.isSigner}\n`;
output += ` │ ├─ Writable: ${acc.isWritable}\n`;
if (acc.data) {
output += ` │ └─ Data: ${JSON.stringify(formatData(acc.data))}\n`;
}
});
return output;
},
});
}
}
catch (e) {
console.log(`Failed to decode instruction: ${e}`);
}
}
}
return {
instructions: decodedInstructions,
toString: function () {
let output = "\n=== Decoded Transaction ===\n";
this.instructions.forEach((ix, index) => {
output += `\nInstruction ${index + 1}:${ix.toString()}`;
});
return output;
},
};
}
// Helper function to format data
function formatData(data) {
if (data instanceof bn_js_1.default) {
return `<BN: ${data.toString()}>`;
}
if (Array.isArray(data)) {
return data.map(formatData);
}
if (typeof data === "object" && data !== null) {
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, formatData(v)]));
}
return data;
}
//# sourceMappingURL=idl.js.map