Skip to content

Commit ab4b762

Browse files
md0xReinis-FRP
andauthored
feat(svm): query events script (#804)
* feat(svm): add query events script Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * refactor: organize Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * feat: unused import Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * feat(svm): parse ids Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * Update src/svm/solanaProgramUtils.ts Co-authored-by: Reinis Martinsons <77973553+Reinis-FRP@users.noreply.github.com> * fix: starts with Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * refactor: comment Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> * refactor: fix comment Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> --------- Signed-off-by: Pablo Maldonado <pablomaldonadoturci@gmail.com> Co-authored-by: Reinis Martinsons <77973553+Reinis-FRP@users.noreply.github.com>
1 parent 4bb8383 commit ab4b762

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

Anchor.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ wallet = "test/svm/keys/localnet-wallet.json"
2222

2323
[scripts]
2424
test = "anchor run generateExternalTypes && yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/svm/**/*.ts"
25+
queryEvents = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryEvents.ts"
2526
initialize = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/initialize.ts"
2627
queryState = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryState.ts"
2728
enableRoute = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/enableRoute.ts"

scripts/svm/queryEvents.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// This script queries the events of the spoke pool and prints them in a human readable format.
2+
import * as anchor from "@coral-xyz/anchor";
3+
import { AnchorProvider, Program } from "@coral-xyz/anchor";
4+
import yargs from "yargs";
5+
import { hideBin } from "yargs/helpers";
6+
import { readProgramEvents, stringifyCpiEvent } from "../../src/svm";
7+
import { SvmSpoke } from "../../target/types/svm_spoke";
8+
9+
// Set up the provider
10+
const provider = AnchorProvider.env();
11+
anchor.setProvider(provider);
12+
const idl = require("../../target/idl/svm_spoke.json");
13+
const program = new Program<SvmSpoke>(idl, provider);
14+
const programId = program.programId;
15+
console.log("SVM-Spoke Program ID:", programId.toString());
16+
17+
const argvPromise = yargs(hideBin(process.argv)).option("eventName", {
18+
type: "string",
19+
demandOption: false,
20+
describe: "Name of the event to query",
21+
choices: [
22+
"any",
23+
"filledV3Relay",
24+
"v3FundsDeposited",
25+
"enabledDepositRoute",
26+
"relayedRootBundle",
27+
"executedRelayerRefundRoot",
28+
"bridgedToHubPool",
29+
"pausedDeposits",
30+
"pausedFills",
31+
"setXDomainAdmin",
32+
"emergencyDeletedRootBundle",
33+
"requestedV3SlowFill",
34+
"claimedRelayerRefund",
35+
"tokensBridged",
36+
],
37+
}).argv;
38+
39+
async function queryEvents(): Promise<void> {
40+
const argv = await argvPromise;
41+
const eventName = argv.eventName || "any";
42+
const events = await readProgramEvents(provider.connection, program, "confirmed");
43+
const filteredEvents = events.filter((event) => (eventName == "any" ? true : event.name == eventName));
44+
const formattedEvents = filteredEvents.map((event) => stringifyCpiEvent(event));
45+
console.log(JSON.stringify(formattedEvents, null, 2));
46+
}
47+
48+
// Run the queryEvents function
49+
queryEvents();

src/svm/solanaProgramUtils.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Idl, Program, utils, web3 } from "@coral-xyz/anchor";
1+
import { BN, Idl, Program, utils, web3 } from "@coral-xyz/anchor";
22
import {
33
ConfirmedSignatureInfo,
44
Connection,
@@ -8,6 +8,8 @@ import {
88
SignaturesForAddressOptions,
99
} from "@solana/web3.js";
1010
import { EventType } from "../types/svm";
11+
import { publicKeyToEvmAddress } from "./conversionUtils";
12+
import { deserialize } from "borsh";
1113

1214
/**
1315
* Finds a program address with a given label and optional extra seeds.
@@ -189,3 +191,69 @@ export async function subscribeToCpiEventsForProgram(
189191

190192
return subscriptionId;
191193
}
194+
195+
/**
196+
* Class for DepositId.
197+
*/
198+
class DepositId {
199+
value: Uint8Array; // Fixed-length array as Uint8Array
200+
201+
constructor(properties: { value: Uint8Array }) {
202+
this.value = properties.value;
203+
}
204+
}
205+
206+
/**
207+
* Borsh schema for deserializing DepositId.
208+
*/
209+
const depositIdSchema = new Map(
210+
[[DepositId, { kind: "struct", fields: [["value", [32]]] }]] // Fixed array [u8; 32]
211+
);
212+
213+
/**
214+
* Parses depositId: checks if only the first 4 bytes are non-zero and returns a u32 or deserializes the full array.
215+
*/
216+
function parseDepositId(value: Uint8Array): string {
217+
const restAreZero = value.slice(4).every((byte) => byte === 0);
218+
219+
if (restAreZero) {
220+
// Parse the first 4 bytes as a little-endian u32
221+
const u32Value = new DataView(value.buffer).getUint32(0, true); // true for little-endian
222+
return u32Value.toString();
223+
}
224+
225+
// Deserialize the full depositId using the Borsh schema
226+
const depositId = deserialize(depositIdSchema, DepositId, Buffer.from(value));
227+
return new BN(depositId.value).toString();
228+
}
229+
230+
/**
231+
* Stringifies a CPI event.
232+
*/
233+
export function stringifyCpiEvent(obj: any): any {
234+
if (obj?.constructor?.toString()?.includes("PublicKey")) {
235+
if (obj.toString().startsWith("111111111111")) {
236+
// First 12 bytes are 0 for EVM addresses.
237+
return publicKeyToEvmAddress(obj);
238+
}
239+
return obj.toString();
240+
} else if (BN.isBN(obj)) {
241+
return obj.toString();
242+
} else if (Array.isArray(obj) && obj.length == 32) {
243+
return Buffer.from(obj).toString("hex"); // Hex representation for fixed-length arrays
244+
} else if (Array.isArray(obj)) {
245+
return obj.map(stringifyCpiEvent);
246+
} else if (obj !== null && typeof obj === "object") {
247+
return Object.fromEntries(
248+
Object.entries(obj).map(([key, value]) => {
249+
if (key === "depositId" && Array.isArray(value) && value.length === 32) {
250+
// Parse depositId using the helper function
251+
const parsedValue = parseDepositId(new Uint8Array(value));
252+
return [key, parsedValue];
253+
}
254+
return [key, stringifyCpiEvent(value)];
255+
})
256+
);
257+
}
258+
return obj;
259+
}

0 commit comments

Comments
 (0)