From b02c143bb7cba1c22367942a66e297a27061636d Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Mon, 24 Feb 2025 11:37:57 +0100 Subject: [PATCH 01/17] feat(svm): add solana event fetching utils Signed-off-by: Pablo Maldonado --- package.json | 6 +- src/index.ts | 1 + src/svm/index.ts | 1 + src/svm/solanaProgramUtils.ts | 123 ++++++++++++ yarn.lock | 368 +++++++++++++++++++++++++++++++++- 5 files changed, 493 insertions(+), 6 deletions(-) create mode 100644 src/svm/index.ts create mode 100644 src/svm/solanaProgramUtils.ts diff --git a/package.json b/package.json index 2a1d88278..1f814f79c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "node": ">=20.18.0" }, "scripts": { + "build-bigint-buffer": "[ -d node_modules/bigint-buffer ] && command -v node-gyp > /dev/null && cd node_modules/bigint-buffer && node-gyp configure && node-gyp build || echo 'Skipping bigint-buffer build: folder or node-gyp not found'", + "postinstall": "yarn build-bigint-buffer", "start": "yarn typechain && nodemon -e ts,tsx,json,js,jsx --watch ./src --ignore ./dist --exec 'yarn dev'", "build": "yarn run clean && yarn typechain && yarn run build:cjs & yarn run build:esm & yarn run build:types; wait", "dev": "yarn run build:cjs & yarn run build:esm & yarn run build:types; wait", @@ -106,6 +108,7 @@ "@ethersproject/bignumber": "^5.7.0", "@pinata/sdk": "^2.1.0", "@solana/web3.js": "^2.0.0", + "@coral-xyz/anchor": "^0.30.1", "@types/mocha": "^10.0.1", "@uma/sdk": "^0.34.10", "arweave": "^1.14.4", @@ -116,6 +119,7 @@ "ethers": "^5.7.2", "lodash": "^4.17.21", "lodash.get": "^4.4.2", + "node-gyp": "^11.0.0", "superstruct": "^0.15.4", "tslib": "^2.6.2", "viem": "^2.21.15" @@ -161,4 +165,4 @@ "secp256k1@4.0.3": "4.0.4", "secp256k1@5.0.0": "5.0.1" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8331239eb..4cfa5b09b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,3 +14,4 @@ export * as clients from "./clients"; export * as typechain from "./typechain"; export * as caching from "./caching"; export * as providers from "./providers"; +export * as svm from "./svm"; diff --git a/src/svm/index.ts b/src/svm/index.ts new file mode 100644 index 000000000..b787b7d10 --- /dev/null +++ b/src/svm/index.ts @@ -0,0 +1 @@ +export * from "./solanaProgramUtils"; diff --git a/src/svm/solanaProgramUtils.ts b/src/svm/solanaProgramUtils.ts new file mode 100644 index 000000000..257a7ac96 --- /dev/null +++ b/src/svm/solanaProgramUtils.ts @@ -0,0 +1,123 @@ +import { BorshEventCoder, Idl, utils } from "@coral-xyz/anchor"; +import web3, { + Address, + Commitment, + GetSignaturesForAddressApi, + GetTransactionApi, + RpcTransport, + Signature, +} from "@solana/web3-v2.js"; + +type GetTransactionReturnType = ReturnType; + +type GetSignaturesForAddressConfig = Parameters[1]; + +type GetSignaturesForAddressTransaction = ReturnType[number]; + +type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransaction[]; + +/** + * Reads all events for a specific program. + */ +export async function readProgramEvents( + rpc: web3.Rpc>, + program: Address, + anchorIdl: Idl, + finality: Commitment = "confirmed", + options: GetSignaturesForAddressConfig = { limit: 1000 } +) { + const allSignatures: GetSignaturesForAddressTransaction[] = []; + + // Fetch all signatures in sequential batches + while (true) { + const signatures: GetSignaturesForAddressApiResponse = await rpc.getSignaturesForAddress(program, options).send(); + allSignatures.push(...signatures); + + // Update options for the next batch. Set before to the last fetched signature. + if (signatures.length > 0) { + options = { ...options, before: signatures[signatures.length - 1].signature }; + } + + if (options.limit && signatures.length < options.limit) break; // Exit early if the number of signatures < limit + } + + // Fetch events for all signatures in parallel + const eventsWithSlots = await Promise.all( + allSignatures.map(async (signatureTransaction) => { + const events = await readEvents(rpc, signatureTransaction.signature, program, anchorIdl, finality); + + return events.map((event) => ({ + ...event, + confirmationStatus: signatureTransaction.confirmationStatus || "Unknown", + blockTime: signatureTransaction.blockTime || 0, + signature: signatureTransaction.signature, + slot: signatureTransaction.slot, + name: event.name || "Unknown", + })); + }) + ); + return eventsWithSlots.flat(); +} + +/** + * Reads events from a transaction. + */ +export async function readEvents( + rpc: web3.Rpc>, + txSignature: Signature, + programId: Address, + programIdl: Idl, + commitment: Commitment = "confirmed" +) { + const txResult = await rpc.getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 }).send(); + + if (txResult === null) return []; + + return processEventFromTx(txResult, programId, programIdl); +} + +/** + * Processes events from a transaction. + */ +async function processEventFromTx( + txResult: GetTransactionReturnType, + programId: Address, + programIdl: Idl +): Promise<{ program: Address; data: any; name: string | undefined }[]> { + if (!txResult) return []; + const eventAuthorities: Map = new Map(); + const events: { program: Address; data: any; name: string | undefined }[] = []; + const [pda] = await web3.getProgramDerivedAddress({ programAddress: programId, seeds: ["__event_authority"] }); + eventAuthorities.set(programId, pda); + + const accountKeys = txResult.transaction.message.accountKeys; + const messageAccountKeys = [...accountKeys]; + // Order matters here. writable accounts must be processed before readonly accounts. + // See https://docs.anza.xyz/proposals/versioned-transactions#new-transaction-format + messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.writable ?? [])); + messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.readonly ?? [])); + + for (const ixBlock of txResult.meta?.innerInstructions ?? []) { + for (const ix of ixBlock.instructions) { + const ixProgramId = messageAccountKeys[ix.programIdIndex]; + const singleIxAccount = ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined; + if ( + ixProgramId !== undefined && + singleIxAccount !== undefined && + programId == ixProgramId && + eventAuthorities.get(ixProgramId.toString()) == singleIxAccount + ) { + const ixData = utils.bytes.bs58.decode(ix.data); + const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); + let event = new BorshEventCoder(programIdl).decode(eventData); + events.push({ + program: programId, + data: event?.data, + name: event?.name, + }); + } + } + } + + return events; +} diff --git a/yarn.lock b/yarn.lock index c28a5268d..917e9c6c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1449,6 +1449,25 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -2020,6 +2039,17 @@ dependencies: "@types/bignumber.js" "^5.0.0" +"@npmcli/agent@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz#1685b1fbd4a1b7bb4f930cbb68ce801edfe7aa44" + integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.3" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -2028,6 +2058,13 @@ "@gar/promisify" "^1.0.1" semver "^7.3.5" +"@npmcli/fs@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz#a1eb1aeddefd2a4a347eca0fab30bc62c0e1c0f2" + integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q== + dependencies: + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -2119,6 +2156,11 @@ is-ipfs "^0.6.0" path "^0.12.7" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/utils@^2.3.1": version "2.4.2" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc" @@ -4204,6 +4246,11 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= +abbrev@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz#c29a6337e167ac61a84b41b80461b29c5c271a27" + integrity sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA== + "abi-decoder@github:UMAprotocol/abi-decoder": version "2.2.2" resolved "https://codeload.github.com/UMAprotocol/abi-decoder/tar.gz/2f9ed1a31fb7a21de7aa71d98cde3175483740bb" @@ -4351,6 +4398,11 @@ agent-base@^7.0.2: dependencies: debug "^4.3.4" +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + agentkeepalive@^4.1.3, agentkeepalive@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" @@ -4484,6 +4536,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + antlr4ts@^0.5.0-alpha.4: version "0.5.0-alpha.4" resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" @@ -5508,6 +5565,24 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +cacache@^19.0.1: + version "19.0.1" + resolved "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz#3370cc28a758434c85c2585008bd5bdcff17d6cd" + integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ== + dependencies: + "@npmcli/fs" "^4.0.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^7.0.2" + ssri "^12.0.0" + tar "^7.4.3" + unique-filename "^4.0.0" + cache-content-type@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" @@ -5822,6 +5897,11 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -6350,9 +6430,9 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.12" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" @@ -8122,6 +8202,11 @@ exponential-backoff@^3.1.0: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +exponential-backoff@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz#a8f26adb96bf78e8cd8ad1037928d5e5c0679d91" + integrity sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA== + express@^4.14.0, express@^4.16.3: version "4.17.3" resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" @@ -8418,6 +8503,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -8580,6 +8673,13 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" +fs-minipass@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== + dependencies: + minipass "^7.0.3" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -8847,6 +8947,18 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.2.2, glob@^10.3.10, glob@^10.3.7: + version "10.4.5" + resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -9459,7 +9571,7 @@ http-basic@^8.1.1: http-response-object "^3.0.1" parse-cache-control "^1.0.1" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== @@ -9509,6 +9621,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-response-object@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" @@ -10241,6 +10361,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + iso-constants@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/iso-constants/-/iso-constants-0.1.2.tgz#3d2456ed5aeaa55d18564f285ba02a47a0d885b4" @@ -10373,6 +10498,15 @@ it-to-stream@^1.0.0: p-fifo "^1.0.0" readable-stream "^3.6.0" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jayson@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" @@ -11324,6 +11458,11 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== +lru-cache@^10.0.1, lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -11367,6 +11506,23 @@ make-error@1.x, make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^14.0.3: + version "14.0.3" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz#d74c3ecb0028f08ab604011e0bc6baed483fcdcd" + integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ== + dependencies: + "@npmcli/agent" "^3.0.0" + cacache "^19.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^4.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^5.0.0" + promise-retry "^2.0.1" + ssri "^12.0.0" + make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" @@ -11679,6 +11835,13 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -11691,6 +11854,13 @@ minipass-collect@^1.0.2: dependencies: minipass "^3.0.0" +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + minipass-fetch@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" @@ -11702,6 +11872,17 @@ minipass-fetch@^1.3.2: optionalDependencies: encoding "^0.1.12" +minipass-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.0.tgz#b8ea716464747aeafb7edf2e110114c38089a09c" + integrity sha512-2v6aXUXwLP1Epd/gc32HAMIWoczx+fZwEPRHm/VwtrJzRGwR1qGZXEYV3Zp8ZjjbwaZhMrM6uHV4KVkk+XCc2w== + dependencies: + minipass "^7.0.3" + minipass-sized "^1.0.3" + minizlib "^3.0.1" + optionalDependencies: + encoding "^0.1.13" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -11743,6 +11924,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -11758,6 +11944,14 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +minizlib@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== + dependencies: + minipass "^7.0.4" + rimraf "^5.0.5" + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -11789,6 +11983,11 @@ mkdirp@0.5.x, mkdirp@^0.5.1, mkdirp@^0.5.5: dependencies: minimist "^1.2.6" +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + mnemonist@^0.38.0: version "0.38.5" resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" @@ -12174,6 +12373,11 @@ negotiator@0.6.3, negotiator@^0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + neo-async@^2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -12320,6 +12524,22 @@ node-gyp@8.x: tar "^6.1.2" which "^2.0.2" +node-gyp@^11.0.0: + version "11.1.0" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-11.1.0.tgz#212a1d9c167c50d727d42659410780b40e07bbd3" + integrity sha512-/+7TuHKnBpnMvUQnsYEb0JOozDZqarQbfNuSGLXIjhStMT0fbw7IdSqWgopOP5xhRZE+lsbIvAHcekddruPZgQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^10.3.10" + graceful-fs "^4.2.6" + make-fetch-happen "^14.0.3" + nopt "^8.0.0" + proc-log "^5.0.0" + semver "^7.3.5" + tar "^7.4.3" + which "^5.0.0" + "node-metamask@github:UMAprotocol/node-metamask": version "1.1.2" resolved "https://codeload.github.com/UMAprotocol/node-metamask/tar.gz/36b33cb82558bafcd3ab0dcfbd35b26c2c0a2584" @@ -12372,6 +12592,13 @@ nopt@^5.0.0: dependencies: abbrev "1" +nopt@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz#b11d38caf0f8643ce885818518064127f602eae3" + integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A== + dependencies: + abbrev "^3.0.0" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -12737,6 +12964,11 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-map@^7.0.2: + version "7.0.3" + resolved "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6" + integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA== + p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" @@ -12754,6 +12986,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + pako@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" @@ -12885,6 +13122,14 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -13097,6 +13342,11 @@ prng-well1024a@~1.0.0: resolved "https://registry.yarnpkg.com/prng-well1024a/-/prng-well1024a-1.0.1.tgz#05e8ed923e4ea2b3f78af5ee94f056b4d5cfab24" integrity sha512-lBXfAW5Vgpej/QVHNYhTSsiz1IIlgo7kv8zzQL7v5crD8jgA4Fk3axwb9aCrDHUqJ4zKXsb3U3m6sw21165Trg== +proc-log@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" + integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -13796,6 +14046,13 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.5: + version "5.0.10" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + ripemd160-min@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62" @@ -14203,6 +14460,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + signed-varint@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/signed-varint/-/signed-varint-2.0.1.tgz#50a9989da7c98c2c61dad119bc97470ef8528129" @@ -14352,6 +14614,15 @@ socks-proxy-agent@^6.0.0: debug "^4.3.3" socks "^2.6.2" +socks-proxy-agent@^8.0.3: + version "8.0.5" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + socks@^2.6.2: version "2.7.3" resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.3.tgz#7d8a75d7ce845c0a96f710917174dba0d543a785" @@ -14360,6 +14631,14 @@ socks@^2.6.2: ip-address "^9.0.5" smart-buffer "^4.2.0" +socks@^2.8.3: + version "2.8.4" + resolved "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" @@ -14553,6 +14832,13 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssri@^12.0.0: + version "12.0.0" + resolved "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" + integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ== + dependencies: + minipass "^7.0.3" + ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" @@ -14646,6 +14932,15 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -14681,9 +14976,9 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^5.0.0: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" @@ -14736,6 +15031,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -15001,6 +15303,18 @@ tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +tar@^7.4.3: + version "7.4.3" + resolved "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.0.1" + mkdirp "^3.0.1" + yallist "^5.0.0" + tarn@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" @@ -15538,6 +15852,13 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" +unique-filename@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz#a06534d370e7c977a939cd1d11f7f0ab8f1fed13" + integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ== + dependencies: + unique-slug "^5.0.0" + unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -15545,6 +15866,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-slug@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz#ca72af03ad0dbab4dad8aa683f633878b1accda8" + integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg== + dependencies: + imurmurhash "^0.1.4" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -16353,6 +16681,13 @@ which@2.0.2, which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +which@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6" + integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ== + dependencies: + isexe "^3.1.1" + wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -16426,6 +16761,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -16461,6 +16805,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -16604,6 +16957,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" From 600fcabf361557474f9ed70e513ea37e52e8ce17 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Mon, 24 Feb 2025 12:34:17 +0100 Subject: [PATCH 02/17] fix: lint Signed-off-by: Pablo Maldonado --- src/svm/solanaProgramUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/svm/solanaProgramUtils.ts b/src/svm/solanaProgramUtils.ts index 257a7ac96..61f46ccee 100644 --- a/src/svm/solanaProgramUtils.ts +++ b/src/svm/solanaProgramUtils.ts @@ -109,7 +109,7 @@ async function processEventFromTx( ) { const ixData = utils.bytes.bs58.decode(ix.data); const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); - let event = new BorshEventCoder(programIdl).decode(eventData); + const event = new BorshEventCoder(programIdl).decode(eventData); events.push({ program: programId, data: event?.data, From 0e1dde43559ea5c5a4212fe29a6ac77545b45159 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Mon, 24 Feb 2025 18:03:39 +0100 Subject: [PATCH 03/17] fix: lint 2 Signed-off-by: Pablo Maldonado --- src/svm/solanaProgramUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/svm/solanaProgramUtils.ts b/src/svm/solanaProgramUtils.ts index 61f46ccee..298579a3b 100644 --- a/src/svm/solanaProgramUtils.ts +++ b/src/svm/solanaProgramUtils.ts @@ -28,8 +28,8 @@ export async function readProgramEvents( ) { const allSignatures: GetSignaturesForAddressTransaction[] = []; - // Fetch all signatures in sequential batches - while (true) { + let hasMoreSignatures = true; + while (hasMoreSignatures) { const signatures: GetSignaturesForAddressApiResponse = await rpc.getSignaturesForAddress(program, options).send(); allSignatures.push(...signatures); @@ -38,7 +38,7 @@ export async function readProgramEvents( options = { ...options, before: signatures[signatures.length - 1].signature }; } - if (options.limit && signatures.length < options.limit) break; // Exit early if the number of signatures < limit + hasMoreSignatures = Boolean(options.limit && signatures.length === options.limit); } // Fetch events for all signatures in parallel From c553a9f49f36b3786fdd26a22f9abfe097baf342 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Mon, 24 Feb 2025 19:28:06 +0100 Subject: [PATCH 04/17] feat: event mapping Signed-off-by: Pablo Maldonado --- src/svm/solanaProgramUtils.ts | 51 +++++++++++- src/svm/types.ts | 147 ++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 src/svm/types.ts diff --git a/src/svm/solanaProgramUtils.ts b/src/svm/solanaProgramUtils.ts index 298579a3b..237d37cb2 100644 --- a/src/svm/solanaProgramUtils.ts +++ b/src/svm/solanaProgramUtils.ts @@ -7,6 +7,7 @@ import web3, { RpcTransport, Signature, } from "@solana/web3-v2.js"; +import { BridgedToHubPoolEvent, ClaimedRelayerRefundEvent, EmergencyDeletedRootBundleEvent, EnabledDepositRouteEvent, EventData, EventName, ExecutedRelayerRefundRootEvent, FilledRelayEvent, FundsDepositedEvent, PausedDepositsEvent, PausedFillsEvent, RelayedRootBundleEvent, RequestedSlowFillEvent, SetXDomainAdminEvent, SVMEventNames, TokensBridgedEvent } from "./types"; type GetTransactionReturnType = ReturnType; @@ -83,10 +84,10 @@ async function processEventFromTx( txResult: GetTransactionReturnType, programId: Address, programIdl: Idl -): Promise<{ program: Address; data: any; name: string | undefined }[]> { +): Promise<{ program: Address; data: EventData; name: string }[]> { if (!txResult) return []; const eventAuthorities: Map = new Map(); - const events: { program: Address; data: any; name: string | undefined }[] = []; + const events: { program: Address; data: EventData; name: string }[] = []; const [pda] = await web3.getProgramDerivedAddress({ programAddress: programId, seeds: ["__event_authority"] }); eventAuthorities.set(programId, pda); @@ -110,10 +111,11 @@ async function processEventFromTx( const ixData = utils.bytes.bs58.decode(ix.data); const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); const event = new BorshEventCoder(programIdl).decode(eventData); + const name = getEventName(event?.name); events.push({ program: programId, - data: event?.data, - name: event?.name, + data: mapEventData(event?.data, name), + name, }); } } @@ -121,3 +123,44 @@ async function processEventFromTx( return events; } + + + +function getEventName(rawName?: string): EventName { + if (!rawName) throw new Error("Raw name is undefined"); + if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; + throw new Error(`Unknown event name: ${rawName}`); +} + +function mapEventData(eventData: any, name: EventName): EventData { + switch (name) { + case "FilledRelay": + return eventData as FilledRelayEvent; + case "FundsDeposited": + return eventData as FundsDepositedEvent; + case "BridgedToHubPool": + return eventData as BridgedToHubPoolEvent; + case "TokensBridged": + return eventData as TokensBridgedEvent; + case "ExecutedRelayerRefundRoot": + return eventData as ExecutedRelayerRefundRootEvent; + case "RelayedRootBundle": + return eventData as RelayedRootBundleEvent; + case "PausedDeposits": + return eventData as PausedDepositsEvent; + case "PausedFills": + return eventData as PausedFillsEvent; + case "SetXDomainAdmin": + return eventData as SetXDomainAdminEvent; + case "EnabledDepositRoute": + return eventData as EnabledDepositRouteEvent; + case "EmergencyDeletedRootBundle": + return eventData as EmergencyDeletedRootBundleEvent; + case "RequestedSlowFill": + return eventData as RequestedSlowFillEvent; + case "ClaimedRelayerRefund": + return eventData as ClaimedRelayerRefundEvent; + default: + throw new Error(`Unknown event name: ${name}`); + } +} diff --git a/src/svm/types.ts b/src/svm/types.ts new file mode 100644 index 000000000..e7bf53696 --- /dev/null +++ b/src/svm/types.ts @@ -0,0 +1,147 @@ +export type BridgedToHubPoolEvent = { + amount: string; + mint: string; +}; + +export type TokensBridgedEvent = { + amount_to_return: string; + chain_id: string; + leaf_id: number; + l2_token_address: string; + caller: string; +}; + +export type ExecutedRelayerRefundRootEvent = { + amount_to_return: string; + chain_id: string; + refund_amounts: string[]; + root_bundle_id: number; + leaf_id: number; + l2_token_address: string; + refund_addresses: string[]; + deferred_refunds: boolean; + caller: string; +}; + +export type RelayedRootBundleEvent = { + root_bundle_id: number; + relayer_refund_root: string; + slow_relay_root: string; +}; + +export type PausedDepositsEvent = { + is_paused: boolean; +}; + +export type PausedFillsEvent = { + is_paused: boolean; +}; + +export type SetXDomainAdminEvent = { + new_admin: string; +}; + +export type EnabledDepositRouteEvent = { + origin_token: string; + destination_chain_id: string; + enabled: boolean; +}; + +export type FilledRelayEvent = { + input_token: string; + output_token: string; + input_amount: string; + output_amount: string; + repayment_chain_id: string; + origin_chain_id: string; + deposit_id: string; + fill_deadline: number; + exclusivity_deadline: number; + exclusive_relayer: string; + relayer: string; + depositor: string; + recipient: string; + message_hash: string; + relay_execution_info: { + updated_recipient: string; + updated_message_hash: string; + updated_output_amount: string; + fill_type: { + FastFill: {}; + }; + }; +}; + +export type FundsDepositedEvent = { + input_token: string; + output_token: string; + input_amount: string; + output_amount: string; + destination_chain_id: string; + deposit_id: string; + quote_timestamp: number; + fill_deadline: number; + exclusivity_deadline: number; + depositor: string; + recipient: string; + exclusive_relayer: string; + message: {}; +}; + +export type EmergencyDeletedRootBundleEvent = { + root_bundle_id: number; +}; + +export type RequestedSlowFillEvent = { + input_token: string; + output_token: string; + input_amount: string; + output_amount: string; + origin_chain_id: string; + deposit_id: string; + fill_deadline: number; + exclusivity_deadline: number; + exclusive_relayer: string; + depositor: string; + recipient: string; + message_hash: string; +}; + +export type ClaimedRelayerRefundEvent = { + l2_token_address: string; + claim_amount: string; + refund_address: string; +}; + +export type EventData = + | BridgedToHubPoolEvent + | TokensBridgedEvent + | ExecutedRelayerRefundRootEvent + | RelayedRootBundleEvent + | PausedDepositsEvent + | PausedFillsEvent + | SetXDomainAdminEvent + | EnabledDepositRouteEvent + | FilledRelayEvent + | FundsDepositedEvent + | EmergencyDeletedRootBundleEvent + | RequestedSlowFillEvent + | ClaimedRelayerRefundEvent; + +export enum SVMEventNames { + FilledRelay = "FilledRelay", + FundsDeposited = "FundsDeposited", + EnabledDepositRoute = "EnabledDepositRoute", + RelayedRootBundle = "RelayedRootBundle", + ExecutedRelayerRefundRoot = "ExecutedRelayerRefundRoot", + BridgedToHubPool = "BridgedToHubPool", + PausedDeposits = "PausedDeposits", + PausedFills = "PausedFills", + SetXDomainAdmin = "SetXDomainAdmin", + EmergencyDeletedRootBundle = "EmergencyDeletedRootBundle", + RequestedSlowFill = "RequestedSlowFill", + ClaimedRelayerRefund = "ClaimedRelayerRefund", + TokensBridged = "TokensBridged", +} + +export type EventName = keyof typeof SVMEventNames; \ No newline at end of file From 0ff8eb743f3fcd1c282330c56ef9aab3103a7899 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 17:12:15 +0100 Subject: [PATCH 05/17] feat(svm): svm spoke events client Signed-off-by: Pablo Maldonado --- package.json | 3 +- src/providers/solana/baseRpcFactories.ts | 4 +- src/providers/solana/defaultRpcFactory.ts | 2 +- src/providers/solana/rateLimitedRpcFactory.ts | 2 +- src/svm/eventsClient.ts | 233 ++++++++++++++++++ src/svm/index.ts | 2 +- src/svm/solanaProgramUtils.ts | 166 ------------- src/svm/types.ts | 124 +++++----- src/svm/utils/events.ts | 74 ++++++ src/svm/utils/helpers.ts | 9 + yarn.lock | 24 -- 11 files changed, 391 insertions(+), 252 deletions(-) create mode 100644 src/svm/eventsClient.ts delete mode 100644 src/svm/solanaProgramUtils.ts create mode 100644 src/svm/utils/events.ts create mode 100644 src/svm/utils/helpers.ts diff --git a/package.json b/package.json index 1f814f79c..d8150466e 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "@eth-optimism/sdk": "^3.3.1", "@ethersproject/bignumber": "^5.7.0", "@pinata/sdk": "^2.1.0", - "@solana/web3.js": "^2.0.0", + "@solana/web3-v2.js": "npm:@solana/web3.js@2", + "@solana/web3.js": "^1.31.0", "@coral-xyz/anchor": "^0.30.1", "@types/mocha": "^10.0.1", "@uma/sdk": "^0.34.10", diff --git a/src/providers/solana/baseRpcFactories.ts b/src/providers/solana/baseRpcFactories.ts index 67e620bd3..9068aa1f7 100644 --- a/src/providers/solana/baseRpcFactories.ts +++ b/src/providers/solana/baseRpcFactories.ts @@ -1,8 +1,8 @@ -import { ClusterUrl, createSolanaRpcFromTransport, RpcTransport } from "@solana/web3.js"; +import { ClusterUrl, createSolanaRpcFromTransport, RpcTransport } from "@solana/web3-v2.js"; // This is abstract base class for creating Solana RPC clients and transports. export abstract class SolanaBaseRpcFactory { - constructor(readonly chainId: number) {} + constructor(readonly chainId: number) { } // This method must be implemented by the derived class to create a transport. public abstract createTransport(): RpcTransport; diff --git a/src/providers/solana/defaultRpcFactory.ts b/src/providers/solana/defaultRpcFactory.ts index b562cffbf..008d98b60 100644 --- a/src/providers/solana/defaultRpcFactory.ts +++ b/src/providers/solana/defaultRpcFactory.ts @@ -1,4 +1,4 @@ -import { createDefaultRpcTransport, RpcTransport } from "@solana/web3.js"; +import { createDefaultRpcTransport, RpcTransport } from "@solana/web3-v2.js"; import { SolanaClusterRpcFactory } from "./baseRpcFactories"; // Exposes default RPC transport for Solana in the SolanaClusterRpcFactory class. diff --git a/src/providers/solana/rateLimitedRpcFactory.ts b/src/providers/solana/rateLimitedRpcFactory.ts index 8f2f815cd..860617a56 100644 --- a/src/providers/solana/rateLimitedRpcFactory.ts +++ b/src/providers/solana/rateLimitedRpcFactory.ts @@ -1,4 +1,4 @@ -import { RpcResponse, RpcTransport } from "@solana/web3.js"; +import { RpcResponse, RpcTransport } from "@solana/web3-v2.js"; import { QueueObject, queue } from "async"; import winston, { Logger } from "winston"; import { SolanaClusterRpcFactory } from "./baseRpcFactories"; diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts new file mode 100644 index 000000000..460f6160b --- /dev/null +++ b/src/svm/eventsClient.ts @@ -0,0 +1,233 @@ +import { getDeployedAddress, SvmSpokeIdl } from "@across-protocol/contracts"; +import { getSolanaChainId } from "@across-protocol/contracts/dist/src/svm/web3-v1"; +import { BorshEventCoder, Idl, utils } from "@coral-xyz/anchor"; +import web3, { + Address, + Commitment, + GetSignaturesForAddressApi, + GetTransactionApi, + RpcTransport, + Signature, + unixTimestamp +} from "@solana/web3-v2.js"; +import { EventData, EventName, EventWithData } from "./types"; +import { getEventName, mapEventData, parseEventData } from "./utils/events"; +import { isDevnet } from "./utils/helpers"; + +type GetTransactionReturnType = ReturnType; +type GetSignaturesForAddressConfig = Parameters[1]; +type GetSignaturesForAddressTransaction = ReturnType[number]; +type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransaction[]; + +export class SvmSpokeEventsClient { + private rpc: web3.Rpc>; + private svmSpokeAddress: Address; + + /** + * Private constructor. Use the async create() method to instantiate. + */ + private constructor( + rpc: web3.Rpc>, + svmSpokeAddress: Address + ) { + this.rpc = rpc; + this.svmSpokeAddress = svmSpokeAddress; + } + + /** + * Factory method to asynchronously create an instance of SvmSpokeEventsClient. + */ + public static async create( + rpc: web3.Rpc> + ): Promise { + const isTestnet = await isDevnet(rpc); + const programId = getDeployedAddress( + "SvmSpoke", + getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString() + ); + if (!programId) throw new Error("Program not found"); + return new SvmSpokeEventsClient(rpc, web3.address(programId)); + } + + /** + * Queries events for the SvmSpoke program filtered by event name. + * + * @param eventName - The name of the event to filter by. + * @param fromSlot - Optional starting slot. + * @param toSlot - Optional ending slot. + * @param options - Options for fetching signatures. + * @param finality - Commitment level. + * @returns A promise that resolves to an array of events matching the eventName. + */ + public async queryEvents( + eventName: EventName, + fromSlot?: bigint, + toSlot?: bigint, + options: GetSignaturesForAddressConfig = { limit: 1000 }, + finality: Commitment = "confirmed" + ): Promise[]> { + const events = await this.queryAllEvents( + this.svmSpokeAddress, + SvmSpokeIdl, + fromSlot, + toSlot, + options, + finality + ); + return events.filter((event) => event.name === eventName) as EventWithData[]; + } + + /** + * Queries all events for a specific program. + * + * @param program - The program address. + * @param anchorIdl - The IDL describing the program events. + * @param fromSlot - Optional starting slot. + * @param toSlot - Optional ending slot. + * @param options - Options for fetching signatures. + * @param finality - Commitment level. + * @returns A promise that resolves to an array of all events with additional metadata. + */ + public async queryAllEvents( + program: Address, + anchorIdl: Idl, + fromSlot?: bigint, + toSlot?: bigint, + options: GetSignaturesForAddressConfig = { limit: 1000 }, + finality: Commitment = "confirmed" + ): Promise[]> { + const allSignatures: GetSignaturesForAddressTransaction[] = []; + let hasMoreSignatures = true; + let currentOptions = options; + + while (hasMoreSignatures) { + const signatures: GetSignaturesForAddressApiResponse = await this.rpc + .getSignaturesForAddress(program, currentOptions) + .send(); + // Signatures are sorted by slot in descending order. + allSignatures.push(...signatures); + + // Update options for the next batch. Set "before" to the last fetched signature. + if (signatures.length > 0) { + currentOptions = { ...currentOptions, before: signatures[signatures.length - 1].signature }; + } + + if (fromSlot && allSignatures.length > 0 && allSignatures[allSignatures.length - 1].slot < fromSlot) { + hasMoreSignatures = false; + } + + hasMoreSignatures = Boolean(hasMoreSignatures && currentOptions.limit && signatures.length === currentOptions.limit); + } + + const filteredSignatures = allSignatures.filter((signatureTransaction) => { + if (fromSlot && signatureTransaction.slot < fromSlot) return false; + if (toSlot && signatureTransaction.slot > toSlot) return false; + return true; + }); + + // Fetch events for all signatures in parallel. + const eventsWithSlots = await Promise.all( + filteredSignatures.map(async (signatureTransaction) => { + const events = await this.readEventsFromSignature( + signatureTransaction.signature, + program, + anchorIdl, + finality + ); + return events.map((event) => ({ + ...event, + confirmationStatus: signatureTransaction.confirmationStatus || "Unknown", + blockTime: signatureTransaction.blockTime || unixTimestamp(BigInt(0)), + signature: signatureTransaction.signature, + slot: signatureTransaction.slot, + })); + }) + ); + return eventsWithSlots.flat(); + } + + /** + * Reads events from a transaction signature. + * + * @param txSignature - The transaction signature. + * @param programId - The program address. + * @param programIdl - The program IDL. + * @param commitment - Commitment level. + * @returns A promise that resolves to an array of events. + */ + private async readEventsFromSignature( + txSignature: Signature, + programId: Address, + programIdl: Idl, + commitment: Commitment = "confirmed" + ) { + const txResult = await this.rpc + .getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 }) + .send(); + + if (txResult === null) return []; + return this.processEventFromTx(txResult, programId, programIdl); + } + + /** + * Processes events from a transaction. + * + * @param txResult - The transaction result. + * @param programId - The program address. + * @param programIdl - The program IDL. + * @returns A promise that resolves to an array of events with their data and name. + */ + private async processEventFromTx( + txResult: GetTransactionReturnType, + programId: Address, + programIdl: Idl + ): Promise<{ program: Address; data: EventData; name: string }[]> { + if (!txResult) return []; + + const eventAuthorities: Map = new Map(); + const events: { program: Address; data: EventData; name: string }[] = []; + + // Derive the event authority PDA. + const [pda] = await web3.getProgramDerivedAddress({ + programAddress: programId, + seeds: ["__event_authority"], + }); + eventAuthorities.set(programId, pda); + + const accountKeys = txResult.transaction.message.accountKeys; + const messageAccountKeys = [...accountKeys]; + // Writable accounts come first, then readonly. + // See https://docs.anza.xyz/proposals/versioned-transactions#new-transaction-format + messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.writable ?? [])); + messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.readonly ?? [])); + + for (const ixBlock of txResult.meta?.innerInstructions ?? []) { + for (const ix of ixBlock.instructions) { + const ixProgramId = messageAccountKeys[ix.programIdIndex]; + const singleIxAccount = + ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined; + if ( + ixProgramId !== undefined && + singleIxAccount !== undefined && + programId == ixProgramId && + eventAuthorities.get(ixProgramId.toString()) == singleIxAccount + ) { + const ixData = utils.bytes.bs58.decode(ix.data); + // Skip the first 8 bytes (assumed header) and encode the rest. + const eventData = utils.bytes.base64.encode( + Buffer.from(new Uint8Array(ixData).slice(8)) + ); + const event = new BorshEventCoder(programIdl).decode(eventData); + const name = getEventName(event?.name); + events.push({ + program: programId, + data: mapEventData(parseEventData(event?.data), name), + name, + }); + } + } + } + + return events; + } +} \ No newline at end of file diff --git a/src/svm/index.ts b/src/svm/index.ts index b787b7d10..76a73e1b1 100644 --- a/src/svm/index.ts +++ b/src/svm/index.ts @@ -1 +1 @@ -export * from "./solanaProgramUtils"; +export * from "./eventsClient"; diff --git a/src/svm/solanaProgramUtils.ts b/src/svm/solanaProgramUtils.ts deleted file mode 100644 index 237d37cb2..000000000 --- a/src/svm/solanaProgramUtils.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { BorshEventCoder, Idl, utils } from "@coral-xyz/anchor"; -import web3, { - Address, - Commitment, - GetSignaturesForAddressApi, - GetTransactionApi, - RpcTransport, - Signature, -} from "@solana/web3-v2.js"; -import { BridgedToHubPoolEvent, ClaimedRelayerRefundEvent, EmergencyDeletedRootBundleEvent, EnabledDepositRouteEvent, EventData, EventName, ExecutedRelayerRefundRootEvent, FilledRelayEvent, FundsDepositedEvent, PausedDepositsEvent, PausedFillsEvent, RelayedRootBundleEvent, RequestedSlowFillEvent, SetXDomainAdminEvent, SVMEventNames, TokensBridgedEvent } from "./types"; - -type GetTransactionReturnType = ReturnType; - -type GetSignaturesForAddressConfig = Parameters[1]; - -type GetSignaturesForAddressTransaction = ReturnType[number]; - -type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransaction[]; - -/** - * Reads all events for a specific program. - */ -export async function readProgramEvents( - rpc: web3.Rpc>, - program: Address, - anchorIdl: Idl, - finality: Commitment = "confirmed", - options: GetSignaturesForAddressConfig = { limit: 1000 } -) { - const allSignatures: GetSignaturesForAddressTransaction[] = []; - - let hasMoreSignatures = true; - while (hasMoreSignatures) { - const signatures: GetSignaturesForAddressApiResponse = await rpc.getSignaturesForAddress(program, options).send(); - allSignatures.push(...signatures); - - // Update options for the next batch. Set before to the last fetched signature. - if (signatures.length > 0) { - options = { ...options, before: signatures[signatures.length - 1].signature }; - } - - hasMoreSignatures = Boolean(options.limit && signatures.length === options.limit); - } - - // Fetch events for all signatures in parallel - const eventsWithSlots = await Promise.all( - allSignatures.map(async (signatureTransaction) => { - const events = await readEvents(rpc, signatureTransaction.signature, program, anchorIdl, finality); - - return events.map((event) => ({ - ...event, - confirmationStatus: signatureTransaction.confirmationStatus || "Unknown", - blockTime: signatureTransaction.blockTime || 0, - signature: signatureTransaction.signature, - slot: signatureTransaction.slot, - name: event.name || "Unknown", - })); - }) - ); - return eventsWithSlots.flat(); -} - -/** - * Reads events from a transaction. - */ -export async function readEvents( - rpc: web3.Rpc>, - txSignature: Signature, - programId: Address, - programIdl: Idl, - commitment: Commitment = "confirmed" -) { - const txResult = await rpc.getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 }).send(); - - if (txResult === null) return []; - - return processEventFromTx(txResult, programId, programIdl); -} - -/** - * Processes events from a transaction. - */ -async function processEventFromTx( - txResult: GetTransactionReturnType, - programId: Address, - programIdl: Idl -): Promise<{ program: Address; data: EventData; name: string }[]> { - if (!txResult) return []; - const eventAuthorities: Map = new Map(); - const events: { program: Address; data: EventData; name: string }[] = []; - const [pda] = await web3.getProgramDerivedAddress({ programAddress: programId, seeds: ["__event_authority"] }); - eventAuthorities.set(programId, pda); - - const accountKeys = txResult.transaction.message.accountKeys; - const messageAccountKeys = [...accountKeys]; - // Order matters here. writable accounts must be processed before readonly accounts. - // See https://docs.anza.xyz/proposals/versioned-transactions#new-transaction-format - messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.writable ?? [])); - messageAccountKeys.push(...(txResult?.meta?.loadedAddresses?.readonly ?? [])); - - for (const ixBlock of txResult.meta?.innerInstructions ?? []) { - for (const ix of ixBlock.instructions) { - const ixProgramId = messageAccountKeys[ix.programIdIndex]; - const singleIxAccount = ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined; - if ( - ixProgramId !== undefined && - singleIxAccount !== undefined && - programId == ixProgramId && - eventAuthorities.get(ixProgramId.toString()) == singleIxAccount - ) { - const ixData = utils.bytes.bs58.decode(ix.data); - const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); - const event = new BorshEventCoder(programIdl).decode(eventData); - const name = getEventName(event?.name); - events.push({ - program: programId, - data: mapEventData(event?.data, name), - name, - }); - } - } - } - - return events; -} - - - -function getEventName(rawName?: string): EventName { - if (!rawName) throw new Error("Raw name is undefined"); - if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; - throw new Error(`Unknown event name: ${rawName}`); -} - -function mapEventData(eventData: any, name: EventName): EventData { - switch (name) { - case "FilledRelay": - return eventData as FilledRelayEvent; - case "FundsDeposited": - return eventData as FundsDepositedEvent; - case "BridgedToHubPool": - return eventData as BridgedToHubPoolEvent; - case "TokensBridged": - return eventData as TokensBridgedEvent; - case "ExecutedRelayerRefundRoot": - return eventData as ExecutedRelayerRefundRootEvent; - case "RelayedRootBundle": - return eventData as RelayedRootBundleEvent; - case "PausedDeposits": - return eventData as PausedDepositsEvent; - case "PausedFills": - return eventData as PausedFillsEvent; - case "SetXDomainAdmin": - return eventData as SetXDomainAdminEvent; - case "EnabledDepositRoute": - return eventData as EnabledDepositRouteEvent; - case "EmergencyDeletedRootBundle": - return eventData as EmergencyDeletedRootBundleEvent; - case "RequestedSlowFill": - return eventData as RequestedSlowFillEvent; - case "ClaimedRelayerRefund": - return eventData as ClaimedRelayerRefundEvent; - default: - throw new Error(`Unknown event name: ${name}`); - } -} diff --git a/src/svm/types.ts b/src/svm/types.ts index e7bf53696..947b9c248 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -1,32 +1,34 @@ +import { Signature, Address, UnixTimestamp } from "@solana/web3-v2.js"; + export type BridgedToHubPoolEvent = { - amount: string; - mint: string; + amount: bigint; + mint: Address; }; export type TokensBridgedEvent = { - amount_to_return: string; - chain_id: string; + amount_to_return: bigint; + chain_id: bigint; leaf_id: number; - l2_token_address: string; - caller: string; + l2_token_address: Address; + caller: Address; }; export type ExecutedRelayerRefundRootEvent = { - amount_to_return: string; - chain_id: string; - refund_amounts: string[]; + amount_to_return: bigint; + chain_id: bigint; + refund_amounts: bigint[]; root_bundle_id: number; leaf_id: number; - l2_token_address: string; - refund_addresses: string[]; + l2_token_address: Address; + refund_addresses: Address[]; deferred_refunds: boolean; - caller: string; + caller: Address; }; export type RelayedRootBundleEvent = { root_bundle_id: number; - relayer_refund_root: string; - slow_relay_root: string; + relayer_refund_root: Array; + slow_relay_root: Array; }; export type PausedDepositsEvent = { @@ -38,54 +40,54 @@ export type PausedFillsEvent = { }; export type SetXDomainAdminEvent = { - new_admin: string; + new_admin: Address; }; export type EnabledDepositRouteEvent = { - origin_token: string; - destination_chain_id: string; + origin_token: Address; + destination_chain_id: bigint; enabled: boolean; }; +export type FillType = "FastFill" | "ReplacedSlowFill" | "SlowFill"; + export type FilledRelayEvent = { - input_token: string; - output_token: string; - input_amount: string; - output_amount: string; - repayment_chain_id: string; - origin_chain_id: string; - deposit_id: string; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + repayment_chain_id: bigint; + origin_chain_id: bigint; + deposit_id: Array; fill_deadline: number; exclusivity_deadline: number; - exclusive_relayer: string; - relayer: string; - depositor: string; - recipient: string; + exclusive_relayer: Address; + relayer: Address; + depositor: Address; + recipient: Address; message_hash: string; relay_execution_info: { - updated_recipient: string; + updated_recipient: Address; updated_message_hash: string; - updated_output_amount: string; - fill_type: { - FastFill: {}; - }; + updated_output_amount: bigint; + fill_type: Record; }; }; export type FundsDepositedEvent = { - input_token: string; - output_token: string; - input_amount: string; - output_amount: string; - destination_chain_id: string; - deposit_id: string; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + destination_chain_id: bigint; + deposit_id: Array; quote_timestamp: number; fill_deadline: number; exclusivity_deadline: number; - depositor: string; - recipient: string; - exclusive_relayer: string; - message: {}; + depositor: Address; + recipient: Address; + exclusive_relayer: Address; + message: Buffer; }; export type EmergencyDeletedRootBundleEvent = { @@ -93,24 +95,24 @@ export type EmergencyDeletedRootBundleEvent = { }; export type RequestedSlowFillEvent = { - input_token: string; - output_token: string; - input_amount: string; - output_amount: string; - origin_chain_id: string; - deposit_id: string; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + origin_chain_id: bigint; + deposit_id: Array; fill_deadline: number; exclusivity_deadline: number; - exclusive_relayer: string; - depositor: string; - recipient: string; + exclusive_relayer: Address; + depositor: Address; + recipient: Address; message_hash: string; }; export type ClaimedRelayerRefundEvent = { - l2_token_address: string; - claim_amount: string; - refund_address: string; + l2_token_address: Address; + claim_amount: bigint; + refund_address: Address; }; export type EventData = @@ -144,4 +146,14 @@ export enum SVMEventNames { TokensBridged = "TokensBridged", } -export type EventName = keyof typeof SVMEventNames; \ No newline at end of file +export type EventName = keyof typeof SVMEventNames; + +export type EventWithData = { + confirmationStatus: string; + blockTime: UnixTimestamp; + signature: Signature; + slot: bigint; + name: string; + data: T; + program: Address; +}; \ No newline at end of file diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts new file mode 100644 index 000000000..6610b22a9 --- /dev/null +++ b/src/svm/utils/events.ts @@ -0,0 +1,74 @@ +import { BN } from "@coral-xyz/anchor"; +import web3 from "@solana/web3-v2.js"; +import { BridgedToHubPoolEvent, ClaimedRelayerRefundEvent, EmergencyDeletedRootBundleEvent, EnabledDepositRouteEvent, EventData, EventName, ExecutedRelayerRefundRootEvent, FilledRelayEvent, FundsDepositedEvent, PausedDepositsEvent, PausedFillsEvent, RelayedRootBundleEvent, RequestedSlowFillEvent, SetXDomainAdminEvent, SVMEventNames, TokensBridgedEvent } from "../types"; + +/** + * Parses event data from a transaction. + */ +export function parseEventData(eventData: any): any { + if (!eventData) return eventData; + + if (Array.isArray(eventData)) { + return eventData.map(parseEventData); + } + + if (typeof eventData === "object") { + if (eventData.constructor.name === "PublicKey") { + return web3.address(eventData.toString()); + } + if (BN.isBN(eventData)) { + return BigInt(eventData.toString()); + } + + return Object.fromEntries( + Object.entries(eventData).map(([key, value]) => [key, parseEventData(value)]) + ); + } + + return eventData; +} + +/** + * Gets the event name from a raw name. + */ +export function getEventName(rawName?: string): EventName { + if (!rawName) throw new Error("Raw name is undefined"); + if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; + throw new Error(`Unknown event name: ${rawName}`); +} + +/** + * Maps event data to an event type. + */ +export function mapEventData(eventData: any, name: EventName): EventData { + switch (name) { + case "FilledRelay": + return eventData as FilledRelayEvent; + case "FundsDeposited": + return eventData as FundsDepositedEvent; + case "BridgedToHubPool": + return eventData as BridgedToHubPoolEvent; + case "TokensBridged": + return eventData as TokensBridgedEvent; + case "ExecutedRelayerRefundRoot": + return eventData as ExecutedRelayerRefundRootEvent; + case "RelayedRootBundle": + return eventData as RelayedRootBundleEvent; + case "PausedDeposits": + return eventData as PausedDepositsEvent; + case "PausedFills": + return eventData as PausedFillsEvent; + case "SetXDomainAdmin": + return eventData as SetXDomainAdminEvent; + case "EnabledDepositRoute": + return eventData as EnabledDepositRouteEvent; + case "EmergencyDeletedRootBundle": + return eventData as EmergencyDeletedRootBundleEvent; + case "RequestedSlowFill": + return eventData as RequestedSlowFillEvent; + case "ClaimedRelayerRefund": + return eventData as ClaimedRelayerRefundEvent; + default: + throw new Error(`Unknown event name: ${name}`); + } +} \ No newline at end of file diff --git a/src/svm/utils/helpers.ts b/src/svm/utils/helpers.ts new file mode 100644 index 000000000..9d57ab04c --- /dev/null +++ b/src/svm/utils/helpers.ts @@ -0,0 +1,9 @@ +import web3, { RpcTransport } from "@solana/web3-v2.js"; + +/** + * Helper to determine if the current RPC network is devnet. + */ +export async function isDevnet(rpc: web3.Rpc>): Promise { + const genesisHash = await rpc.getGenesisHash().send(); + return genesisHash === "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 917e9c6c8..5da3faca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3008,30 +3008,6 @@ rpc-websockets "^9.0.2" superstruct "^2.0.2" -"@solana/web3.js@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-2.0.0.tgz#192918343982e1964269b3adb2567532c1e12c89" - integrity sha512-x+ZRB2/r5tVK/xw8QRbAfgPcX51G9f2ifEyAQ/J5npOO+6+MPeeCjtr5UxHNDAYs9Ypo0PN+YJATCO4vhzQJGg== - dependencies: - "@solana/accounts" "2.0.0" - "@solana/addresses" "2.0.0" - "@solana/codecs" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/instructions" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/programs" "2.0.0" - "@solana/rpc" "2.0.0" - "@solana/rpc-parsed-types" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-subscriptions" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/signers" "2.0.0" - "@solana/sysvars" "2.0.0" - "@solana/transaction-confirmation" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solidity-parser/parser@^0.14.0": version "0.14.1" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.1.tgz#179afb29f4e295a77cc141151f26b3848abc3c46" From bcf1fdbd8af9fe1deab01e93932488493a88aaf9 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 17:17:03 +0100 Subject: [PATCH 06/17] feat: bump version Signed-off-by: Pablo Maldonado --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8150466e..7d319e03d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "4.1.17", + "version": "4.1.18", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ From 9964733d24c829f8b4e5c3cd03d9b9244b34f4bd Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 19:00:33 +0100 Subject: [PATCH 07/17] fix: lint Signed-off-by: Pablo Maldonado --- src/providers/solana/baseRpcFactories.ts | 2 +- src/svm/eventsClient.ts | 41 ++--- src/svm/types.ts | 216 +++++++++++------------ src/svm/utils/events.ts | 117 ++++++------ src/svm/utils/helpers.ts | 6 +- 5 files changed, 189 insertions(+), 193 deletions(-) diff --git a/src/providers/solana/baseRpcFactories.ts b/src/providers/solana/baseRpcFactories.ts index 9068aa1f7..eae92252d 100644 --- a/src/providers/solana/baseRpcFactories.ts +++ b/src/providers/solana/baseRpcFactories.ts @@ -2,7 +2,7 @@ import { ClusterUrl, createSolanaRpcFromTransport, RpcTransport } from "@solana/ // This is abstract base class for creating Solana RPC clients and transports. export abstract class SolanaBaseRpcFactory { - constructor(readonly chainId: number) { } + constructor(readonly chainId: number) {} // This method must be implemented by the derived class to create a transport. public abstract createTransport(): RpcTransport; diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 460f6160b..6a88510cd 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -8,7 +8,7 @@ import web3, { GetTransactionApi, RpcTransport, Signature, - unixTimestamp + unixTimestamp, } from "@solana/web3-v2.js"; import { EventData, EventName, EventWithData } from "./types"; import { getEventName, mapEventData, parseEventData } from "./utils/events"; @@ -26,10 +26,7 @@ export class SvmSpokeEventsClient { /** * Private constructor. Use the async create() method to instantiate. */ - private constructor( - rpc: web3.Rpc>, - svmSpokeAddress: Address - ) { + private constructor(rpc: web3.Rpc>, svmSpokeAddress: Address) { this.rpc = rpc; this.svmSpokeAddress = svmSpokeAddress; } @@ -41,10 +38,7 @@ export class SvmSpokeEventsClient { rpc: web3.Rpc> ): Promise { const isTestnet = await isDevnet(rpc); - const programId = getDeployedAddress( - "SvmSpoke", - getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString() - ); + const programId = getDeployedAddress("SvmSpoke", getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString()); if (!programId) throw new Error("Program not found"); return new SvmSpokeEventsClient(rpc, web3.address(programId)); } @@ -66,14 +60,7 @@ export class SvmSpokeEventsClient { options: GetSignaturesForAddressConfig = { limit: 1000 }, finality: Commitment = "confirmed" ): Promise[]> { - const events = await this.queryAllEvents( - this.svmSpokeAddress, - SvmSpokeIdl, - fromSlot, - toSlot, - options, - finality - ); + const events = await this.queryAllEvents(this.svmSpokeAddress, SvmSpokeIdl, fromSlot, toSlot, options, finality); return events.filter((event) => event.name === eventName) as EventWithData[]; } @@ -116,7 +103,9 @@ export class SvmSpokeEventsClient { hasMoreSignatures = false; } - hasMoreSignatures = Boolean(hasMoreSignatures && currentOptions.limit && signatures.length === currentOptions.limit); + hasMoreSignatures = Boolean( + hasMoreSignatures && currentOptions.limit && signatures.length === currentOptions.limit + ); } const filteredSignatures = allSignatures.filter((signatureTransaction) => { @@ -128,12 +117,7 @@ export class SvmSpokeEventsClient { // Fetch events for all signatures in parallel. const eventsWithSlots = await Promise.all( filteredSignatures.map(async (signatureTransaction) => { - const events = await this.readEventsFromSignature( - signatureTransaction.signature, - program, - anchorIdl, - finality - ); + const events = await this.readEventsFromSignature(signatureTransaction.signature, program, anchorIdl, finality); return events.map((event) => ({ ...event, confirmationStatus: signatureTransaction.confirmationStatus || "Unknown", @@ -204,8 +188,7 @@ export class SvmSpokeEventsClient { for (const ixBlock of txResult.meta?.innerInstructions ?? []) { for (const ix of ixBlock.instructions) { const ixProgramId = messageAccountKeys[ix.programIdIndex]; - const singleIxAccount = - ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined; + const singleIxAccount = ix.accounts.length === 1 ? messageAccountKeys[ix.accounts[0]] : undefined; if ( ixProgramId !== undefined && singleIxAccount !== undefined && @@ -214,9 +197,7 @@ export class SvmSpokeEventsClient { ) { const ixData = utils.bytes.bs58.decode(ix.data); // Skip the first 8 bytes (assumed header) and encode the rest. - const eventData = utils.bytes.base64.encode( - Buffer.from(new Uint8Array(ixData).slice(8)) - ); + const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); const event = new BorshEventCoder(programIdl).decode(eventData); const name = getEventName(event?.name); events.push({ @@ -230,4 +211,4 @@ export class SvmSpokeEventsClient { return events; } -} \ No newline at end of file +} diff --git a/src/svm/types.ts b/src/svm/types.ts index 947b9c248..06a74a8ad 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -1,159 +1,159 @@ import { Signature, Address, UnixTimestamp } from "@solana/web3-v2.js"; export type BridgedToHubPoolEvent = { - amount: bigint; - mint: Address; + amount: bigint; + mint: Address; }; export type TokensBridgedEvent = { - amount_to_return: bigint; - chain_id: bigint; - leaf_id: number; - l2_token_address: Address; - caller: Address; + amount_to_return: bigint; + chain_id: bigint; + leaf_id: number; + l2_token_address: Address; + caller: Address; }; export type ExecutedRelayerRefundRootEvent = { - amount_to_return: bigint; - chain_id: bigint; - refund_amounts: bigint[]; - root_bundle_id: number; - leaf_id: number; - l2_token_address: Address; - refund_addresses: Address[]; - deferred_refunds: boolean; - caller: Address; + amount_to_return: bigint; + chain_id: bigint; + refund_amounts: bigint[]; + root_bundle_id: number; + leaf_id: number; + l2_token_address: Address; + refund_addresses: Address[]; + deferred_refunds: boolean; + caller: Address; }; export type RelayedRootBundleEvent = { - root_bundle_id: number; - relayer_refund_root: Array; - slow_relay_root: Array; + root_bundle_id: number; + relayer_refund_root: Array; + slow_relay_root: Array; }; export type PausedDepositsEvent = { - is_paused: boolean; + is_paused: boolean; }; export type PausedFillsEvent = { - is_paused: boolean; + is_paused: boolean; }; export type SetXDomainAdminEvent = { - new_admin: Address; + new_admin: Address; }; export type EnabledDepositRouteEvent = { - origin_token: Address; - destination_chain_id: bigint; - enabled: boolean; + origin_token: Address; + destination_chain_id: bigint; + enabled: boolean; }; export type FillType = "FastFill" | "ReplacedSlowFill" | "SlowFill"; export type FilledRelayEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - repayment_chain_id: bigint; - origin_chain_id: bigint; - deposit_id: Array; - fill_deadline: number; - exclusivity_deadline: number; - exclusive_relayer: Address; - relayer: Address; - depositor: Address; - recipient: Address; - message_hash: string; - relay_execution_info: { - updated_recipient: Address; - updated_message_hash: string; - updated_output_amount: bigint; - fill_type: Record; - }; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + repayment_chain_id: bigint; + origin_chain_id: bigint; + deposit_id: Array; + fill_deadline: number; + exclusivity_deadline: number; + exclusive_relayer: Address; + relayer: Address; + depositor: Address; + recipient: Address; + message_hash: string; + relay_execution_info: { + updated_recipient: Address; + updated_message_hash: string; + updated_output_amount: bigint; + fill_type: Record; + }; }; export type FundsDepositedEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - destination_chain_id: bigint; - deposit_id: Array; - quote_timestamp: number; - fill_deadline: number; - exclusivity_deadline: number; - depositor: Address; - recipient: Address; - exclusive_relayer: Address; - message: Buffer; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + destination_chain_id: bigint; + deposit_id: Array; + quote_timestamp: number; + fill_deadline: number; + exclusivity_deadline: number; + depositor: Address; + recipient: Address; + exclusive_relayer: Address; + message: Buffer; }; export type EmergencyDeletedRootBundleEvent = { - root_bundle_id: number; + root_bundle_id: number; }; export type RequestedSlowFillEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - origin_chain_id: bigint; - deposit_id: Array; - fill_deadline: number; - exclusivity_deadline: number; - exclusive_relayer: Address; - depositor: Address; - recipient: Address; - message_hash: string; + input_token: Address; + output_token: Address; + input_amount: bigint; + output_amount: bigint; + origin_chain_id: bigint; + deposit_id: Array; + fill_deadline: number; + exclusivity_deadline: number; + exclusive_relayer: Address; + depositor: Address; + recipient: Address; + message_hash: string; }; export type ClaimedRelayerRefundEvent = { - l2_token_address: Address; - claim_amount: bigint; - refund_address: Address; + l2_token_address: Address; + claim_amount: bigint; + refund_address: Address; }; export type EventData = - | BridgedToHubPoolEvent - | TokensBridgedEvent - | ExecutedRelayerRefundRootEvent - | RelayedRootBundleEvent - | PausedDepositsEvent - | PausedFillsEvent - | SetXDomainAdminEvent - | EnabledDepositRouteEvent - | FilledRelayEvent - | FundsDepositedEvent - | EmergencyDeletedRootBundleEvent - | RequestedSlowFillEvent - | ClaimedRelayerRefundEvent; + | BridgedToHubPoolEvent + | TokensBridgedEvent + | ExecutedRelayerRefundRootEvent + | RelayedRootBundleEvent + | PausedDepositsEvent + | PausedFillsEvent + | SetXDomainAdminEvent + | EnabledDepositRouteEvent + | FilledRelayEvent + | FundsDepositedEvent + | EmergencyDeletedRootBundleEvent + | RequestedSlowFillEvent + | ClaimedRelayerRefundEvent; export enum SVMEventNames { - FilledRelay = "FilledRelay", - FundsDeposited = "FundsDeposited", - EnabledDepositRoute = "EnabledDepositRoute", - RelayedRootBundle = "RelayedRootBundle", - ExecutedRelayerRefundRoot = "ExecutedRelayerRefundRoot", - BridgedToHubPool = "BridgedToHubPool", - PausedDeposits = "PausedDeposits", - PausedFills = "PausedFills", - SetXDomainAdmin = "SetXDomainAdmin", - EmergencyDeletedRootBundle = "EmergencyDeletedRootBundle", - RequestedSlowFill = "RequestedSlowFill", - ClaimedRelayerRefund = "ClaimedRelayerRefund", - TokensBridged = "TokensBridged", + FilledRelay = "FilledRelay", + FundsDeposited = "FundsDeposited", + EnabledDepositRoute = "EnabledDepositRoute", + RelayedRootBundle = "RelayedRootBundle", + ExecutedRelayerRefundRoot = "ExecutedRelayerRefundRoot", + BridgedToHubPool = "BridgedToHubPool", + PausedDeposits = "PausedDeposits", + PausedFills = "PausedFills", + SetXDomainAdmin = "SetXDomainAdmin", + EmergencyDeletedRootBundle = "EmergencyDeletedRootBundle", + RequestedSlowFill = "RequestedSlowFill", + ClaimedRelayerRefund = "ClaimedRelayerRefund", + TokensBridged = "TokensBridged", } export type EventName = keyof typeof SVMEventNames; export type EventWithData = { - confirmationStatus: string; - blockTime: UnixTimestamp; - signature: Signature; - slot: bigint; - name: string; - data: T; - program: Address; -}; \ No newline at end of file + confirmationStatus: string; + blockTime: UnixTimestamp; + signature: Signature; + slot: bigint; + name: string; + data: T; + program: Address; +}; diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index 6610b22a9..945d0a9ec 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -1,74 +1,89 @@ import { BN } from "@coral-xyz/anchor"; import web3 from "@solana/web3-v2.js"; -import { BridgedToHubPoolEvent, ClaimedRelayerRefundEvent, EmergencyDeletedRootBundleEvent, EnabledDepositRouteEvent, EventData, EventName, ExecutedRelayerRefundRootEvent, FilledRelayEvent, FundsDepositedEvent, PausedDepositsEvent, PausedFillsEvent, RelayedRootBundleEvent, RequestedSlowFillEvent, SetXDomainAdminEvent, SVMEventNames, TokensBridgedEvent } from "../types"; +import { + BridgedToHubPoolEvent, + ClaimedRelayerRefundEvent, + EmergencyDeletedRootBundleEvent, + EnabledDepositRouteEvent, + EventData, + EventName, + ExecutedRelayerRefundRootEvent, + FilledRelayEvent, + FundsDepositedEvent, + PausedDepositsEvent, + PausedFillsEvent, + RelayedRootBundleEvent, + RequestedSlowFillEvent, + SetXDomainAdminEvent, + SVMEventNames, + TokensBridgedEvent, +} from "../types"; /** * Parses event data from a transaction. */ export function parseEventData(eventData: any): any { - if (!eventData) return eventData; + if (!eventData) return eventData; - if (Array.isArray(eventData)) { - return eventData.map(parseEventData); - } - - if (typeof eventData === "object") { - if (eventData.constructor.name === "PublicKey") { - return web3.address(eventData.toString()); - } - if (BN.isBN(eventData)) { - return BigInt(eventData.toString()); - } + if (Array.isArray(eventData)) { + return eventData.map(parseEventData); + } - return Object.fromEntries( - Object.entries(eventData).map(([key, value]) => [key, parseEventData(value)]) - ); + if (typeof eventData === "object") { + if (eventData.constructor.name === "PublicKey") { + return web3.address(eventData.toString()); + } + if (BN.isBN(eventData)) { + return BigInt(eventData.toString()); } - return eventData; + return Object.fromEntries(Object.entries(eventData).map(([key, value]) => [key, parseEventData(value)])); + } + + return eventData; } /** * Gets the event name from a raw name. */ export function getEventName(rawName?: string): EventName { - if (!rawName) throw new Error("Raw name is undefined"); - if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; - throw new Error(`Unknown event name: ${rawName}`); + if (!rawName) throw new Error("Raw name is undefined"); + if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; + throw new Error(`Unknown event name: ${rawName}`); } /** * Maps event data to an event type. */ export function mapEventData(eventData: any, name: EventName): EventData { - switch (name) { - case "FilledRelay": - return eventData as FilledRelayEvent; - case "FundsDeposited": - return eventData as FundsDepositedEvent; - case "BridgedToHubPool": - return eventData as BridgedToHubPoolEvent; - case "TokensBridged": - return eventData as TokensBridgedEvent; - case "ExecutedRelayerRefundRoot": - return eventData as ExecutedRelayerRefundRootEvent; - case "RelayedRootBundle": - return eventData as RelayedRootBundleEvent; - case "PausedDeposits": - return eventData as PausedDepositsEvent; - case "PausedFills": - return eventData as PausedFillsEvent; - case "SetXDomainAdmin": - return eventData as SetXDomainAdminEvent; - case "EnabledDepositRoute": - return eventData as EnabledDepositRouteEvent; - case "EmergencyDeletedRootBundle": - return eventData as EmergencyDeletedRootBundleEvent; - case "RequestedSlowFill": - return eventData as RequestedSlowFillEvent; - case "ClaimedRelayerRefund": - return eventData as ClaimedRelayerRefundEvent; - default: - throw new Error(`Unknown event name: ${name}`); - } -} \ No newline at end of file + switch (name) { + case "FilledRelay": + return eventData as FilledRelayEvent; + case "FundsDeposited": + return eventData as FundsDepositedEvent; + case "BridgedToHubPool": + return eventData as BridgedToHubPoolEvent; + case "TokensBridged": + return eventData as TokensBridgedEvent; + case "ExecutedRelayerRefundRoot": + return eventData as ExecutedRelayerRefundRootEvent; + case "RelayedRootBundle": + return eventData as RelayedRootBundleEvent; + case "PausedDeposits": + return eventData as PausedDepositsEvent; + case "PausedFills": + return eventData as PausedFillsEvent; + case "SetXDomainAdmin": + return eventData as SetXDomainAdminEvent; + case "EnabledDepositRoute": + return eventData as EnabledDepositRouteEvent; + case "EmergencyDeletedRootBundle": + return eventData as EmergencyDeletedRootBundleEvent; + case "RequestedSlowFill": + return eventData as RequestedSlowFillEvent; + case "ClaimedRelayerRefund": + return eventData as ClaimedRelayerRefundEvent; + default: + throw new Error(`Unknown event name: ${name}`); + } +} diff --git a/src/svm/utils/helpers.ts b/src/svm/utils/helpers.ts index 9d57ab04c..7aaaa1a10 100644 --- a/src/svm/utils/helpers.ts +++ b/src/svm/utils/helpers.ts @@ -4,6 +4,6 @@ import web3, { RpcTransport } from "@solana/web3-v2.js"; * Helper to determine if the current RPC network is devnet. */ export async function isDevnet(rpc: web3.Rpc>): Promise { - const genesisHash = await rpc.getGenesisHash().send(); - return genesisHash === "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"; -} \ No newline at end of file + const genesisHash = await rpc.getGenesisHash().send(); + return genesisHash === "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"; +} From b23a15097a3c5ff7eb722f9f0c3572640a5c78f8 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 19:16:41 +0100 Subject: [PATCH 08/17] feat: add missing event and private function Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 2 +- src/svm/types.ts | 8 +++++++- src/svm/utils/events.ts | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 6a88510cd..4e28ffa0f 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -75,7 +75,7 @@ export class SvmSpokeEventsClient { * @param finality - Commitment level. * @returns A promise that resolves to an array of all events with additional metadata. */ - public async queryAllEvents( + private async queryAllEvents( program: Address, anchorIdl: Idl, fromSlot?: bigint, diff --git a/src/svm/types.ts b/src/svm/types.ts index 06a74a8ad..dc938264f 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -115,6 +115,10 @@ export type ClaimedRelayerRefundEvent = { refund_address: Address; }; +export type TransferredOwnershipEvent = { + new_owner: Address; +}; + export type EventData = | BridgedToHubPoolEvent | TokensBridgedEvent @@ -128,7 +132,8 @@ export type EventData = | FundsDepositedEvent | EmergencyDeletedRootBundleEvent | RequestedSlowFillEvent - | ClaimedRelayerRefundEvent; + | ClaimedRelayerRefundEvent + | TransferredOwnershipEvent; export enum SVMEventNames { FilledRelay = "FilledRelay", @@ -144,6 +149,7 @@ export enum SVMEventNames { RequestedSlowFill = "RequestedSlowFill", ClaimedRelayerRefund = "ClaimedRelayerRefund", TokensBridged = "TokensBridged", + TransferredOwnership = "TransferredOwnership", } export type EventName = keyof typeof SVMEventNames; diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index 945d0a9ec..fd57293f8 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -17,6 +17,7 @@ import { SetXDomainAdminEvent, SVMEventNames, TokensBridgedEvent, + TransferredOwnershipEvent, } from "../types"; /** @@ -83,6 +84,8 @@ export function mapEventData(eventData: any, name: EventName): EventData { return eventData as RequestedSlowFillEvent; case "ClaimedRelayerRefund": return eventData as ClaimedRelayerRefundEvent; + case "TransferredOwnership": + return eventData as TransferredOwnershipEvent; default: throw new Error(`Unknown event name: ${name}`); } From 80d9af558aa5061bcd7f798bddcb0464ebdc7381 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 19:26:36 +0100 Subject: [PATCH 09/17] fix: lint Signed-off-by: Pablo Maldonado --- src/svm/utils/events.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index fd57293f8..b6209e79d 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -23,6 +23,7 @@ import { /** * Parses event data from a transaction. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function parseEventData(eventData: any): any { if (!eventData) return eventData; @@ -56,6 +57,7 @@ export function getEventName(rawName?: string): EventName { /** * Maps event data to an event type. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function mapEventData(eventData: any, name: EventName): EventData { switch (name) { case "FilledRelay": From 405c4e91a3844e143cce37a068bd33a9c4fb87bc Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 19:37:45 +0100 Subject: [PATCH 10/17] fix: lint types Signed-off-by: Pablo Maldonado --- src/svm/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/svm/types.ts b/src/svm/types.ts index dc938264f..e8f7c845c 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -70,7 +70,7 @@ export type FilledRelayEvent = { updated_recipient: Address; updated_message_hash: string; updated_output_amount: bigint; - fill_type: Record; + fill_type: Record>; }; }; From 9bcad79a035f59aa832576b7c5bc64ae0c58fa27 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Tue, 25 Feb 2025 19:59:10 +0100 Subject: [PATCH 11/17] feat: use codama client types Signed-off-by: Pablo Maldonado --- src/svm/types.ts | 148 ++++------------------------------------ src/svm/utils/events.ts | 61 ++++++++--------- 2 files changed, 42 insertions(+), 167 deletions(-) diff --git a/src/svm/types.ts b/src/svm/types.ts index e8f7c845c..2b69985ed 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -1,139 +1,21 @@ import { Signature, Address, UnixTimestamp } from "@solana/web3-v2.js"; - -export type BridgedToHubPoolEvent = { - amount: bigint; - mint: Address; -}; - -export type TokensBridgedEvent = { - amount_to_return: bigint; - chain_id: bigint; - leaf_id: number; - l2_token_address: Address; - caller: Address; -}; - -export type ExecutedRelayerRefundRootEvent = { - amount_to_return: bigint; - chain_id: bigint; - refund_amounts: bigint[]; - root_bundle_id: number; - leaf_id: number; - l2_token_address: Address; - refund_addresses: Address[]; - deferred_refunds: boolean; - caller: Address; -}; - -export type RelayedRootBundleEvent = { - root_bundle_id: number; - relayer_refund_root: Array; - slow_relay_root: Array; -}; - -export type PausedDepositsEvent = { - is_paused: boolean; -}; - -export type PausedFillsEvent = { - is_paused: boolean; -}; - -export type SetXDomainAdminEvent = { - new_admin: Address; -}; - -export type EnabledDepositRouteEvent = { - origin_token: Address; - destination_chain_id: bigint; - enabled: boolean; -}; - -export type FillType = "FastFill" | "ReplacedSlowFill" | "SlowFill"; - -export type FilledRelayEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - repayment_chain_id: bigint; - origin_chain_id: bigint; - deposit_id: Array; - fill_deadline: number; - exclusivity_deadline: number; - exclusive_relayer: Address; - relayer: Address; - depositor: Address; - recipient: Address; - message_hash: string; - relay_execution_info: { - updated_recipient: Address; - updated_message_hash: string; - updated_output_amount: bigint; - fill_type: Record>; - }; -}; - -export type FundsDepositedEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - destination_chain_id: bigint; - deposit_id: Array; - quote_timestamp: number; - fill_deadline: number; - exclusivity_deadline: number; - depositor: Address; - recipient: Address; - exclusive_relayer: Address; - message: Buffer; -}; - -export type EmergencyDeletedRootBundleEvent = { - root_bundle_id: number; -}; - -export type RequestedSlowFillEvent = { - input_token: Address; - output_token: Address; - input_amount: bigint; - output_amount: bigint; - origin_chain_id: bigint; - deposit_id: Array; - fill_deadline: number; - exclusivity_deadline: number; - exclusive_relayer: Address; - depositor: Address; - recipient: Address; - message_hash: string; -}; - -export type ClaimedRelayerRefundEvent = { - l2_token_address: Address; - claim_amount: bigint; - refund_address: Address; -}; - -export type TransferredOwnershipEvent = { - new_owner: Address; -}; +import { SvmSpokeClient } from "@across-protocol/contracts"; export type EventData = - | BridgedToHubPoolEvent - | TokensBridgedEvent - | ExecutedRelayerRefundRootEvent - | RelayedRootBundleEvent - | PausedDepositsEvent - | PausedFillsEvent - | SetXDomainAdminEvent - | EnabledDepositRouteEvent - | FilledRelayEvent - | FundsDepositedEvent - | EmergencyDeletedRootBundleEvent - | RequestedSlowFillEvent - | ClaimedRelayerRefundEvent - | TransferredOwnershipEvent; + | SvmSpokeClient.BridgedToHubPool + | SvmSpokeClient.TokensBridged + | SvmSpokeClient.ExecutedRelayerRefundRoot + | SvmSpokeClient.RelayedRootBundle + | SvmSpokeClient.PausedDeposits + | SvmSpokeClient.PausedFills + | SvmSpokeClient.SetXDomainAdmin + | SvmSpokeClient.EnabledDepositRoute + | SvmSpokeClient.FilledRelay + | SvmSpokeClient.FundsDeposited + | SvmSpokeClient.EmergencyDeletedRootBundle + | SvmSpokeClient.RequestedSlowFill + | SvmSpokeClient.ClaimedRelayerRefund + | SvmSpokeClient.TransferredOwnership; export enum SVMEventNames { FilledRelay = "FilledRelay", diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index b6209e79d..f1be9cf3d 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -1,24 +1,7 @@ +import { SvmSpokeClient } from "@across-protocol/contracts"; import { BN } from "@coral-xyz/anchor"; import web3 from "@solana/web3-v2.js"; -import { - BridgedToHubPoolEvent, - ClaimedRelayerRefundEvent, - EmergencyDeletedRootBundleEvent, - EnabledDepositRouteEvent, - EventData, - EventName, - ExecutedRelayerRefundRootEvent, - FilledRelayEvent, - FundsDepositedEvent, - PausedDepositsEvent, - PausedFillsEvent, - RelayedRootBundleEvent, - RequestedSlowFillEvent, - SetXDomainAdminEvent, - SVMEventNames, - TokensBridgedEvent, - TransferredOwnershipEvent, -} from "../types"; +import { EventData, EventName, SVMEventNames } from "../types"; /** * Parses event data from a transaction. @@ -39,12 +22,22 @@ export function parseEventData(eventData: any): any { return BigInt(eventData.toString()); } - return Object.fromEntries(Object.entries(eventData).map(([key, value]) => [key, parseEventData(value)])); + // Convert each key from snake_case to camelCase and process the value recursively. + return Object.fromEntries( + Object.entries(eventData).map(([key, value]) => [snakeToCamel(key), parseEventData(value)]) + ); } return eventData; } +/** + * Converts a snake_case string to camelCase. + */ +function snakeToCamel(s: string): string { + return s.replace(/(_\w)/g, (match) => match[1].toUpperCase()); +} + /** * Gets the event name from a raw name. */ @@ -61,33 +54,33 @@ export function getEventName(rawName?: string): EventName { export function mapEventData(eventData: any, name: EventName): EventData { switch (name) { case "FilledRelay": - return eventData as FilledRelayEvent; + return eventData as SvmSpokeClient.FilledRelay; case "FundsDeposited": - return eventData as FundsDepositedEvent; + return eventData as SvmSpokeClient.FundsDeposited; case "BridgedToHubPool": - return eventData as BridgedToHubPoolEvent; + return eventData as SvmSpokeClient.BridgedToHubPool; case "TokensBridged": - return eventData as TokensBridgedEvent; + return eventData as SvmSpokeClient.TokensBridged; case "ExecutedRelayerRefundRoot": - return eventData as ExecutedRelayerRefundRootEvent; + return eventData as SvmSpokeClient.ExecutedRelayerRefundRoot; case "RelayedRootBundle": - return eventData as RelayedRootBundleEvent; + return eventData as SvmSpokeClient.RelayedRootBundle; case "PausedDeposits": - return eventData as PausedDepositsEvent; + return eventData as SvmSpokeClient.PausedDeposits; case "PausedFills": - return eventData as PausedFillsEvent; + return eventData as SvmSpokeClient.PausedFills; case "SetXDomainAdmin": - return eventData as SetXDomainAdminEvent; + return eventData as SvmSpokeClient.SetXDomainAdmin; case "EnabledDepositRoute": - return eventData as EnabledDepositRouteEvent; + return eventData as SvmSpokeClient.EnabledDepositRoute; case "EmergencyDeletedRootBundle": - return eventData as EmergencyDeletedRootBundleEvent; + return eventData as SvmSpokeClient.EmergencyDeletedRootBundle; case "RequestedSlowFill": - return eventData as RequestedSlowFillEvent; + return eventData as SvmSpokeClient.RequestedSlowFill; case "ClaimedRelayerRefund": - return eventData as ClaimedRelayerRefundEvent; + return eventData as SvmSpokeClient.ClaimedRelayerRefund; case "TransferredOwnership": - return eventData as TransferredOwnershipEvent; + return eventData as SvmSpokeClient.TransferredOwnership; default: throw new Error(`Unknown event name: ${name}`); } From b07b9b43e6cb667d77199670be2d5a27bf2c6b7d Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 26 Feb 2025 12:50:30 +0100 Subject: [PATCH 12/17] feat: simplify types Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 8 ++++---- src/svm/types.ts | 2 +- src/svm/utils/events.ts | 42 +---------------------------------------- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 4e28ffa0f..257a11c8c 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -11,7 +11,7 @@ import web3, { unixTimestamp, } from "@solana/web3-v2.js"; import { EventData, EventName, EventWithData } from "./types"; -import { getEventName, mapEventData, parseEventData } from "./utils/events"; +import { getEventName, parseEventData } from "./utils/events"; import { isDevnet } from "./utils/helpers"; type GetTransactionReturnType = ReturnType; @@ -165,11 +165,11 @@ export class SvmSpokeEventsClient { txResult: GetTransactionReturnType, programId: Address, programIdl: Idl - ): Promise<{ program: Address; data: EventData; name: string }[]> { + ): Promise<{ program: Address; data: EventData; name: EventName }[]> { if (!txResult) return []; const eventAuthorities: Map = new Map(); - const events: { program: Address; data: EventData; name: string }[] = []; + const events: { program: Address; data: EventData; name: EventName }[] = []; // Derive the event authority PDA. const [pda] = await web3.getProgramDerivedAddress({ @@ -202,7 +202,7 @@ export class SvmSpokeEventsClient { const name = getEventName(event?.name); events.push({ program: programId, - data: mapEventData(parseEventData(event?.data), name), + data: parseEventData(event?.data), name, }); } diff --git a/src/svm/types.ts b/src/svm/types.ts index 2b69985ed..58a61d58f 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -41,7 +41,7 @@ export type EventWithData = { blockTime: UnixTimestamp; signature: Signature; slot: bigint; - name: string; + name: EventName; data: T; program: Address; }; diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index f1be9cf3d..7cb467bae 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -1,7 +1,6 @@ -import { SvmSpokeClient } from "@across-protocol/contracts"; import { BN } from "@coral-xyz/anchor"; import web3 from "@solana/web3-v2.js"; -import { EventData, EventName, SVMEventNames } from "../types"; +import { EventName, SVMEventNames } from "../types"; /** * Parses event data from a transaction. @@ -46,42 +45,3 @@ export function getEventName(rawName?: string): EventName { if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; throw new Error(`Unknown event name: ${rawName}`); } - -/** - * Maps event data to an event type. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function mapEventData(eventData: any, name: EventName): EventData { - switch (name) { - case "FilledRelay": - return eventData as SvmSpokeClient.FilledRelay; - case "FundsDeposited": - return eventData as SvmSpokeClient.FundsDeposited; - case "BridgedToHubPool": - return eventData as SvmSpokeClient.BridgedToHubPool; - case "TokensBridged": - return eventData as SvmSpokeClient.TokensBridged; - case "ExecutedRelayerRefundRoot": - return eventData as SvmSpokeClient.ExecutedRelayerRefundRoot; - case "RelayedRootBundle": - return eventData as SvmSpokeClient.RelayedRootBundle; - case "PausedDeposits": - return eventData as SvmSpokeClient.PausedDeposits; - case "PausedFills": - return eventData as SvmSpokeClient.PausedFills; - case "SetXDomainAdmin": - return eventData as SvmSpokeClient.SetXDomainAdmin; - case "EnabledDepositRoute": - return eventData as SvmSpokeClient.EnabledDepositRoute; - case "EmergencyDeletedRootBundle": - return eventData as SvmSpokeClient.EmergencyDeletedRootBundle; - case "RequestedSlowFill": - return eventData as SvmSpokeClient.RequestedSlowFill; - case "ClaimedRelayerRefund": - return eventData as SvmSpokeClient.ClaimedRelayerRefund; - case "TransferredOwnership": - return eventData as SvmSpokeClient.TransferredOwnership; - default: - throw new Error(`Unknown event name: ${name}`); - } -} From 885b792d710ee7fae72f421384b2dc43747138ac Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Thu, 27 Feb 2025 09:49:13 +0100 Subject: [PATCH 13/17] feat: bump contracts Signed-off-by: Pablo Maldonado --- package.json | 2 +- yarn.lock | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 18eb87fee..8830037cd 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "dependencies": { "@across-protocol/across-token": "^1.0.0", "@across-protocol/constants": "^3.1.38", - "@across-protocol/contracts": "4.0.2", + "@across-protocol/contracts": "4.0.3", "@eth-optimism/sdk": "^3.3.1", "@ethersproject/bignumber": "^5.7.0", "@pinata/sdk": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 3a55766c6..557264afb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,22 +16,17 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants@^3.1.35": - version "3.1.35" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.35.tgz#80ee8e569bc5c1fc94b5087d357d9612fd782151" - integrity sha512-2Fj9mqBEVQu4Bsq6o7helUkhEjpce+uqni0pTV51y1QOEQdgAJU5U5BNQFXUMUDMQRaM3DqB4ys89GVJ4TuA/w== - -"@across-protocol/constants@^3.1.38": +"@across-protocol/constants@^3.1.37", "@across-protocol/constants@^3.1.38": version "3.1.38" resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.38.tgz#63f4d9b86576b0003655152c51293dd1c7ab6a1f" integrity sha512-/85ACwpu4oxAADOah8VP3esxU3FVb+RieMCINgn4oUf4Rq9cC1LSJm157hy5cc4CBUaySV312MP1iM1d8ysljA== -"@across-protocol/contracts@4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-4.0.2.tgz#7c61f7e1c58fa2d9478cb384c579847ad202170e" - integrity sha512-22RdGn5DURq1UGVf+55ZlcdaTqRRbG6izHHxbRywa7UOGIB2dz8TPeNRcc8ST6mOkb1kavEfYT9vnqxLHObK4A== +"@across-protocol/contracts@4.0.3": + version "4.0.3" + resolved "https://registry.npmjs.org/@across-protocol/contracts/-/contracts-4.0.3.tgz#f4c67d7782f36093381ceca4aa3aa0decd79f60f" + integrity sha512-p8rwRqhmjUPnX8Vae6V4+cbOSNqwHQAOOK4GntSkTpSKXFjEPUistoD/8bi+lawMTDZHTlW8dB6PICcMZn5ONQ== dependencies: - "@across-protocol/constants" "^3.1.35" + "@across-protocol/constants" "^3.1.37" "@coral-xyz/anchor" "^0.30.1" "@defi-wonderland/smock" "^2.3.4" "@eth-optimism/contracts" "^0.5.40" From cf3ba96b0c1e8feebb9d99672a20298490a8e655 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 28 Feb 2025 16:40:30 +0100 Subject: [PATCH 14/17] fix: fixes Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 50 ++++++++++++++++++----------------------- src/svm/types.ts | 4 ++-- src/svm/utils/events.ts | 3 +-- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 257a11c8c..46483ff2e 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -1,19 +1,20 @@ import { getDeployedAddress, SvmSpokeIdl } from "@across-protocol/contracts"; import { getSolanaChainId } from "@across-protocol/contracts/dist/src/svm/web3-v1"; -import { BorshEventCoder, Idl, utils } from "@coral-xyz/anchor"; +import { BorshEventCoder, utils } from "@coral-xyz/anchor"; import web3, { Address, Commitment, GetSignaturesForAddressApi, GetTransactionApi, RpcTransport, - Signature, - unixTimestamp, + Signature } from "@solana/web3-v2.js"; import { EventData, EventName, EventWithData } from "./types"; import { getEventName, parseEventData } from "./utils/events"; import { isDevnet } from "./utils/helpers"; +// Note: Type assumes default JSON encoding. Using different encoding options (e.g. base58) would result in incompatible +// return types. type GetTransactionReturnType = ReturnType; type GetSignaturesForAddressConfig = Parameters[1]; type GetSignaturesForAddressTransaction = ReturnType[number]; @@ -57,10 +58,9 @@ export class SvmSpokeEventsClient { eventName: EventName, fromSlot?: bigint, toSlot?: bigint, - options: GetSignaturesForAddressConfig = { limit: 1000 }, - finality: Commitment = "confirmed" + options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }, ): Promise[]> { - const events = await this.queryAllEvents(this.svmSpokeAddress, SvmSpokeIdl, fromSlot, toSlot, options, finality); + const events = await this.queryAllEvents(fromSlot, toSlot, options); return events.filter((event) => event.name === eventName) as EventWithData[]; } @@ -72,16 +72,13 @@ export class SvmSpokeEventsClient { * @param fromSlot - Optional starting slot. * @param toSlot - Optional ending slot. * @param options - Options for fetching signatures. - * @param finality - Commitment level. + * @param commitment - Commitment level. * @returns A promise that resolves to an array of all events with additional metadata. */ private async queryAllEvents( - program: Address, - anchorIdl: Idl, fromSlot?: bigint, toSlot?: bigint, - options: GetSignaturesForAddressConfig = { limit: 1000 }, - finality: Commitment = "confirmed" + options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" } ): Promise[]> { const allSignatures: GetSignaturesForAddressTransaction[] = []; let hasMoreSignatures = true; @@ -89,7 +86,7 @@ export class SvmSpokeEventsClient { while (hasMoreSignatures) { const signatures: GetSignaturesForAddressApiResponse = await this.rpc - .getSignaturesForAddress(program, currentOptions) + .getSignaturesForAddress(this.svmSpokeAddress, currentOptions) .send(); // Signatures are sorted by slot in descending order. allSignatures.push(...signatures); @@ -117,11 +114,11 @@ export class SvmSpokeEventsClient { // Fetch events for all signatures in parallel. const eventsWithSlots = await Promise.all( filteredSignatures.map(async (signatureTransaction) => { - const events = await this.readEventsFromSignature(signatureTransaction.signature, program, anchorIdl, finality); + const events = await this.readEventsFromSignature(signatureTransaction.signature, options.commitment); return events.map((event) => ({ ...event, - confirmationStatus: signatureTransaction.confirmationStatus || "Unknown", - blockTime: signatureTransaction.blockTime || unixTimestamp(BigInt(0)), + confirmationStatus: signatureTransaction.confirmationStatus, + blockTime: signatureTransaction.blockTime, signature: signatureTransaction.signature, slot: signatureTransaction.slot, })); @@ -141,8 +138,6 @@ export class SvmSpokeEventsClient { */ private async readEventsFromSignature( txSignature: Signature, - programId: Address, - programIdl: Idl, commitment: Commitment = "confirmed" ) { const txResult = await this.rpc @@ -150,7 +145,7 @@ export class SvmSpokeEventsClient { .send(); if (txResult === null) return []; - return this.processEventFromTx(txResult, programId, programIdl); + return this.processEventFromTx(txResult); } /** @@ -162,9 +157,7 @@ export class SvmSpokeEventsClient { * @returns A promise that resolves to an array of events with their data and name. */ private async processEventFromTx( - txResult: GetTransactionReturnType, - programId: Address, - programIdl: Idl + txResult: GetTransactionReturnType ): Promise<{ program: Address; data: EventData; name: EventName }[]> { if (!txResult) return []; @@ -173,10 +166,10 @@ export class SvmSpokeEventsClient { // Derive the event authority PDA. const [pda] = await web3.getProgramDerivedAddress({ - programAddress: programId, + programAddress: this.svmSpokeAddress, seeds: ["__event_authority"], }); - eventAuthorities.set(programId, pda); + eventAuthorities.set(this.svmSpokeAddress.toString(), pda); const accountKeys = txResult.transaction.message.accountKeys; const messageAccountKeys = [...accountKeys]; @@ -192,16 +185,17 @@ export class SvmSpokeEventsClient { if ( ixProgramId !== undefined && singleIxAccount !== undefined && - programId == ixProgramId && - eventAuthorities.get(ixProgramId.toString()) == singleIxAccount + this.svmSpokeAddress === ixProgramId && + eventAuthorities.get(ixProgramId.toString()) === singleIxAccount ) { const ixData = utils.bytes.bs58.decode(ix.data); // Skip the first 8 bytes (assumed header) and encode the rest. const eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8))); - const event = new BorshEventCoder(programIdl).decode(eventData); - const name = getEventName(event?.name); + const event = new BorshEventCoder(SvmSpokeIdl).decode(eventData); + if (!event?.name) throw new Error("Event name is undefined"); + const name = getEventName(event.name); events.push({ - program: programId, + program: this.svmSpokeAddress, data: parseEventData(event?.data), name, }); diff --git a/src/svm/types.ts b/src/svm/types.ts index 58a61d58f..cce7f4849 100644 --- a/src/svm/types.ts +++ b/src/svm/types.ts @@ -37,8 +37,8 @@ export enum SVMEventNames { export type EventName = keyof typeof SVMEventNames; export type EventWithData = { - confirmationStatus: string; - blockTime: UnixTimestamp; + confirmationStatus: string | null; + blockTime: UnixTimestamp | null; signature: Signature; slot: bigint; name: EventName; diff --git a/src/svm/utils/events.ts b/src/svm/utils/events.ts index 7cb467bae..67bf52120 100644 --- a/src/svm/utils/events.ts +++ b/src/svm/utils/events.ts @@ -40,8 +40,7 @@ function snakeToCamel(s: string): string { /** * Gets the event name from a raw name. */ -export function getEventName(rawName?: string): EventName { - if (!rawName) throw new Error("Raw name is undefined"); +export function getEventName(rawName: string): EventName { if (Object.values(SVMEventNames).some((name) => rawName.includes(name))) return rawName as EventName; throw new Error(`Unknown event name: ${rawName}`); } From 716ad49689a01cd75bcfa4d5c67c75e85081f7e0 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 28 Feb 2025 16:53:44 +0100 Subject: [PATCH 15/17] feat: optimise Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 46483ff2e..47b33c5b4 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -23,13 +23,15 @@ type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransa export class SvmSpokeEventsClient { private rpc: web3.Rpc>; private svmSpokeAddress: Address; + private svmSpokeEventAuthority: Address; /** * Private constructor. Use the async create() method to instantiate. */ - private constructor(rpc: web3.Rpc>, svmSpokeAddress: Address) { + private constructor(rpc: web3.Rpc>, svmSpokeAddress: Address, eventAuthority: Address) { this.rpc = rpc; this.svmSpokeAddress = svmSpokeAddress; + this.svmSpokeEventAuthority = eventAuthority; } /** @@ -41,7 +43,12 @@ export class SvmSpokeEventsClient { const isTestnet = await isDevnet(rpc); const programId = getDeployedAddress("SvmSpoke", getSolanaChainId(isTestnet ? "devnet" : "mainnet").toString()); if (!programId) throw new Error("Program not found"); - return new SvmSpokeEventsClient(rpc, web3.address(programId)); + const svmSpokeAddress = web3.address(programId); + const [svmSpokeEventAuthority] = await web3.getProgramDerivedAddress({ + programAddress: svmSpokeAddress, + seeds: ["__event_authority"], + }); + return new SvmSpokeEventsClient(rpc, svmSpokeAddress, svmSpokeEventAuthority); } /** @@ -51,7 +58,6 @@ export class SvmSpokeEventsClient { * @param fromSlot - Optional starting slot. * @param toSlot - Optional ending slot. * @param options - Options for fetching signatures. - * @param finality - Commitment level. * @returns A promise that resolves to an array of events matching the eventName. */ public async queryEvents( @@ -67,12 +73,9 @@ export class SvmSpokeEventsClient { /** * Queries all events for a specific program. * - * @param program - The program address. - * @param anchorIdl - The IDL describing the program events. * @param fromSlot - Optional starting slot. * @param toSlot - Optional ending slot. * @param options - Options for fetching signatures. - * @param commitment - Commitment level. * @returns A promise that resolves to an array of all events with additional metadata. */ private async queryAllEvents( @@ -113,15 +116,9 @@ export class SvmSpokeEventsClient { // Fetch events for all signatures in parallel. const eventsWithSlots = await Promise.all( - filteredSignatures.map(async (signatureTransaction) => { + filteredSignatures.flatMap(async (signatureTransaction) => { const events = await this.readEventsFromSignature(signatureTransaction.signature, options.commitment); - return events.map((event) => ({ - ...event, - confirmationStatus: signatureTransaction.confirmationStatus, - blockTime: signatureTransaction.blockTime, - signature: signatureTransaction.signature, - slot: signatureTransaction.slot, - })); + return events.map((event) => ({ ...event, confirmationStatus: signatureTransaction.confirmationStatus, blockTime: signatureTransaction.blockTime, signature: signatureTransaction.signature, slot: signatureTransaction.slot })); }) ); return eventsWithSlots.flat(); @@ -131,8 +128,6 @@ export class SvmSpokeEventsClient { * Reads events from a transaction signature. * * @param txSignature - The transaction signature. - * @param programId - The program address. - * @param programIdl - The program IDL. * @param commitment - Commitment level. * @returns A promise that resolves to an array of events. */ @@ -152,25 +147,14 @@ export class SvmSpokeEventsClient { * Processes events from a transaction. * * @param txResult - The transaction result. - * @param programId - The program address. - * @param programIdl - The program IDL. * @returns A promise that resolves to an array of events with their data and name. */ private async processEventFromTx( txResult: GetTransactionReturnType ): Promise<{ program: Address; data: EventData; name: EventName }[]> { if (!txResult) return []; - - const eventAuthorities: Map = new Map(); const events: { program: Address; data: EventData; name: EventName }[] = []; - // Derive the event authority PDA. - const [pda] = await web3.getProgramDerivedAddress({ - programAddress: this.svmSpokeAddress, - seeds: ["__event_authority"], - }); - eventAuthorities.set(this.svmSpokeAddress.toString(), pda); - const accountKeys = txResult.transaction.message.accountKeys; const messageAccountKeys = [...accountKeys]; // Writable accounts come first, then readonly. @@ -186,7 +170,7 @@ export class SvmSpokeEventsClient { ixProgramId !== undefined && singleIxAccount !== undefined && this.svmSpokeAddress === ixProgramId && - eventAuthorities.get(ixProgramId.toString()) === singleIxAccount + this.svmSpokeEventAuthority === singleIxAccount ) { const ixData = utils.bytes.bs58.decode(ix.data); // Skip the first 8 bytes (assumed header) and encode the rest. From 93ceb300939f4d2ea7ae48bee9c8be56b89e259f Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 28 Feb 2025 16:56:40 +0100 Subject: [PATCH 16/17] feat: remove unnecessary async Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index 47b33c5b4..eca36756f 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -7,7 +7,7 @@ import web3, { GetSignaturesForAddressApi, GetTransactionApi, RpcTransport, - Signature + Signature, } from "@solana/web3-v2.js"; import { EventData, EventName, EventWithData } from "./types"; import { getEventName, parseEventData } from "./utils/events"; @@ -28,7 +28,11 @@ export class SvmSpokeEventsClient { /** * Private constructor. Use the async create() method to instantiate. */ - private constructor(rpc: web3.Rpc>, svmSpokeAddress: Address, eventAuthority: Address) { + private constructor( + rpc: web3.Rpc>, + svmSpokeAddress: Address, + eventAuthority: Address + ) { this.rpc = rpc; this.svmSpokeAddress = svmSpokeAddress; this.svmSpokeEventAuthority = eventAuthority; @@ -64,7 +68,7 @@ export class SvmSpokeEventsClient { eventName: EventName, fromSlot?: bigint, toSlot?: bigint, - options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" }, + options: GetSignaturesForAddressConfig = { limit: 1000, commitment: "confirmed" } ): Promise[]> { const events = await this.queryAllEvents(fromSlot, toSlot, options); return events.filter((event) => event.name === eventName) as EventWithData[]; @@ -118,7 +122,13 @@ export class SvmSpokeEventsClient { const eventsWithSlots = await Promise.all( filteredSignatures.flatMap(async (signatureTransaction) => { const events = await this.readEventsFromSignature(signatureTransaction.signature, options.commitment); - return events.map((event) => ({ ...event, confirmationStatus: signatureTransaction.confirmationStatus, blockTime: signatureTransaction.blockTime, signature: signatureTransaction.signature, slot: signatureTransaction.slot })); + return events.map((event) => ({ + ...event, + confirmationStatus: signatureTransaction.confirmationStatus, + blockTime: signatureTransaction.blockTime, + signature: signatureTransaction.signature, + slot: signatureTransaction.slot, + })); }) ); return eventsWithSlots.flat(); @@ -131,10 +141,7 @@ export class SvmSpokeEventsClient { * @param commitment - Commitment level. * @returns A promise that resolves to an array of events. */ - private async readEventsFromSignature( - txSignature: Signature, - commitment: Commitment = "confirmed" - ) { + private async readEventsFromSignature(txSignature: Signature, commitment: Commitment = "confirmed") { const txResult = await this.rpc .getTransaction(txSignature, { commitment, maxSupportedTransactionVersion: 0 }) .send(); @@ -149,9 +156,9 @@ export class SvmSpokeEventsClient { * @param txResult - The transaction result. * @returns A promise that resolves to an array of events with their data and name. */ - private async processEventFromTx( + private processEventFromTx( txResult: GetTransactionReturnType - ): Promise<{ program: Address; data: EventData; name: EventName }[]> { + ): { program: Address; data: EventData; name: EventName }[] { if (!txResult) return []; const events: { program: Address; data: EventData; name: EventName }[] = []; From 82e8d1728656b802c52562b80a40c4fb7dee04b3 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Fri, 28 Feb 2025 17:06:31 +0100 Subject: [PATCH 17/17] feat: infer return type Signed-off-by: Pablo Maldonado --- src/svm/eventsClient.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/svm/eventsClient.ts b/src/svm/eventsClient.ts index eca36756f..13076290e 100644 --- a/src/svm/eventsClient.ts +++ b/src/svm/eventsClient.ts @@ -13,9 +13,15 @@ import { EventData, EventName, EventWithData } from "./types"; import { getEventName, parseEventData } from "./utils/events"; import { isDevnet } from "./utils/helpers"; -// Note: Type assumes default JSON encoding. Using different encoding options (e.g. base58) would result in incompatible -// return types. -type GetTransactionReturnType = ReturnType; +// Utility type to extract the return type for the JSON encoding overload. We only care about the overload where the +// configuration parameter (C) has the optional property 'encoding' set to 'json'. +type ExtractJsonOverload = T extends (signature: infer _S, config: infer C) => infer R + ? C extends { encoding?: "json" } + ? R + : never + : never; + +type GetTransactionReturnType = ExtractJsonOverload; type GetSignaturesForAddressConfig = Parameters[1]; type GetSignaturesForAddressTransaction = ReturnType[number]; type GetSignaturesForAddressApiResponse = readonly GetSignaturesForAddressTransaction[];