生成的文件
目录
安装
-
通过国家预防机制:
npm i @debridge-finance/solana-transaction-parser -
Vi:
npm i git+https://github.com/debridge-finance/solana-tx-parser-public。 。 。 . -
手动软件包. json 编辑 : 依赖/依赖部分 - >
"@debridge-finance/solana-transaction-parser": "github:debridge-finance/solana-tx-parser-public"然后运行npm i
此工具可用于
- 使用锁定 IDL/ Custom 解析器解析 solana 指令 .
- 解析
SystemProgram, (中文(简体) ),TokenProgram, (中文(简体) ),AssociatedTokenProgram框中的指示. - 分析交易日志.
- 将分解交易/编译交易/base64编码交易转换为交易指令列表并解析.
- 将消费物价指数交易与消费物价指数交易合并成人工交易,包括作为通常的交易指令.
如何使用
由IDL解析
第一步:输入解析器
import { Idl } from "@project-serum/anchor";
import { PublicKey, Connection } from "@solana/web3.js";
import { SolanaParser } from "@debridge-finance/solana-transaction-parser";
import { IDL as JupiterIdl, Jupiter } from "./idl/jupiter"; // idl and types file generated by Anchor
const rpcConnection = new Connection("https://jupiter.genesysgo.net");
const txParser = new SolanaParser([{ idl: JupiterIdl as unknown as Idl, programId: "JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo" }]);
第二步: 由 tx 散列分析交易 :
const parsed = await txParser.parseTransaction(
rpcConnection,
"5zgvxQjV6BisU8SfahqasBZGfXy5HJ3YxYseMBG7VbR4iypDdtdymvE1jmEMG7G39bdVBaHhLYUHUejSTtuZEpEj",
false,
);
呜! 我们有一份交易指令清单,其中有指令名称、参数和账户.
// we can find instruction by name
const tokenSwapIx = parsed?.find((pix) => pix.name === "tokenSwap");
// or just use index
const setTokenLedgerIx = parsed[0] as ParsedIdlInstruction<Jupiter, "setTokenLedger">;
用自定义解析器解析
如果我们想要分析的指令不属于Anchor程序呢?
我们可以给索拉纳帕瑟提供定制解析器!
自定义解析器需要以下接口 : (instruction: TransactionInstruction): ParsedCustomInstruction。 。 。 .
我们实施了一个小程序,用于测试,可以执行两个动作: 总结两个数字(通过为u64数字) OR
打印为日志提供了数据(通过ASCII代码和账户列表中的第二个账户).
| 第一个字节 | 行动 |
|---|---|
| 0 个 | 将所余数据打印为文本 + 字符串“ 从:” + second counter pubkey 作为基数58 |
| 1个 | 汇总两个数字并打印结果到日志 |
在这种情况下,Parser会非常简单:
function customParser(instruction: TransactionInstruction): ParsedCustomInstruction {
let args: unknown;
let keys: ParsedAccount[];
let name: string;
switch (instruction.data[0]) {
case 0:
args = { message: instruction.data.slice(1).toString("utf8") };
keys = [instruction.keys[0], { name: "messageFrom", ...instruction.keys[1] }];
name = "echo";
break;
case 1:
args = { a: instruction.data.readBigInt64LE(1), b: instruction.data.readBigInt64LE(9) };
keys = instruction.keys;
name = "sum";
break;
default:
throw new Error("unknown instruction!");
}
return {
programId: instruction.programId,
accounts: keys,
args,
name,
};
}
现在我们需要输入 SolanaParser 对象( 它包含不同的解析器, 可以解析指定程序的指示)
const parser = new SolanaParser([]);
parser.addParser(new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"), customParser);
UPD: 由于Solana devnet交易在一段时间后无法使用, 这里的代码可用于测试交易
import { Connection, Keypair, PublicKey, Transaction, TransactionInstruction, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { Wallet } from "@project-serum/anchor";
function echoIx(msg: string, from: PublicKey) {
return new TransactionInstruction({
programId: new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"),
data: Buffer.concat([Buffer.from([0]), Buffer.from(msg)]),
keys: [
{ isSigner: true, isWritable: false, pubkey: from },
{ isSigner: false, isWritable: false, pubkey: new Keypair().publicKey },
],
});
}
function addIx(a: bigint, b: bigint, signer: PublicKey) {
const data: Buffer = Buffer.alloc(2 * 8 + 1);
data[0] = 1;
data.writeBigInt64LE(a, 1);
data.writeBigInt64LE(b, 9);
return new TransactionInstruction({
programId: new PublicKey("5wZA8owNKtmfWGBc7rocEXBvTBxMtbpVpkivXNKXNuCV"),
data,
keys: [
{ isSigner: true, isWritable: false, pubkey: signer },
{ isSigner: false, isWritable: false, pubkey: new Keypair().publicKey },
],
});
}
const tx = new Transaction().add(echoIx("some test msg", kp.publicKey), addIx(555n, 1234n, kp.publicKey));
// send tx using devnet connection and your wallet
要分析包含此指令的交易,只需调用 parseTransaction 解析器对象上的方法
const connection = new Connection(clusterApiUrl("devnet"));
const parsed = await parser.parseTransaction(connection, "5xEeZMdrWVG7i8Fbcbu718FtbgSbXsK9c4GPBv21W2e35vP4DdkVghqH4p8dPKdmroUzNe2mBkctm4RAxaVbo78G");
// check if no errors was produced during parsing
if (!parsed) throw new Error("failed to get tx/parse!");
console.log(parsed[0].name); // will print "echo"
更多细节
该项目的主要职能是:
- 指令去区域化
- 平坦 用CPI调用的交易
- 解析交易日志
指令去序列化
这部分很简单:我们有一些 const mapping = Map<string, Parser> 其中按键为程序 ids,而值为对应程序 Id 的解析器 -- -- 函数将交易指令作为输入并返回解析指令(解析后的数据、指令名称和账户元[有名称]).
我们可以去死 交易指令 使用 Anchor 的 IDL 或自定义 解析器, 因此我们可以将包含( 或可转换成) 的 全部解析 交易指示: 交易, 解析交易, 交易回复, 解析确认的交易, 信件, 编译信, 编译指令或电线交易等.
分析步骤如下:
- 将输入转换为交易指令, 让它命名 页:1 (不同的功能需要调用不同的输入格式)
- 查找
ix.programId在绘图。 如果解析器存在通过 页:1 对它 - 检查解析指令名, 使用通用名设置正确的数据类型
消费物价指数平整
函数 : 平移反应
只能用交易回复/支持交易 使用Meta 对象,因为我们需要 transaction.meta.innerInstructions 字段.
transaction.meta.innerInstructions 是下列结构对象的列表:
export type CompiledInnerInstruction = {
index: number, // index of instruction in Transaction object which produced CPI,
instructions: CompiledInstruction[] // ordered list of instructions which were called after instruction with index *index*
}
我们创造人工 const result: Transaction = new Transaction(); 包含交易响应+CPI调用的所有指令的对象:
添加第一个交易指令以得出结果,检查指数=0的CPI呼叫是否存在,添加到结果,移动到下一个指令,重复.
最后,我们检查一下 result.instructions.length === input.instructions.length + total number of CPI instructions。 。 。 .
我们可以调用结果指数。 指令 调用 - 整个交易的通话索引 一样 调用 将用于日志部分
分析交易日志
函数 : 解析日志
与索拉纳的日志合作并不是一项微不足道的任务——确定哪些程序释放了当前日志行,我们必须恢复调用堆栈,检查调用深度并设定正确 调用 每个日志行。 paraseLogs 函数执行所有那些东西( 带有调用深度和调用 ID 检查) :
- 日志上的斜体
- 使用 regex 检查日志类型( invoke/ return/error/program log/program 数据)
- 根据日志类型执行动作 :
- Invoke: init并保存包含程序 id,调用指令深度的新上下文对象, 调用, 交易中生成日志( 深度 = 0) 或当前 CPI 调用( 深度 = 0) 的指示索引, 保存调用堆栈( 将当前调用为堆栈)
- 返回成功/ 失败: 从调用堆栈中调用 pop caller id, 当前调用Id = 弹出调用Id
- 程序日志/程序数据/程序消耗:将日志保存到上下文对象中 调用 === 翻译:
- 返回上下文对象列表
一起利用一切东西
import { ourIdl } from "programIdl";
const parser = new SolanaParser([{programId: "someBase58Address", idl: ourIdl}]);
const flattened = flattenTransactionResponse(response);
const logs = parseLogs(response.meta.logs || []);
const parsed = flattened.map((ix) => parser.parse(ix));
const callId = parsed.findIndex( (parsedIx) => parsedIx.name === "someInstruction" );
if (callId === -1) return Promise.reject("instruction not found");
const someInstruction = parsed[callId];
const someInstructionLogs = logs.find((context) => context.id === callId);
const onlyNeededProgramLogs = logs.filter((context) => context.programId === "someBase58Address");
