From adb4d5804b2b75f1f2497bb1718ec9ea80d8beba Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Sat, 16 Nov 2024 04:17:02 +0530 Subject: [PATCH 1/7] Refactor: Add readMulticall route for batch contract reads --- src/server/routes/contract/read/read-batch.ts | 167 ++++++++++++++++++ src/server/routes/index.ts | 2 + 2 files changed, 169 insertions(+) create mode 100644 src/server/routes/contract/read/read-batch.ts diff --git a/src/server/routes/contract/read/read-batch.ts b/src/server/routes/contract/read/read-batch.ts new file mode 100644 index 000000000..0caf17bec --- /dev/null +++ b/src/server/routes/contract/read/read-batch.ts @@ -0,0 +1,167 @@ +import { Type, type Static } from "@sinclair/typebox"; +import type { FastifyInstance } from "fastify"; +import { StatusCodes } from "http-status-codes"; +import { + getContract, + prepareContractCall, + readContract, + resolveMethod, +} from "thirdweb"; +import { prepareMethod } from "thirdweb/dist/types/utils/abi/prepare-method"; +import { resolvePromisedValue, type AbiFunction } from "thirdweb/utils"; +import { decodeAbiParameters } from "viem/utils"; +import { getChain } from "../../../../utils/chain"; +import { prettifyError } from "../../../../utils/error"; +import { thirdwebClient } from "../../../../utils/sdk"; +import { createCustomError } from "../../../middleware/error"; +import { standardResponseSchema } from "../../../schemas/sharedApiSchemas"; +import { getChainIdFromChain } from "../../../utils/chain"; +import { bigNumberReplacer } from "../../../utils/convertor"; + +const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; + +const MULTICALL3_AGGREGATE_ABI = + "function aggregate3((address target, bool allowFailure, bytes callData)[] calls) external payable returns ((bool success, bytes returnData)[])"; + +const readCallRequestItemSchema = Type.Object({ + contractAddress: Type.String(), + functionName: Type.String(), + functionAbi: Type.Optional(Type.String()), + args: Type.Optional(Type.Array(Type.Any())), +}); + +const readMulticallRequestSchema = Type.Object({ + calls: Type.Array(readCallRequestItemSchema), + multicallAddress: Type.Optional(Type.String()), +}); + +const responseSchema = Type.Object({ + results: Type.Array( + Type.Object({ + success: Type.Boolean(), + result: Type.Any(), + }), + ), +}); + +const paramsSchema = Type.Object({ + chain: Type.String(), +}); + +type RouteGeneric = { + Params: { chain: string }; + Body: Static; + Reply: Static; +}; + +export async function readMulticall(fastify: FastifyInstance) { + fastify.route({ + method: "POST", + url: "/contract/:chain/read-batch", + schema: { + summary: "Batch read from multiple contracts", + description: + "Execute multiple contract read operations in a single call using Multicall3", + tags: ["Contract"], + operationId: "readMulticall", + params: paramsSchema, + body: readMulticallRequestSchema, + response: { + ...standardResponseSchema, + [StatusCodes.OK]: responseSchema, + }, + }, + handler: async (request, reply) => { + const { chain: chainSlug } = request.params; + const { calls, multicallAddress = MULTICALL3_ADDRESS } = request.body; + + const chainId = await getChainIdFromChain(chainSlug); + const chain = await getChain(chainId); + + try { + // Encode each read call + const encodedCalls = await Promise.all( + calls.map(async (call) => { + const contract = await getContract({ + client: thirdwebClient, + chain, + address: call.contractAddress, + }); + + const method = + (call.functionAbi as unknown as AbiFunction) ?? + (await resolveMethod(call.functionName)(contract)); + + const transaction = prepareContractCall({ + contract, + method, + params: call.args || [], + // stubbing gas values so that the call can be encoded + maxFeePerGas: 30n, + maxPriorityFeePerGas: 1n, + value: 0n, + }); + + const calldata = await resolvePromisedValue(transaction.data); + if (!calldata) { + throw new Error("Failed to encode call data"); + } + + return { + target: call.contractAddress, + abiFunction: method, + allowFailure: true, + callData: calldata, + }; + }), + ); + + // Get Multicall3 contract + const multicall = await getContract({ + chain, + address: multicallAddress, + client: thirdwebClient, + }); + + // Execute batch read + const results = await readContract({ + contract: multicall, + method: MULTICALL3_AGGREGATE_ABI, + params: [encodedCalls], + }); + + // Process results + const processedResults = results.map((result: unknown, i) => { + const { success, returnData } = result as { + success: boolean; + returnData: unknown; + }; + + const [_sig, _inputs, outputs] = prepareMethod( + encodedCalls[i].abiFunction, + ); + + const decoded = decodeAbiParameters( + outputs, + returnData as `0x${string}`, + ); + + return { + success, + result: success ? bigNumberReplacer(decoded) : null, + }; + }); + + reply.status(StatusCodes.OK).send({ + results: processedResults, + }); + } catch (e) { + throw createCustomError( + prettifyError(e), + StatusCodes.BAD_REQUEST, + "BAD_REQUEST", + ); + } + }, + }); +} diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 9daf6b452..a13a8b707 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -67,6 +67,7 @@ import { extractEvents } from "./contract/metadata/events"; import { getContractExtensions } from "./contract/metadata/extensions"; import { extractFunctions } from "./contract/metadata/functions"; import { readContract } from "./contract/read/read"; +import { readMulticall } from "./contract/read/read-batch"; import { getRoles } from "./contract/roles/read/get"; import { getAllRoles } from "./contract/roles/read/getAll"; import { grantRole } from "./contract/roles/write/grant"; @@ -190,6 +191,7 @@ export const withRoutes = async (fastify: FastifyInstance) => { // Generic await fastify.register(readContract); + await fastify.register(readMulticall); await fastify.register(writeToContract); // Contract Events From 302f1f9e4d05e5e43b57c81e4cf9be39982f886c Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Thu, 21 Nov 2024 19:23:32 +0530 Subject: [PATCH 2/7] bump thirdweb + serialise bigint --- package.json | 2 +- src/server/routes/contract/read/read-batch.ts | 7 +- yarn.lock | 409 +++++++++++------- 3 files changed, 256 insertions(+), 162 deletions(-) diff --git a/package.json b/package.json index 6d156ac43..f631dba7c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "prom-client": "^15.1.3", "prool": "^0.0.16", "superjson": "^2.2.1", - "thirdweb": "5.61.3", + "thirdweb": "5.69.0", "uuid": "^9.0.1", "winston": "^3.14.1", "zod": "^3.23.8" diff --git a/src/server/routes/contract/read/read-batch.ts b/src/server/routes/contract/read/read-batch.ts index 0caf17bec..40c59f7ad 100644 --- a/src/server/routes/contract/read/read-batch.ts +++ b/src/server/routes/contract/read/read-batch.ts @@ -1,13 +1,14 @@ import { Type, type Static } from "@sinclair/typebox"; import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; +import SuperJSON from "superjson"; import { getContract, prepareContractCall, readContract, resolveMethod, } from "thirdweb"; -import { prepareMethod } from "thirdweb/dist/types/utils/abi/prepare-method"; +import { prepareMethod } from "thirdweb/contract"; import { resolvePromisedValue, type AbiFunction } from "thirdweb/utils"; import { decodeAbiParameters } from "viem/utils"; import { getChain } from "../../../../utils/chain"; @@ -153,7 +154,9 @@ export async function readMulticall(fastify: FastifyInstance) { }); reply.status(StatusCodes.OK).send({ - results: processedResults, + results: SuperJSON.serialize(processedResults).json as Static< + typeof responseSchema + >["results"], }); } catch (e) { throw createCustomError( diff --git a/yarn.lock b/yarn.lock index 833d26027..fd08ad7d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1127,15 +1132,15 @@ preact "^10.16.0" sha.js "^2.4.11" -"@coinbase/wallet-sdk@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-4.1.0.tgz#3224a102b724dcb1a63005f371d596ae2999953b" - integrity sha512-SkJJ72X/AA3+aS21sPs/4o4t6RVeDSA7HuBW4zauySX3eBiPU0zmVw95tXH/eNSX50agKz9WzeN8P5F+HcwLOw== +"@coinbase/wallet-sdk@4.2.3": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-4.2.3.tgz#a30fa0605b24bc42c37f52a62d2442bcbb7734af" + integrity sha512-BcyHZ/Ec84z0emORzqdXDv4P0oV+tV3a0OirfA8Ko1JGBIAVvB+hzLvZzCDvnuZx7MTK+Dd8Y9Tjlo446BpCIg== dependencies: "@noble/hashes" "^1.4.0" clsx "^1.2.1" eventemitter3 "^5.0.1" - preact "^10.16.0" + preact "^10.24.2" "@coinbase/wallet-sdk@^3.9.0": version "3.9.3" @@ -2883,6 +2888,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@1.6.0", "@noble/curves@^1.6.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/curves@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -2890,13 +2902,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@~1.4.0": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" - integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== - dependencies: - "@noble/hashes" "1.4.0" - "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" @@ -2907,12 +2912,12 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@~1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -3243,6 +3248,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== +"@radix-ui/react-context@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" + integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== + "@radix-ui/react-dialog@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" @@ -3264,25 +3274,25 @@ aria-hidden "^1.1.1" react-remove-scroll "2.5.5" -"@radix-ui/react-dialog@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44" - integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg== +"@radix-ui/react-dialog@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c" + integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA== dependencies: "@radix-ui/primitive" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-dismissable-layer" "1.1.0" - "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.1" + "@radix-ui/react-focus-guards" "1.1.1" "@radix-ui/react-focus-scope" "1.1.0" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-portal" "1.1.1" - "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-portal" "1.1.2" + "@radix-ui/react-presence" "1.1.1" "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-slot" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" aria-hidden "^1.1.1" - react-remove-scroll "2.5.7" + react-remove-scroll "2.6.0" "@radix-ui/react-dismissable-layer@1.0.5": version "1.0.5" @@ -3296,10 +3306,10 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" -"@radix-ui/react-dismissable-layer@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e" - integrity sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig== +"@radix-ui/react-dismissable-layer@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396" + integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ== dependencies: "@radix-ui/primitive" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0" @@ -3314,10 +3324,10 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-focus-guards@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13" - integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw== +"@radix-ui/react-focus-guards@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe" + integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg== "@radix-ui/react-focus-scope@1.0.4": version "1.0.4" @@ -3343,6 +3353,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69" integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw== +"@radix-ui/react-icons@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz#09be63d178262181aeca5fb7f7bc944b10a7f441" + integrity sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g== + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3399,10 +3414,10 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" -"@radix-ui/react-portal@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f" - integrity sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g== +"@radix-ui/react-portal@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e" + integrity sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg== dependencies: "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-use-layout-effect" "1.1.0" @@ -3416,10 +3431,10 @@ "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" -"@radix-ui/react-presence@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478" - integrity sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ== +"@radix-ui/react-presence@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" + integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A== dependencies: "@radix-ui/react-compose-refs" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" @@ -3473,19 +3488,19 @@ "@radix-ui/react-use-controllable-state" "1.0.1" "@radix-ui/react-visually-hidden" "1.0.3" -"@radix-ui/react-tooltip@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz#c42db2ffd7dcc6ff3d65407c8cb70490288f518d" - integrity sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w== +"@radix-ui/react-tooltip@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz#152d8485859b80d395d6b3229f676fef3cec56b3" + integrity sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw== dependencies: "@radix-ui/primitive" "1.1.0" "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.1" "@radix-ui/react-id" "1.1.0" "@radix-ui/react-popper" "1.2.0" - "@radix-ui/react-portal" "1.1.1" - "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-portal" "1.1.2" + "@radix-ui/react-presence" "1.1.1" "@radix-ui/react-primitive" "2.0.0" "@radix-ui/react-slot" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0" @@ -3747,10 +3762,10 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== -"@scure/base@~1.1.6": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" - integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== +"@scure/base@~1.1.7": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== "@scure/base@~1.1.8": version "1.1.8" @@ -3775,14 +3790,14 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" -"@scure/bip32@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" - integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== +"@scure/bip32@1.5.0", "@scure/bip32@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== dependencies: - "@noble/curves" "~1.4.0" - "@noble/hashes" "~1.4.0" - "@scure/base" "~1.1.6" + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" "@scure/bip39@1.2.1": version "1.2.1" @@ -3800,7 +3815,7 @@ "@noble/hashes" "~1.3.2" "@scure/base" "~1.1.4" -"@scure/bip39@1.4.0": +"@scure/bip39@1.4.0", "@scure/bip39@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== @@ -4681,10 +4696,10 @@ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.29.0.tgz#d0b3d12c07d5a47f42ab0c1ed4f317106f3d4b20" integrity sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww== -"@tanstack/query-core@5.56.2": - version "5.56.2" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.56.2.tgz#2def2fb0290cd2836bbb08afb0c175595bb8109b" - integrity sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q== +"@tanstack/query-core@5.59.20": + version "5.59.20" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.59.20.tgz#356718976536727b9af0ad1163a21fd6a44ee0a9" + integrity sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg== "@tanstack/react-query@5.29.2": version "5.29.2" @@ -4693,12 +4708,12 @@ dependencies: "@tanstack/query-core" "5.29.0" -"@tanstack/react-query@5.56.2": - version "5.56.2" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.56.2.tgz#3a0241b9d010910905382f5e99160997b8795f91" - integrity sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg== +"@tanstack/react-query@5.60.2": + version "5.60.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.60.2.tgz#a65db96702c11f3f868c40372ce66f05c2a77cc6" + integrity sha512-JhpJNxIAPuE0YCpP1Py4zAsgx+zY0V531McRMtQbwVlJF8+mlZwcOPrzGmPV248K8IP+mPbsfxXToVNMNwjUcw== dependencies: - "@tanstack/query-core" "5.56.2" + "@tanstack/query-core" "5.59.20" "@thirdweb-dev/auth@^4.1.87": version "4.1.88" @@ -5278,10 +5293,10 @@ lodash.isequal "4.5.0" uint8arrays "3.1.0" -"@walletconnect/core@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.16.3.tgz#b9b5bd240aac220f87ba3ace2ef70331ab5199b1" - integrity sha512-rY2j4oypdF8kR6JesT5zgPbkZaKJ74RySpIq4gEnxEYE7yrQmh0W11gUjs+3g2Z0kKSY546cqd7JOxdW3yhvsQ== +"@walletconnect/core@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.17.2.tgz#877dc03f190d7b262bff8ce346330fdf1019cd83" + integrity sha512-O9VUsFg78CbvIaxfQuZMsHcJ4a2Z16DRz/O4S+uOAcGKhH/i/ln8hp864Tb+xRvifWSzaZ6CeAVxk657F+pscA== dependencies: "@walletconnect/heartbeat" "1.2.2" "@walletconnect/jsonrpc-provider" "1.0.14" @@ -5294,8 +5309,9 @@ "@walletconnect/relay-auth" "1.0.4" "@walletconnect/safe-json" "1.0.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.3" - "@walletconnect/utils" "2.16.3" + "@walletconnect/types" "2.17.2" + "@walletconnect/utils" "2.17.2" + "@walletconnect/window-getters" "1.0.1" events "3.3.0" lodash.isequal "4.5.0" uint8arrays "3.1.0" @@ -5323,20 +5339,21 @@ "@walletconnect/utils" "2.12.2" events "^3.3.0" -"@walletconnect/ethereum-provider@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.16.3.tgz#248a8b902cd5dfa414a0b92911b550aedabdfd46" - integrity sha512-5hUrtyDf6sWzyJTlbpIbUK4mgLVs3IoJ6I+kcum4rEimx4F3WjI64sYThquZCDboWqftddxbG9s7hdKCVhjqWQ== +"@walletconnect/ethereum-provider@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.17.2.tgz#7ac8091daf65f33c9f77cb08f524246c638e9e66" + integrity sha512-o4aL4KkUKT+n0iDwGzC6IY4bl+9n8bwOeT2KwifaVHsFw/irhtRPlsAQQH4ezOiPyk8cri1KN9dPk/YeU0pe6w== dependencies: "@walletconnect/jsonrpc-http-connection" "1.0.8" "@walletconnect/jsonrpc-provider" "1.0.14" "@walletconnect/jsonrpc-types" "1.0.4" "@walletconnect/jsonrpc-utils" "1.0.8" - "@walletconnect/modal" "2.6.2" - "@walletconnect/sign-client" "2.16.3" - "@walletconnect/types" "2.16.3" - "@walletconnect/universal-provider" "2.16.3" - "@walletconnect/utils" "2.16.3" + "@walletconnect/keyvaluestorage" "1.1.1" + "@walletconnect/modal" "2.7.0" + "@walletconnect/sign-client" "2.17.2" + "@walletconnect/types" "2.17.2" + "@walletconnect/universal-provider" "2.17.2" + "@walletconnect/utils" "2.17.2" events "3.3.0" "@walletconnect/events@1.0.1", "@walletconnect/events@^1.0.1": @@ -5452,6 +5469,13 @@ dependencies: valtio "1.11.2" +"@walletconnect/modal-core@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.7.0.tgz#73c13c3b7b0abf9ccdbac9b242254a86327ce0a4" + integrity sha512-oyMIfdlNdpyKF2kTJowTixZSo0PGlCJRdssUN/EZdA6H6v03hZnf09JnwpljZNfir2M65Dvjm/15nGrDQnlxSA== + dependencies: + valtio "1.11.2" + "@walletconnect/modal-ui@2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.6.2.tgz#fa57c087c57b7f76aaae93deab0f84bb68b59cf9" @@ -5462,7 +5486,25 @@ motion "10.16.2" qrcode "1.5.3" -"@walletconnect/modal@2.6.2", "@walletconnect/modal@^2.6.2": +"@walletconnect/modal-ui@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal-ui/-/modal-ui-2.7.0.tgz#dbbb7ee46a5a25f7d39db622706f2d197b268cbb" + integrity sha512-gERYvU7D7K1ANCN/8vUgsE0d2hnRemfAFZ2novm9aZBg7TEd/4EgB+AqbJ+1dc7GhOL6dazckVq78TgccHb7mQ== + dependencies: + "@walletconnect/modal-core" "2.7.0" + lit "2.8.0" + motion "10.16.2" + qrcode "1.5.3" + +"@walletconnect/modal@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.7.0.tgz#55f969796d104cce1205f5f844d8f8438b79723a" + integrity sha512-RQVt58oJ+rwqnPcIvRFeMGKuXb9qkgSmwz4noF8JZGUym3gUAzVs+uW2NQ1Owm9XOJAV+sANrtJ+VoVq1ftElw== + dependencies: + "@walletconnect/modal-core" "2.7.0" + "@walletconnect/modal-ui" "2.7.0" + +"@walletconnect/modal@^2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.6.2.tgz#4b534a836f5039eeb3268b80be7217a94dd12651" integrity sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA== @@ -5533,19 +5575,19 @@ "@walletconnect/utils" "2.13.1" events "3.3.0" -"@walletconnect/sign-client@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.16.3.tgz#abf686e54408802a626a105b096aa9cc3c8a2d14" - integrity sha512-m86S5ig4xkejpOushGY2WluUUXSkj+f3D3mNgpG4JPQSsXeE26n6wVCTyR9EiIPfSNhSeRmwR2rqDlMOyFpwXA== +"@walletconnect/sign-client@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.17.2.tgz#b8bd125d7c34a67916745ebbdbbc834db5518c8b" + integrity sha512-/wigdCIQjlBXSWY43Id0IPvZ5biq4HiiQZti8Ljvx408UYjmqcxcBitbj2UJXMYkid7704JWAB2mw32I1HgshQ== dependencies: - "@walletconnect/core" "2.16.3" + "@walletconnect/core" "2.17.2" "@walletconnect/events" "1.0.1" "@walletconnect/heartbeat" "1.2.2" "@walletconnect/jsonrpc-utils" "1.0.8" "@walletconnect/logger" "2.1.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.3" - "@walletconnect/utils" "2.16.3" + "@walletconnect/types" "2.17.2" + "@walletconnect/utils" "2.17.2" events "3.3.0" "@walletconnect/time@1.0.2", "@walletconnect/time@^1.0.2": @@ -5579,10 +5621,10 @@ "@walletconnect/logger" "2.1.2" events "3.3.0" -"@walletconnect/types@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.16.3.tgz#e5886b3ac072634847bbfb60b178c09c8fe89199" - integrity sha512-ul/AKC/+3+GqJfK17XNvjxRUec8YVRqCEe32O0CQrO21umfeIcU5aPgSW0j+9OKE87KKmRZuERysBh18bMxT/Q== +"@walletconnect/types@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.17.2.tgz#f9afff242563be33f377de689b03b482f5b20aee" + integrity sha512-j/+0WuO00lR8ntu7b1+MKe/r59hNwYLFzW0tTmozzhfAlDL+dYwWasDBNq4AH8NbVd7vlPCQWmncH7/6FVtOfQ== dependencies: "@walletconnect/events" "1.0.1" "@walletconnect/heartbeat" "1.2.2" @@ -5606,20 +5648,23 @@ "@walletconnect/utils" "2.12.2" events "^3.3.0" -"@walletconnect/universal-provider@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.16.3.tgz#93926934ae4a45330962bb217dadb121d56a1ed1" - integrity sha512-MwP0sdKMC29aL63LP6hjGLq4Ir/ArRukC+wE2Nora37VzDQmegHnipOYoNFMReNvCMpGNsk+zKq9GKp0p/0glA== +"@walletconnect/universal-provider@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.17.2.tgz#f4627dd9b66db3bacc31864584112868be23bf08" + integrity sha512-yIWDhBODRa9J349d/i1sObzon0vy4n+7R3MvGQQYaU1EVrV+WfoGSRsu8U7rYsL067/MAUu9t/QrpPblaSbz7g== dependencies: + "@walletconnect/events" "1.0.1" "@walletconnect/jsonrpc-http-connection" "1.0.8" "@walletconnect/jsonrpc-provider" "1.0.14" "@walletconnect/jsonrpc-types" "1.0.4" "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/keyvaluestorage" "1.1.1" "@walletconnect/logger" "2.1.2" - "@walletconnect/sign-client" "2.16.3" - "@walletconnect/types" "2.16.3" - "@walletconnect/utils" "2.16.3" + "@walletconnect/sign-client" "2.17.2" + "@walletconnect/types" "2.17.2" + "@walletconnect/utils" "2.17.2" events "3.3.0" + lodash "4.17.21" "@walletconnect/utils@2.12.2": version "2.12.2" @@ -5661,25 +5706,29 @@ query-string "7.1.3" uint8arrays "3.1.0" -"@walletconnect/utils@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.16.3.tgz#1413fbca64e8eedb8eed00945a984a11dbd2a3b3" - integrity sha512-gWPIQ4LhOKEnHsFbV8Ey++3mkaRopLN7F9aLT6mwcoQ5JdZiMK4P6gP7p0b9V4YnBmA4x8Yt4ySv919tjN3Z0A== +"@walletconnect/utils@2.17.2": + version "2.17.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.17.2.tgz#b4b12e3f5ebbfd883b2a5c87fb818e53501dc7ea" + integrity sha512-T7eLRiuw96fgwUy2A5NZB5Eu87ukX8RCVoO9lji34RFV4o2IGU9FhTEWyd4QQKI8OuQRjSknhbJs0tU0r0faPw== dependencies: + "@ethersproject/hash" "5.7.0" + "@ethersproject/transactions" "5.7.0" "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" "@stablelib/random" "1.0.2" "@stablelib/sha256" "1.0.1" "@stablelib/x25519" "1.0.3" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/keyvaluestorage" "1.1.1" "@walletconnect/relay-api" "1.0.11" "@walletconnect/relay-auth" "1.0.4" "@walletconnect/safe-json" "1.0.2" "@walletconnect/time" "1.0.2" - "@walletconnect/types" "2.16.3" + "@walletconnect/types" "2.17.2" "@walletconnect/window-getters" "1.0.1" "@walletconnect/window-metadata" "1.0.1" detect-browser "5.3.0" - elliptic "^6.5.7" + elliptic "6.6.0" query-string "7.1.3" uint8arrays "3.1.0" @@ -5717,10 +5766,10 @@ abitype@1.0.0: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== -abitype@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" - integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== +abitype@1.0.6, abitype@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" + integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== abort-controller@^3.0.0: version "3.0.0" @@ -7006,7 +7055,7 @@ ejs@^3.1.10: dependencies: jake "^10.8.5" -elliptic@6.5.4, elliptic@>=6.6.0, elliptic@^6.4.1, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.5, elliptic@^6.5.7: +elliptic@6.5.4, elliptic@6.6.0, elliptic@>=6.6.0, elliptic@^6.4.1, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.5, elliptic@^6.5.7: version "6.6.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210" integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA== @@ -7579,16 +7628,16 @@ eventemitter3@4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -8484,6 +8533,11 @@ input-otp@^1.2.4: resolved "https://registry.yarnpkg.com/input-otp/-/input-otp-1.2.4.tgz#9834af8675ac72c7f1b7c010f181b3b4ffdd0f72" integrity sha512-md6rhmD+zmMnUh5crQNSQxq3keBRYvE3odbr4Qb9g2NWzQv9azi+t1a3X4TBTbh98fsGHgEEJlzbe1q860uGCA== +input-otp@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/input-otp/-/input-otp-1.4.1.tgz#bc22e68b14b1667219d54adf74243e37ea79cf84" + integrity sha512-+yvpmKYKHi9jIGngxagY9oWiiblPB7+nEO75F2l2o4vs+6vpPZZmUl4tBNYuTCvQjhvEIbdNeJu70bhfYP2nbw== + int64-buffer@^0.1.9: version "0.1.10" resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423" @@ -8698,6 +8752,11 @@ isows@1.0.4: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + istanbul-lib-coverage@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -9209,7 +9268,7 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== -lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9877,6 +9936,32 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== +ox@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560" + integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + +ox@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.2.1.tgz#19386e39d289099f7716a7a5f1e623f97c8027d8" + integrity sha512-BmB0+yDHi/OELuP6zPTtHk0A7sGtUVZ31TUMVlqnnf0Mu834LZegw7RliMsCFSHrL1j62yxdSPIkSLmBM0rD4Q== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -10274,6 +10359,11 @@ preact@^10.16.0: resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.2.tgz#52deec92796ae0f0cc6b034d9c66e0fbc1b837dc" integrity sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA== +preact@^10.24.2: + version "10.24.3" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64" + integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -10553,7 +10643,7 @@ react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4: +react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== @@ -10572,12 +10662,12 @@ react-remove-scroll@2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-remove-scroll@2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb" - integrity sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA== +react-remove-scroll@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07" + integrity sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ== dependencies: - react-remove-scroll-bar "^2.3.4" + react-remove-scroll-bar "^2.3.6" react-style-singleton "^2.2.1" tslib "^2.1.0" use-callback-ref "^1.3.0" @@ -11310,31 +11400,32 @@ thirdweb@5.26.0: uqr "0.1.2" viem "2.10.9" -thirdweb@5.61.3: - version "5.61.3" - resolved "https://registry.yarnpkg.com/thirdweb/-/thirdweb-5.61.3.tgz#f52e5fee0ba8c3b7ac6cd68bc33d7a85f185ebd2" - integrity sha512-7rZ46VF/8wd8tj534+LTY9MQQmglsSl0cnsgCo7+xFyZx7uArXdqMDi6a6NKZwsHdEaijlFD36lYlTXm6DCLIw== +thirdweb@5.69.0: + version "5.69.0" + resolved "https://registry.yarnpkg.com/thirdweb/-/thirdweb-5.69.0.tgz#c48a0942d8d8fd43484652d39bc77dfd2bc35d4c" + integrity sha512-Foo03tpsZsoqWLCJWljDUNaksCQtyQuFzuiFNWd9aIiyIoqGl/Mtg8/0twGXiF30MtVrgHSduHpsh6cKtfFK6g== dependencies: - "@coinbase/wallet-sdk" "4.1.0" + "@coinbase/wallet-sdk" "4.2.3" "@emotion/react" "11.13.3" "@emotion/styled" "11.13.0" "@google/model-viewer" "2.1.1" - "@noble/curves" "1.4.0" - "@noble/hashes" "1.4.0" + "@noble/curves" "1.6.0" + "@noble/hashes" "1.5.0" "@passwordless-id/webauthn" "^1.6.1" - "@radix-ui/react-dialog" "1.1.1" + "@radix-ui/react-dialog" "1.1.2" "@radix-ui/react-focus-scope" "1.1.0" - "@radix-ui/react-icons" "1.3.0" - "@radix-ui/react-tooltip" "1.1.2" - "@tanstack/react-query" "5.56.2" - "@walletconnect/ethereum-provider" "2.16.3" - "@walletconnect/sign-client" "2.16.3" - abitype "1.0.5" + "@radix-ui/react-icons" "1.3.2" + "@radix-ui/react-tooltip" "1.1.4" + "@tanstack/react-query" "5.60.2" + "@walletconnect/ethereum-provider" "2.17.2" + "@walletconnect/sign-client" "2.17.2" + abitype "1.0.6" fuse.js "7.0.0" - input-otp "^1.2.4" + input-otp "^1.4.1" mipd "0.0.7" + ox "0.2.1" uqr "0.1.2" - viem "2.21.16" + viem "2.21.45" thread-stream@^0.15.1: version "0.15.2" @@ -11774,20 +11865,20 @@ viem@2.10.9: isows "1.0.4" ws "8.13.0" -viem@2.21.16: - version "2.21.16" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.16.tgz#cf32200bbd1696bd6e86de7e1c12fcdfd77b63c1" - integrity sha512-SvhaPzTj3a+zR/5OmtJ0acjA6oGDrgPg4vtO8KboXtvbjksXEkz+oFaNjZDgxpkqbps2SLi8oPCjdpRm6WgDmw== +viem@2.21.45: + version "2.21.45" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.45.tgz#7a445428d4909cc334f231ee916ede1b69190603" + integrity sha512-I+On/IiaObQdhDKWU5Rurh6nf3G7reVkAODG5ECIfjsrGQ3EPJnxirUPT4FNV6bWER5iphoG62/TidwuTSOA1A== dependencies: - "@adraffy/ens-normalize" "1.10.0" - "@noble/curves" "1.4.0" - "@noble/hashes" "1.4.0" - "@scure/bip32" "1.4.0" + "@noble/curves" "1.6.0" + "@noble/hashes" "1.5.0" + "@scure/bip32" "1.5.0" "@scure/bip39" "1.4.0" - abitype "1.0.5" - isows "1.0.4" - webauthn-p256 "0.0.5" - ws "8.17.1" + abitype "1.0.6" + isows "1.0.6" + ox "0.1.2" + webauthn-p256 "0.0.10" + ws "8.18.0" vite-node@2.0.3: version "2.0.3" @@ -12060,10 +12151,10 @@ web3-validator@^2.0.6: web3-types "^1.6.0" zod "^3.21.4" -webauthn-p256@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" - integrity sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg== +webauthn-p256@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" + integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== dependencies: "@noble/curves" "^1.4.0" "@noble/hashes" "^1.4.0" @@ -12201,7 +12292,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@7.4.6, ws@8.13.0, ws@8.17.1, ws@8.9.0, ws@>=8.17.1, ws@^7.4.0, ws@^7.5.1, ws@^8.0.0: +ws@7.4.6, ws@8.13.0, ws@8.18.0, ws@8.9.0, ws@>=8.17.1, ws@^7.4.0, ws@^7.5.1, ws@^8.0.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== From e56086cf12d802e86032a20265a5f4404ac313a7 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Mon, 13 Jan 2025 10:16:32 +0530 Subject: [PATCH 3/7] Address review comments --- src/server/routes/contract/read/read-batch.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/server/routes/contract/read/read-batch.ts b/src/server/routes/contract/read/read-batch.ts index 40c59f7ad..6cc466057 100644 --- a/src/server/routes/contract/read/read-batch.ts +++ b/src/server/routes/contract/read/read-batch.ts @@ -3,21 +3,21 @@ import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; import SuperJSON from "superjson"; import { + encode, getContract, prepareContractCall, readContract, resolveMethod, } from "thirdweb"; import { prepareMethod } from "thirdweb/contract"; -import { resolvePromisedValue, type AbiFunction } from "thirdweb/utils"; import { decodeAbiParameters } from "viem/utils"; -import { getChain } from "../../../../utils/chain"; -import { prettifyError } from "../../../../utils/error"; -import { thirdwebClient } from "../../../../utils/sdk"; +import type { AbiFunction } from "viem"; import { createCustomError } from "../../../middleware/error"; -import { standardResponseSchema } from "../../../schemas/sharedApiSchemas"; import { getChainIdFromChain } from "../../../utils/chain"; -import { bigNumberReplacer } from "../../../utils/convertor"; +import { standardResponseSchema } from "../../../schemas/shared-api-schemas"; +import { getChain } from "../../../../shared/utils/chain"; +import { thirdwebClient } from "../../../../shared/utils/sdk"; +import { prettifyError } from "../../../../shared/utils/error"; const MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; @@ -33,7 +33,11 @@ const readCallRequestItemSchema = Type.Object({ const readMulticallRequestSchema = Type.Object({ calls: Type.Array(readCallRequestItemSchema), - multicallAddress: Type.Optional(Type.String()), + multicallAddress: Type.Optional( + Type.String({ + description: `Address of the multicall contract to use. If omitted, multicall3 contract will be used (${MULTICALL3_ADDRESS}).`, + }), + ), }); const responseSchema = Type.Object({ @@ -55,16 +59,16 @@ type RouteGeneric = { Reply: Static; }; -export async function readMulticall(fastify: FastifyInstance) { +export async function readMulticallRoute(fastify: FastifyInstance) { fastify.route({ method: "POST", url: "/contract/:chain/read-batch", schema: { summary: "Batch read from multiple contracts", description: - "Execute multiple contract read operations in a single call using Multicall3", + "Execute multiple contract read operations in a single call using Multicall", tags: ["Contract"], - operationId: "readMulticall", + operationId: "readBatch", params: paramsSchema, body: readMulticallRequestSchema, response: { @@ -83,7 +87,7 @@ export async function readMulticall(fastify: FastifyInstance) { // Encode each read call const encodedCalls = await Promise.all( calls.map(async (call) => { - const contract = await getContract({ + const contract = getContract({ client: thirdwebClient, chain, address: call.contractAddress, @@ -97,13 +101,9 @@ export async function readMulticall(fastify: FastifyInstance) { contract, method, params: call.args || [], - // stubbing gas values so that the call can be encoded - maxFeePerGas: 30n, - maxPriorityFeePerGas: 1n, - value: 0n, }); - const calldata = await resolvePromisedValue(transaction.data); + const calldata = await encode(transaction); if (!calldata) { throw new Error("Failed to encode call data"); } @@ -149,7 +149,7 @@ export async function readMulticall(fastify: FastifyInstance) { return { success, - result: success ? bigNumberReplacer(decoded) : null, + result: success ? decoded : null, }; }); From 82b8ce6d5d424ed545a562e89a4ab2e9310e7ce8 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Mon, 13 Jan 2025 10:18:31 +0530 Subject: [PATCH 4/7] fixed import --- src/server/routes/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 7382565f2..fd854f47d 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -68,7 +68,6 @@ import { extractEvents } from "./contract/metadata/events"; import { getContractExtensions } from "./contract/metadata/extensions"; import { extractFunctions } from "./contract/metadata/functions"; import { readContract } from "./contract/read/read"; -import { readMulticall } from "./contract/read/read-batch"; import { getRoles } from "./contract/roles/read/get"; import { getAllRoles } from "./contract/roles/read/get-all"; import { grantRole } from "./contract/roles/write/grant"; @@ -112,6 +111,7 @@ import { getWebhooksEventTypes } from "./webhooks/events"; import { getAllWebhooksData } from "./webhooks/get-all"; import { revokeWebhook } from "./webhooks/revoke"; import { testWebhookRoute } from "./webhooks/test"; +import { readMulticallRoute } from "./contract/read/read-batch"; export async function withRoutes(fastify: FastifyInstance) { // Backend Wallets @@ -193,7 +193,7 @@ export async function withRoutes(fastify: FastifyInstance) { // Generic await fastify.register(readContract); - await fastify.register(readMulticall); + await fastify.register(readMulticallRoute); await fastify.register(writeToContract); // Contract Events From f24e84b0ca7a77319d1d27e4c090d966f5ad1aff Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Mon, 13 Jan 2025 13:15:41 +0530 Subject: [PATCH 5/7] feat: add batch operations support for transactions and enhance wallet details retrieval --- .../send-transactions-atomic.ts | 151 ++++++++++++++++++ src/server/routes/index.ts | 2 + src/server/schemas/transaction/index.ts | 23 +++ src/shared/db/wallets/get-wallet-details.ts | 24 ++- .../utils/transaction/insert-transaction.ts | 24 ++- src/shared/utils/transaction/types.ts | 9 ++ src/worker/tasks/send-transaction-worker.ts | 35 ++-- 7 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 src/server/routes/backend-wallet/send-transactions-atomic.ts diff --git a/src/server/routes/backend-wallet/send-transactions-atomic.ts b/src/server/routes/backend-wallet/send-transactions-atomic.ts new file mode 100644 index 000000000..3892df78c --- /dev/null +++ b/src/server/routes/backend-wallet/send-transactions-atomic.ts @@ -0,0 +1,151 @@ +import { Type, type Static } from "@sinclair/typebox"; +import type { FastifyInstance } from "fastify"; +import { StatusCodes } from "http-status-codes"; +import type { Address, Hex } from "thirdweb"; +import { insertTransaction } from "../../../shared/utils/transaction/insert-transaction"; +import { AddressSchema } from "../../schemas/address"; +import { + requestQuerystringSchema, + standardResponseSchema, + transactionWritesResponseSchema, +} from "../../schemas/shared-api-schemas"; +import { + maybeAddress, + walletChainParamSchema, + walletWithAAHeaderSchema, +} from "../../schemas/wallet"; +import { getChainIdFromChain } from "../../utils/chain"; +import { + getWalletDetails, + isSmartBackendWallet, + type ParsedWalletDetails, + WalletDetailsError, +} from "../../../shared/db/wallets/get-wallet-details"; +import { createCustomError } from "../../middleware/error"; + +const requestBodySchema = Type.Object({ + transactions: Type.Array( + Type.Object({ + toAddress: Type.Optional(AddressSchema), + data: Type.String({ + examples: ["0x..."], + }), + value: Type.String({ + examples: ["10000000"], + }), + }), + ), +}); + +export async function sendTransactionsAtomicRoute(fastify: FastifyInstance) { + fastify.route<{ + Params: Static; + Body: Static; + Reply: Static; + Querystring: Static; + }>({ + method: "POST", + url: "/backend-wallet/:chain/send-transactions-atomic", + schema: { + summary: "Send a batch of raw transactions atomically", + description: + "Send a batch of raw transactions in a single UserOp. Can only be used with smart wallets.", + tags: ["Backend Wallet"], + operationId: "sendTransactionsAtomic", + params: walletChainParamSchema, + body: requestBodySchema, + headers: Type.Omit(walletWithAAHeaderSchema, ["x-transaction-mode"]), + querystring: requestQuerystringSchema, + response: { + ...standardResponseSchema, + [StatusCodes.OK]: transactionWritesResponseSchema, + }, + }, + handler: async (request, reply) => { + const { chain } = request.params; + const { + "x-backend-wallet-address": fromAddress, + "x-idempotency-key": idempotencyKey, + "x-account-address": accountAddress, + "x-account-factory-address": accountFactoryAddress, + "x-account-salt": accountSalt, + } = request.headers as Static; + const chainId = await getChainIdFromChain(chain); + const shouldSimulate = request.query.simulateTx ?? false; + const transactionRequests = request.body.transactions; + + const hasSmartHeaders = !!accountAddress; + + // check that we either use SBW, or send using EOA with smart wallet headers + if (!hasSmartHeaders) { + let backendWallet: ParsedWalletDetails | undefined; + + try { + backendWallet = await getWalletDetails({ + address: fromAddress, + }); + } catch (e: unknown) { + if (e instanceof WalletDetailsError) { + throw createCustomError( + `Failed to get wallet details for backend wallet ${fromAddress}. ${e.message}`, + StatusCodes.BAD_REQUEST, + "WALLET_DETAILS_ERROR", + ); + } + } + + if (!backendWallet) { + throw createCustomError( + "Failed to get wallet details for backend wallet. See: https://portal.thirdweb.com/engine/troubleshooting", + StatusCodes.INTERNAL_SERVER_ERROR, + "WALLET_DETAILS_ERROR", + ); + } + + if (!isSmartBackendWallet(backendWallet)) { + throw createCustomError( + "Backend wallet is not a smart wallet, and x-account-address is not provided. Either use a smart backend wallet or provide x-account-address. This endpoint can only be used with smart wallets.", + StatusCodes.BAD_REQUEST, + "BACKEND_WALLET_NOT_SMART", + ); + } + } + + if (transactionRequests.length === 0) { + throw createCustomError( + "No transactions provided", + StatusCodes.BAD_REQUEST, + "NO_TRANSACTIONS_PROVIDED", + ); + } + + const queueId = await insertTransaction({ + insertedTransaction: { + transactionMode: undefined, + isUserOp: false, + chainId, + from: fromAddress as Address, + accountAddress: maybeAddress(accountAddress, "x-account-address"), + accountFactoryAddress: maybeAddress( + accountFactoryAddress, + "x-account-factory-address", + ), + accountSalt: accountSalt, + batchOperations: transactionRequests.map((transactionRequest) => ({ + to: transactionRequest.toAddress as Address | undefined, + data: transactionRequest.data as Hex, + value: BigInt(transactionRequest.value), + })), + }, + shouldSimulate, + idempotencyKey, + }); + + reply.status(StatusCodes.OK).send({ + result: { + queueId, + }, + }); + }, + }); +} diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index fd854f47d..8139bb47a 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -112,6 +112,7 @@ import { getAllWebhooksData } from "./webhooks/get-all"; import { revokeWebhook } from "./webhooks/revoke"; import { testWebhookRoute } from "./webhooks/test"; import { readMulticallRoute } from "./contract/read/read-batch"; +import { sendTransactionsAtomicRoute } from "./backend-wallet/send-transactions-atomic"; export async function withRoutes(fastify: FastifyInstance) { // Backend Wallets @@ -125,6 +126,7 @@ export async function withRoutes(fastify: FastifyInstance) { await fastify.register(withdraw); await fastify.register(sendTransaction); await fastify.register(sendTransactionBatch); + await fastify.register(sendTransactionsAtomicRoute); await fastify.register(signTransaction); await fastify.register(signMessageRoute); await fastify.register(signTypedData); diff --git a/src/server/schemas/transaction/index.ts b/src/server/schemas/transaction/index.ts index c2ddb8825..ff82934fa 100644 --- a/src/server/schemas/transaction/index.ts +++ b/src/server/schemas/transaction/index.ts @@ -210,6 +210,16 @@ export const TransactionSchema = Type.Object({ }), Type.Null(), ]), + batchOperations: Type.Union([ + Type.Array( + Type.Object({ + to: Type.Union([Type.String(), Type.Null()]), + data: Type.Union([Type.String(), Type.Null()]), + value: Type.String(), + }), + ), + Type.Null(), + ]), }); export const toTransactionSchema = ( @@ -255,6 +265,17 @@ export const toTransactionSchema = ( return null; }; + const resolveBatchOperations = (): Static< + typeof TransactionSchema + >["batchOperations"] => { + if (!transaction.batchOperations) return null; + return transaction.batchOperations.map((op) => ({ + to: op.to ?? null, + data: op.data ?? null, + value: op.value.toString(), + })); + }; + const resolveGas = (): string | null => { if (transaction.status === "sent") { return transaction.gas.toString(); @@ -351,6 +372,8 @@ export const toTransactionSchema = ( userOpHash: "userOpHash" in transaction ? (transaction.userOpHash as Hex) : null, + batchOperations: resolveBatchOperations(), + // Deprecated retryGasValues: null, retryMaxFeePerGas: null, diff --git a/src/shared/db/wallets/get-wallet-details.ts b/src/shared/db/wallets/get-wallet-details.ts index eaa475143..c818a6a5a 100644 --- a/src/shared/db/wallets/get-wallet-details.ts +++ b/src/shared/db/wallets/get-wallet-details.ts @@ -1,3 +1,4 @@ +import LruMap from "mnemonist/lru-map"; import { getAddress } from "thirdweb"; import { z } from "zod"; import type { PrismaTransaction } from "../../schemas/prisma"; @@ -130,6 +131,7 @@ export type SmartBackendWalletType = (typeof SmartBackendWalletTypes)[number]; export type BackendWalletType = (typeof BackendWalletTypes)[number]; export type ParsedWalletDetails = z.infer; +export const walletDetailsCache = new LruMap(2048); /** * Return the wallet details for the given address. * @@ -143,20 +145,26 @@ export type ParsedWalletDetails = z.infer; */ export const getWalletDetails = async ({ pgtx, - address, + address: _walletAddress, }: GetWalletDetailsParams) => { + const walletAddress = _walletAddress.toLowerCase(); + const cachedDetails = walletDetailsCache.get(walletAddress); + if (cachedDetails) { + return cachedDetails; + } + const prisma = getPrismaWithPostgresTx(pgtx); const config = await getConfig(); const walletDetails = await prisma.walletDetails.findUnique({ where: { - address: address.toLowerCase(), + address: walletAddress.toLowerCase(), }, }); if (!walletDetails) { throw new WalletDetailsError( - `No wallet details found for address ${address}`, + `No wallet details found for address ${walletAddress}`, ); } @@ -167,7 +175,7 @@ export const getWalletDetails = async ({ ) { if (!walletDetails.awsKmsArn) { throw new WalletDetailsError( - `AWS KMS ARN is missing for the wallet with address ${address}`, + `AWS KMS ARN is missing for the wallet with address ${walletAddress}`, ); } @@ -188,7 +196,7 @@ export const getWalletDetails = async ({ ) { if (!walletDetails.gcpKmsResourcePath) { throw new WalletDetailsError( - `GCP KMS resource path is missing for the wallet with address ${address}`, + `GCP KMS resource path is missing for the wallet with address ${walletAddress}`, ); } @@ -209,14 +217,16 @@ export const getWalletDetails = async ({ // zod schema can validate all necessary fields are populated after decryption try { - return walletDetailsSchema.parse(walletDetails, { + const result = walletDetailsSchema.parse(walletDetails, { errorMap: (issue) => { const fieldName = issue.path.join("."); return { - message: `${fieldName} is necessary for wallet ${address} of type ${walletDetails.type}, but not found in wallet details or configuration`, + message: `${fieldName} is necessary for wallet ${walletAddress} of type ${walletDetails.type}, but not found in wallet details or configuration`, }; }, }); + walletDetailsCache.set(walletAddress, result); + return result; } catch (e) { if (e instanceof z.ZodError) { throw new WalletDetailsError( diff --git a/src/shared/utils/transaction/insert-transaction.ts b/src/shared/utils/transaction/insert-transaction.ts index 625088a30..25fd99967 100644 --- a/src/shared/utils/transaction/insert-transaction.ts +++ b/src/shared/utils/transaction/insert-transaction.ts @@ -4,6 +4,7 @@ import { TransactionDB } from "../../../shared/db/transactions/db"; import { getWalletDetails, isSmartBackendWallet, + WalletDetailsError, type ParsedWalletDetails, } from "../../../shared/db/wallets/get-wallet-details"; import { doesChainSupportService } from "../../lib/chain/chain-capabilities"; @@ -105,8 +106,12 @@ export const insertTransaction = async ( entrypointAddress: walletDetails.entrypointAddress ?? undefined, }; } - } catch { - // if wallet details are not found, this is a smart backend wallet using a v4 endpoint + } catch (e) { + if (e instanceof WalletDetailsError) { + // do nothing. The this is a smart backend wallet using a v4 endpoint + } + // if other type of error, rethrow + throw e; } if (!walletDetails && queuedTransaction.accountAddress) { @@ -139,13 +144,16 @@ export const insertTransaction = async ( walletDetails.accountFactoryAddress ?? undefined, }; } - } catch { + } catch (e: unknown) { // if wallet details are not found for this either, this backend wallet does not exist at all - throw createCustomError( - "Account not found", - StatusCodes.BAD_REQUEST, - "ACCOUNT_NOT_FOUND", - ); + if (e instanceof WalletDetailsError) { + throw createCustomError( + "Account not found", + StatusCodes.BAD_REQUEST, + "ACCOUNT_NOT_FOUND", + ); + } + throw e; } } diff --git a/src/shared/utils/transaction/types.ts b/src/shared/utils/transaction/types.ts index b57a5e5a5..6b28b88d3 100644 --- a/src/shared/utils/transaction/types.ts +++ b/src/shared/utils/transaction/types.ts @@ -13,6 +13,14 @@ export type AnyTransaction = | CancelledTransaction | ErroredTransaction; +export type BatchOperation = { + to?: Address; + value: bigint; + data?: Hex; + functionName?: string; + functionArgs?: unknown[]; +}; + // InsertedTransaction is the raw input from the caller. export type InsertedTransaction = { isUserOp: boolean; @@ -49,6 +57,7 @@ export type InsertedTransaction = { accountSalt?: string; accountFactoryAddress?: Address; entrypointAddress?: Address; + batchOperations?: BatchOperation[]; target?: Address; sender?: Address; }; diff --git a/src/worker/tasks/send-transaction-worker.ts b/src/worker/tasks/send-transaction-worker.ts index d109cb24d..e00825db4 100644 --- a/src/worker/tasks/send-transaction-worker.ts +++ b/src/worker/tasks/send-transaction-worker.ts @@ -143,7 +143,14 @@ const _sendUserOp = async ( assert(accountAddress, "Invalid userOp parameters: accountAddress"); const toAddress = to ?? target; - assert(toAddress, "Invalid transaction parameters: to"); + + if (queuedTransaction.batchOperations) { + queuedTransaction.batchOperations.map((op) => { + assert(op.to, "Invalid transaction parameters: to"); + }); + } else { + assert(toAddress, "Invalid transaction parameters: to"); + } // this can either be a regular backend wallet userop or a smart backend wallet userop let adminAccount: Account | undefined; @@ -199,17 +206,25 @@ const _sendUserOp = async ( } } + const transactions = queuedTransaction.batchOperations + ? queuedTransaction.batchOperations.map((op) => ({ + ...op, + chain, + client: thirdwebClient, + })) + : [ + { + client: thirdwebClient, + chain, + ...queuedTransaction, + ...overrides, + to: getChecksumAddress(toAddress), + }, + ]; + signedUserOp = (await createAndSignUserOp({ client: thirdwebClient, - transactions: [ - { - client: thirdwebClient, - chain, - ...queuedTransaction, - ...overrides, - to: getChecksumAddress(toAddress), - }, - ], + transactions, adminAccount, smartWalletOptions: { chain, From 1904ce58f99c695423d4f8235bb2f7f0f9092edc Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Mon, 13 Jan 2025 13:24:18 +0530 Subject: [PATCH 6/7] SDK changes --- sdk/src/services/BackendWalletService.ts | 68 ++++++++++++++++++++++++ sdk/src/services/ContractService.ts | 44 +++++++++++++++ sdk/src/services/TransactionService.ts | 3 ++ src/scripts/generate-sdk.ts | 2 +- src/server/routes/webhooks/test.ts | 1 + 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/sdk/src/services/BackendWalletService.ts b/sdk/src/services/BackendWalletService.ts index 0001fb3aa..dd9bedff9 100644 --- a/sdk/src/services/BackendWalletService.ts +++ b/sdk/src/services/BackendWalletService.ts @@ -629,6 +629,72 @@ export class BackendWalletService { }); } + /** + * Send a batch of raw transactions atomically + * Send a batch of raw transactions in a single UserOp. Can only be used with smart wallets. + * @param chain A chain ID ("137") or slug ("polygon-amoy-testnet"). Chain ID is preferred. + * @param xBackendWalletAddress Backend wallet address + * @param requestBody + * @param simulateTx Simulates the transaction before adding it to the queue, returning an error if it fails simulation. Note: This step is less performant and recommended only for debugging purposes. + * @param xIdempotencyKey Transactions submitted with the same idempotency key will be de-duplicated. Only the last 100000 transactions are compared. + * @param xAccountAddress Smart account address + * @param xAccountFactoryAddress Smart account factory address. If omitted, Engine will try to resolve it from the contract. + * @param xAccountSalt Smart account salt as string or hex. This is used to predict the smart account address. Useful when creating multiple accounts with the same admin and only needed when deploying the account as part of a userop. + * @returns any Default Response + * @throws ApiError + */ + public sendTransactionsAtomic( + chain: string, + xBackendWalletAddress: string, + requestBody: { + transactions: Array<{ + /** + * A contract or wallet address + */ + toAddress?: string; + data: string; + value: string; + }>; + }, + simulateTx: boolean = false, + xIdempotencyKey?: string, + xAccountAddress?: string, + xAccountFactoryAddress?: string, + xAccountSalt?: string, + ): CancelablePromise<{ + result: { + /** + * Queue ID + */ + queueId: string; + }; + }> { + return this.httpRequest.request({ + method: 'POST', + url: '/backend-wallet/{chain}/send-transactions-atomic', + path: { + 'chain': chain, + }, + headers: { + 'x-backend-wallet-address': xBackendWalletAddress, + 'x-idempotency-key': xIdempotencyKey, + 'x-account-address': xAccountAddress, + 'x-account-factory-address': xAccountFactoryAddress, + 'x-account-salt': xAccountSalt, + }, + query: { + 'simulateTx': simulateTx, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + 404: `Not Found`, + 500: `Internal Server Error`, + }, + }); + } + /** * Sign a transaction * Sign a transaction @@ -833,6 +899,7 @@ export class BackendWalletService { onchainStatus: ('success' | 'reverted' | null); effectiveGasPrice: (string | null); cumulativeGasUsed: (string | null); + batchOperations: null; }>; }; }> { @@ -928,6 +995,7 @@ export class BackendWalletService { onchainStatus: ('success' | 'reverted' | null); effectiveGasPrice: (string | null); cumulativeGasUsed: (string | null); + batchOperations: null; } | string); }>; }> { diff --git a/sdk/src/services/ContractService.ts b/sdk/src/services/ContractService.ts index 6930052bc..caef13274 100644 --- a/sdk/src/services/ContractService.ts +++ b/sdk/src/services/ContractService.ts @@ -46,6 +46,50 @@ export class ContractService { }); } + /** + * Batch read from multiple contracts + * Execute multiple contract read operations in a single call using Multicall + * @param chain + * @param requestBody + * @returns any Default Response + * @throws ApiError + */ + public readBatch( + chain: string, + requestBody: { + calls: Array<{ + contractAddress: string; + functionName: string; + functionAbi?: string; + args?: Array; + }>; + /** + * Address of the multicall contract to use. If omitted, multicall3 contract will be used (0xcA11bde05977b3631167028862bE2a173976CA11). + */ + multicallAddress?: string; + }, + ): CancelablePromise<{ + results: Array<{ + success: boolean; + result: any; + }>; + }> { + return this.httpRequest.request({ + method: 'POST', + url: '/contract/{chain}/read-batch', + path: { + 'chain': chain, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + 404: `Not Found`, + 500: `Internal Server Error`, + }, + }); + } + /** * Write to contract * Call a write function on a contract. diff --git a/sdk/src/services/TransactionService.ts b/sdk/src/services/TransactionService.ts index a10a731d2..76f9ef016 100644 --- a/sdk/src/services/TransactionService.ts +++ b/sdk/src/services/TransactionService.ts @@ -78,6 +78,7 @@ export class TransactionService { onchainStatus: ('success' | 'reverted' | null); effectiveGasPrice: (string | null); cumulativeGasUsed: (string | null); + batchOperations: null; }>; totalCount: number; }; @@ -162,6 +163,7 @@ export class TransactionService { onchainStatus: ('success' | 'reverted' | null); effectiveGasPrice: (string | null); cumulativeGasUsed: (string | null); + batchOperations: null; }; }> { return this.httpRequest.request({ @@ -245,6 +247,7 @@ export class TransactionService { onchainStatus: ('success' | 'reverted' | null); effectiveGasPrice: (string | null); cumulativeGasUsed: (string | null); + batchOperations: null; }>; totalCount: number; }; diff --git a/src/scripts/generate-sdk.ts b/src/scripts/generate-sdk.ts index f39c377f8..759dd8f00 100644 --- a/src/scripts/generate-sdk.ts +++ b/src/scripts/generate-sdk.ts @@ -4,7 +4,7 @@ import path from "node:path"; import { kill } from "node:process"; const ENGINE_OPENAPI_URL = "https://demo.web3api.thirdweb.com/json"; -// const ENGINE_OPENAPI_URL = "http://localhost:3005/json"; +// const ENGINE_OPENAPI_URL = "http://127.0.0.1:3005/json"; const REPLACE_LOG_FILE = "sdk/replacement_log.txt"; type BasicOpenAPISpec = { diff --git a/src/server/routes/webhooks/test.ts b/src/server/routes/webhooks/test.ts index d61861f0f..868f21ebc 100644 --- a/src/server/routes/webhooks/test.ts +++ b/src/server/routes/webhooks/test.ts @@ -93,6 +93,7 @@ export async function testWebhookRoute(fastify: FastifyInstance) { paymasterAndData: null, userOpHash: null, accountSalt: null, + batchOperations: null, // Off-chain details functionName: "transfer", From 215c458ce803f44a1264130c9f41fce12c5fbc75 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 15 Jan 2025 03:41:40 +0530 Subject: [PATCH 7/7] Address review comments --- sdk/src/services/BackendWalletService.ts | 12 +++++++--- ...ic.ts => send-transaction-batch-atomic.ts} | 24 +++++++------------ src/server/routes/contract/read/read-batch.ts | 2 +- src/server/routes/index.ts | 8 +++---- .../transaction/raw-transaction-parms.ts | 9 +++++++ src/shared/db/wallets/get-wallet-details.ts | 4 ++-- 6 files changed, 33 insertions(+), 26 deletions(-) rename src/server/routes/backend-wallet/{send-transactions-atomic.ts => send-transaction-batch-atomic.ts} (88%) create mode 100644 src/server/schemas/transaction/raw-transaction-parms.ts diff --git a/sdk/src/services/BackendWalletService.ts b/sdk/src/services/BackendWalletService.ts index dd9bedff9..7c25b097f 100644 --- a/sdk/src/services/BackendWalletService.ts +++ b/sdk/src/services/BackendWalletService.ts @@ -631,7 +631,7 @@ export class BackendWalletService { /** * Send a batch of raw transactions atomically - * Send a batch of raw transactions in a single UserOp. Can only be used with smart wallets. + * Send a batch of raw transactions in a single UserOp. Transactions will be sent in-order and atomically. Can only be used with smart wallets. * @param chain A chain ID ("137") or slug ("polygon-amoy-testnet"). Chain ID is preferred. * @param xBackendWalletAddress Backend wallet address * @param requestBody @@ -643,7 +643,7 @@ export class BackendWalletService { * @returns any Default Response * @throws ApiError */ - public sendTransactionsAtomic( + public sendTransactionBatchAtomic( chain: string, xBackendWalletAddress: string, requestBody: { @@ -652,7 +652,13 @@ export class BackendWalletService { * A contract or wallet address */ toAddress?: string; + /** + * A valid hex string + */ data: string; + /** + * An amount in wei (no decimals). Example: "50000000000" + */ value: string; }>; }, @@ -671,7 +677,7 @@ export class BackendWalletService { }> { return this.httpRequest.request({ method: 'POST', - url: '/backend-wallet/{chain}/send-transactions-atomic', + url: '/backend-wallet/{chain}/send-transaction-batch-atomic', path: { 'chain': chain, }, diff --git a/src/server/routes/backend-wallet/send-transactions-atomic.ts b/src/server/routes/backend-wallet/send-transaction-batch-atomic.ts similarity index 88% rename from src/server/routes/backend-wallet/send-transactions-atomic.ts rename to src/server/routes/backend-wallet/send-transaction-batch-atomic.ts index 3892df78c..80506b6e8 100644 --- a/src/server/routes/backend-wallet/send-transactions-atomic.ts +++ b/src/server/routes/backend-wallet/send-transaction-batch-atomic.ts @@ -3,7 +3,6 @@ import type { FastifyInstance } from "fastify"; import { StatusCodes } from "http-status-codes"; import type { Address, Hex } from "thirdweb"; import { insertTransaction } from "../../../shared/utils/transaction/insert-transaction"; -import { AddressSchema } from "../../schemas/address"; import { requestQuerystringSchema, standardResponseSchema, @@ -22,22 +21,15 @@ import { WalletDetailsError, } from "../../../shared/db/wallets/get-wallet-details"; import { createCustomError } from "../../middleware/error"; +import { RawTransactionParamsSchema } from "../../schemas/transaction/raw-transaction-parms"; const requestBodySchema = Type.Object({ - transactions: Type.Array( - Type.Object({ - toAddress: Type.Optional(AddressSchema), - data: Type.String({ - examples: ["0x..."], - }), - value: Type.String({ - examples: ["10000000"], - }), - }), - ), + transactions: Type.Array(RawTransactionParamsSchema, { + minItems: 1, + }), }); -export async function sendTransactionsAtomicRoute(fastify: FastifyInstance) { +export async function sendTransactionBatchAtomicRoute(fastify: FastifyInstance) { fastify.route<{ Params: Static; Body: Static; @@ -45,13 +37,13 @@ export async function sendTransactionsAtomicRoute(fastify: FastifyInstance) { Querystring: Static; }>({ method: "POST", - url: "/backend-wallet/:chain/send-transactions-atomic", + url: "/backend-wallet/:chain/send-transaction-batch-atomic", schema: { summary: "Send a batch of raw transactions atomically", description: - "Send a batch of raw transactions in a single UserOp. Can only be used with smart wallets.", + "Send a batch of raw transactions in a single UserOp. Transactions will be sent in-order and atomically. Can only be used with smart wallets.", tags: ["Backend Wallet"], - operationId: "sendTransactionsAtomic", + operationId: "sendTransactionBatchAtomic", params: walletChainParamSchema, body: requestBodySchema, headers: Type.Omit(walletWithAAHeaderSchema, ["x-transaction-mode"]), diff --git a/src/server/routes/contract/read/read-batch.ts b/src/server/routes/contract/read/read-batch.ts index 6cc466057..0a026bacd 100644 --- a/src/server/routes/contract/read/read-batch.ts +++ b/src/server/routes/contract/read/read-batch.ts @@ -59,7 +59,7 @@ type RouteGeneric = { Reply: Static; }; -export async function readMulticallRoute(fastify: FastifyInstance) { +export async function readBatchRoute(fastify: FastifyInstance) { fastify.route({ method: "POST", url: "/contract/:chain/read-batch", diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 8139bb47a..1606debcc 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -111,8 +111,8 @@ import { getWebhooksEventTypes } from "./webhooks/events"; import { getAllWebhooksData } from "./webhooks/get-all"; import { revokeWebhook } from "./webhooks/revoke"; import { testWebhookRoute } from "./webhooks/test"; -import { readMulticallRoute } from "./contract/read/read-batch"; -import { sendTransactionsAtomicRoute } from "./backend-wallet/send-transactions-atomic"; +import { readBatchRoute } from "./contract/read/read-batch"; +import { sendTransactionBatchAtomicRoute } from "./backend-wallet/send-transaction-batch-atomic"; export async function withRoutes(fastify: FastifyInstance) { // Backend Wallets @@ -126,7 +126,7 @@ export async function withRoutes(fastify: FastifyInstance) { await fastify.register(withdraw); await fastify.register(sendTransaction); await fastify.register(sendTransactionBatch); - await fastify.register(sendTransactionsAtomicRoute); + await fastify.register(sendTransactionBatchAtomicRoute); await fastify.register(signTransaction); await fastify.register(signMessageRoute); await fastify.register(signTypedData); @@ -195,7 +195,7 @@ export async function withRoutes(fastify: FastifyInstance) { // Generic await fastify.register(readContract); - await fastify.register(readMulticallRoute); + await fastify.register(readBatchRoute); await fastify.register(writeToContract); // Contract Events diff --git a/src/server/schemas/transaction/raw-transaction-parms.ts b/src/server/schemas/transaction/raw-transaction-parms.ts new file mode 100644 index 000000000..fbf64d81a --- /dev/null +++ b/src/server/schemas/transaction/raw-transaction-parms.ts @@ -0,0 +1,9 @@ +import { Type } from "@sinclair/typebox"; +import { AddressSchema, HexSchema } from "../address"; +import { WeiAmountStringSchema } from "../number"; + +export const RawTransactionParamsSchema = Type.Object({ + toAddress: Type.Optional(AddressSchema), + data: HexSchema, + value: WeiAmountStringSchema, +}); diff --git a/src/shared/db/wallets/get-wallet-details.ts b/src/shared/db/wallets/get-wallet-details.ts index c818a6a5a..c6612d628 100644 --- a/src/shared/db/wallets/get-wallet-details.ts +++ b/src/shared/db/wallets/get-wallet-details.ts @@ -1,4 +1,4 @@ -import LruMap from "mnemonist/lru-map"; +import LRUMap from "mnemonist/lru-map"; import { getAddress } from "thirdweb"; import { z } from "zod"; import type { PrismaTransaction } from "../../schemas/prisma"; @@ -131,7 +131,7 @@ export type SmartBackendWalletType = (typeof SmartBackendWalletTypes)[number]; export type BackendWalletType = (typeof BackendWalletTypes)[number]; export type ParsedWalletDetails = z.infer; -export const walletDetailsCache = new LruMap(2048); +export const walletDetailsCache = new LRUMap(2048); /** * Return the wallet details for the given address. *