Say I want to parse a transaction fetched from chain, and get the specifics of the input accounts, data and output of the instructions in that transaction. How can I do this in JavaScript? Given the program was developed with Anchor and I have the IDL, of course.
4 Answers
Just figured this out by hacking around (and stole inspiration from Saber's anchor-contrib):
import { BorshCoder } from '@project-serum/anchor';
import { IDL } from "path/to/idl";
const tx = await conn.getTransaction(sig);
const coder = new BorshCoder(IDL);
const ix = coder.instruction.decode(
tx.transaction.message.instructions[0].data,
'base58',
);
if(!ix) throw new Error("could not parse data");
const accountMetas = tx.transaction.message.instructions[0].map(
(idx) => ({
pubkey: tx.transaction.message.accountKeys[idx],
isSigner: tx.transaction.message.isAccountSigner(idx),
isWritable: tx.transaction.message.isAccountWritable(idx),
}),
);
const formatted = coder.instruction.format(ix, accountMetas);
console.log(ix, formatted);
This should give you:
//ix
{
data: {
foo1: {
foo2: [Object],
foo3: [Object],
foo4: <BN: df09b1110>,
foo5: <BN: c4372ef2>,
foo6: false,
foo7: null
}
},
name: 'initFoo'
}
// formatted
{
args: [
{
name: 'foo1',
type: 'FooObj',
data: '{ foo2: ..., foo3: ..., foo4: 59871269136, foo5: 3291950834, foo6: false, foo7: null }'
}
],
accounts: [
{
name: 'Account Foo',
pubkey: [PublicKey],
isSigner: true,
isWritable: true
},
{
name: 'Account Bar',
pubkey: [PublicKey],
isSigner: true,
isWritable: true
},
...
{
name: 'System Program',
pubkey: [PublicKey],
isSigner: false,
isWritable: false
},
{
name: undefined,
pubkey: [PublicKey],
isSigner: false,
isWritable: false
}
]
}
AllBlooming's answer was useful, but like Henry said in the comments it doesn't answer the core question of how to parse the transaction.
I found out that the only way you can reliably do that is emit an Anchor event from the instruction with all the data you might need, and then get the events later on. Below is the code I'm currently using to do that
// Get past events emitted by a program
const getPastEvents = async <T extends anchor.Idl>(
program: anchor.Program<T>,
signaturesForAddressOptions?: anchor.web3.SignaturesForAddressOptions
): Promise<TransactionEvents[]> => {
// Create the event parser, which parses logs into structured events
const eventParser = new anchor.EventParser(
program.programId,
program.coder
);
// Get all transactions which include the program ID
const signatures = await connection.getSignaturesForAddress(
program.programId,
signaturesForAddressOptions
);
const transactionOrNulls = await connection.getTransactions(
signatures.map((x) => x.signature)
);
// Signatures for which getTransaction returned null
const problemTransactions = transactionOrNulls
.map((tx, index) => (tx ? null : signatures[index].signature))
.filter(isNotNullOrUndefined);
if (problemTransactions.length) {
throw Error(
'Details for the following transactions were not found: ' +
problemTransactions.join(', ')
);
}
// All the transactions that we could fetch
const transactions = transactionOrNulls.filter(isNotNullOrUndefined);
// Parse the transactions logs into events
const events: TransactionEvents[] = transactions
.map((tx) => {
const logs = tx.meta?.logMessages;
return logs
? {
transaction: tx.transaction.signatures[0],
timestamp: tx.blockTime,
events: [...eventParser.parseLogs(logs)],
}
: null;
})
.filter(isNotNullOrUndefined);
return events;
};
There is a nice overview of how to do that here.
That page is Quicknode branded, but it works just as well without Quicknode ;-).
Basically, you use getSignaturesForAddress
, getTransactions
, getParsedTransaction
, and you can access the transactionDetails
.
It's all documented in the official @solana/web3.js docs. For your purposes, you can start at the getSignaturesForAddress page and click your way from there to drill down deeper.
-
2This doesn't quite answer the part of the question about how to parse a transaction using the anchor IDL– Henry ECommented Aug 6, 2022 at 23:32
-
Building on @Richard's example, this will try and fetch the idl for you:
const transaction =
VersionedTransaction.deserialize(swapTransactionBuf);
const wallet = new Wallet(Keypair.generate());
const provider = new AnchorProvider(connection, wallet, {});
for (const compiledInstruction of transaction.message
.compiledInstructions) {
const programId =
transaction.message.staticAccountKeys[
compiledInstruction.programIdIndex
].toBase58();
const idl = await Program.fetchIdl(programId, provider);
if (!idl) {
console.log("No IDL found for program:", programId);
continue;
}
const coder = new BorshCoder(idl);
const ix = coder.instruction.decode(
new Buffer(compiledInstruction.data),
"base58",
);
console.log("IDL instruction: ", ix);
}