diff --git a/.changeset/.lucky-actors-smell.md b/.changeset/.lucky-actors-smell.md index 2c16bdffb3..b84c5c7ca6 100644 --- a/.changeset/.lucky-actors-smell.md +++ b/.changeset/.lucky-actors-smell.md @@ -2,4 +2,4 @@ "viem": patch --- -Added Crab and Koi chain \ No newline at end of file +Added Atleta Olympia chain \ No newline at end of file diff --git a/.changeset/chilly-bugs-tell.md b/.changeset/chilly-bugs-tell.md deleted file mode 100644 index 9730cedf66..0000000000 --- a/.changeset/chilly-bugs-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"viem": patch ---- - -Exported `universalSignatureValidatorAbi`. diff --git a/.changeset/rich-seas-jog.md b/.changeset/rich-seas-jog.md new file mode 100644 index 0000000000..b46fcd3cee --- /dev/null +++ b/.changeset/rich-seas-jog.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Added Tron chain diff --git a/.changeset/slimy-melons-bathe.md b/.changeset/slimy-melons-bathe.md new file mode 100644 index 0000000000..f3feddf08f --- /dev/null +++ b/.changeset/slimy-melons-bathe.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added built-in support for Linea gas & fee estimations. diff --git a/.changeset/three-fireants-beg.md b/.changeset/three-fireants-beg.md new file mode 100644 index 0000000000..454b984a28 --- /dev/null +++ b/.changeset/three-fireants-beg.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Adjusted sophon native token symbol. diff --git a/.changeset/wise-planets-press.md b/.changeset/wise-planets-press.md new file mode 100644 index 0000000000..9b629546cc --- /dev/null +++ b/.changeset/wise-planets-press.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Deprecated `chain.fees.defaultPriorityFee`, use `chain.fees.maxPriorityFeePerGas` instead. diff --git a/FUNDING.json b/FUNDING.json index fa29d44658..feeec679d8 100644 --- a/FUNDING.json +++ b/FUNDING.json @@ -3,5 +3,8 @@ "ethereum": { "ownedBy": "0xd2135CfB216b74109775236E36d4b433F1DF507B" } + }, + "opRetro": { + "projectId": "0x6bd057da522918a4675396313ae33a2f2788a1ceeb3bd7ae228015e3eb317a7d" } } diff --git a/package.json b/package.json index 55fe4b131e..6ad6676a9e 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "src": { "entry": [ "index.ts!", - "{account-abstraction,accounts,actions,celo,chains,ens,experimental,experimental/erc7739,node,nonce,op-stack,siwe,utils,window,zksync}/index.ts!", + "{account-abstraction,accounts,actions,celo,chains,ens,experimental,experimental/erc7739,linea,node,nonce,op-stack,siwe,utils,window,zksync}/index.ts!", "chains/utils.ts!" ], "ignore": ["node/trustedSetups_cjs.ts"] @@ -163,7 +163,7 @@ { "name": "import * from 'viem/chains'", "path": "./src/_esm/chains/index.js", - "limit": "31.5 kB", + "limit": "38 kB", "import": "*" }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f637dc793..67496ccf56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,7 +156,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -169,7 +169,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -182,7 +182,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -195,7 +195,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -214,7 +214,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -242,7 +242,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -264,7 +264,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -277,7 +277,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -296,7 +296,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -318,7 +318,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -331,7 +331,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -344,7 +344,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -357,7 +357,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -404,7 +404,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -432,7 +432,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.5.4)(zod@3.22.4) + version: 2.20.0(typescript@5.5.4)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -454,7 +454,7 @@ importers: dependencies: viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: typescript: specifier: ^5.0.3 @@ -473,7 +473,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -507,7 +507,7 @@ importers: version: 18.2.0(react@18.2.0) viem: specifier: latest - version: 2.19.1(typescript@5.4.2)(zod@3.22.4) + version: 2.20.0(typescript@5.4.2)(zod@3.22.4) devDependencies: '@types/react': specifier: ^18.0.27 @@ -3225,7 +3225,6 @@ packages: bun@1.1.12: resolution: {integrity: sha512-NZzeZuZk7VwCs8VAXnXUHCPOlTS/IyHCscChtT1M1FLSwhBcVMsGVStYlXaaoqsinBKgp0CGJdhnJw2gR3NkDw==} - cpu: [arm64, x64] os: [darwin, linux, win32] hasBin: true @@ -6757,8 +6756,16 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} - viem@2.19.1: - resolution: {integrity: sha512-a0ca/ACEz3FRZB3OmiSfRUogWZGQh700wu7Pg3GmAWiGD+0PS9bVaWG67JQ+9azFZLq0BU/m0t2CeWd3xi8IzQ==} + viem@2.20.0: + resolution: {integrity: sha512-cM4vs81HnSNbfceI1MLkx4pCVzbVjl9xiNSv5SCutYjUyFFOVSPDlEyhpg2iHinxx1NM4Qne3END5eLT8rvUdg==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + + viem@2.20.1: + resolution: {integrity: sha512-a/BSe25TSfkc423GTSKYl1O0ON2J5huoQeOLkylHT1WS8wh3JFqb8nfAq7vg+aZ+W06BCTn36bbi47yp4D92Cg==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -8447,7 +8454,7 @@ snapshots: pino-http: 8.6.1 pino-pretty: 10.3.1 prom-client: 14.2.0 - viem: 2.19.1(typescript@5.5.2)(zod@3.22.4) + viem: 2.20.1(typescript@5.5.2)(zod@3.22.4) yargs: 17.7.2 zod: 3.22.4 zod-validation-error: 1.5.0(zod@3.22.4) @@ -14339,7 +14346,7 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - viem@2.19.1(typescript@5.4.2)(zod@3.22.4): + viem@2.20.0(typescript@5.4.2)(zod@3.22.4): dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.4.0 @@ -14357,37 +14364,37 @@ snapshots: - utf-8-validate - zod - viem@2.19.1(typescript@5.5.2)(zod@3.22.4): + viem@2.20.0(typescript@5.5.4)(zod@3.22.4): dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.4.0 '@noble/hashes': 1.4.0 '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 - abitype: 1.0.5(typescript@5.5.2)(zod@3.22.4) + abitype: 1.0.5(typescript@5.5.4)(zod@3.22.4) isows: 1.0.4(ws@8.17.1) webauthn-p256: 0.0.5 ws: 8.17.1 optionalDependencies: - typescript: 5.5.2 + typescript: 5.5.4 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - viem@2.19.1(typescript@5.5.4)(zod@3.22.4): + viem@2.20.1(typescript@5.5.2)(zod@3.22.4): dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.4.0 '@noble/hashes': 1.4.0 '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 - abitype: 1.0.5(typescript@5.5.4)(zod@3.22.4) + abitype: 1.0.5(typescript@5.5.2)(zod@3.22.4) isows: 1.0.4(ws@8.17.1) webauthn-p256: 0.0.5 ws: 8.17.1 optionalDependencies: - typescript: 5.5.4 + typescript: 5.5.2 transitivePeerDependencies: - bufferutil - utf-8-validate diff --git a/site/pages/account-abstraction.mdx b/site/pages/account-abstraction.mdx index 34f39c7fdd..da0b05e105 100644 --- a/site/pages/account-abstraction.mdx +++ b/site/pages/account-abstraction.mdx @@ -74,7 +74,7 @@ const bundlerClient = createBundlerClient({ // [!code focus] ``` :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: [See `createBundlerClient` Docs](/account-abstraction/clients/bundler) diff --git a/site/pages/account-abstraction/accounts/smart/toSmartAccount.md b/site/pages/account-abstraction/accounts/smart/toSmartAccount.md index 03ccda1ab0..d1cdab9fee 100644 --- a/site/pages/account-abstraction/accounts/smart/toSmartAccount.md +++ b/site/pages/account-abstraction/accounts/smart/toSmartAccount.md @@ -2,7 +2,7 @@ description: Creates a Smart Account with a provided Account Implementation. --- -# Custom +# toSmartAccount The `toSmartAccount` function allows you to create a Smart Account with a custom Account Implementation. diff --git a/site/pages/account-abstraction/actions/bundler/estimateUserOperationGas.md b/site/pages/account-abstraction/actions/bundler/estimateUserOperationGas.md index 3ff671d5c0..4f3661247d 100644 --- a/site/pages/account-abstraction/actions/bundler/estimateUserOperationGas.md +++ b/site/pages/account-abstraction/actions/bundler/estimateUserOperationGas.md @@ -48,7 +48,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ### Account Hoisting diff --git a/site/pages/account-abstraction/actions/bundler/getChainId.md b/site/pages/account-abstraction/actions/bundler/getChainId.md index 6929476bb0..c0ef023f1a 100644 --- a/site/pages/account-abstraction/actions/bundler/getChainId.md +++ b/site/pages/account-abstraction/actions/bundler/getChainId.md @@ -29,7 +29,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Returns diff --git a/site/pages/account-abstraction/actions/bundler/getSupportedEntryPoints.md b/site/pages/account-abstraction/actions/bundler/getSupportedEntryPoints.md index dc2c74b67e..48d7ea7773 100644 --- a/site/pages/account-abstraction/actions/bundler/getSupportedEntryPoints.md +++ b/site/pages/account-abstraction/actions/bundler/getSupportedEntryPoints.md @@ -29,7 +29,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Returns diff --git a/site/pages/account-abstraction/actions/bundler/getUserOperation.md b/site/pages/account-abstraction/actions/bundler/getUserOperation.md index b75b399b2e..85756879a3 100644 --- a/site/pages/account-abstraction/actions/bundler/getUserOperation.md +++ b/site/pages/account-abstraction/actions/bundler/getUserOperation.md @@ -32,7 +32,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Returns diff --git a/site/pages/account-abstraction/actions/bundler/getUserOperationReceipt.md b/site/pages/account-abstraction/actions/bundler/getUserOperationReceipt.md index 04aa6bc185..2b198cf4ba 100644 --- a/site/pages/account-abstraction/actions/bundler/getUserOperationReceipt.md +++ b/site/pages/account-abstraction/actions/bundler/getUserOperationReceipt.md @@ -39,7 +39,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Returns diff --git a/site/pages/account-abstraction/actions/bundler/prepareUserOperation.md b/site/pages/account-abstraction/actions/bundler/prepareUserOperation.md index cf318c8937..f1e6eec402 100644 --- a/site/pages/account-abstraction/actions/bundler/prepareUserOperation.md +++ b/site/pages/account-abstraction/actions/bundler/prepareUserOperation.md @@ -48,7 +48,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ### Account Hoisting diff --git a/site/pages/account-abstraction/actions/bundler/sendUserOperation.md b/site/pages/account-abstraction/actions/bundler/sendUserOperation.md index 73f7f63a9c..61037b0cbc 100644 --- a/site/pages/account-abstraction/actions/bundler/sendUserOperation.md +++ b/site/pages/account-abstraction/actions/bundler/sendUserOperation.md @@ -48,7 +48,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ### Account Hoisting diff --git a/site/pages/account-abstraction/actions/bundler/waitForUserOperationReceipt.md b/site/pages/account-abstraction/actions/bundler/waitForUserOperationReceipt.md index 24369a41ca..d106e5b7b6 100644 --- a/site/pages/account-abstraction/actions/bundler/waitForUserOperationReceipt.md +++ b/site/pages/account-abstraction/actions/bundler/waitForUserOperationReceipt.md @@ -39,7 +39,7 @@ export const bundlerClient = createBundlerClient({ ::: :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Returns diff --git a/site/pages/account-abstraction/clients/bundler.md b/site/pages/account-abstraction/clients/bundler.md index 609f6ae5b2..461a212a69 100644 --- a/site/pages/account-abstraction/clients/bundler.md +++ b/site/pages/account-abstraction/clients/bundler.md @@ -27,7 +27,7 @@ const bundlerClient = createBundlerClient({ // [!code focus] ``` :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: ## Parameters @@ -380,4 +380,4 @@ const bundlerClient = createBundlerClient({ } // [!code focus] } // [!code focus] }) -``` \ No newline at end of file +``` diff --git a/site/pages/account-abstraction/guides/sending-user-operations.md b/site/pages/account-abstraction/guides/sending-user-operations.md index 75d73934dd..ae5a16985e 100644 --- a/site/pages/account-abstraction/guides/sending-user-operations.md +++ b/site/pages/account-abstraction/guides/sending-user-operations.md @@ -93,7 +93,7 @@ const bundlerClient = createBundlerClient({ // [!code ++] // [!code focus] ``` :::info -The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io) or another Bundler service. +The Bundler URL above is a public endpoint. Please do not use it in production as you will likely be rate-limited. Consider using [Pimlico's Bundler](https://www.pimlico.io), [Biconomy's Bundler](https://www.biconomy.io), or another Bundler service. ::: [See `createBundlerClient` Docs](/account-abstraction/clients/bundler) diff --git a/site/pages/docs/accounts/local/mnemonicToAccount.md b/site/pages/docs/accounts/local/mnemonicToAccount.md index 5e4fd1edb2..81d3b5e826 100644 --- a/site/pages/docs/accounts/local/mnemonicToAccount.md +++ b/site/pages/docs/accounts/local/mnemonicToAccount.md @@ -56,6 +56,7 @@ Available wordlists: - `italian` - `japanese` - `korean` +- `portuguese` - `simplifiedChinese` - `spanish` - `traditionalChinese` diff --git a/site/pages/docs/actions/public/verifyMessage.md b/site/pages/docs/actions/public/verifyMessage.md index 4c32ccff3c..de0d9a9d7a 100644 --- a/site/pages/docs/actions/public/verifyMessage.md +++ b/site/pages/docs/actions/public/verifyMessage.md @@ -7,9 +7,9 @@ description: Verifies if a signed message was generated by the provided address. Verify that a message was signed by the provided address. :::info -**Why should I use this over the [`verifyMessage`](../../utilities/verifyMessage.md) util?** +**Why should I use this over the [`verifyMessage`](/docs/utilities/verifyMessage) util?** -This Action supports verifying messages that were signed by either a Smart Contract Account or Externally Owned Account. The [`verifyMessage`](../../utilities/verifyMessage.md) util only supports Externally Owned Accounts. This is getting increasingly important as more wallets implement [Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337). +This Action supports verifying messages that were signed by either a Smart Contract Account or Externally Owned Account. The [`verifyMessage`](/docs/utilities/verifyMessage.md) util only supports Externally Owned Accounts. This is getting increasingly important as more wallets implement [Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337). ::: ## Usage diff --git a/site/pages/docs/actions/public/verifyTypedData.md b/site/pages/docs/actions/public/verifyTypedData.md index 0d170a7ddc..38423f472a 100644 --- a/site/pages/docs/actions/public/verifyTypedData.md +++ b/site/pages/docs/actions/public/verifyTypedData.md @@ -7,9 +7,9 @@ description: Verifies a typed data signature Verify that typed data was signed by the provided address. :::info -**Why should I use this over the [`verifyTypedData`](../../utilities/verifyTypedData.md) util?** +**Why should I use this over the [`verifyTypedData`](/docs/utilities/verifyTypedData) util?** -This Action supports verifying typed data that was signed by either a Smart Contract Account or Externally Owned Account (via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492)). The [`verifyTypedData`](../../utilities/verifyTypedData.md) util only supports Externally Owned Accounts. This is getting increasingly important as more wallets implement [Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337). +This Action supports verifying typed data that was signed by either a Smart Contract Account or Externally Owned Account (via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492)). The [`verifyTypedData`](/docs/utilities/verifyTypedData) util only supports Externally Owned Accounts. This is getting increasingly important as more wallets implement [Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337). ::: ## Usage diff --git a/site/pages/docs/contract/estimateContractGas.md b/site/pages/docs/contract/estimateContractGas.md index f8d5dc87a5..b4c89da22b 100644 --- a/site/pages/docs/contract/estimateContractGas.md +++ b/site/pages/docs/contract/estimateContractGas.md @@ -92,7 +92,7 @@ export const wagmiAbi = [ inputs: [{ name: "owner", type: "uint32" }], name: "mint", outputs: [{ name: "", type: "uint32" }], - stateMutability: "view", + stateMutability: "nonpayable", type: "function", }, ... diff --git a/site/pages/experimental/eip5792/sendCalls.mdx b/site/pages/experimental/eip5792/sendCalls.mdx index fd9ecdb230..69feb5c866 100644 --- a/site/pages/experimental/eip5792/sendCalls.mdx +++ b/site/pages/experimental/eip5792/sendCalls.mdx @@ -101,6 +101,82 @@ export const walletClient = createWalletClient({ ::: +### Contract Calls + +The `calls` property also accepts **Contract Calls**, and can be used via the `abi`, `functionName`, and `args` properties. + +:::code-group + +```ts twoslash [example.ts] +import { parseAbi } from 'viem' +import { walletClient } from './config' + +const abi = parseAbi([ + 'function approve(address, uint256) returns (bool)', + 'function transferFrom(address, address, uint256) returns (bool)', +]) + +const id = await walletClient.sendCalls({ // [!code focus:99] + calls: [ + { + to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + value: parseEther('1') + }, + { + to: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'approve', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + 100n + ], + }, + { + to: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi, + functionName: 'transferFrom', + args: [ + '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', + '0x0000000000000000000000000000000000000000', + 100n + ], + }, + ], +}) +``` + +```ts twoslash [abi.ts] filename="abi.ts" +export const wagmiAbi = [ + // ... + { + inputs: [], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + // ... +] as const; +``` + +```ts [config.ts] filename="config.ts" +import 'viem/window' +import { createWalletClient, custom } from 'viem' +import { walletActionsEip5792 } from 'viem/experimental' + +// Retrieve Account from an EIP-1193 Provider. +const [account] = await window.ethereum!.request({ + method: 'eth_requestAccounts' +}) + +export const walletClient = createWalletClient({ + account, + transport: custom(window.ethereum!) +}).extend(walletActionsEip5792()) +``` + +::: + ## Returns `string` diff --git a/site/pages/experimental/eip5792/writeContracts.mdx b/site/pages/experimental/eip5792/writeContracts.mdx deleted file mode 100644 index 9727c81306..0000000000 --- a/site/pages/experimental/eip5792/writeContracts.mdx +++ /dev/null @@ -1,425 +0,0 @@ ---- -description: Sign and broadcast a batch of write contract calls to the network. ---- - -# writeContracts - -Requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. - -[Read more.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) - -:::warning[Warning] -This is an experimental action that is not supported in most wallets. It is recommended to have a fallback mechanism if using this in production. -::: - -## Usage - -:::code-group - -```ts twoslash [example.ts] -import { parseAbi } from 'viem' -import { account, walletClient } from './config' - -const abi = parseAbi([ - 'function approve(address, uint256) returns (bool)', - 'function transferFrom(address, address, uint256) returns (bool)', -]) - -const id = await walletClient.writeContracts({ - account, - contracts: [ - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'approve', - args: [ - '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', - 100n - ], - }, - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'transferFrom', - args: [ - '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', - '0x0000000000000000000000000000000000000000', - 100n - ], - }, - ], -}) -``` - -```ts twoslash [config.ts] filename="config.ts" -import 'viem/window' -// ---cut--- -import { createWalletClient, custom } from 'viem' -import { mainnet } from 'viem/chains' -import { walletActionsEip5792 } from 'viem/experimental' - -export const walletClient = createWalletClient({ - chain: mainnet, - transport: custom(window.ethereum!), -}).extend(walletActionsEip5792()) - -export const [account] = await walletClient.getAddresses() -``` - -::: - -Notes: - -- Internally calls [`sendCalls`](/experimental/eip5792/writeContracts) -- `account` and `chain` are top level properties as all calls should be sent by the same account and chain. -- [Read `wallet_sendCalls` on EIP-5792.](https://github.com/ethereum/EIPs/blob/815028dc634463e1716fc5ce44c019a6040f0bef/EIPS/eip-5792.md#wallet_sendcalls) - -### Account Hoisting - -If you do not wish to pass an `account` to every `writeContracts`, you can also hoist the Account on the Wallet Client (see `config.ts`). - -[Learn more](/docs/clients/wallet#account). - -:::code-group - -```ts twoslash [example.ts] -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi([ - 'function approve(address, uint256) returns (bool)', - 'function transferFrom(address, address, uint256) returns (bool)', -]) - -const id = await walletClient.writeContracts({ - contracts: [ - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'approve', - args: [ - '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', - 100n - ], - }, - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'transferFrom', - args: [ - '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', - '0x0000000000000000000000000000000000000000', - 100n - ], - }, - ], -}) -``` - -```ts [config.ts] filename="config.ts" -import 'viem/window' -import { createWalletClient, custom } from 'viem' -import { walletActionsEip5792 } from 'viem/experimental' - -// Retrieve Account from an EIP-1193 Provider. -const [account] = await window.ethereum!.request({ - method: 'eth_requestAccounts' -}) - -export const walletClient = createWalletClient({ - account, - transport: custom(window.ethereum!) -}).extend(walletActionsEip5792()) -``` - -::: - -## Returns - -`string` - -The identifier can be any arbitrary string. The only requirement is that for a given session, consumers should be able to call `getCallsStatus` with this identifier to retrieve a batch call status and call receipts. - -## Parameters - -### account - -- **Type:** `Account | Address` - -The Account to sign & broadcast the call from. - -Accepts a [JSON-RPC Account](/docs/clients/wallet#json-rpc-accounts). - -```ts twoslash -import { parseAbi, mainnet } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // [!code focus] - contracts: [ - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'mint', - args: [69n], - }, - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'mint', - args: [420n], - }, - ], -}) -``` - -### chain - -- **Type:** [`Chain`](/docs/glossary/types#chain) -- **Default:** `walletClient.chain` - -The target chain to broadcast the calls. - -```ts twoslash -import { parseAbi, mainnet } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - chain: mainnet, // [!code focus] - contracts: [ - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'mint', - args: [69n], - }, - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - abi, - functionName: 'mint', - args: [420n], - }, - ], -}) -``` - -### contracts - -- **Type:** `MulticallContracts[]` - -An array of write contract calls to be signed and broadcasted. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ // [!code focus] - { // [!code focus] - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] - abi, // [!code focus] - functionName: 'mint', // [!code focus] - args: [69n], // [!code focus] - }, // [!code focus] - { // [!code focus] - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] - abi, // [!code focus] - functionName: 'mint', // [!code focus] - args: [420n], // [!code focus] - }, // [!code focus] - ], -}) -``` - -#### contracts.abi - -- **Type:** `Abi` - -The contract's ABI. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - abi, // [!code focus] - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [69n], - }, - { - abi, // [!code focus] - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [420n], - }, - ], -}) -``` - -#### contracts.address - -- **Type:** `Address` - -The contract address. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] - abi, - functionName: 'mint', - args: [69n], - }, - { - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // [!code focus] - abi, - functionName: 'mint', - args: [420n], - }, - ], -}) -``` - -#### contracts.functionName - -- **Type:** `string` - -A function to extract from the ABI. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', // [!code focus] - args: [69n], - }, - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', // [!code focus] - args: [420n], - }, - ], -}) -``` - -#### contracts.args - -- **Type:** Inferred from ABI. - -Arguments to pass to function call. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [69n], // [!code focus] - }, - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [420n], // [!code focus] - }, - ], -}) -``` - -#### contracts.value - -- **Type:** `number` - -Value in wei sent with this call. - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [69n], - }, - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [420n], - value: 69420n, // [!code focus] - }, - ], -}) -``` - -### capabilities - -- **Type:** `WalletCapabilities` - -Capability metadata for the calls (e.g. specifying a paymaster). - -```ts twoslash -import { parseAbi } from 'viem' -import { walletClient } from './config' - -const abi = parseAbi(['function mint(uint256)']) - -const id = await walletClient.writeContracts({ - contracts: [ - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [69n], - }, - { - abi, - address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', - functionName: 'mint', - args: [420n], - }, - ], - capabilities: { // [!code focus] - paymasterService: { // [!code focus] - url: 'https://...' // [!code focus] - } // [!code focus] - } // [!code focus] -}) -``` - diff --git a/site/pages/op-stack/utilities/opaqueDataToDepositData.md b/site/pages/op-stack/utilities/opaqueDataToDepositData.md new file mode 100644 index 0000000000..1e28cecd7d --- /dev/null +++ b/site/pages/op-stack/utilities/opaqueDataToDepositData.md @@ -0,0 +1,51 @@ +--- +description: Converts opaque data into a structured deposit data format. +--- + +# opaqueDataToDepositData + +Converts an opaque data into a structured deposit data object. This includes extracting and converting the `mint`, `value`, `gas`, `isCreation` flag, and `data` from a hex string. + +## Import + +```ts +import { opaqueDataToDepositData } from "viem"; +``` + +## Usage + +```ts +import { opaqueDataToDepositData } from "viem"; + +const opaqueData = + "0x00000000000000000000000000000000000000000000000000470DE4DF82000000000000000000000000000000000000000000000000000000470DE4DF82000000000000000186A00001"; + +const depositData = opaqueDataToDepositData(opaqueData); +// { +// mint: 20000000000000000n, +// value: 20000000000000000n, +// gas: 100000n, +// isCreation: false, +// data: '0x01', +// } +``` + +## Returns + +`OpaqueDataToDepositDataReturnType` + +An object containing the parsed deposit data. + +## Parameters + +### opaqueData + +- **Type:** `Hex` + +The opaque hex-encoded data. + +## Errors + +`OpaqueDataToDepositDataErrorType` + +An error type that includes potential slice, size, and generic errors encountered during the parsing process. diff --git a/site/pages/zksync/accounts/toMultisigSmartAccount.md b/site/pages/zksync/accounts/toMultisigSmartAccount.md new file mode 100644 index 0000000000..93e92ea678 --- /dev/null +++ b/site/pages/zksync/accounts/toMultisigSmartAccount.md @@ -0,0 +1,46 @@ +--- +description: Creates a multi-signature ZKsync Smart Account +--- + +# toMultisigSmartAccount (ZKsync) + +Creates a multi-signature [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and the Private Key of the owner. + +## Usage + +```ts twoslash +import { toMultisigSmartAccount } from 'viem/zksync' + +const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266', + privateKeys: ['0x...', '0x...'] +}) +``` + +## Parameters + +### address + +- **Type:** `Hex` + +Address of the deployed Account's Contract implementation. + +```ts +const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus] + privateKeys: ['0x...', '0x...'] +}) +``` + +### privateKeys + +- **Type:** `Hex[]` + +Private Keys of the owners. + +```ts +const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: ['0x...', '0x...'] // [!code focus] +}) +``` \ No newline at end of file diff --git a/site/pages/zksync/accounts/toSinglesigSmartAccount.md b/site/pages/zksync/accounts/toSinglesigSmartAccount.md new file mode 100644 index 0000000000..ff94b3e80b --- /dev/null +++ b/site/pages/zksync/accounts/toSinglesigSmartAccount.md @@ -0,0 +1,46 @@ +--- +description: Creates a single-signature ZKsync Smart Account +--- + +# toSinglesigSmartAccount (ZKsync) + +Creates a single-signature [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and the Private Key of the owner. + +## Usage + +```ts twoslash +import { toSinglesigSmartAccount } from 'viem/zksync' + +const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266', + privateKey: '0x...' +}) +``` + +## Parameters + +### address + +- **Type:** `Hex` + +Address of the deployed Account's Contract implementation. + +```ts +const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus] + privateKey: '0x...' +}) +``` + +### privateKey + +- **Type:** `Hex` + +Private Key of the owner. + +```ts +const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: '0x...' // [!code focus] +}) +``` \ No newline at end of file diff --git a/site/pages/zksync/accounts/toSmartAccount.md b/site/pages/zksync/accounts/toSmartAccount.md new file mode 100644 index 0000000000..dc9513920b --- /dev/null +++ b/site/pages/zksync/accounts/toSmartAccount.md @@ -0,0 +1,53 @@ +--- +description: Creates a ZKsync Smart Account +--- + +# toSmartAccount (ZKsync) + +Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) from a Contract Address and a custom sign function. + +## Usage + +```ts twoslash +import { toSmartAccount } from 'viem/zksync' + +const account = toSmartAccount({ + address: '0xf39Fd6e51aad8F6F4ce6aB8827279cffFb92266', + async sign({ hash }) { + // ... signing logic + return '0x...' + } +}) +``` + +## Parameters + +### address + +- **Type:** `Hex` + +Address of the deployed Account's Contract implementation. + +```ts +const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // [!code focus] + async sign({ hash }) { + // ... + } +}) +``` + +### sign + +- **Type:** `({ hash: Hex }) => Hex` + +Custom sign function for the Smart Account. + +```ts +const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + async sign({ hash }) { // [!code focus] + // ... // [!code focus] + } // [!code focus] +}) +``` \ No newline at end of file diff --git a/site/sidebar.ts b/site/sidebar.ts index 6907a65a78..8c4daad846 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -1242,10 +1242,6 @@ export const sidebar = { text: 'showCallsStatus', link: '/experimental/eip5792/showCallsStatus', }, - { - text: 'writeContracts', - link: '/experimental/eip5792/writeContracts', - }, ], }, ], @@ -1593,6 +1589,23 @@ export const sidebar = { { text: 'Chains', link: '/zksync/chains' }, ], }, + { + text: 'Smart Accounts', + items: [ + { + text: 'Singlesig', + link: '/zksync/accounts/toSinglesigSmartAccount', + }, + { + text: 'Multisig', + link: '/zksync/accounts/toMultisigSmartAccount', + }, + { + text: 'Custom', + link: '/zksync/accounts/toSmartAccount', + }, + ], + }, { text: 'EIP-712 Actions', items: [ diff --git a/site/vercel.json b/site/vercel.json index 5a023af28f..21e6d9c7ec 100644 --- a/site/vercel.json +++ b/site/vercel.json @@ -55,6 +55,10 @@ { "source": "/:match/accounts/signTypedData", "destination": "/:match/accounts/local/signTypedData" + }, + { + "source": "/:match/experimental/eip5792/writeContracts", + "destination": "/:match/experimental/eip5792/sendCalls#contract-calls" } ] } diff --git a/site/vocs.config.tsx b/site/vocs.config.tsx index 3a103a988e..9cb10133f3 100644 --- a/site/vocs.config.tsx +++ b/site/vocs.config.tsx @@ -8,7 +8,7 @@ export default defineConfig({ // backgroundColor: '#3a393b', // textColor: 'white', // content: - // 'Viem is participating in the Gitcoin Grants 20 round. Consider [supporting the project](https://explorer.gitcoin.co/#/round/42161/27/20). Thank you. 🙏', + // 'Viem is participating in Gitcoin Grants round 21. Consider [supporting the project](https://explorer.gitcoin.co/#/round/42161/389/73). Thank you. 🙏', // }, baseUrl: process.env.VERCEL_ENV === 'production' @@ -45,7 +45,9 @@ export default defineConfig({ rootDir: '.', search: { boostDocument(documentId) { - if (documentId.startsWith('pages/docs')) return 2 + if (documentId.startsWith('pages/docs')) return 3 + if (documentId.startsWith('pages/account-abstraction')) return 2 + if (documentId.startsWith('pages/experimental')) return 2 return 1 }, }, @@ -221,6 +223,31 @@ export default defineConfig({ 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/reservoir-light.svg', }, ], + [ + { + name: 'Uniswap', + link: 'https://uniswap.org', + image: + 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/uniswap-light.svg', + }, + { + name: 'Biconomy', + image: + 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/biconomy-light.svg', + link: 'https://biconomy.io', + }, + { + name: 'Thirdweb', + image: + 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/thirdweb-light.svg', + link: 'https://thirdweb.com', + }, + { + name: '', + image: '', + link: 'https://github.com/sponsors/wevm', + }, + ], ], }, ], diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 8697858c80..3f7eebb281 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,5 +1,106 @@ # viem +## 2.20.1 + +### Patch Changes + +- [#2646](https://github.com/wevm/viem/pull/2646) [`50bde96b`](https://github.com/wevm/viem/commit/50bde96b39e4e2980e995f5288ea9b6a2f62a530) Thanks [@thobbyAk](https://github.com/thobbyAk)! - Added Botanix Testnet. + +- [#2644](https://github.com/wevm/viem/pull/2644) [`2eb817a2`](https://github.com/wevm/viem/commit/2eb817a25bfe0bfdb36df02c7907f38b889f474a) Thanks [@RobbyUitbeijerse](https://github.com/RobbyUitbeijerse)! - Added Sophon Testnet. + +- [#2656](https://github.com/wevm/viem/pull/2656) [`df905fe5`](https://github.com/wevm/viem/commit/df905fe5dd87e1a2a78494eba1845c6012606a51) Thanks [@Jabher](https://github.com/Jabher)! - Added Holesky API URL. + +- [#2654](https://github.com/wevm/viem/pull/2654) [`02415bce`](https://github.com/wevm/viem/commit/02415bcef331eeeee18f5e427766048b6fb33a96) Thanks [@lyszhang](https://github.com/lyszhang)! - Added Hashkey Chain testnet. + +- [#2657](https://github.com/wevm/viem/pull/2657) [`f31e93ec`](https://github.com/wevm/viem/commit/f31e93ec5ad65e551bb3a152f138aff3a157b9f6) Thanks [@jeanregisser](https://github.com/jeanregisser)! - Exported `portuguese` wordlist. + +- [#2643](https://github.com/wevm/viem/pull/2643) [`8ac740e9`](https://github.com/wevm/viem/commit/8ac740e9cec18a3138a771053eeb45397179885a) Thanks [@0oooooooo0](https://github.com/0oooooooo0)! - Added kaia chain. + +- [#2655](https://github.com/wevm/viem/pull/2655) [`4d874283`](https://github.com/wevm/viem/commit/4d8742836f53753e3ab1312c8aa3a66284253d22) Thanks [@RobbyUitbeijerse](https://github.com/RobbyUitbeijerse)! - Added multicall3 to Sophon Testnet. + +## 2.20.0 + +### Minor Changes + +- [#2641](https://github.com/wevm/viem/pull/2641) [`89d11ed`](https://github.com/wevm/viem/commit/89d11edb558656bc3c97f0c410a448f99f92a1f4) Thanks [@jxom](https://github.com/jxom)! - **Experimental:** Deprecated `writeContracts`. Use `sendCalls` instead. + +- [#2641](https://github.com/wevm/viem/pull/2641) [`89d11ed`](https://github.com/wevm/viem/commit/89d11edb558656bc3c97f0c410a448f99f92a1f4) Thanks [@jxom](https://github.com/jxom)! - **Experimental:** Updated `sendCalls` to match the updated EIP-5792 spec (`chainId` per call). + +- [#2641](https://github.com/wevm/viem/pull/2641) [`89d11ed`](https://github.com/wevm/viem/commit/89d11edb558656bc3c97f0c410a448f99f92a1f4) Thanks [@jxom](https://github.com/jxom)! - **Experimental:** Updated `sendCalls` to also accept contract function interface. + +### Patch Changes + +- [#2638](https://github.com/wevm/viem/pull/2638) [`9cbd082`](https://github.com/wevm/viem/commit/9cbd0820533c278bb4e40ff390a7091a607152b3) Thanks [@jxom](https://github.com/jxom)! - Added \`nonceKeyManager\` as a property to \`toSmartAccount\`. + +- [#2638](https://github.com/wevm/viem/pull/2638) [`9cbd082`](https://github.com/wevm/viem/commit/9cbd0820533c278bb4e40ff390a7091a607152b3) Thanks [@jxom](https://github.com/jxom)! - Added ability to pass full-formed User Operations to `sendUserOperation` and `estimateUserOperationGas`. + +- [#2639](https://github.com/wevm/viem/pull/2639) [`9a1c6ab`](https://github.com/wevm/viem/commit/9a1c6abe6c89c444c8fc28cea1fc6ef9759ae53b) Thanks [@jxom](https://github.com/jxom)! - **OP Stack:** Tweaked proof submitter logic. + +## 2.19.9 + +### Patch Changes + +- [#2598](https://github.com/wevm/viem/pull/2598) [`627274b`](https://github.com/wevm/viem/commit/627274b0cf70906d6d521f53e3290a87bcaee2b3) Thanks [@jxom](https://github.com/jxom)! - Added ZKsync `toSmartAccount`. + +- [#2636](https://github.com/wevm/viem/pull/2636) [`5f60093`](https://github.com/wevm/viem/commit/5f6009360eaa41caf7318deb832dae7484190b5b) Thanks [@saeta-eth](https://github.com/saeta-eth)! - Added support for `'evm_setAccountCode'` to `setCode` action. + +## 2.19.8 + +### Patch Changes + +- [#2631](https://github.com/wevm/viem/pull/2631) [`b36cb2db`](https://github.com/wevm/viem/commit/b36cb2dbe7c83c36c54810839506399cf2882945) Thanks [@jxom](https://github.com/jxom)! - **OP Stack:** Handled case for `InvalidGameType` error on `getWithdrawalStatus` + +## 2.19.7 + +### Patch Changes + +- [#2624](https://github.com/wevm/viem/pull/2624) [`46dd252`](https://github.com/wevm/viem/commit/46dd2523a96d8372b0d0cb5ffe56c613bf073048) Thanks [@holic](https://github.com/holic)! - Improved `writeContract` error handling. + +- [#2628](https://github.com/wevm/viem/pull/2628) [`a040bc4`](https://github.com/wevm/viem/commit/a040bc430293604cd8532c3f6349a56b2a5d366a) Thanks [@boavenn](https://github.com/boavenn)! - Added cronoszkEVM chain. + +- [`918bed5`](https://github.com/wevm/viem/commit/918bed5ee48b39b08d8ab8e879722358cc91ec56) Thanks [@jxom](https://github.com/jxom)! - Update 7702 implementation to be compatible with devnet3. + +- [#2629](https://github.com/wevm/viem/pull/2629) [`34093e1`](https://github.com/wevm/viem/commit/34093e12076639f110017cb5f9196884608eb76c) Thanks [@KONFeature](https://github.com/KONFeature)! - Exported `PaymasterRpcSchema`. + +- [#2625](https://github.com/wevm/viem/pull/2625) [`507eed7`](https://github.com/wevm/viem/commit/507eed7284c2ac6867fef850a0e2923b9078671e) Thanks [@qiwu7](https://github.com/qiwu7)! - Added B3 chain + +## 2.19.6 + +### Patch Changes + +- [#2619](https://github.com/wevm/viem/pull/2619) [`ccaddcd9`](https://github.com/wevm/viem/commit/ccaddcd909b5f957f9b8352f7646f349402bb776) Thanks [@nialexsan](https://github.com/nialexsan)! - Added Multicall contract to Flow Testnet. + +- [#2620](https://github.com/wevm/viem/pull/2620) [`a8c78cb4`](https://github.com/wevm/viem/commit/a8c78cb4cbd5224259482114c6d65ce5b0b10f6b) Thanks [@jxom](https://github.com/jxom)! - Made `getNonce` optional on `SmartAccountImplementation`. + +- [#2614](https://github.com/wevm/viem/pull/2614) [`3749838f`](https://github.com/wevm/viem/commit/3749838fdd915ebccc56505ecd5a8047bfb8f38d) Thanks [@joshuanwankwo](https://github.com/joshuanwankwo)! - Added Curtis chain + +## 2.19.5 + +### Patch Changes + +- [#2608](https://github.com/wevm/viem/pull/2608) [`cff2aac6`](https://github.com/wevm/viem/commit/cff2aac683f4e2e60e8ce84e3933f02b31311ed0) Thanks [@HighGecko](https://github.com/HighGecko)! - Added IOTA, IOTA Testnet and CHIPS chains. + +- [#2609](https://github.com/wevm/viem/pull/2609) [`bf0fe9c6`](https://github.com/wevm/viem/commit/bf0fe9c653697a2eadc16ec2ce53d149b10d68a8) Thanks [@CeoFred](https://github.com/CeoFred)! - Added AssetChain Testnet chain. + +## 2.19.4 + +### Patch Changes + +- [`e708c5bd`](https://github.com/wevm/viem/commit/e708c5bd5dea9ee97fefaf6c4bf1d70080898851) Thanks [@jxom](https://github.com/jxom)! - Updated zkSync `getEip712Domain` name. + +- [#2606](https://github.com/wevm/viem/pull/2606) [`44cc5ecd`](https://github.com/wevm/viem/commit/44cc5ecd6bca2ee81ab2db0cd4ae273310f37302) Thanks [@ezynda3](https://github.com/ezynda3)! - Updated Taiko block explorer. + +- [`9aaa159f`](https://github.com/wevm/viem/commit/9aaa159f8e3f0aef45248368a2dd65a16e101c90) Thanks [@jxom](https://github.com/jxom)! - Removed redundant chain assertion for Local Accounts. + +## 2.19.3 + +### Patch Changes + +- [#2595](https://github.com/wevm/viem/pull/2595) [`e022146`](https://github.com/wevm/viem/commit/e022146393bc3f3f315f2cf6a5b419adc1e5983d) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Exported `universalSignatureValidatorAbi`. + +- [#2596](https://github.com/wevm/viem/pull/2596) [`cb127ab`](https://github.com/wevm/viem/commit/cb127ab7d1334c7a73dfdc1d22ba4e4e7fa868dc) Thanks [@danielsimao](https://github.com/danielsimao)! - Added Bob Sepolia chain. + Added OP Stack addresses to Bob chain. + ## 2.19.2 ### Patch Changes diff --git a/src/README.md b/src/README.md index bace303b34..be378b552b 100644 --- a/src/README.md +++ b/src/README.md @@ -1,3 +1,6 @@ + + +

@@ -222,6 +225,24 @@ Check out the following places for more viem-related content: linea logo + + + + uniswap logo + + + + + + biconomy logo + + + + + + thirdweb logo + + ## Contributing diff --git a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts index 66cabcbdce..7995fb62b6 100644 --- a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts +++ b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts @@ -1,4 +1,4 @@ -import { type Address, type TypedData, parseAbi } from 'abitype' +import type { Address, TypedData } from 'abitype' import { type WebAuthnData, parseSignature as parseP256Signature, @@ -132,19 +132,6 @@ export async function toCoinbaseSmartAccount( return { factory: factory.address, factoryData } }, - async getNonce({ key = 0n } = {}) { - const address = await this.getAddress() - const nonce = await readContract(client, { - abi: parseAbi([ - 'function getNonce(address, uint192) pure returns (uint256)', - ]), - address: entryPoint.address, - functionName: 'getNonce', - args: [address, key], - }) - return nonce - }, - async getStubSignature() { if (owner.type === 'webAuthn') return '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001949fc7c88032b9fcb5f6efc7a7b8c63668eae9871b765e23123bb473ff57aa831a7c0d9276168ebcc29f2875a0239cffdf2a9cd1c2007c5c77c071db9264df1d000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2273496a396e6164474850596759334b7156384f7a4a666c726275504b474f716d59576f4d57516869467773222c226f726967696e223a2268747470733a2f2f7369676e2e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000' diff --git a/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts b/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts index 9c9308d22b..204eadfc3a 100644 --- a/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts +++ b/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts @@ -1,4 +1,4 @@ -import { type Abi, type Address, type TypedData, parseAbi } from 'abitype' +import type { Abi, Address, TypedData } from 'abitype' import { parseAccount } from '../../../accounts/utils/parseAccount.js' import { readContract } from '../../../actions/public/readContract.js' @@ -34,6 +34,7 @@ export type ToSoladySmartAccountParameters< } | undefined factoryAddress?: Address | undefined + getNonce?: SmartAccountImplementation['getNonce'] | undefined owner: Address | Account salt?: Hex | undefined } @@ -86,6 +87,7 @@ export async function toSoladySmartAccount< version: '0.7', }, factoryAddress = '0x5d82735936c6Cd5DE57cC3c1A799f6B2E6F933Df', + getNonce, salt = '0x0', } = parameters @@ -103,6 +105,7 @@ export async function toSoladySmartAccount< return toSmartAccount({ client, entryPoint, + getNonce, extend: { abi, factory }, @@ -144,19 +147,6 @@ export async function toSoladySmartAccount< return { factory: factory.address, factoryData } }, - async getNonce({ key = 0n } = {}) { - const address = await this.getAddress() - const nonce = await readContract(client, { - abi: parseAbi([ - 'function getNonce(address, uint192) pure returns (uint256)', - ]), - address: entryPoint.address, - functionName: 'getNonce', - args: [address, key], - }) - return nonce - }, - async getStubSignature() { return '0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c' }, diff --git a/src/account-abstraction/accounts/toSmartAccount.test.ts b/src/account-abstraction/accounts/toSmartAccount.test.ts index e677d8a143..d26d80a9e9 100644 --- a/src/account-abstraction/accounts/toSmartAccount.test.ts +++ b/src/account-abstraction/accounts/toSmartAccount.test.ts @@ -4,8 +4,9 @@ import { anvilMainnet } from '../../../test/src/anvil.js' import { accounts } from '../../../test/src/constants.js' import { deploySoladyAccount_07 } from '../../../test/src/utils.js' import { mine, writeContract } from '../../actions/index.js' -import { pad } from '../../utils/index.js' +import { createNonceManager, pad } from '../../utils/index.js' import { toSoladySmartAccount } from './implementations/toSoladySmartAccount.js' +import { toSmartAccount } from './toSmartAccount.js' const client = anvilMainnet.getClient({ account: true }) @@ -912,6 +913,40 @@ test('default', async () => { `) }) +test('args: nonceKeyManager', async () => { + const { factoryAddress } = await deploySoladyAccount_07() + + const nonceKeyManager = createNonceManager({ + source: { + get() { + return 69 + }, + set() {}, + }, + }) + + const implementation = await toSoladySmartAccount({ + client, + factoryAddress, + owner: accounts[1].address, + }) + const account = await toSmartAccount({ + ...implementation, + async getNonce(parameters) { + return parameters!.key as bigint + }, + nonceKeyManager, + }) + + const nonces = await Promise.all([ + account.getNonce(), + account.getNonce(), + account.getNonce(), + ]) + + expect(nonces).toEqual([69n, 70n, 71n]) +}) + test('return value: `isDeployed`', async () => { const { factoryAddress } = await deploySoladyAccount_07() @@ -946,7 +981,7 @@ test('return value: `getFactoryArgs`', async () => { expect(await account.getFactoryArgs()).toMatchInlineSnapshot(` { - "factory": "0xd73bab8f06db28c87932571f87d0d2c0fdf13d94", + "factory": "0x82a9286db983093ff234cefcea1d8fa66382876b", "factoryData": "0xf14ddffc00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000000", } `) @@ -966,3 +1001,28 @@ test('return value: `getFactoryArgs`', async () => { } `) }) + +test('return value: `getNonce`', async () => { + const { factoryAddress } = await deploySoladyAccount_07() + + const account = await toSoladySmartAccount({ + client, + factoryAddress, + owner: accounts[1].address, + }) + expect(await account.getNonce()).toBeDefined() +}) + +test('return value: `getNonce` (implementation override)', async () => { + const { factoryAddress } = await deploySoladyAccount_07() + + const account = await toSoladySmartAccount({ + client, + factoryAddress, + owner: accounts[1].address, + async getNonce() { + return 69n + }, + }) + expect(await account.getNonce()).toBe(69n) +}) diff --git a/src/account-abstraction/accounts/toSmartAccount.ts b/src/account-abstraction/accounts/toSmartAccount.ts index fec430171f..e3ca0c2a0f 100644 --- a/src/account-abstraction/accounts/toSmartAccount.ts +++ b/src/account-abstraction/accounts/toSmartAccount.ts @@ -1,6 +1,7 @@ -import type { Abi } from 'abitype' +import { type Abi, parseAbi } from 'abitype' import { getCode } from '../../actions/public/getCode.js' +import { readContract } from '../../actions/public/readContract.js' import type { Prettify } from '../../types/utils.js' import { getAction } from '../../utils/getAction.js' import { createNonceManager } from '../../utils/nonceManager.js' @@ -30,21 +31,23 @@ export async function toSmartAccount< >( implementation: implementation, ): Promise> { - const { extend, ...rest } = implementation + const { + extend, + nonceKeyManager = createNonceManager({ + source: { + get() { + return Date.now() + }, + set() {}, + }, + }), + ...rest + } = implementation let deployed = false const address = await implementation.getAddress() - const nonceKeyManager = createNonceManager({ - source: { - get() { - return Date.now() - }, - set() {}, - }, - }) - return { ...extend, ...rest, @@ -64,7 +67,19 @@ export async function toSmartAccount< client: implementation.client, }), ) - return await implementation.getNonce({ ...parameters, key }) + + if (implementation.getNonce) + return await implementation.getNonce({ ...parameters, key }) + + const nonce = await readContract(implementation.client, { + abi: parseAbi([ + 'function getNonce(address, uint192) pure returns (uint256)', + ]), + address: implementation.entryPoint.address, + functionName: 'getNonce', + args: [address, key], + }) + return nonce }, async isDeployed() { if (deployed) return true diff --git a/src/account-abstraction/accounts/types.ts b/src/account-abstraction/accounts/types.ts index 5ed9e3342a..180380c110 100644 --- a/src/account-abstraction/accounts/types.ts +++ b/src/account-abstraction/accounts/types.ts @@ -5,6 +5,7 @@ import type { Client } from '../../clients/createClient.js' import type { Hash, Hex, SignableMessage } from '../../types/misc.js' import type { TypedDataDefinition } from '../../types/typedData.js' import type { Assign, ExactPartial, UnionPartialBy } from '../../types/utils.js' +import type { NonceManager } from '../../utils/nonceManager.js' import type { EntryPointVersion } from '../types/entryPointVersion.js' import type { EstimateUserOperationGasReturnType, @@ -88,9 +89,11 @@ export type SmartAccountImplementation< * // 1n * ``` */ - getNonce: ( - parameters?: { key?: bigint | undefined } | undefined, - ) => Promise + getNonce?: + | (( + parameters?: { key?: bigint | undefined } | undefined, + ) => Promise) + | undefined /** * Retrieves the User Operation "stub" signature for gas estimation. * @@ -100,6 +103,8 @@ export type SmartAccountImplementation< * ``` */ getStubSignature: () => Promise + /** Custom nonce key manager. */ + nonceKeyManager?: NonceManager | undefined /** * Signs a hash via the Smart Account's owner. * @@ -184,6 +189,16 @@ export type SmartAccount< { /** Address of the Smart Account. */ address: Address + /** + * Retrieves the nonce of the Account. + * + * @example + * ```ts + * const nonce = await account.getNonce() + * // 1n + * ``` + */ + getNonce: NonNullable /** Whether or not the Smart Account has been deployed. */ isDeployed: () => Promise /** Type of account. */ diff --git a/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts b/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts index 0b7e563c94..586c202a0e 100644 --- a/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts +++ b/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test, vi } from 'vitest' +import { beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest' import { ErrorsExample } from '../../../../contracts/generated.js' import { wagmiContractConfig } from '../../../../test/src/abis.js' import { @@ -15,7 +15,9 @@ import { mine, writeContract } from '../../../actions/index.js' import { http } from '../../../clients/transports/http.js' import { pad, parseEther, parseGwei } from '../../../utils/index.js' import { createPaymasterClient } from '../../clients/createPaymasterClient.js' +import type { UserOperation } from '../../types/userOperation.js' import { estimateUserOperationGas } from './estimateUserOperationGas.js' +import { prepareUserOperation } from './prepareUserOperation.js' const client = anvilMainnet.getClient({ account: true }) const bundlerClient = bundlerMainnet.getBundlerClient() @@ -139,6 +141,39 @@ describe('entryPointVersion: 0.7', async () => { `) }) + test('behavior: prepared user operation', async () => { + const request = { + ...(await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + ...fees, + })), + account: undefined, + } as const + + expectTypeOf(request).toMatchTypeOf() + + expect( + await estimateUserOperationGas(bundlerClient, { + ...request, + entryPointAddress: account.entryPoint?.address, + }), + ).toMatchInlineSnapshot(` + { + "callGasLimit": 80000n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 51722n, + "verificationGasLimit": 259060n, + } + `) + }) + test('error: insufficient funds', async () => { await expect(() => estimateUserOperationGas(bundlerClient, { @@ -160,7 +195,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761076688711039842254848 + nonce: 30902162761095135455113551806464 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -198,7 +233,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761095135455113551806464 + nonce: 30902162761113582199187261358080 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -235,7 +270,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761113582199187261358080 + nonce: 30902162761132028943260970909696 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -273,7 +308,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761132028943260970909696 + nonce: 30902162761150475687334680461312 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -314,7 +349,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761150475687334680461312 + nonce: 30902162761168922431408390012928 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -355,7 +390,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761168922431408390012928 + nonce: 30902162761187369175482099564544 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -396,7 +431,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761187369175482099564544 + nonce: 30902162761205815919555809116160 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -437,7 +472,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761205815919555809116160 + nonce: 30902162761224262663629518667776 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -478,7 +513,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761224262663629518667776 + nonce: 30902162761242709407703228219392 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -521,7 +556,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761242709407703228219392 + nonce: 30902162761261156151776937771008 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -564,7 +599,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761261156151776937771008 + nonce: 30902162761279602895850647322624 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -642,7 +677,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0x maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761279602895850647322624 + nonce: 30902162761298049639924356874240 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -689,7 +724,7 @@ describe('entryPointVersion: 0.7', async () => { factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761298049639924356874240 + nonce: 30902162761316496383998066425856 sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -723,6 +758,37 @@ describe('entryPointVersion: 0.6', async () => { `) }) + test('behavior: prepared user operation', async () => { + const request = { + ...(await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + ...fees, + })), + account: undefined, + } as const + + expectTypeOf(request).toMatchTypeOf() + + expect( + await estimateUserOperationGas(bundlerClient, { + ...request, + entryPointAddress: account.entryPoint.address, + }), + ).toMatchInlineSnapshot(` + { + "callGasLimit": 80000n, + "preVerificationGas": 55233n, + "verificationGasLimit": 258801n, + } + `) + }) + test('error: aa13', async () => { await expect(() => estimateUserOperationGas(bundlerClient, { @@ -746,7 +812,7 @@ describe('entryPointVersion: 0.6', async () => { initCode: 0x0000000000000000000000000000000000000000deadbeef maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761039795222892423151616 + nonce: 30902162761058241966966132703232 paymasterAndData: 0x sender: 0x6edf7db791fC4D438D4A683E857B2fE1a84947Ce signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c @@ -759,8 +825,8 @@ describe('entryPointVersion: 0.6', async () => { test('error: account not defined', async () => { await expect(() => + // @ts-expect-error estimateUserOperationGas(bundlerClient, { - // @ts-expect-error account: undefined, calls: [{ to: '0x0000000000000000000000000000000000000000' }], ...fees, diff --git a/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts b/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts index 38bc9c8633..24c6943374 100644 --- a/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts +++ b/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts @@ -10,7 +10,12 @@ import type { BaseError } from '../../../errors/base.js' import type { ErrorType } from '../../../errors/utils.js' import type { Chain } from '../../../types/chain.js' import type { Hex } from '../../../types/misc.js' -import type { Assign, OneOf, Prettify } from '../../../types/utils.js' +import type { + Assign, + MaybeRequired, + OneOf, + Prettify, +} from '../../../types/utils.js' import type { RequestErrorType } from '../../../utils/buildRequest.js' import { getAction } from '../../../utils/getAction.js' import type { SmartAccount } from '../../accounts/types.js' @@ -55,26 +60,39 @@ export type EstimateUserOperationGasParameters< >, _derivedVersion extends EntryPointVersion = DeriveEntryPointVersion<_derivedAccount>, -> = Assign< - UserOperationRequest<_derivedVersion>, - OneOf<{ calls: UserOperationCalls> } | { callData: Hex }> & { - paymaster?: - | Address - | true - | { - /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ - getPaymasterData?: PaymasterActions['getPaymasterData'] | undefined - /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ - getPaymasterStubData?: - | PaymasterActions['getPaymasterStubData'] +> = GetSmartAccountParameter & + ( + | UserOperation // Accept a full-formed User Operation. + | Assign< + // Accept a partially-formed User Operation (UserOperationRequest) to be filled. + UserOperationRequest<_derivedVersion>, + OneOf< + { calls: UserOperationCalls> } | { callData: Hex } + > & { + paymaster?: + | Address + | true + | { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: + | PaymasterActions['getPaymasterData'] + | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions['getPaymasterStubData'] + | undefined + } | undefined + /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ + paymasterContext?: unknown | undefined } - | undefined - /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ - paymasterContext?: unknown | undefined - } -> & - GetSmartAccountParameter + > + ) & + // Allow the EntryPoint address to be overridden, if no Account is provided, it will need to be required. + MaybeRequired< + { entryPointAddress?: Address }, + _derivedAccount extends undefined ? true : false + > export type EstimateUserOperationGasReturnType< account extends SmartAccount | undefined = SmartAccount | undefined, @@ -135,26 +153,28 @@ export async function estimateUserOperationGas< calls >, ): Promise> { - const { account: account_ = client.account } = parameters + const { account: account_ = client.account, entryPointAddress } = parameters - if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) + if (!account_ && !parameters.sender) throw new AccountNotFoundError() + const account = account_ ? parseAccount(account_) : undefined - const request = await getAction( - client, - prepareUserOperation, - 'prepareUserOperation', - )({ - ...parameters, - parameters: ['factory', 'nonce', 'paymaster', 'signature'], - } as unknown as PrepareUserOperationParameters) + const request = account + ? await getAction( + client, + prepareUserOperation, + 'prepareUserOperation', + )({ + ...parameters, + parameters: ['factory', 'nonce', 'paymaster', 'signature'], + } as unknown as PrepareUserOperationParameters) + : parameters try { const result = await client.request({ method: 'eth_estimateUserOperationGas', params: [ formatUserOperationRequest(request as UserOperation), - account.entryPoint.address, + (entryPointAddress ?? account?.entryPoint?.address)!, ], }) return formatUserOperationGas(result) as EstimateUserOperationGasReturnType< @@ -162,9 +182,10 @@ export async function estimateUserOperationGas< accountOverride > } catch (error) { + const calls = (parameters as any).calls throw getUserOperationError(error as BaseError, { ...(request as UserOperation), - calls: parameters.calls, + ...(calls ? { calls } : {}), }) } } diff --git a/src/account-abstraction/actions/bundler/sendUserOperation.test.ts b/src/account-abstraction/actions/bundler/sendUserOperation.test.ts index 76cc1b220b..61f1700547 100644 --- a/src/account-abstraction/actions/bundler/sendUserOperation.test.ts +++ b/src/account-abstraction/actions/bundler/sendUserOperation.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test, vi } from 'vitest' +import { beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest' import { wagmiContractConfig } from '../../../../test/src/abis.js' import { createVerifyingPaymasterServer, @@ -24,6 +24,8 @@ import { pad, parseEther, parseGwei } from '../../../utils/index.js' import { toCoinbaseSmartAccount } from '../../accounts/implementations/toCoinbaseSmartAccount.js' import { createBundlerClient } from '../../clients/createBundlerClient.js' import { createPaymasterClient } from '../../clients/createPaymasterClient.js' +import type { UserOperation } from '../../types/userOperation.js' +import { prepareUserOperation } from './prepareUserOperation.js' import { sendUserOperation } from './sendUserOperation.js' const client = anvilMainnet.getClient({ account: true }) @@ -154,6 +156,53 @@ describe('entryPointVersion: 0.7', async () => { await mine(client, { blocks: 1 }) }) + test('behavior: prepared user operation', async () => { + const request = { + ...(await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: alice, + value: parseEther('1'), + }, + { + to: bob, + value: parseEther('2'), + }, + ], + ...fees, + })), + account: undefined, + } as const + const signature = await account.signUserOperation(request) + + expectTypeOf(request).toMatchTypeOf() + + const hash = await sendUserOperation(bundlerClient, { + ...request, + entryPointAddress: account.entryPoint.address, + signature, + }) + expect(hash).toBeDefined() + + await bundlerClient.request({ method: 'debug_bundler_sendBundleNow' }) + await mine(client, { blocks: 1 }) + + expect(await getBalance(client, { address: alice })).toMatchInlineSnapshot( + '10001000000000000000000n', + ) + expect(await getBalance(client, { address: bob })).toMatchInlineSnapshot( + '10002000000000000000000n', + ) + expect( + await readContract(client, { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69420451n], + }), + ).toBe(account.address) + }) + test('error: no account', async () => { await expect(() => // @ts-expect-error @@ -289,7 +338,7 @@ describe('entryPointVersion: 0.7', async () => { callGasLimit: 80000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei - nonce: 30902162761076688711039842254848 + nonce: 30902162761095135455113551806464 paymasterPostOpGasLimit: 0 paymasterVerificationGasLimit: 0 preVerificationGas: 50692 diff --git a/src/account-abstraction/actions/bundler/sendUserOperation.ts b/src/account-abstraction/actions/bundler/sendUserOperation.ts index 9edb52ad17..030a535a26 100644 --- a/src/account-abstraction/actions/bundler/sendUserOperation.ts +++ b/src/account-abstraction/actions/bundler/sendUserOperation.ts @@ -7,7 +7,7 @@ import type { BaseError } from '../../../errors/base.js' import type { ErrorType } from '../../../errors/utils.js' import type { Chain } from '../../../types/chain.js' import type { Hex } from '../../../types/misc.js' -import type { Assign, OneOf } from '../../../types/utils.js' +import type { Assign, MaybeRequired, OneOf } from '../../../types/utils.js' import type { RequestErrorType } from '../../../utils/buildRequest.js' import { getAction } from '../../../utils/getAction.js' import type { SmartAccount } from '../../accounts/types.js' @@ -47,27 +47,39 @@ export type SendUserOperationParameters< >, _derivedVersion extends EntryPointVersion = DeriveEntryPointVersion<_derivedAccount>, -> = Assign< - UserOperationRequest<_derivedVersion>, - OneOf<{ calls: UserOperationCalls> } | { callData: Hex }> & { - paymaster?: - | Address - | true - | { - /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ - getPaymasterData?: PaymasterActions['getPaymasterData'] | undefined - /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ - getPaymasterStubData?: - | PaymasterActions['getPaymasterStubData'] +> = GetSmartAccountParameter & + ( + | UserOperation // Accept a full-formed User Operation. + | Assign< + // Accept a partially-formed User Operation (UserOperationRequest) to be filled. + UserOperationRequest<_derivedVersion>, + OneOf< + { calls: UserOperationCalls> } | { callData: Hex } + > & { + paymaster?: + | Address + | true + | { + /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ + getPaymasterData?: + | PaymasterActions['getPaymasterData'] + | undefined + /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ + getPaymasterStubData?: + | PaymasterActions['getPaymasterStubData'] + | undefined + } | undefined + /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ + paymasterContext?: unknown | undefined } - | undefined - /** Paymaster context to pass to `getPaymasterData` and `getPaymasterStubData` calls. */ - paymasterContext?: unknown - } -> & - GetSmartAccountParameter - + > + ) & + // Allow the EntryPoint address to be overridden, if no Account is provided, it will need to be required. + MaybeRequired< + { entryPointAddress?: Address }, + _derivedAccount extends undefined ? true : false + > export type SendUserOperationReturnType = Hex export type SendUserOperationErrorType = @@ -111,20 +123,21 @@ export async function sendUserOperation< client: Client, parameters: SendUserOperationParameters, ) { - const { account: account_ = client.account } = parameters + const { account: account_ = client.account, entryPointAddress } = parameters - if (!account_) throw new AccountNotFoundError() - const account = parseAccount(account_) + if (!account_ && !parameters.sender) throw new AccountNotFoundError() + const account = account_ ? parseAccount(account_) : undefined - const request = await getAction( - client, - prepareUserOperation, - 'prepareUserOperation', - )(parameters as unknown as PrepareUserOperationParameters) + const request = account + ? await getAction( + client, + prepareUserOperation, + 'prepareUserOperation', + )(parameters as unknown as PrepareUserOperationParameters) + : parameters - const signature = - parameters.signature || - (await account.signUserOperation(request as UserOperation)) + const signature = (parameters.signature || + (await account?.signUserOperation(request as UserOperation)))! const rpcParameters = formatUserOperationRequest({ ...request, @@ -135,14 +148,18 @@ export async function sendUserOperation< return await client.request( { method: 'eth_sendUserOperation', - params: [rpcParameters, account.entryPoint.address], + params: [ + rpcParameters, + (entryPointAddress ?? account?.entryPoint.address)!, + ], }, { retryCount: 0 }, ) } catch (error) { + const calls = (parameters as any).calls throw getUserOperationError(error as BaseError, { ...(request as UserOperation), - calls: parameters.calls, + ...(calls ? { calls } : {}), signature, }) } diff --git a/src/accounts/index.test.ts b/src/accounts/index.test.ts index 92fd6db4be..bb3be338d6 100644 --- a/src/accounts/index.test.ts +++ b/src/accounts/index.test.ts @@ -12,6 +12,7 @@ test('exports utils', () => { "italian", "japanese", "korean", + "portuguese", "simplifiedChinese", "spanish", "traditionalChinese", diff --git a/src/accounts/index.ts b/src/accounts/index.ts index 9f58cc626a..8b1a533754 100644 --- a/src/accounts/index.ts +++ b/src/accounts/index.ts @@ -8,6 +8,7 @@ export { wordlist as french } from '@scure/bip39/wordlists/french' export { wordlist as italian } from '@scure/bip39/wordlists/italian' export { wordlist as japanese } from '@scure/bip39/wordlists/japanese' export { wordlist as korean } from '@scure/bip39/wordlists/korean' +export { wordlist as portuguese } from '@scure/bip39/wordlists/portuguese' export { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese' export { wordlist as spanish } from '@scure/bip39/wordlists/spanish' export { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese' diff --git a/src/accounts/privateKeyToAccount.test.ts b/src/accounts/privateKeyToAccount.test.ts index 49aaa2097b..fea74b30e0 100644 --- a/src/accounts/privateKeyToAccount.test.ts +++ b/src/accounts/privateKeyToAccount.test.ts @@ -48,8 +48,8 @@ test('sign authorization', async () => { "chainId": 1, "contractAddress": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "nonce": 0, - "r": "0x623129c9fcc520bee4b19fbb5148b178d67e1c854d2baee0e64cd518aad5549f", - "s": "0x17997fb5ef9d7521c09f0208b1082a9fecbeabdad90ef0a806a50d1b9c7b5d66", + "r": "0x1b633d8fa4b6822d010b17bdec4bd305eb024d302588edf0618fd04b26d686fd", + "s": "0x2633183f08457bbb7355b26280a122c7e6f5e38d5a23ece2cccf3068b6dd06ca", "v": 27n, "yParity": 0, } diff --git a/src/accounts/privateKeyToAccount.ts b/src/accounts/privateKeyToAccount.ts index 70fa49066f..77182df937 100644 --- a/src/accounts/privateKeyToAccount.ts +++ b/src/accounts/privateKeyToAccount.ts @@ -66,7 +66,7 @@ export function privateKeyToAccount( return signTransaction({ privateKey, transaction, serializer }) }, async signTypedData(typedData) { - return signTypedData({ ...typedData, privateKey }) + return signTypedData({ ...typedData, privateKey } as any) }, }) diff --git a/src/accounts/utils/signAuthorization.test.ts b/src/accounts/utils/signAuthorization.test.ts index b50a117596..23c5e0ebe2 100644 --- a/src/accounts/utils/signAuthorization.test.ts +++ b/src/accounts/utils/signAuthorization.test.ts @@ -58,7 +58,7 @@ test('args: to (hex)', async () => { }) expect(signature).toMatchInlineSnapshot( - `"0x623129c9fcc520bee4b19fbb5148b178d67e1c854d2baee0e64cd518aad5549f17997fb5ef9d7521c09f0208b1082a9fecbeabdad90ef0a806a50d1b9c7b5d661b"`, + `"0x1b633d8fa4b6822d010b17bdec4bd305eb024d302588edf0618fd04b26d686fd2633183f08457bbb7355b26280a122c7e6f5e38d5a23ece2cccf3068b6dd06ca1b"`, ) expect( await verifyAuthorization({ diff --git a/src/accounts/utils/signTransaction.test.ts b/src/accounts/utils/signTransaction.test.ts index 67ca4d993c..ae1586751f 100644 --- a/src/accounts/utils/signTransaction.test.ts +++ b/src/accounts/utils/signTransaction.test.ts @@ -56,7 +56,7 @@ describe('eip7702', async () => { privateKey: accounts[0].privateKey, }) expect(signature).toMatchInlineSnapshot( - `"0x04f9010e018203118080825208808080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a055f6b60208df4fec601dc838392e8512878d6d687889cbe2691787f6367fdd35a018af5c71f1d558a9167c47f67e49d1cde703e874c3ec1a5fb84d4a6abd5606f0f85b0a94fba3912ca04dd458c843e2ee08967fc04f3579c2c14580a039d02ee4d1a85269ed8470719ff4280912d7906f7d039af477994533784f8367a035169646947de890bbd468b60a5aba5b9b7fb7a08677f2389c3a9259d05e60f380a07489103f2133ff365733764c6aa9e30340bbbcca871283a38c7fe38f854d03e4a02f14f71225153db10143b7aba573e36c188e67f00453fbe31661f39746cfcd59"`, + `"0x04f9010b018203118080825208808080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a401a0f6beafe7507f0c98ae9bc8d9e15d6b53c2f0714ccfc01663f658cb9f29caced4a00dc4dfc53537f8b09047eceb674c454acba020ce786d9004175f41669304dee0f85a0a94fba3912ca04dd458c843e2ee08967fc04f3579c24501a0c3fea6e0bd0f5743d6e2f2df8f1aa63ff262a6636ca96ac572da3ea5cd33344ca02a4f006f9a0cf7cd5f5528af86524c984b05a8446c8c124aede1d3531e91de9180a0c3d5845755debbe90b2fa6258ba04ec93c46bfb7b2657b66a11d8c7c5221ac939fbf44fdacf9e9f87b5074df6ba1f3f9c50c82d5b0be8e4e96686410ca405a30"`, ) }) }) diff --git a/src/actions/ens/getEnsAvatar.test.ts b/src/actions/ens/getEnsAvatar.test.ts index 372a520ecb..dd0f34e162 100644 --- a/src/actions/ens/getEnsAvatar.test.ts +++ b/src/actions/ens/getEnsAvatar.test.ts @@ -163,20 +163,20 @@ describe('args: gateways', async () => { // uri: ipfs record: 'ipfs://ipfs/Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', expected: - 'https://cloudflare-ipfs.com/ipfs/Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', + 'https://gateway.pinata.cloud/ipfs/Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', }, { // uri: ipfs (no prefix) record: 'Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', expected: - 'https://cloudflare-ipfs.com/ipfs/Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', + 'https://gateway.pinata.cloud/ipfs/Qma8mnp6xV3J2cRNf3mTth5C8nV11CAnceVinc3y8jSbio', }, ])('$record -> $expected', async ({ record, expected }) => { await setEnsAvatar(record) await expect( getEnsAvatar(client, { name: 'vitalik.eth', - assetGatewayUrls: { ipfs: 'https://cloudflare-ipfs.com' }, + assetGatewayUrls: { ipfs: 'https://gateway.pinata.cloud' }, }), ).resolves.toEqual(expected) }) diff --git a/src/actions/public/estimateFeesPerGas.ts b/src/actions/public/estimateFeesPerGas.ts index fe4e736a46..2ed70749c2 100644 --- a/src/actions/public/estimateFeesPerGas.ts +++ b/src/actions/public/estimateFeesPerGas.ts @@ -7,6 +7,7 @@ import { type Eip1559FeesNotSupportedErrorType, } from '../../errors/fee.js' import type { ErrorType } from '../../errors/utils.js' +import type { Account } from '../../types/account.js' import type { Block } from '../../types/block.js' import type { Chain, @@ -99,7 +100,7 @@ export async function internal_estimateFeesPerGas< client: Client, args: EstimateFeesPerGasParameters & { block?: Block | undefined - request?: PrepareTransactionRequestParameters | undefined + request?: PrepareTransactionRequestParameters | undefined }, ): Promise> { const { diff --git a/src/actions/public/estimateGas.test.ts b/src/actions/public/estimateGas.test.ts index e7d61ad0fa..bb961940a7 100644 --- a/src/actions/public/estimateGas.test.ts +++ b/src/actions/public/estimateGas.test.ts @@ -82,7 +82,7 @@ test('args: authorizationList', async () => { ], }), }), - ).toMatchInlineSnapshot('87132n') + ).toMatchInlineSnapshot('100168n') }) test('args: blockNumber', async () => { diff --git a/src/actions/public/estimateMaxPriorityFeePerGas.test.ts b/src/actions/public/estimateMaxPriorityFeePerGas.test.ts index 52a23ec974..18eede5f89 100644 --- a/src/actions/public/estimateMaxPriorityFeePerGas.test.ts +++ b/src/actions/public/estimateMaxPriorityFeePerGas.test.ts @@ -31,7 +31,7 @@ test('fallback', async () => { expect(await estimateMaxPriorityFeePerGas(client_1)).toBeDefined() }) -test('args: chain `defaultPriorityFee` override', async () => { +test('args: chain `priorityFee` override', async () => { // value const client_1 = createPublicClient({ transport: http(anvilMainnet.rpcUrl.http), @@ -41,7 +41,7 @@ test('args: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: 69420n, + maxPriorityFeePerGas: 69420n, }, }, }), @@ -56,7 +56,7 @@ test('args: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: () => 69420n, + maxPriorityFeePerGas: () => 69420n, }, }, }), @@ -71,7 +71,7 @@ test('args: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: async () => 69420n, + maxPriorityFeePerGas: async () => 69420n, }, }, }), @@ -86,7 +86,7 @@ test('args: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: 0n, + maxPriorityFeePerGas: 0n, }, }, }), @@ -101,20 +101,50 @@ test('args: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: async () => 0n, + maxPriorityFeePerGas: async () => 0n, }, }, }), ).toBe(0n) + + // fallback + const client_6 = createPublicClient({ + transport: http(anvilMainnet.rpcUrl.http), + }) + expect( + await estimateMaxPriorityFeePerGas(client_6, { + chain: { + ...anvilMainnet.chain, + fees: { + maxPriorityFeePerGas: async () => null, + }, + }, + }), + ).toBeDefined() + + // deprecated `defaultPriorityFee` + const client_7 = createPublicClient({ + transport: http(anvilMainnet.rpcUrl.http), + }) + expect( + await estimateMaxPriorityFeePerGas(client_7, { + chain: { + ...anvilMainnet.chain, + fees: { + defaultPriorityFee: async () => 69420n, + }, + }, + }), + ).toBe(69420n) }) -test('client: chain `defaultPriorityFee` override', async () => { +test('client: chain `priorityFee` override', async () => { // value const client_1 = createPublicClient({ chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: 69420n, + maxPriorityFeePerGas: 69420n, }, }, transport: http(), @@ -126,7 +156,7 @@ test('client: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: () => 69420n, + maxPriorityFeePerGas: () => 69420n, }, }, transport: http(), @@ -138,7 +168,7 @@ test('client: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: async () => 69420n, + maxPriorityFeePerGas: async () => 69420n, }, }, transport: http(), @@ -150,7 +180,7 @@ test('client: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: 0n, + maxPriorityFeePerGas: 0n, }, }, transport: http(), @@ -162,7 +192,7 @@ test('client: chain `defaultPriorityFee` override', async () => { chain: { ...anvilMainnet.chain, fees: { - defaultPriorityFee: async () => 0n, + maxPriorityFeePerGas: async () => 0n, }, }, transport: http(), diff --git a/src/actions/public/estimateMaxPriorityFeePerGas.ts b/src/actions/public/estimateMaxPriorityFeePerGas.ts index 17445307c7..1dc0ed2c7c 100644 --- a/src/actions/public/estimateMaxPriorityFeePerGas.ts +++ b/src/actions/public/estimateMaxPriorityFeePerGas.ts @@ -86,19 +86,25 @@ export async function internal_estimateMaxPriorityFeePerGas< }, ): Promise { const { block: block_, chain = client.chain, request } = args || {} - if (typeof chain?.fees?.defaultPriorityFee === 'function') { - const block = block_ || (await getAction(client, getBlock, 'getBlock')({})) - return chain.fees.defaultPriorityFee({ - block, - client, - request, - } as ChainFeesFnParameters) - } - - if (typeof chain?.fees?.defaultPriorityFee !== 'undefined') - return chain?.fees?.defaultPriorityFee try { + const maxPriorityFeePerGas = + chain?.fees?.maxPriorityFeePerGas ?? chain?.fees?.defaultPriorityFee + + if (typeof maxPriorityFeePerGas === 'function') { + const block = + block_ || (await getAction(client, getBlock, 'getBlock')({})) + const maxPriorityFeePerGas_ = await maxPriorityFeePerGas({ + block, + client, + request, + } as ChainFeesFnParameters) + if (maxPriorityFeePerGas_ === null) throw new Error() + return maxPriorityFeePerGas_ + } + + if (typeof maxPriorityFeePerGas !== 'undefined') return maxPriorityFeePerGas + const maxPriorityFeePerGasHex = await client.request({ method: 'eth_maxPriorityFeePerGas', }) diff --git a/src/actions/public/getTransaction.test.ts b/src/actions/public/getTransaction.test.ts index 101481e3e2..aa17d4b751 100644 --- a/src/actions/public/getTransaction.test.ts +++ b/src/actions/public/getTransaction.test.ts @@ -222,32 +222,32 @@ test('gets transaction (eip7702)', async () => { "chainId": 1, "contractAddress": "0xfb6dab6200b8958c2655c3747708f82243d3f32e", "nonce": 112, - "r": "0x1ae3cd78a7f079fcb58415a5a1a647c9785c0a4d1993139f881b5659f7f88c6", - "s": "0x5c9e71244dcbc2b197b76972817a4585af95e06d4cf7d2ec70c685ce3c586390", - "yParity": 0, + "r": "0x9b837dd8fb66ebea939e6d7e35ea6e3888fbb197bd60dfe95e42b5564cf28da3", + "s": "0x645fe33f8be6e67ded51f1f65d68cda8cce7f76996bc34f7bf76921ce20c1ba8", + "yParity": 1, }, ], "blockHash": null, "blockNumber": 19868022n, "chainId": 1, "from": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "gas": 87312n, + "gas": 89812n, "gasPrice": 8599866030n, - "hash": "0x5c00e6b61f404b926d8ecfe9de5042be82dbff06d9df43651d710ecd09b0fba3", + "hash": "0xd43c161f2e6fbfbfeefc952fdfd8f6d6eba731b621c2cad1d54b9c716dd1b0e4", "input": "0xa6d0ad61000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000", "maxFeePerBlobGas": undefined, "maxFeePerGas": 11392560424n, "maxPriorityFeePerGas": 1000000000n, "nonce": 112, - "r": "0x9f6268ab14164a997a553089e993ab28bc7bdee62b147fb0a13dc18b9d485903", - "s": "0x54a1d590b4dc376c59c0dbe833bec0801de77cc75c6d4d90af515b91643b91b6", + "r": "0xe215e0c9031b57f6a4e6000766d3c2a91d71b6171d47cff26554ba285bc19b3f", + "s": "0x350b3af55bb5079cc1eb09a53550b45b80599acddfb5a37e009471fd54fbb53", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "transactionIndex": 0, "type": "eip7702", "typeHex": "0x4", - "v": 0n, + "v": 1n, "value": 0n, - "yParity": 0, + "yParity": 1, } `) }) diff --git a/src/actions/public/getTransactionReceipt.test.ts b/src/actions/public/getTransactionReceipt.test.ts index ae2e6efe45..32d691961b 100644 --- a/src/actions/public/getTransactionReceipt.test.ts +++ b/src/actions/public/getTransactionReceipt.test.ts @@ -364,7 +364,7 @@ test('chain w/ custom block type', async () => { "transactionLogIndex": 10, }, ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x10880004000400000000000000000000000040000000020000000000000400120000000000100000000080000001000000000000000000000000000000804000004000101002040000000008000000200000000000000000001000000000080000840000020000000000100000000800000000020100000000000010000000000000000400000000000004000040000000000000280000000000002000000000000000004000120000000200000000000000000000000000000000200000800000001002008008000000000000000000000000200000800000000000000020001800000000000000000000001000000000000000000000000000000000000000", "root": "0xc621ee95e2d4ab65ecf499805dba770b20297c64029816b18c618fc49fe3d748", "status": "success", "to": "0x54de43b6ba21a5553697a2b78338e046dd7e0278", diff --git a/src/actions/test/setCode.ts b/src/actions/test/setCode.ts index e215a5ebe7..8c3a3a8d1f 100644 --- a/src/actions/test/setCode.ts +++ b/src/actions/test/setCode.ts @@ -50,8 +50,14 @@ export async function setCode< client: TestClient, { address, bytecode }: SetCodeParameters, ) { - await client.request({ - method: `${client.mode}_setCode`, - params: [address, bytecode], - }) + if (client.mode === 'ganache') + await client.request({ + method: 'evm_setAccountCode', + params: [address, bytecode], + }) + else + await client.request({ + method: `${client.mode}_setCode`, + params: [address, bytecode], + }) } diff --git a/src/actions/wallet/sendTransaction.test.ts b/src/actions/wallet/sendTransaction.test.ts index 495d543122..c4093249bf 100644 --- a/src/actions/wallet/sendTransaction.test.ts +++ b/src/actions/wallet/sendTransaction.test.ts @@ -830,7 +830,7 @@ describe('local account', () => { test('args: authorizationList', async () => { const invoker = privateKeyToAccount(accounts[0].privateKey) - const authority = privateKeyToAccount(accounts[1].privateKey) + const authority = privateKeyToAccount(accounts[9].privateKey) const recipient = privateKeyToAccount( '0x4a751f9ddcef30fd28648f415480f74eb418bd5145a56586a32e8c959c330742', ) @@ -904,7 +904,7 @@ describe('local account', () => { }) test('args: authorizationList (authority as invoker)', async () => { - const authority = privateKeyToAccount(accounts[1].privateKey) + const authority = privateKeyToAccount(accounts[9].privateKey) const recipient = privateKeyToAccount( '0x4a751f9ddcef30fd28648f415480f74eb418bd5145a56586a32e8c959c330742', ) @@ -1124,10 +1124,10 @@ describe('local account', () => { ]) expect((await getTransaction(client, { hash: hash_1 })).nonce).toBe(681) - expect((await getTransaction(client, { hash: hash_2 })).nonce).toBe(113) + expect((await getTransaction(client, { hash: hash_2 })).nonce).toBe(112) expect((await getTransaction(client, { hash: hash_3 })).nonce).toBe(682) expect((await getTransaction(client, { hash: hash_4 })).nonce).toBe(683) - expect((await getTransaction(client, { hash: hash_5 })).nonce).toBe(114) + expect((await getTransaction(client, { hash: hash_5 })).nonce).toBe(113) const hash_6 = await sendTransaction(client, { account: account_1, diff --git a/src/actions/wallet/sendTransaction.ts b/src/actions/wallet/sendTransaction.ts index 31021e177d..733babfac0 100644 --- a/src/actions/wallet/sendTransaction.ts +++ b/src/actions/wallet/sendTransaction.ts @@ -175,15 +175,6 @@ export async function sendTransaction< try { assertRequest(parameters as AssertRequestParameters) - let chainId: number | undefined - if (chain !== null) { - chainId = await getAction(client, getChainId, 'getChainId')({}) - assertCurrentChain({ - currentChainId: chainId, - chain, - }) - } - const to = await (async () => { // If `to` exists on the parameters, use that. if (parameters.to) return parameters.to @@ -204,6 +195,15 @@ export async function sendTransaction< })() if (account.type === 'json-rpc') { + let chainId: number | undefined + if (chain !== null) { + chainId = await getAction(client, getChainId, 'getChainId')({}) + assertCurrentChain({ + currentChainId: chainId, + chain, + }) + } + const chainFormat = client.chain?.formatters?.transactionRequest?.format const format = chainFormat || formatTransactionRequest @@ -246,7 +246,6 @@ export async function sendTransaction< authorizationList, blobs, chain, - chainId, data, gas, gasPrice, diff --git a/src/actions/wallet/signTransaction.test.ts b/src/actions/wallet/signTransaction.test.ts index 2aa174c6e6..f0110c4fbb 100644 --- a/src/actions/wallet/signTransaction.test.ts +++ b/src/actions/wallet/signTransaction.test.ts @@ -59,7 +59,7 @@ describe('eip7702', async () => { ...baseEip7702, }) expect(signature).toMatchInlineSnapshot( - `"0x04f8c50182031180808252089400000000000000000000000000000000000000008080c0f85ff85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a05140ccc73f785f9cfbe53f3fbbece6c3bd944669fab3ec95710d89c1acd9a67ea053a19bd48b26ec4dd88a31682061716ac4ae4c806d66704728cc11dba173d65701a0721ed11052bdd2347b2b3a5de7258c440b1b11d85ee890ec7fba00bc5b096d15a0780d9c23b62f9289b14bb010b62b2bf7d3129009bcb2740020425cb0930adcc9"`, + `"0x04f8c40182031180808252089400000000000000000000000000000000000000008080c0f85ef85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a08d7765afec6e09d93be91a1324f0dbbd6bcb96f4b37e8645a4c65d08a979ab69a070b81c53368b35a58af8630903c57d2b106842f9a081e3dc607c0c0cd990c77d01a0d2e4cf87a02ffd9a6cf0854ff5036b987847c562bf7a5f79fe2d322fba51e3eda002b72b1124ca8c2e43d8895601055cf4af7bf1cd0f757451483cc4dc5cdfb4a3"`, ) }) @@ -70,7 +70,7 @@ describe('eip7702', async () => { }) const signature = await signTransaction(client, request) expect(signature).toMatchInlineSnapshot( - `"0x04f8ce01820311843b9aca008502ae1107ec8252089400000000000000000000000000000000000000008080c0f85ff85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a05140ccc73f785f9cfbe53f3fbbece6c3bd944669fab3ec95710d89c1acd9a67ea053a19bd48b26ec4dd88a31682061716ac4ae4c806d66704728cc11dba173d65701a0dd7a61bcce42a949cc47981020c67fe5c38247501560753f26aa4f4a01cfe682a0214a76ccb14d8f4db68a8224840a3b377943e9bc51f10dc8f7b7f4bbf81fcc65"`, + `"0x04f8cd01820311843b9aca008502ae1107ec8252089400000000000000000000000000000000000000008080c0f85ef85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a08d7765afec6e09d93be91a1324f0dbbd6bcb96f4b37e8645a4c65d08a979ab69a070b81c53368b35a58af8630903c57d2b106842f9a081e3dc607c0c0cd990c77d01a00c0d98a5aa820287d07cd20b5b05a1a5c19f50ff3b0e700c795313522515a21fa00450425c5f5a9b6862d9c1f9513b6254d38391eee7d3147f67f9b7bb6d3b94c2"`, ) }) }) diff --git a/src/actions/wallet/writeContract.test.ts b/src/actions/wallet/writeContract.test.ts index bc4f801be6..5c954bc3dd 100644 --- a/src/actions/wallet/writeContract.test.ts +++ b/src/actions/wallet/writeContract.test.ts @@ -1,9 +1,13 @@ import { describe, expect, test, vi } from 'vitest' -import { BatchCallInvoker, Payable } from '~contracts/generated.js' +import { + BatchCallInvoker, + ErrorsExample, + Payable, +} from '~contracts/generated.js' import { wagmiContractConfig } from '~test/src/abis.js' import { accounts } from '~test/src/constants.js' -import { deploy, deployPayable } from '~test/src/utils.js' +import { deploy, deployErrorExample, deployPayable } from '~test/src/utils.js' import { anvilMainnet } from '../../../test/src/anvil.js' import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js' import { optimism } from '../../chains/index.js' @@ -55,7 +59,7 @@ test('client chain mismatch', async () => { functionName: 'mint', }), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). + [ContractFunctionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). Current Chain ID: 1 Expected Chain ID: 10 – OP Mainnet @@ -64,7 +68,13 @@ test('client chain mismatch', async () => { from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0x1249c58b + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint() + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + Docs: https://viem.sh/docs/contract/writeContract Version: viem@x.y.z] `) }) @@ -81,14 +91,20 @@ test('no chain', async () => { functionName: 'mint', }), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: No chain was provided to the request. + [ContractFunctionExecutionError: No chain was provided to the request. Please provide a chain with the \`chain\` argument on the Action, or by supplying a \`chain\` to WalletClient. Request Arguments: from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0x1249c58b + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint() + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + Docs: https://viem.sh/docs/contract/writeContract Version: viem@x.y.z] `) }) @@ -118,7 +134,7 @@ describe('args: chain', () => { chain: optimism, }), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). + [ContractFunctionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). Current Chain ID: 1 Expected Chain ID: 10 – OP Mainnet @@ -128,7 +144,13 @@ describe('args: chain', () => { from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0x1249c58b + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint() + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + Docs: https://viem.sh/docs/contract/writeContract Version: viem@x.y.z] `) }) @@ -281,7 +303,10 @@ test('args: dataSuffix', async () => { dataSuffix: '0x12345678', }) expect(spy).toHaveBeenCalledWith({ - account: accounts[0].address, + account: { + address: accounts[0].address, + type: 'json-rpc', + }, data: '0x1249c58b12345678', to: wagmiContractConfig.address, }) @@ -381,7 +406,7 @@ test('w/ simulateContract (args chain mismatch)', async () => { await expect(() => writeContract(client, request), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). + [ContractFunctionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). Current Chain ID: 1 Expected Chain ID: 10 – OP Mainnet @@ -391,7 +416,13 @@ test('w/ simulateContract (args chain mismatch)', async () => { from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0x1249c58b + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint() + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + Docs: https://viem.sh/docs/contract/writeContract Version: viem@x.y.z] `) }) @@ -410,7 +441,7 @@ test('w/ simulateContract (client chain mismatch)', async () => { await expect(() => writeContract(client, request), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). + [ContractFunctionExecutionError: The current chain of the wallet (id: 1) does not match the target chain for the transaction (id: 10 – OP Mainnet). Current Chain ID: 1 Expected Chain ID: 10 – OP Mainnet @@ -419,7 +450,186 @@ test('w/ simulateContract (client chain mismatch)', async () => { from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0x1249c58b + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint() + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + Docs: https://viem.sh/docs/contract/writeContract Version: viem@x.y.z] `) }) + +describe('behavior: contract revert', () => { + test('revert', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'revertWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "revertWrite" reverted with the following reason: + This is a revert message + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: revertWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('assert', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'assertWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "assertWrite" reverted with the following reason: + An \`assert\` condition failed. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: assertWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('overflow', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'overflowWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "overflowWrite" reverted with the following reason: + Arithmetic operation resulted in underflow or overflow. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: overflowWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('divide by zero', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'divideByZeroWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "divideByZeroWrite" reverted with the following reason: + Division or modulo by zero (e.g. \`5 / 0\` or \`23 % 0\`). + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: divideByZeroWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('require', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'requireWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "requireWrite" reverted. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: requireWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('custom error: simple', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'simpleCustomWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "simpleCustomWrite" reverted. + + Error: SimpleError(string message) + (bugger) + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: simpleCustomWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) + + test('custom error: complex', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(() => + writeContract(client, { + abi: ErrorsExample.abi, + address: contractAddress!, + functionName: 'complexCustomWrite', + account: privateKeyToAccount(accounts[0].privateKey), + }), + ).rejects.toMatchInlineSnapshot(` + [ContractFunctionExecutionError: The contract function "complexCustomWrite" reverted. + + Error: ComplexError((address sender, uint256 bar), string message, uint256 number) + ({"sender":"0x0000000000000000000000000000000000000000","bar":"69"}, bugger, 69) + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: complexCustomWrite() + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + + Docs: https://viem.sh/docs/contract/writeContract + Version: viem@x.y.z] + `) + }) +}) diff --git a/src/actions/wallet/writeContract.ts b/src/actions/wallet/writeContract.ts index 44765980cc..af8f212019 100644 --- a/src/actions/wallet/writeContract.ts +++ b/src/actions/wallet/writeContract.ts @@ -1,8 +1,17 @@ import type { Abi } from 'abitype' import type { Account } from '../../accounts/types.js' +import { + type ParseAccountErrorType, + parseAccount, +} from '../../accounts/utils/parseAccount.js' import type { Client } from '../../clients/createClient.js' import type { Transport } from '../../clients/transports/createTransport.js' +import { + AccountNotFoundError, + type AccountNotFoundErrorType, +} from '../../errors/account.js' +import type { BaseError } from '../../errors/base.js' import type { ErrorType } from '../../errors/utils.js' import type { GetAccountParameter } from '../../types/account.js' import type { @@ -22,6 +31,10 @@ import { type EncodeFunctionDataParameters, encodeFunctionData, } from '../../utils/abi/encodeFunctionData.js' +import { + type GetContractErrorReturnType, + getContractError, +} from '../../utils/errors/getContractError.js' import type { FormattedTransactionRequest } from '../../utils/formatters/transactionRequest.js' import { getAction } from '../../utils/getAction.js' import type { GetMutabilityAwareValue } from '../public/simulateContract.js' @@ -81,7 +94,9 @@ export type WriteContractReturnType = SendTransactionReturnType export type WriteContractErrorType = | EncodeFunctionDataErrorType - | SendTransactionErrorType + | AccountNotFoundErrorType + | ParseAccountErrorType + | GetContractErrorReturnType | ErrorType /** @@ -156,20 +171,47 @@ export async function writeContract< chainOverride >, ): Promise { - const { abi, address, args, dataSuffix, functionName, ...request } = - parameters as WriteContractParameters + const { + abi, + account: account_ = client.account, + address, + args, + dataSuffix, + functionName, + ...request + } = parameters as WriteContractParameters + + if (!account_) + throw new AccountNotFoundError({ + docsPath: '/docs/contract/writeContract', + }) + const account = parseAccount(account_) + const data = encodeFunctionData({ abi, args, functionName, } as EncodeFunctionDataParameters) - return getAction( - client, - sendTransaction, - 'sendTransaction', - )({ - data: `${data}${dataSuffix ? dataSuffix.replace('0x', '') : ''}`, - to: address, - ...request, - }) + + try { + return await getAction( + client, + sendTransaction, + 'sendTransaction', + )({ + data: `${data}${dataSuffix ? dataSuffix.replace('0x', '') : ''}`, + to: address, + account, + ...request, + }) + } catch (error) { + throw getContractError(error as BaseError, { + abi, + address, + args, + docsPath: '/docs/contract/writeContract', + functionName, + sender: account.address, + }) + } } diff --git a/src/chains/definitions/assetChainTestnet.ts b/src/chains/definitions/assetChainTestnet.ts new file mode 100644 index 0000000000..c5b5062b93 --- /dev/null +++ b/src/chains/definitions/assetChainTestnet.ts @@ -0,0 +1,28 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const assetChainTestnet = /*#__PURE__*/ defineChain({ + id: 42_421, + name: 'AssetChain Testnet', + nativeCurrency: { + decimals: 18, + name: 'Real World Asset', + symbol: 'RWA', + }, + rpcUrls: { + default: { http: ['https://enugu-rpc.assetchain.org'] }, + }, + blockExplorers: { + default: { + name: 'Asset Chain Testnet Explorer', + url: 'https://scan-testnet.assetchain.org', + apiUrl: 'https://scan-testnet.assetchain.org/api', + }, + }, + testnet: true, + contracts: { + multicall3: { + address: '0x989F832D35988cb5e3eB001Fa2Fe789469EC31Ea', + blockCreated: 17177, + }, + }, +}) diff --git a/src/chains/definitions/atletaOlympia.ts b/src/chains/definitions/atletaOlympia.ts new file mode 100644 index 0000000000..9bdcdb576d --- /dev/null +++ b/src/chains/definitions/atletaOlympia.ts @@ -0,0 +1,30 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const atletaOlympia = /*#__PURE__*/ defineChain({ + id: 2340, + name: 'Atleta Olympia', + nativeCurrency: { decimals: 18, name: 'Atla', symbol: 'ATLA' }, + rpcUrls: { + default: { + http: [ + 'https://testnet-rpc.atleta.network:9944', + 'https://testnet-rpc.atleta.network', + ], + ws: ['wss://testnet-rpc.atleta.network:9944'], + }, + }, + blockExplorers: { + default: { + name: 'Atleta Olympia Explorer', + url: 'https://blockscout.atleta.network', + apiUrl: 'https://blockscout.atleta.network/api', + }, + }, + contracts: { + multicall3: { + address: '0x1472ec6392180fb84F345d2455bCC75B26577115', + blockCreated: 1076473, + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/b3.ts b/src/chains/definitions/b3.ts new file mode 100644 index 0000000000..4af7d78eff --- /dev/null +++ b/src/chains/definitions/b3.ts @@ -0,0 +1,25 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +const sourceId = 8453 // base + +export const b3 = /*#__PURE__*/ defineChain({ + id: 8333, + name: 'B3', + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + rpcUrls: { + default: { + http: ['https://mainnet-rpc.b3.fun/http'], + }, + }, + blockExplorers: { + default: { + name: 'Blockscout', + url: 'https://explorer.b3.fun', + }, + }, + sourceId, +}) diff --git a/src/chains/definitions/b3Sepolia.ts b/src/chains/definitions/b3Sepolia.ts index 74e4a16419..f832a571a9 100644 --- a/src/chains/definitions/b3Sepolia.ts +++ b/src/chains/definitions/b3Sepolia.ts @@ -12,7 +12,7 @@ export const b3Sepolia = /*#__PURE__*/ defineChain({ }, rpcUrls: { default: { - http: ['https://sepolia.b3.fun'], + http: ['https://sepolia.b3.fun/http'], }, }, blockExplorers: { diff --git a/src/chains/definitions/bob.ts b/src/chains/definitions/bob.ts index 729bbac60a..12cdfa3369 100644 --- a/src/chains/definitions/bob.ts +++ b/src/chains/definitions/bob.ts @@ -1,6 +1,10 @@ +import { chainConfig } from '../../op-stack/chainConfig.js' import { defineChain } from '../../utils/chain/defineChain.js' +const sourceId = 1 // mainnet + export const bob = defineChain({ + ...chainConfig, id: 60808, name: 'BOB', nativeCurrency: { @@ -16,15 +20,28 @@ export const bob = defineChain({ }, blockExplorers: { default: { - name: 'Blockscout', + name: 'BOB Explorer', url: 'https://explorer.gobob.xyz', }, }, contracts: { + ...chainConfig.contracts, multicall3: { - address: '0x63f8279bccDb75c0F38e0CD6B6A0c72a0a760FF9', - blockCreated: 457045, + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 23131, + }, + l2OutputOracle: { + [sourceId]: { + address: '0xdDa53E23f8a32640b04D7256e651C1db98dB11C1', + blockCreated: 4462615, + }, + }, + portal: { + [sourceId]: { + address: '0x8AdeE124447435fE03e3CD24dF3f4cAE32E65a3E', + blockCreated: 4462615, + }, }, }, - testnet: false, + sourceId, }) diff --git a/src/chains/definitions/bobSepolia.ts b/src/chains/definitions/bobSepolia.ts new file mode 100644 index 0000000000..d8eb765a4b --- /dev/null +++ b/src/chains/definitions/bobSepolia.ts @@ -0,0 +1,52 @@ +import { chainConfig } from '../../op-stack/chainConfig.js' +import { defineChain } from '../../utils/chain/defineChain.js' + +const sourceId = 11_155_111 // sepolia + +export const bobSepolia = defineChain({ + ...chainConfig, + id: 808813, + name: 'BOB Sepolia', + nativeCurrency: { + decimals: 18, + name: 'ETH', + symbol: 'ETH', + }, + rpcUrls: { + default: { + http: ['https://bob-sepolia.rpc.gobob.xyz'], + webSocket: ['wss://bob-sepolia.rpc.gobob.xyz'], + }, + public: { + http: ['https://bob-sepolia.rpc.gobob.xyz'], + webSocket: ['wss://bob-sepolia.rpc.gobob.xyz'], + }, + }, + blockExplorers: { + default: { + name: 'BOB Sepolia Explorer', + url: 'https://bob-sepolia.explorer.gobob.xyz', + }, + }, + contracts: { + ...chainConfig.contracts, + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 35677, + }, + l2OutputOracle: { + [sourceId]: { + address: '0x14D0069452b4AE2b250B395b8adAb771E4267d2f', + blockCreated: 4462615, + }, + }, + portal: { + [sourceId]: { + address: '0x867B1Aa872b9C8cB5E9F7755feDC45BB24Ad0ae4', + blockCreated: 4462615, + }, + }, + }, + testnet: true, + sourceId, +}) diff --git a/src/chains/definitions/botanixTestnet.ts b/src/chains/definitions/botanixTestnet.ts new file mode 100644 index 0000000000..04e82fdce5 --- /dev/null +++ b/src/chains/definitions/botanixTestnet.ts @@ -0,0 +1,20 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const botanixTestnet = /*#__PURE__*/ defineChain({ + id: 3636, + name: 'Botanix Testnet', + nativeCurrency: { name: 'Botanix', symbol: 'BTC', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://poa-node.botanixlabs.dev'], + }, + }, + blockExplorers: { + default: { + name: 'blockscout', + url: 'https://blockscout.botanixlabs.dev', + apiUrl: 'https://blockscout.botanixlabs.dev', + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/chips.ts b/src/chains/definitions/chips.ts new file mode 100644 index 0000000000..29c1529e92 --- /dev/null +++ b/src/chains/definitions/chips.ts @@ -0,0 +1,19 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const chips = /*#__PURE__*/ defineChain({ + id: 2882, + name: 'Chips Network', + network: 'CHIPS', + nativeCurrency: { + decimals: 18, + name: 'IOTA', + symbol: 'IOTA', + }, + rpcUrls: { + default: { + http: [ + 'https://node.chips.ooo/wasp/api/v1/chains/iota1pp3d3mnap3ufmgqnjsnw344sqmf5svjh26y2khnmc89sv6788y3r207a8fn/evm', + ], + }, + }, +}) diff --git a/src/chains/definitions/cronoszkEVM.ts b/src/chains/definitions/cronoszkEVM.ts new file mode 100644 index 0000000000..1802326aa3 --- /dev/null +++ b/src/chains/definitions/cronoszkEVM.ts @@ -0,0 +1,20 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const cronoszkEVM = /*#__PURE__*/ defineChain({ + id: 388, + name: 'Cronos zkEVM Mainnet', + nativeCurrency: { + decimals: 18, + name: 'Cronos zkEVM CRO', + symbol: 'zkCRO', + }, + rpcUrls: { + default: { http: ['https://mainnet.zkevm.cronos.org'] }, + }, + blockExplorers: { + default: { + name: 'Cronos zkEVM (Mainnet) Chain Explorer', + url: 'https://explorer.zkevm.cronos.org', + }, + }, +}) diff --git a/src/chains/definitions/curtis.ts b/src/chains/definitions/curtis.ts new file mode 100644 index 0000000000..4124f71da7 --- /dev/null +++ b/src/chains/definitions/curtis.ts @@ -0,0 +1,19 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const curtis = /*#__PURE__*/ defineChain({ + id: 33_111, + name: 'Curtis', + nativeCurrency: { name: 'ApeCoin', symbol: 'APE', decimals: 18 }, + rpcUrls: { + default: { + http: ['https://rpc.curtis.apechain.com'], + }, + }, + blockExplorers: { + default: { + name: 'Curtis Explorer', + url: 'https://explorer.curtis.apechain.com', + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/flowTestnet.ts b/src/chains/definitions/flowTestnet.ts index d44ea37d67..58b79bcb9f 100644 --- a/src/chains/definitions/flowTestnet.ts +++ b/src/chains/definitions/flowTestnet.ts @@ -19,4 +19,10 @@ export const flowTestnet = /*#__PURE__*/ defineChain({ url: 'https://testnet.flowdiver.io', }, }, + contracts: { + multicall3: { + address: '0xca11bde05977b3631167028862be2a173976ca11', + blockCreated: 137518, + }, + }, }) diff --git a/src/chains/definitions/hashkeyChainTestnet.ts b/src/chains/definitions/hashkeyChainTestnet.ts new file mode 100644 index 0000000000..12940304c0 --- /dev/null +++ b/src/chains/definitions/hashkeyChainTestnet.ts @@ -0,0 +1,22 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const hashkeyTestnet = /*#__PURE__*/ defineChain({ + id: 133, + name: 'HashKey Chain Testnet', + nativeCurrency: { + decimals: 18, + name: 'HashKey EcoPoints', + symbol: 'HSK', + }, + rpcUrls: { + default: { + http: ['https://hashkeychain-testnet.alt.technology'], + }, + }, + blockExplorers: { + default: { + name: 'HashKey Chain Explorer', + url: 'https://hashkeychain-testnet-explorer.alt.technology', + }, + }, +}) diff --git a/src/chains/definitions/holesky.ts b/src/chains/definitions/holesky.ts index 3e944fd24b..7d395b376e 100644 --- a/src/chains/definitions/holesky.ts +++ b/src/chains/definitions/holesky.ts @@ -13,6 +13,7 @@ export const holesky = /*#__PURE__*/ defineChain({ default: { name: 'Etherscan', url: 'https://holesky.etherscan.io', + apiUrl: 'https://api-holesky.etherscan.io/api', }, }, contracts: { diff --git a/src/chains/definitions/immutableZkEvm.ts b/src/chains/definitions/immutableZkEvm.ts index 2a569442e1..634a7de583 100644 --- a/src/chains/definitions/immutableZkEvm.ts +++ b/src/chains/definitions/immutableZkEvm.ts @@ -17,6 +17,7 @@ export const immutableZkEvm = /*#__PURE__*/ defineChain({ default: { name: 'Immutable Explorer', url: 'https://explorer.immutable.com', + apiUrl: 'https://explorer.immutable.com/api', }, }, contracts: { diff --git a/src/chains/definitions/iota.ts b/src/chains/definitions/iota.ts new file mode 100644 index 0000000000..efcacb3886 --- /dev/null +++ b/src/chains/definitions/iota.ts @@ -0,0 +1,25 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const iota = /*#__PURE__*/ defineChain({ + id: 8822, + name: 'IOTA EVM', + network: 'iotaevm', + nativeCurrency: { + decimals: 18, + name: 'IOTA', + symbol: 'IOTA', + }, + rpcUrls: { + default: { + http: ['https://json-rpc.evm.iotaledger.net'], + webSocket: ['wss://ws.json-rpc.evm.iotaledger.net'], + }, + }, + blockExplorers: { + default: { + name: 'Explorer', + url: 'https://explorer.evm.iota.org', + apiUrl: 'https://explorer.evm.iota.org/api', + }, + }, +}) diff --git a/src/chains/definitions/iotaTestnet.ts b/src/chains/definitions/iotaTestnet.ts new file mode 100644 index 0000000000..3309facdc8 --- /dev/null +++ b/src/chains/definitions/iotaTestnet.ts @@ -0,0 +1,26 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const iotaTestnet = /*#__PURE__*/ defineChain({ + id: 1075, + name: 'IOTA EVM Testnet', + network: 'iotaevm-testnet', + nativeCurrency: { + decimals: 18, + name: 'IOTA', + symbol: 'IOTA', + }, + rpcUrls: { + default: { + http: ['https://json-rpc.evm.testnet.iotaledger.net'], + webSocket: ['wss://ws.json-rpc.evm.testnet.iotaledger.net'], + }, + }, + blockExplorers: { + default: { + name: 'Explorer', + url: 'https://explorer.evm.testnet.iotaledger.net', + apiUrl: 'https://explorer.evm.testnet.iotaledger.net/api', + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/kaia.ts b/src/chains/definitions/kaia.ts new file mode 100644 index 0000000000..0eb35b90d1 --- /dev/null +++ b/src/chains/definitions/kaia.ts @@ -0,0 +1,26 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const kaia = /*#__PURE__*/ defineChain({ + id: 8_217, + name: 'Kaia', + nativeCurrency: { + decimals: 18, + name: 'Kaia', + symbol: 'KAIA', + }, + rpcUrls: { + default: { http: ['https://public-en.node.kaia.io'] }, + }, + blockExplorers: { + default: { + name: 'KaiaScope', + url: 'https://kaiascope.com', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 96002415, + }, + }, +}) diff --git a/src/chains/definitions/kairos.ts b/src/chains/definitions/kairos.ts new file mode 100644 index 0000000000..eab25abe3f --- /dev/null +++ b/src/chains/definitions/kairos.ts @@ -0,0 +1,28 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const kairos = /*#__PURE__*/ defineChain({ + id: 1_001, + name: 'Kairos Testnet', + network: 'kairos', + nativeCurrency: { + decimals: 18, + name: 'Kairos KAIA', + symbol: 'KAIA', + }, + rpcUrls: { + default: { http: ['https://public-en-kairos.node.kaia.io'] }, + }, + blockExplorers: { + default: { + name: 'KaiaScope', + url: 'https://kairos.kaiascope.com', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 123390593, + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/linea.ts b/src/chains/definitions/linea.ts index 109c6d29ad..cdae08e74c 100644 --- a/src/chains/definitions/linea.ts +++ b/src/chains/definitions/linea.ts @@ -1,6 +1,8 @@ +import { chainConfig } from '../../linea/chainConfig.js' import { defineChain } from '../../utils/chain/defineChain.js' export const linea = /*#__PURE__*/ defineChain({ + ...chainConfig, id: 59_144, name: 'Linea Mainnet', nativeCurrency: { name: 'Linea Ether', symbol: 'ETH', decimals: 18 }, diff --git a/src/chains/definitions/lineaSepolia.ts b/src/chains/definitions/lineaSepolia.ts index c719794bf3..07d08d194e 100644 --- a/src/chains/definitions/lineaSepolia.ts +++ b/src/chains/definitions/lineaSepolia.ts @@ -1,6 +1,8 @@ +import { chainConfig } from '../../linea/chainConfig.js' import { defineChain } from '../../utils/chain/defineChain.js' export const lineaSepolia = /*#__PURE__*/ defineChain({ + ...chainConfig, id: 59_141, name: 'Linea Sepolia Testnet', nativeCurrency: { name: 'Linea Ether', symbol: 'ETH', decimals: 18 }, diff --git a/src/chains/definitions/sophonTestnet.ts b/src/chains/definitions/sophonTestnet.ts new file mode 100644 index 0000000000..47a2bc3c2d --- /dev/null +++ b/src/chains/definitions/sophonTestnet.ts @@ -0,0 +1,30 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const sophonTestnet = /*#__PURE__*/ defineChain({ + id: 531_050_104, + name: 'Sophon Testnet', + nativeCurrency: { + decimals: 18, + name: 'Sophon', + symbol: 'SOPH', + }, + rpcUrls: { + default: { + http: ['https://rpc.testnet.sophon.xyz'], + webSocket: ['wss://rpc.testnet.sophon.xyz/ws'], + }, + }, + blockExplorers: { + default: { + name: 'Sophon Block Explorer', + url: 'https://explorer.testnet.sophon.xyz', + }, + }, + contracts: { + multicall3: { + address: '0x83c04d112adedA2C6D9037bb6ecb42E7f0b108Af', + blockCreated: 15_642, + }, + }, + testnet: true, +}) diff --git a/src/chains/definitions/taiko.ts b/src/chains/definitions/taiko.ts index ab1ac9f8c3..f41a31f58c 100644 --- a/src/chains/definitions/taiko.ts +++ b/src/chains/definitions/taiko.ts @@ -17,8 +17,8 @@ export const taiko = /*#__PURE__*/ defineChain({ blockExplorers: { default: { name: 'Taikoscan', - url: 'https://taikoscan.network', - apiUrl: 'https://taikoscan.network/api', + url: 'https://taikoscan.io', + apiUrl: 'https://api.taikoscan.io/api', }, }, contracts: { diff --git a/src/chains/definitions/tron.ts b/src/chains/definitions/tron.ts new file mode 100644 index 0000000000..2280378baa --- /dev/null +++ b/src/chains/definitions/tron.ts @@ -0,0 +1,19 @@ +import { defineChain } from '../../utils/chain/defineChain.js' + +export const tron = /*#__PURE__*/ defineChain({ + id: 728126428, + name: 'Tron', + nativeCurrency: { name: 'TRON', symbol: 'TRX', decimals: 6 }, + rpcUrls: { + default: { + http: ['https://api.trongrid.io/jsonrpc'], + }, + }, + blockExplorers: { + default: { + name: 'Tronscan', + url: 'https://tronscan.org', + apiUrl: 'https://apilist.tronscanapi.com/api', + }, + }, +}) diff --git a/src/chains/index.ts b/src/chains/index.ts index 2e8ce8e5b2..061d826364 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -10,6 +10,7 @@ export { apexTestnet } from './definitions/apexTestnet.js' export { arbitrum } from './definitions/arbitrum.js' export { arbitrumGoerli } from './definitions/arbitrumGoerli.js' export { arbitrumNova } from './definitions/arbitrumNova.js' +export { assetChainTestnet } from './definitions/assetChainTestnet.js' export { astar } from './definitions/astar.js' export { astarZkEVM } from './definitions/astarZkEVM.js' export { astarZkyoto } from './definitions/astarZkyoto.js' @@ -17,12 +18,14 @@ export { arbitrumSepolia } from './definitions/arbitrumSepolia.js' export { areonNetwork } from './definitions/areonNetwork.js' export { areonNetworkTestnet } from './definitions/areonNetworkTestnet.js' export { artelaTestnet } from './definitions/artelaTestnet.js' +export { atletaOlympia } from './definitions/atletaOlympia.js' export { aurora } from './definitions/aurora.js' export { auroraTestnet } from './definitions/auroraTestnet.js' export { auroria } from './definitions/auroria.js' export { avalanche } from './definitions/avalanche.js' export { avalancheFuji } from './definitions/avalancheFuji.js' export { b3Sepolia } from './definitions/b3Sepolia.js' +export { b3 } from './definitions/b3.js' export { bahamut } from './definitions/bahamut.js' export { base } from './definitions/base.js' export { baseGoerli } from './definitions/baseGoerli.js' @@ -41,8 +44,10 @@ export { bitTorrentTestnet } from './definitions/bitTorrentTestnet.js' export { blast } from './definitions/blast.js' export { blastSepolia } from './definitions/blastSepolia.js' export { bob } from './definitions/bob.js' +export { bobSepolia } from './definitions/bobSepolia.js' export { boba } from './definitions/boba.js' export { bobaSepolia } from './definitions/bobaSepolia.js' +export { botanixTestnet } from './definitions/botanixTestnet.js' export { bronos } from './definitions/bronos.js' export { bronosTestnet } from './definitions/bronosTestnet.js' export { bsc } from './definitions/bsc.js' @@ -56,15 +61,18 @@ export { canto } from './definitions/canto.js' export { celo } from './definitions/celo.js' export { celoAlfajores } from './definitions/celoAlfajores.js' export { chiliz } from './definitions/chiliz.js' +export { chips } from './definitions/chips.js' export { classic } from './definitions/classic.js' export { confluxESpace } from './definitions/confluxESpace.js' export { confluxESpaceTestnet } from './definitions/confluxESpaceTestnet.js' export { coreDao } from './definitions/coreDao.js' export { crab } from './definitions/crab.js' export { cronos } from './definitions/cronos.js' +export { cronoszkEVM } from './definitions/cronoszkEVM.js' export { cronoszkEVMTestnet } from './definitions/cronoszkEVMTestnet.js' export { cronosTestnet } from './definitions/cronosTestnet.js' export { crossbell } from './definitions/crossbell.js' +export { curtis } from './definitions/curtis.js' export { cyber } from './definitions/cyber.js' export { cyberTestnet } from './definitions/cyberTestnet.js' export { darwinia } from './definitions/darwinia.js' @@ -125,6 +133,7 @@ export { gravity } from './definitions/gravity.js' export { ham } from './definitions/ham.js' export { hardhat } from './definitions/hardhat.js' export { harmonyOne } from './definitions/harmonyOne.js' +export { hashkeyTestnet } from './definitions/hashkeyChainTestnet.js' export { haqqMainnet } from './definitions/haqqMainnet.js' export { haqqTestedge2 } from './definitions/haqqTestedge2.js' export { hedera } from './definitions/hedera.js' @@ -134,12 +143,18 @@ export { holesky } from './definitions/holesky.js' export { immutableZkEvm } from './definitions/immutableZkEvm.js' export { immutableZkEvmTestnet } from './definitions/immutableZkEvmTestnet.js' export { inEVM } from './definitions/inEVM.js' +export { iota } from './definitions/iota.js' +export { iotaTestnet } from './definitions/iotaTestnet.js' export { kakarotSepolia } from './definitions/kakarotSepolia.js' export { kava } from './definitions/kava.js' export { kavaTestnet } from './definitions/kavaTestnet.js' export { kcc } from './definitions/kcc.js' +/** @deprecated Use `kaia` instead. */ export { klaytn } from './definitions/klaytn.js' +/** @deprecated Use `kairos` instead. */ export { klaytnBaobab } from './definitions/klaytnBaobab.js' +export { kaia } from './definitions/kaia.js' +export { kairos } from './definitions/kairos.js' export { koi } from './definitions/koi.js' export { kroma } from './definitions/kroma.js' export { kromaSepolia } from './definitions/kromaSepolia.js' @@ -264,6 +279,7 @@ export { skaleTitanTestnet } from './definitions/skale/titanTestnet.js' export { sketchpad } from './definitions/sketchpad.js' export { songbird } from './definitions/songbird.js' export { songbirdTestnet } from './definitions/songbirdTestnet.js' +export { sophonTestnet } from './definitions/sophonTestnet.js' export { spicy } from './definitions/spicy.js' export { shardeumSphinx } from './definitions/shardeumSphinx.js' export { shibarium } from './definitions/shibarium.js' @@ -284,6 +300,7 @@ export { telosTestnet } from './definitions/telosTestnet.js' export { tenet } from './definitions/tenet.js' export { thaiChain } from './definitions/thaiChain.js' export { thunderTestnet } from './definitions/thunderTestnet.js' +export { tron } from './definitions/tron.js' export { unreal } from './definitions/unreal.js' export { vechain } from './definitions/vechain.js' export { wanchain } from './definitions/wanchain.js' diff --git a/src/errors/node.ts b/src/errors/node.ts index 3b7f59970b..45299e1348 100644 --- a/src/errors/node.ts +++ b/src/errors/node.ts @@ -150,7 +150,8 @@ export type InsufficientFundsErrorType = InsufficientFundsError & { name: 'InsufficientFundsError' } export class InsufficientFundsError extends BaseError { - static nodeMessage = /insufficient funds/ + static nodeMessage = + /insufficient funds|exceeds transaction sender account balance/ constructor({ cause }: { cause?: BaseError | undefined } = {}) { super( [ diff --git a/src/errors/version.ts b/src/errors/version.ts index 37a900b108..f37d9ff3fb 100644 --- a/src/errors/version.ts +++ b/src/errors/version.ts @@ -1 +1 @@ -export const version = '2.19.2' +export const version = '2.20.1' diff --git a/src/experimental/eip5792/actions/sendCalls.test.ts b/src/experimental/eip5792/actions/sendCalls.test.ts index 223c3a7e73..22d27ce76b 100644 --- a/src/experimental/eip5792/actions/sendCalls.test.ts +++ b/src/experimental/eip5792/actions/sendCalls.test.ts @@ -1,43 +1,108 @@ import { expect, test } from 'vitest' +import { wagmiContractConfig } from '../../../../test/src/abis.js' import { anvilMainnet } from '../../../../test/src/anvil.js' import { accounts } from '../../../../test/src/constants.js' -import { mainnet } from '../../../chains/index.js' -import { createClient } from '../../../clients/createClient.js' +import { reset } from '../../../actions/index.js' +import { type Chain, mainnet } from '../../../chains/index.js' +import { type Client, createClient } from '../../../clients/createClient.js' +import type { Transport } from '../../../clients/transports/createTransport.js' import { custom } from '../../../clients/transports/custom.js' import { RpcRequestError } from '../../../errors/request.js' +import type { WalletCallReceipt } from '../../../types/eip1193.js' +import type { Hex } from '../../../types/misc.js' import { getHttpRpcClient, parseEther } from '../../../utils/index.js' +import { uid } from '../../../utils/uid.js' import { sendCalls } from './sendCalls.js' -const getClient = ({ +type Uid = string +type TxHashes = Hex[] +const calls = new Map() + +const testClient = anvilMainnet.getClient() + +const getClient = ({ + chain, onRequest, -}: { onRequest({ method, params }: any): void }) => +}: { + chain?: chain | undefined + onRequest({ method, params }: any): void +}): Client => createClient({ + chain, transport: custom({ async request({ method, params }) { - if (method !== 'wallet_sendCalls') return - onRequest({ method, params }) const rpcClient = getHttpRpcClient(anvilMainnet.rpcUrl.http) - for (const call of params[0].calls) { - const { error } = await rpcClient.request({ - body: { - method: 'eth_sendTransaction', - params: [call], - id: 0, - }, - }) - if (error) - throw new RpcRequestError({ - body: { method, params }, - error, - url: anvilMainnet.rpcUrl.http, + + if (method === 'wallet_getCallsStatus') { + const hashes = calls.get(params[0]) + if (!hashes) return { status: 'PENDING', receipts: [] } + const receipts = await Promise.all( + hashes.map(async (hash) => { + const { result, error } = await rpcClient.request({ + body: { + method: 'eth_getTransactionReceipt', + params: [hash], + id: 0, + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url: anvilMainnet.rpcUrl.http, + }) + if (!result) throw new Error('receipt not found') + return { + blockHash: result.blockHash, + blockNumber: result.blockNumber, + gasUsed: result.gasUsed, + logs: result.logs, + status: result.status, + transactionHash: result.transactionHash, + } satisfies WalletCallReceipt + }), + ) + return { status: 'CONFIRMED', receipts } + } + + if (method === 'wallet_sendCalls') { + const hashes = [] + for (const call of params[0].calls) { + const callResult = await rpcClient.request({ + body: { + method: 'eth_call', + params: [{ ...call, from: params[0].from }], + id: 0, + }, }) + if (callResult.error) throw new Error(callResult.error.message) + + const { result, error } = await rpcClient.request({ + body: { + method: 'eth_sendTransaction', + params: [{ ...call, from: params[0].from }], + id: 0, + }, + }) + if (error) + throw new RpcRequestError({ + body: { method, params }, + error, + url: anvilMainnet.rpcUrl.http, + }) + hashes.push(result) + } + const uid_ = uid() + calls.set(uid_, hashes) + return uid_ } - return '0xdeadbeef' + + return null }, }), - }) + }) as never test('default', async () => { const requests: unknown[] = [] @@ -48,8 +113,14 @@ test('default', async () => { }, }) + await reset(testClient, { + blockNumber: 16280770n, + jsonRpcUrl: anvilMainnet.forkUrl, + }) + const id_ = await sendCalls(client, { account: accounts[0].address, + chain: mainnet, calls: [ { to: accounts[1].address, @@ -63,29 +134,145 @@ test('default', async () => { to: accounts[3].address, value: parseEther('100'), }, + { + abi: wagmiContractConfig.abi, + functionName: 'mint', + to: wagmiContractConfig.address, + }, + { + abi: wagmiContractConfig.abi, + functionName: 'mint', + to: wagmiContractConfig.address, + }, ], + }) + + expect(id_).toBeDefined() + expect(requests).toMatchInlineSnapshot(` + [ + [ + { + "calls": [ + { + "chainId": "0x1", + "data": undefined, + "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "value": "0xde0b6b3a7640000", + }, + { + "chainId": "0x1", + "data": undefined, + "to": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "value": undefined, + }, + { + "chainId": "0x1", + "data": "0xcafebabe", + "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "value": "0x56bc75e2d63100000", + }, + { + "chainId": "0x1", + "data": "0x1249c58b", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "value": undefined, + }, + { + "chainId": "0x1", + "data": "0x1249c58b", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "value": undefined, + }, + ], + "capabilities": undefined, + "chainId": "0x1", + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "version": "1.0", + }, + ], + ] + `) +}) + +test('behavior: chain on client', async () => { + const requests: unknown[] = [] + + const client = getClient({ chain: mainnet, + onRequest({ params }) { + requests.push(params) + }, }) - expect(id_).toMatchInlineSnapshot(`"0xdeadbeef"`) + await reset(testClient, { + blockNumber: 16280770n, + jsonRpcUrl: anvilMainnet.forkUrl, + }) + + const id_ = await sendCalls(client, { + account: accounts[0].address, + calls: [ + { + to: accounts[1].address, + value: parseEther('1'), + }, + { + to: accounts[2].address, + }, + { + data: '0xcafebabe', + to: accounts[3].address, + value: parseEther('100'), + }, + { + abi: wagmiContractConfig.abi, + functionName: 'mint', + to: wagmiContractConfig.address, + }, + { + abi: wagmiContractConfig.abi, + functionName: 'mint', + to: wagmiContractConfig.address, + }, + ], + }) + + expect(id_).toBeDefined() expect(requests).toMatchInlineSnapshot(` [ [ { "calls": [ { + "chainId": "0x1", + "data": undefined, "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "value": "0xde0b6b3a7640000", }, { + "chainId": "0x1", + "data": undefined, "to": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "value": undefined, }, { + "chainId": "0x1", "data": "0xcafebabe", "to": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "value": "0x56bc75e2d63100000", }, + { + "chainId": "0x1", + "data": "0x1249c58b", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "value": undefined, + }, + { + "chainId": "0x1", + "data": "0x1249c58b", + "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "value": undefined, + }, ], "capabilities": undefined, "chainId": "0x1", @@ -107,18 +294,25 @@ test('error: no chain', async () => { }) await expect(() => - // @ts-expect-error sendCalls(client, { account: accounts[0].address, calls: [ { + chainId: 1, to: accounts[1].address, value: parseEther('1'), }, + { + chain: mainnet, + to: accounts[1].address, + value: parseEther('1'), + }, + // @ts-expect-error { to: accounts[2].address, value: parseEther('10'), }, + // @ts-expect-error { data: '0xcafebabe', to: accounts[3].address, @@ -138,6 +332,7 @@ test('error: no account', async () => { const requests: unknown[] = [] const client = getClient({ + chain: mainnet, onRequest({ params }) { requests.push(params) }, @@ -176,6 +371,7 @@ test('error: insufficient funds', async () => { const requests: unknown[] = [] const client = getClient({ + chain: mainnet, onRequest({ params }) { requests.push(params) }, diff --git a/src/experimental/eip5792/actions/sendCalls.ts b/src/experimental/eip5792/actions/sendCalls.ts index 8c6d2d7652..8f06f1c5df 100644 --- a/src/experimental/eip5792/actions/sendCalls.ts +++ b/src/experimental/eip5792/actions/sendCalls.ts @@ -1,3 +1,4 @@ +import type { AbiStateMutability, Address, Narrow } from 'abitype' import { parseAccount } from '../../../accounts/utils/parseAccount.js' import type { Client } from '../../../clients/createClient.js' import type { Transport } from '../../../clients/transports/createTransport.js' @@ -6,13 +7,16 @@ import type { BaseError } from '../../../errors/base.js' import { ChainNotFoundError } from '../../../errors/chain.js' import type { ErrorType } from '../../../errors/utils.js' import type { Account, GetAccountParameter } from '../../../types/account.js' -import type { Chain, GetChainParameter } from '../../../types/chain.js' +import type { Chain, DeriveChain } from '../../../types/chain.js' +import type { ContractFunctionParameters } from '../../../types/contract.js' import type { WalletCapabilities, WalletSendCallsParameters, } from '../../../types/eip1193.js' import type { Hex } from '../../../types/misc.js' -import type { OneOf } from '../../../types/utils.js' +import type { GetMulticallContractParameters } from '../../../types/multicall.js' +import type { MaybeRequired, OneOf, Prettify } from '../../../types/utils.js' +import { encodeFunctionData } from '../../../utils/abi/encodeFunctionData.js' import type { RequestErrorType } from '../../../utils/buildRequest.js' import { numberToHex } from '../../../utils/encoding/toHex.js' import { getTransactionError } from '../../../utils/errors/getTransactionError.js' @@ -21,23 +25,17 @@ export type SendCallsParameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, chainOverride extends Chain | undefined = Chain | undefined, + calls extends readonly unknown[] = readonly unknown[], + // + _chain extends Chain | undefined = DeriveChain, > = { - calls: OneOf< - | { - to: Hex - data?: Hex | undefined - value?: bigint | undefined - } - | { - data: Hex - } - >[] + chain?: chainOverride | Chain | undefined + calls: Calls, _chain> capabilities?: | WalletSendCallsParameters[number]['capabilities'] | undefined version?: WalletSendCallsParameters[number]['version'] | undefined -} & GetAccountParameter & - GetChainParameter +} & GetAccountParameter export type SendCallsReturnType = string @@ -76,16 +74,16 @@ export type SendCallsErrorType = RequestErrorType | ErrorType * }) */ export async function sendCalls< + const calls extends readonly unknown[], chain extends Chain | undefined, account extends Account | undefined = undefined, chainOverride extends Chain | undefined = undefined, >( client: Client, - parameters: SendCallsParameters, + parameters: SendCallsParameters, ): Promise { const { account: account_ = client.account, - calls, capabilities, chain = client.chain, version = '1.0', @@ -97,7 +95,27 @@ export async function sendCalls< }) const account = parseAccount(account_) - if (!chain) throw new ChainNotFoundError() + const calls = parameters.calls.map((call_: unknown) => { + const call = call_ as Call + + const chainId = call.chain?.id ?? call.chainId ?? chain?.id + if (!chainId) throw new ChainNotFoundError() + + const data = call.abi + ? encodeFunctionData({ + abi: call.abi, + functionName: call.functionName, + args: call.args, + }) + : call.data + + return { + chainId: numberToHex(chainId), + data, + to: call.to, + value: call.value ? numberToHex(call.value) : undefined, + } + }) try { return await client.request( @@ -105,10 +123,7 @@ export async function sendCalls< method: 'wallet_sendCalls', params: [ { - calls: calls.map((call) => ({ - ...call, - value: call.value ? numberToHex(call.value) : undefined, - })) as any, + calls, capabilities, chainId: numberToHex(chain!.id), from: account.address, @@ -126,3 +141,74 @@ export async function sendCalls< }) } } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +type RawCall = { data?: Hex; to?: Address; value?: bigint } + +type Call< + chain extends Chain | undefined = Chain | undefined, + contractFunctionParameters = Omit, +> = OneOf< + | (contractFunctionParameters & { + to: Address + value?: bigint | undefined + }) + | RawCall +> & + OneOf< + | MaybeRequired< + { chain?: Chain | undefined }, + chain extends Chain ? false : true + > + | MaybeRequired<{ chainId?: number }, chain extends Chain ? false : true> + > + +type Calls< + calls extends readonly unknown[], + chain extends Chain | undefined, + /// + result extends readonly any[] = [], +> = calls extends readonly [] // no calls, return empty + ? readonly [] + : calls extends readonly [infer call] // one call left before returning `result` + ? readonly [ + ...result, + Prettify< + Call< + chain, + Omit< + GetMulticallContractParameters, + 'address' + > + > + >, + ] + : calls extends readonly [infer call, ...infer rest] // grab first call and recurse through `rest` + ? Calls< + [...rest], + chain, + [ + ...result, + Prettify< + Call< + chain, + Omit< + GetMulticallContractParameters, + 'address' + > + > + >, + ] + > + : readonly unknown[] extends calls + ? calls + : // If `calls` is *some* array but we couldn't assign `unknown[]` to it, then it must hold some known/homogenous type! + // use this to infer the param types in the case of Array.map() argument + calls extends readonly (infer call extends Call< + chain, + Omit + >)[] + ? readonly Prettify[] + : // Fallback + readonly Call>[] diff --git a/src/experimental/eip5792/actions/writeContracts.test.ts b/src/experimental/eip5792/actions/writeContracts.test.ts index 9e254abadb..94ae7e88a9 100644 --- a/src/experimental/eip5792/actions/writeContracts.test.ts +++ b/src/experimental/eip5792/actions/writeContracts.test.ts @@ -139,16 +139,19 @@ test('default', async () => { { "calls": [ { + "chainId": "0x1", "data": "0x1249c58b", "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "value": undefined, }, { + "chainId": "0x1", "data": "0x1249c58b", "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "value": undefined, }, { + "chainId": "0x1", "data": "0x1249c58b", "to": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", "value": undefined, diff --git a/src/experimental/eip5792/actions/writeContracts.ts b/src/experimental/eip5792/actions/writeContracts.ts index d2889071d7..3f94f66c48 100644 --- a/src/experimental/eip5792/actions/writeContracts.ts +++ b/src/experimental/eip5792/actions/writeContracts.ts @@ -50,6 +50,8 @@ export type WriteContractsErrorType = | ErrorType /** + * @deprecated Use {@link sendCalls} instead. See https://viem.sh/experimental/eip5792/sendCalls#contract-calls. + * * Requests for the wallet to sign and broadcast a batch of write contract calls (transactions) to the network. * * - Docs: https://viem.sh/experimental/eip5792/writeContracts diff --git a/src/experimental/eip7702/utils/hashAuthorization.test.ts b/src/experimental/eip7702/utils/hashAuthorization.test.ts index 1923ed9173..d354fbae6a 100644 --- a/src/experimental/eip7702/utils/hashAuthorization.test.ts +++ b/src/experimental/eip7702/utils/hashAuthorization.test.ts @@ -10,7 +10,7 @@ test('default', () => { nonce: 40, }), ).toMatchInlineSnapshot( - `"0xf4b2818a2452d296afd2a3434f490930548bf91f29196463ab563a9998e698e1"`, + `"0x5919da563810a99caf657d42bd10905adbd28b3b89b8a4577efa471e5e4b3914"`, ) expect( @@ -20,7 +20,7 @@ test('default', () => { nonce: 420, }), ).toMatchInlineSnapshot( - `"0x62d076dc1afee955cc80d50d192d8d60ea68b5b3b871de7dda3eba3f1544b3c3"`, + `"0x9aeacccc1b8571dfc4fb4ba734dbde6e94d6c0188484413585144a755c359aac"`, ) }) @@ -35,38 +35,38 @@ test('args: to', () => { ).toMatchInlineSnapshot( ` Uint8Array [ - 190, - 250, - 208, - 44, - 87, - 218, - 191, - 180, - 93, - 163, - 101, - 147, - 149, - 12, + 144, + 51, + 238, + 19, + 215, + 246, + 3, + 204, + 179, + 170, + 41, + 75, + 60, + 94, + 113, + 124, + 15, + 193, + 57, 102, - 164, - 115, - 81, - 67, - 100, - 239, - 142, - 22, - 81, - 0, - 165, - 98, - 117, - 147, - 192, - 153, - 209, + 229, + 8, + 229, + 140, + 203, + 54, + 237, + 73, + 228, + 251, + 177, + 230, ] `, ) @@ -81,38 +81,38 @@ test('args: to', () => { ).toMatchInlineSnapshot( ` Uint8Array [ - 98, - 208, - 118, - 220, - 26, - 254, - 233, - 85, - 204, - 128, - 213, - 13, - 25, - 45, - 141, - 96, + 154, 234, - 104, - 181, - 179, - 184, + 204, + 204, + 27, + 133, 113, + 223, + 196, + 251, + 75, + 167, + 52, + 219, 222, - 125, - 218, - 62, - 186, - 63, - 21, - 68, - 179, - 195, + 110, + 148, + 214, + 192, + 24, + 132, + 132, + 65, + 53, + 133, + 20, + 74, + 117, + 92, + 53, + 154, + 172, ] `, ) diff --git a/src/experimental/eip7702/utils/hashAuthorization.ts b/src/experimental/eip7702/utils/hashAuthorization.ts index fab6571570..6b544a3fbd 100644 --- a/src/experimental/eip7702/utils/hashAuthorization.ts +++ b/src/experimental/eip7702/utils/hashAuthorization.ts @@ -48,11 +48,7 @@ export function hashAuthorization( const hash = keccak256( concatHex([ '0x05', - toRlp([ - numberToHex(chainId), - contractAddress, - [nonce ? numberToHex(nonce) : '0x'], - ]), + toRlp([numberToHex(chainId), contractAddress, numberToHex(nonce)]), ]), ) if (to === 'bytes') return hexToBytes(hash) as HashAuthorizationReturnType diff --git a/src/experimental/eip7702/utils/serializeAuthorizationList.test.ts b/src/experimental/eip7702/utils/serializeAuthorizationList.test.ts index d1a3c923a3..4d7983b023 100644 --- a/src/experimental/eip7702/utils/serializeAuthorizationList.test.ts +++ b/src/experimental/eip7702/utils/serializeAuthorizationList.test.ts @@ -22,9 +22,7 @@ test('default', () => { [ "0x1", "0x0000000000000000000000000000000000000000", - [ - "0x", - ], + "0x0", "0x", "0x", "0x", @@ -48,9 +46,7 @@ test('default', () => { [ "0x1", "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", - [ - "0x45", - ], + "0x45", "0x1", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", @@ -82,9 +78,7 @@ test('default', () => { [ "0x1", "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", - [ - "0x45", - ], + "0x45", "0x1", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", @@ -92,9 +86,7 @@ test('default', () => { [ "0x45", "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", - [ - "0x1a4", - ], + "0x1a4", "0x", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", diff --git a/src/experimental/eip7702/utils/serializeAuthorizationList.ts b/src/experimental/eip7702/utils/serializeAuthorizationList.ts index cc174ff47e..99d2fcd007 100644 --- a/src/experimental/eip7702/utils/serializeAuthorizationList.ts +++ b/src/experimental/eip7702/utils/serializeAuthorizationList.ts @@ -24,7 +24,7 @@ export function serializeAuthorizationList( serializedAuthorizationList.push([ toHex(chainId), contractAddress, - [nonce ? toHex(nonce) : '0x'], + toHex(nonce), ...toYParitySignatureArray({}, signature), ]) } diff --git a/src/index.test.ts b/src/index.test.ts index 38a8491cb2..584b24c187 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -42,11 +42,11 @@ test('exports', () => { "erc20Abi_bytes32", "erc721Abi", "erc4626Abi", + "universalSignatureValidatorAbi", "zeroAddress", "deploylessCallViaBytecodeBytecode", "deploylessCallViaFactoryBytecode", "universalSignatureValidatorByteCode", - "universalSignatureValidatorAbi", "etherUnits", "gweiUnits", "weiUnits", diff --git a/src/index.ts b/src/index.ts index ea9cf00833..51816b3f27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -467,11 +467,14 @@ export type { } from './actions/wallet/writeContract.js' export type { Chain, + ChainConfig, ChainContract, + ChainEstimateFeesPerGasFn, ChainFees, ChainFeesFnParameters, ChainFormatter, ChainEstimateFeesPerGasFnParameters, + ChainMaxPriorityFeePerGasFn, DeriveChain, GetChainParameter, ChainFormatters, @@ -1049,6 +1052,7 @@ export type { ProviderConnectInfo, ProviderMessage, PublicRpcSchema, + PaymasterRpcSchema, NetworkSync, RpcSchema, RpcSchemaOverride, diff --git a/src/jsr.json b/src/jsr.json index 5c3c83ff1f..2768a189e3 100644 --- a/src/jsr.json +++ b/src/jsr.json @@ -1,6 +1,6 @@ { "name": "@wevm/viem", - "version": "2.19.2", + "version": "2.20.1", "exports": { ".": "./index.ts", "./accounts": "./accounts/index.ts", diff --git a/src/linea/actions/estimateGas.test.ts b/src/linea/actions/estimateGas.test.ts new file mode 100644 index 0000000000..b8a01acabe --- /dev/null +++ b/src/linea/actions/estimateGas.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from 'vitest' + +import { accounts } from '../../../test/src/constants.js' +import { createClient } from '../../clients/createClient.js' +import { http } from '../../clients/transports/http.js' +import { parseEther } from '../../utils/unit/parseEther.js' +import { lineaSepolia } from '../chains.js' +import { estimateGas } from './estimateGas.js' + +const client = createClient({ + chain: lineaSepolia, + transport: http(), +}) + +test('default', async () => { + const { baseFeePerGas, gasLimit, priorityFeePerGas } = await estimateGas( + client, + { + account: '0x0000000000000000000000000000000000000000', + to: '0x0000000000000000000000000000000000000000', + value: parseEther('0.0001'), + }, + ) + expect(baseFeePerGas).toBeGreaterThan(0n) + expect(gasLimit).toBe(21000n) + expect(priorityFeePerGas).toBeGreaterThan(0n) +}) + +test('error: insufficient balance', async () => { + await expect(() => + estimateGas(client, { + account: accounts[0].address, + to: '0x0000000000000000000000000000000000000000', + value: parseEther('0.0001'), + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [CallExecutionError: The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account. + + This error could arise when the account does not have enough funds to: + - pay for the total gas fee, + - pay for the value to send. + + The cost of the transaction is calculated as \`gas * gas fee + value\`, where: + - \`gas\` is the amount of gas needed for transaction to execute, + - \`gas fee\` is the gas fee, + - \`value\` is the amount of ether to send to the recipient. + + Raw Call Arguments: + from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + to: 0x0000000000000000000000000000000000000000 + value: 0.0001 ETH + + Details: transaction up-front cost 0x5af31cfe9880 exceeds transaction sender account balance 0x0 + Version: viem@x.y.z] + `) +}) diff --git a/src/linea/actions/estimateGas.ts b/src/linea/actions/estimateGas.ts new file mode 100644 index 0000000000..5c9f282f26 --- /dev/null +++ b/src/linea/actions/estimateGas.ts @@ -0,0 +1,137 @@ +import type { Account } from '../../accounts/types.js' +import { parseAccount } from '../../accounts/utils/parseAccount.js' +import type { EstimateGasParameters as EstimateGasParameters_base } from '../../actions/public/estimateGas.js' +import { + type PrepareTransactionRequestParameters, + prepareTransactionRequest, +} from '../../actions/wallet/prepareTransactionRequest.js' +import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import { AccountNotFoundError } from '../../errors/account.js' +import type { BaseError } from '../../errors/base.js' +import type { GetAccountParameter } from '../../types/account.js' +import type { Chain } from '../../types/chain.js' +import type { TransactionRequest } from '../../types/transaction.js' +import type { Filter } from '../../types/utils.js' +import { numberToHex } from '../../utils/encoding/toHex.js' +import { getCallError } from '../../utils/errors/getCallError.js' +import { extract } from '../../utils/formatters/extract.js' +import { formatTransactionRequest } from '../../utils/formatters/transactionRequest.js' +import { + type AssertRequestParameters, + assertRequest, +} from '../../utils/transaction/assertRequest.js' +import type { LineaRpcSchema } from '../types/rpc.js' + +export type EstimateGasParameters< + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined, +> = EstimateGasParameters_base & GetAccountParameter + +export type EstimateGasReturnType = { + gasLimit: bigint + baseFeePerGas: bigint + priorityFeePerGas: bigint +} + +/** + * Estimates the gas and fees per gas necessary to complete a transaction without submitting it to the network. + * + * @param client - Client to use + * @param parameters - {@link EstimateGasParameters} + * @returns A gas estimate and fees per gas (in wei). {@link EstimateGasReturnType} + * + * @example + * import { createPublicClient, http, parseEther } from 'viem' + * import { linea } from 'viem/chains' + * import { estimateGas } from 'viem/linea' + * + * const client = createPublicClient({ + * chain: linea, + * transport: http(), + * }) + * const gasEstimate = await estimateGas(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 0n, + * }) + */ +export async function estimateGas< + chain extends Chain | undefined, + account extends Account | undefined, +>( + client: Client, + args: EstimateGasParameters, +): Promise { + const { account: account_ = client.account } = args + + if (!account_) throw new AccountNotFoundError() + const account = parseAccount(account_) + + try { + const { + accessList, + blockNumber, + blockTag, + data, + gas, + gasPrice, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + to, + value, + ...rest + } = + account?.type === 'local' + ? ((await prepareTransactionRequest( + client, + args as PrepareTransactionRequestParameters, + )) as EstimateGasParameters) + : args + + const blockNumberHex = blockNumber ? numberToHex(blockNumber) : undefined + const block = blockNumberHex || blockTag + + assertRequest(args as AssertRequestParameters) + + const chainFormat = client.chain?.formatters?.transactionRequest?.format + const format = chainFormat || formatTransactionRequest + + const request = format({ + // Pick out extra data that might exist on the chain's transaction request type. + ...extract(rest, { format: chainFormat }), + from: account?.address, + accessList, + data, + gas, + gasPrice, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + to, + value, + } as TransactionRequest) + + type LineaEstimateGasSchema = Filter< + LineaRpcSchema, + { Method: 'linea_estimateGas' } + >[0] + const { baseFeePerGas, gasLimit, priorityFeePerGas } = + await client.request({ + method: 'linea_estimateGas', + params: block ? [request, block] : [request], + }) + return { + baseFeePerGas: BigInt(baseFeePerGas), + gasLimit: BigInt(gasLimit), + priorityFeePerGas: BigInt(priorityFeePerGas), + } + } catch (err) { + throw getCallError(err as BaseError, { + ...args, + account, + chain: client.chain, + }) + } +} diff --git a/src/linea/chainConfig.test.ts b/src/linea/chainConfig.test.ts new file mode 100644 index 0000000000..3a48b58d97 --- /dev/null +++ b/src/linea/chainConfig.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, test, vi } from 'vitest' +import { address } from '../../test/src/constants.js' +import { internal_estimateFeesPerGas } from '../actions/public/estimateFeesPerGas.js' +import { createClient } from '../clients/createClient.js' +import { http } from '../clients/transports/http.js' +import { parseEther } from '../utils/index.js' +import * as estimateGas from './actions/estimateGas.js' +import * as chainConfig from './chainConfig.js' +import { lineaSepolia } from './chains.js' + +const client = createClient({ + chain: lineaSepolia, + transport: http(), +}) + +describe('estimateFeesPerGas', () => { + test('default', async () => { + const spy = vi.spyOn(estimateGas, 'estimateGas') + const { maxFeePerGas, maxPriorityFeePerGas } = + await internal_estimateFeesPerGas(client, { + request: { + account: address.burn, + to: address.burn, + value: parseEther('0.0001'), + }, + }) + expect(spy).toBeCalledWith(client, { + account: address.burn, + to: address.burn, + value: parseEther('0.0001'), + }) + expect(maxFeePerGas).toBeDefined() + expect(maxPriorityFeePerGas).toBeDefined() + }) + + test('behavior: `estimateFeesPerGas` returns null', async () => { + vi.spyOn(estimateGas, 'estimateGas').mockRejectedValueOnce(new Error()) + const spy = vi.spyOn(chainConfig.chainConfig.fees, 'estimateFeesPerGas') + const { maxFeePerGas, maxPriorityFeePerGas } = + await internal_estimateFeesPerGas(client, { + request: { + account: address.burn, + to: address.burn, + value: parseEther('0.0001'), + }, + }) + expect(spy).toHaveReturnedWith(null) + expect(maxFeePerGas).toBeDefined() + expect(maxPriorityFeePerGas).toBeDefined() + }) + + test('behavior: `maxPriorityFeePerGas` returns null', async () => { + vi.spyOn(estimateGas, 'estimateGas').mockRejectedValue(new Error()) + const spy = vi.spyOn(chainConfig.chainConfig.fees, 'maxPriorityFeePerGas') + const { maxFeePerGas, maxPriorityFeePerGas } = + await internal_estimateFeesPerGas(client, { + request: { + account: address.burn, + to: address.burn, + value: parseEther('0.0001'), + }, + }) + expect(spy).toHaveReturnedWith(null) + expect(maxFeePerGas).toBeDefined() + expect(maxPriorityFeePerGas).toBeDefined() + }) +}) diff --git a/src/linea/chainConfig.ts b/src/linea/chainConfig.ts new file mode 100644 index 0000000000..bffbb769c7 --- /dev/null +++ b/src/linea/chainConfig.ts @@ -0,0 +1,53 @@ +import type { ChainConfig, ChainEstimateFeesPerGasFn } from '../types/chain.js' +import { estimateGas } from './actions/estimateGas.js' + +export const chainConfig = { + fees: { + estimateFeesPerGas, + async maxPriorityFeePerGas({ block, client, request }) { + const response = await estimateFeesPerGas({ + block, + client, + multiply: (x) => x, + request, + type: 'eip1559', + }) + // Returning `null` will trigger the base `estimateMaxPriorityFeePerGas` to perform + // fallback mechanisms to estimate priority fee. + if (!response?.maxPriorityFeePerGas) return null + return response.maxPriorityFeePerGas + }, + }, +} as const satisfies ChainConfig + +/////////////////////////////////////////////////////////////////////////// +// Internal +/////////////////////////////////////////////////////////////////////////// + +async function estimateFeesPerGas({ + client, + multiply, + request, + type, +}: Parameters[0]): ReturnType { + try { + const response = await estimateGas(client, { + ...request, + account: request?.account!, + }) + const { priorityFeePerGas: maxPriorityFeePerGas } = response + + const baseFeePerGas = multiply(BigInt(response.baseFeePerGas)) + const maxFeePerGas = baseFeePerGas + maxPriorityFeePerGas + + if (type === 'legacy') return { gasPrice: maxFeePerGas } + return { + maxFeePerGas, + maxPriorityFeePerGas, + } + } catch { + // Returning `null` will trigger the base `estimateFeesPerGas` to perform + // fallback mechanisms to estimate fees. + return null + } +} diff --git a/src/linea/chains.ts b/src/linea/chains.ts new file mode 100644 index 0000000000..a305d0fd6b --- /dev/null +++ b/src/linea/chains.ts @@ -0,0 +1,3 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { linea } from '../chains/definitions/linea.js' +export { lineaSepolia } from '../chains/definitions/lineaSepolia.js' diff --git a/src/linea/index.ts b/src/linea/index.ts new file mode 100644 index 0000000000..4e3abe2331 --- /dev/null +++ b/src/linea/index.ts @@ -0,0 +1,8 @@ +// biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + estimateGas, + type EstimateGasParameters, + type EstimateGasReturnType, +} from './actions/estimateGas.js' + +export { linea, lineaSepolia } from './chains.js' diff --git a/src/linea/package.json b/src/linea/package.json new file mode 100644 index 0000000000..ef7e268f2e --- /dev/null +++ b/src/linea/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "types": "../_types/linea/index.d.ts", + "module": "../_esm/linea/index.js", + "main": "../_cjs/linea/index.js" +} diff --git a/src/linea/types/rpc.ts b/src/linea/types/rpc.ts new file mode 100644 index 0000000000..d61570d600 --- /dev/null +++ b/src/linea/types/rpc.ts @@ -0,0 +1,28 @@ +import type { BlockNumber, BlockTag } from '../../types/block.js' +import type { Hex } from '../../types/misc.js' +import type { + RpcStateOverride, + RpcTransactionRequest, +} from '../../types/rpc.js' + +export type LineaRpcSchema = [ + { + Method: 'linea_estimateGas' + Parameters?: + | [transaction: RpcTransactionRequest] + | [ + transaction: RpcTransactionRequest, + block: Hex | BlockNumber | BlockTag, + ] + | [ + transaction: RpcTransactionRequest, + block: BlockNumber | BlockTag, + stateOverride: RpcStateOverride, + ] + ReturnType: { + gasLimit: Hex + baseFeePerGas: Hex + priorityFeePerGas: Hex + } + }, +] diff --git a/src/op-stack/abis.ts b/src/op-stack/abis.ts index 6d887bdcf8..9d4dc02f2c 100644 --- a/src/op-stack/abis.ts +++ b/src/op-stack/abis.ts @@ -913,101 +913,119 @@ export const portal2Abi = [ stateMutability: 'nonpayable', type: 'constructor', }, - { inputs: [], name: 'BadTarget', type: 'error' }, - { inputs: [], name: 'CallPaused', type: 'error' }, - { inputs: [], name: 'GasEstimation', type: 'error' }, - { inputs: [], name: 'LargeCalldata', type: 'error' }, - { inputs: [], name: 'OutOfGas', type: 'error' }, - { inputs: [], name: 'SmallGasLimit', type: 'error' }, - { inputs: [], name: 'Unauthorized', type: 'error' }, { - anonymous: false, - inputs: [ - { indexed: false, internalType: 'uint8', name: 'version', type: 'uint8' }, - ], - name: 'Initialized', - type: 'event', + stateMutability: 'payable', + type: 'receive', }, { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + inputs: [], + name: 'balance', + outputs: [ { - indexed: true, internalType: 'uint256', - name: 'version', + name: '', type: 'uint256', }, - { - indexed: false, - internalType: 'bytes', - name: 'opaqueData', - type: 'bytes', - }, ], - name: 'TransactionDeposited', - type: 'event', + stateMutability: 'view', + type: 'function', }, { - anonymous: false, inputs: [ { - indexed: true, - internalType: 'bytes32', - name: 'withdrawalHash', - type: 'bytes32', + internalType: 'contract IDisputeGame', + name: '_disputeGame', + type: 'address', }, - { indexed: false, internalType: 'bool', name: 'success', type: 'bool' }, ], - name: 'WithdrawalFinalized', - type: 'event', + name: 'blacklistDisputeGame', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', }, { - anonymous: false, inputs: [ { - indexed: true, internalType: 'bytes32', - name: 'withdrawalHash', + name: '_withdrawalHash', type: 'bytes32', }, - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - ], - name: 'WithdrawalProven', - type: 'event', - }, - { - inputs: [ { - internalType: 'contract IDisputeGame', - name: '_disputeGame', + internalType: 'address', + name: '_proofSubmitter', type: 'address', }, ], - name: 'blacklistDisputeGame', + name: 'checkWithdrawal', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'view', type: 'function', }, { inputs: [ - { internalType: 'bytes32', name: '_withdrawalHash', type: 'bytes32' }, - { internalType: 'address', name: '_proofSubmitter', type: 'address' }, + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_mint', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + { + internalType: 'uint64', + name: '_gasLimit', + type: 'uint64', + }, + { + internalType: 'bool', + name: '_isCreation', + type: 'bool', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, ], - name: 'checkWithdrawal', + name: 'depositERC20Transaction', outputs: [], - stateMutability: 'view', + stateMutability: 'nonpayable', type: 'function', }, { inputs: [ - { internalType: 'address', name: '_to', type: 'address' }, - { internalType: 'uint256', name: '_value', type: 'uint256' }, - { internalType: 'uint64', name: '_gasLimit', type: 'uint64' }, - { internalType: 'bool', name: '_isCreation', type: 'bool' }, - { internalType: 'bytes', name: '_data', type: 'bytes' }, + { + internalType: 'address', + name: '_to', + type: 'address', + }, + { + internalType: 'uint256', + name: '_value', + type: 'uint256', + }, + { + internalType: 'uint64', + name: '_gasLimit', + type: 'uint64', + }, + { + internalType: 'bool', + name: '_isCreation', + type: 'bool', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, ], name: 'depositTransaction', outputs: [], @@ -1016,10 +1034,20 @@ export const portal2Abi = [ }, { inputs: [ - { internalType: 'contract IDisputeGame', name: '', type: 'address' }, + { + internalType: 'contract IDisputeGame', + name: '', + type: 'address', + }, ], name: 'disputeGameBlacklist', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], stateMutability: 'view', type: 'function', }, @@ -1039,7 +1067,13 @@ export const portal2Abi = [ { inputs: [], name: 'disputeGameFinalityDelaySeconds', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], stateMutability: 'view', type: 'function', }, @@ -1054,12 +1088,36 @@ export const portal2Abi = [ inputs: [ { components: [ - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'address', name: 'sender', type: 'address' }, - { internalType: 'address', name: 'target', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'gasLimit', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, ], internalType: 'struct Types.WithdrawalTransaction', name: '_tx', @@ -1075,18 +1133,46 @@ export const portal2Abi = [ inputs: [ { components: [ - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'address', name: 'sender', type: 'address' }, - { internalType: 'address', name: 'target', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'gasLimit', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, ], internalType: 'struct Types.WithdrawalTransaction', name: '_tx', type: 'tuple', }, - { internalType: 'address', name: '_proofSubmitter', type: 'address' }, + { + internalType: 'address', + name: '_proofSubmitter', + type: 'address', + }, ], name: 'finalizeWithdrawalTransactionExternalProof', outputs: [], @@ -1094,16 +1180,34 @@ export const portal2Abi = [ type: 'function', }, { - inputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], name: 'finalizedWithdrawals', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'guardian', - outputs: [{ internalType: 'address', name: '', type: 'address' }], + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], stateMutability: 'view', type: 'function', }, @@ -1138,23 +1242,51 @@ export const portal2Abi = [ { inputs: [], name: 'l2Sender', - outputs: [{ internalType: 'address', name: '', type: 'address' }], + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], stateMutability: 'view', type: 'function', }, { - inputs: [{ internalType: 'uint64', name: '_byteCount', type: 'uint64' }], + inputs: [ + { + internalType: 'uint64', + name: '_byteCount', + type: 'uint64', + }, + ], name: 'minimumGasLimit', - outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], + outputs: [ + { + internalType: 'uint64', + name: '', + type: 'uint64', + }, + ], stateMutability: 'pure', type: 'function', }, { inputs: [ - { internalType: 'bytes32', name: '_withdrawalHash', type: 'bytes32' }, + { + internalType: 'bytes32', + name: '_withdrawalHash', + type: 'bytes32', + }, ], name: 'numProofSubmitters', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], stateMutability: 'view', type: 'function', }, @@ -1162,9 +1294,21 @@ export const portal2Abi = [ inputs: [], name: 'params', outputs: [ - { internalType: 'uint128', name: 'prevBaseFee', type: 'uint128' }, - { internalType: 'uint64', name: 'prevBoughtGas', type: 'uint64' }, - { internalType: 'uint64', name: 'prevBlockNum', type: 'uint64' }, + { + internalType: 'uint128', + name: 'prevBaseFee', + type: 'uint128', + }, + { + internalType: 'uint64', + name: 'prevBoughtGas', + type: 'uint64', + }, + { + internalType: 'uint64', + name: 'prevBlockNum', + type: 'uint64', + }, ], stateMutability: 'view', type: 'function', @@ -1172,24 +1316,50 @@ export const portal2Abi = [ { inputs: [], name: 'paused', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'proofMaturityDelaySeconds', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], stateMutability: 'view', type: 'function', }, { inputs: [ - { internalType: 'bytes32', name: '', type: 'bytes32' }, - { internalType: 'uint256', name: '', type: 'uint256' }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, ], name: 'proofSubmitters', - outputs: [{ internalType: 'address', name: '', type: 'address' }], + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], stateMutability: 'view', type: 'function', }, @@ -1197,34 +1367,78 @@ export const portal2Abi = [ inputs: [ { components: [ - { internalType: 'uint256', name: 'nonce', type: 'uint256' }, - { internalType: 'address', name: 'sender', type: 'address' }, - { internalType: 'address', name: 'target', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'uint256', name: 'gasLimit', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - ], - internalType: 'struct Types.WithdrawalTransaction', - name: '_tx', - type: 'tuple', + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'address', + name: 'target', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'gasLimit', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct Types.WithdrawalTransaction', + name: '_tx', + type: 'tuple', + }, + { + internalType: 'uint256', + name: '_disputeGameIndex', + type: 'uint256', }, - { internalType: 'uint256', name: '_disputeGameIndex', type: 'uint256' }, { components: [ - { internalType: 'bytes32', name: 'version', type: 'bytes32' }, - { internalType: 'bytes32', name: 'stateRoot', type: 'bytes32' }, + { + internalType: 'bytes32', + name: 'version', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'stateRoot', + type: 'bytes32', + }, { internalType: 'bytes32', name: 'messagePasserStorageRoot', type: 'bytes32', }, - { internalType: 'bytes32', name: 'latestBlockhash', type: 'bytes32' }, + { + internalType: 'bytes32', + name: 'latestBlockhash', + type: 'bytes32', + }, ], internalType: 'struct Types.OutputRootProof', name: '_outputRootProof', type: 'tuple', }, - { internalType: 'bytes[]', name: '_withdrawalProof', type: 'bytes[]' }, + { + internalType: 'bytes[]', + name: '_withdrawalProof', + type: 'bytes[]', + }, ], name: 'proveWithdrawalTransaction', outputs: [], @@ -1233,8 +1447,16 @@ export const portal2Abi = [ }, { inputs: [ - { internalType: 'bytes32', name: '', type: 'bytes32' }, - { internalType: 'address', name: '', type: 'address' }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, ], name: 'provenWithdrawals', outputs: [ @@ -1243,7 +1465,11 @@ export const portal2Abi = [ name: 'disputeGameProxy', type: 'address', }, - { internalType: 'uint64', name: 'timestamp', type: 'uint64' }, + { + internalType: 'uint64', + name: 'timestamp', + type: 'uint64', + }, ], stateMutability: 'view', type: 'function', @@ -1251,19 +1477,65 @@ export const portal2Abi = [ { inputs: [], name: 'respectedGameType', - outputs: [{ internalType: 'GameType', name: '', type: 'uint32' }], + outputs: [ + { + internalType: 'GameType', + name: '', + type: 'uint32', + }, + ], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'respectedGameTypeUpdatedAt', - outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], + outputs: [ + { + internalType: 'uint64', + name: '', + type: 'uint64', + }, + ], stateMutability: 'view', type: 'function', }, { - inputs: [{ internalType: 'GameType', name: '_gameType', type: 'uint32' }], + inputs: [ + { + internalType: 'address', + name: '_token', + type: 'address', + }, + { + internalType: 'uint8', + name: '_decimals', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: '_name', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: '_symbol', + type: 'bytes32', + }, + ], + name: 'setGasPayingToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'GameType', + name: '_gameType', + type: 'uint32', + }, + ], name: 'setRespectedGameType', outputs: [], stateMutability: 'nonpayable', @@ -1273,7 +1545,11 @@ export const portal2Abi = [ inputs: [], name: 'superchainConfig', outputs: [ - { internalType: 'contract SuperchainConfig', name: '', type: 'address' }, + { + internalType: 'contract SuperchainConfig', + name: '', + type: 'address', + }, ], stateMutability: 'view', type: 'function', @@ -1282,7 +1558,11 @@ export const portal2Abi = [ inputs: [], name: 'systemConfig', outputs: [ - { internalType: 'contract SystemConfig', name: '', type: 'address' }, + { + internalType: 'contract SystemConfig', + name: '', + type: 'address', + }, ], stateMutability: 'view', type: 'function', @@ -1290,11 +1570,280 @@ export const portal2Abi = [ { inputs: [], name: 'version', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'pure', type: 'function', }, - { stateMutability: 'payable', type: 'receive' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'contract IDisputeGame', + name: 'disputeGame', + type: 'address', + }, + ], + name: 'DisputeGameBlacklisted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint8', + name: 'version', + type: 'uint8', + }, + ], + name: 'Initialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'GameType', + name: 'newGameType', + type: 'uint32', + }, + { + indexed: true, + internalType: 'Timestamp', + name: 'updatedAt', + type: 'uint64', + }, + ], + name: 'RespectedGameTypeSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'version', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: 'opaqueData', + type: 'bytes', + }, + ], + name: 'TransactionDeposited', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'withdrawalHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'bool', + name: 'success', + type: 'bool', + }, + ], + name: 'WithdrawalFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'withdrawalHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'WithdrawalProven', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'withdrawalHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'proofSubmitter', + type: 'address', + }, + ], + name: 'WithdrawalProvenExtension1', + type: 'event', + }, + { + inputs: [], + name: 'AlreadyFinalized', + type: 'error', + }, + { + inputs: [], + name: 'BadTarget', + type: 'error', + }, + { + inputs: [], + name: 'Blacklisted', + type: 'error', + }, + { + inputs: [], + name: 'CallPaused', + type: 'error', + }, + { + inputs: [], + name: 'ContentLengthMismatch', + type: 'error', + }, + { + inputs: [], + name: 'EmptyItem', + type: 'error', + }, + { + inputs: [], + name: 'GasEstimation', + type: 'error', + }, + { + inputs: [], + name: 'InvalidDataRemainder', + type: 'error', + }, + { + inputs: [], + name: 'InvalidDisputeGame', + type: 'error', + }, + { + inputs: [], + name: 'InvalidGameType', + type: 'error', + }, + { + inputs: [], + name: 'InvalidHeader', + type: 'error', + }, + { + inputs: [], + name: 'InvalidMerkleProof', + type: 'error', + }, + { + inputs: [], + name: 'InvalidProof', + type: 'error', + }, + { + inputs: [], + name: 'LargeCalldata', + type: 'error', + }, + { + inputs: [], + name: 'NoValue', + type: 'error', + }, + { + inputs: [], + name: 'NonReentrant', + type: 'error', + }, + { + inputs: [], + name: 'OnlyCustomGasToken', + type: 'error', + }, + { + inputs: [], + name: 'OutOfGas', + type: 'error', + }, + { + inputs: [], + name: 'ProposalNotValidated', + type: 'error', + }, + { + inputs: [], + name: 'SmallGasLimit', + type: 'error', + }, + { + inputs: [], + name: 'TransferFailed', + type: 'error', + }, + { + inputs: [], + name: 'Unauthorized', + type: 'error', + }, + { + inputs: [], + name: 'UnexpectedList', + type: 'error', + }, + { + inputs: [], + name: 'UnexpectedString', + type: 'error', + }, + { + inputs: [], + name: 'Unproven', + type: 'error', + }, ] as const export const portalAbi = [ diff --git a/src/op-stack/actions/getTimeToFinalize.ts b/src/op-stack/actions/getTimeToFinalize.ts index b997dca74d..4839f4e476 100644 --- a/src/op-stack/actions/getTimeToFinalize.ts +++ b/src/op-stack/actions/getTimeToFinalize.ts @@ -128,11 +128,18 @@ export async function getTimeToFinalize< return { period: Number(period), seconds, timestamp } } + const numProofSubmitters = await readContract(client, { + abi: portal2Abi, + address: portalAddress, + functionName: 'numProofSubmitters', + args: [withdrawalHash], + }).catch(() => 1n) + const proofSubmitter = await readContract(client, { abi: portal2Abi, address: portalAddress, functionName: 'proofSubmitters', - args: [withdrawalHash, 0n], + args: [withdrawalHash, numProofSubmitters - 1n], }).catch(() => undefined) const [[_disputeGameProxy, proveTimestamp], proofMaturityDelaySeconds] = diff --git a/src/op-stack/actions/getWithdrawalStatus.ts b/src/op-stack/actions/getWithdrawalStatus.ts index 5f2d62e6de..014eaf32fc 100644 --- a/src/op-stack/actions/getWithdrawalStatus.ts +++ b/src/op-stack/actions/getWithdrawalStatus.ts @@ -197,11 +197,18 @@ export async function getWithdrawalStatus< return seconds > 0 ? 'waiting-to-finalize' : 'ready-to-finalize' } + const numProofSubmitters = await readContract(client, { + abi: portal2Abi, + address: portalAddress, + functionName: 'numProofSubmitters', + args: [withdrawal.withdrawalHash], + }).catch(() => 1n) + const proofSubmitter = await readContract(client, { abi: portal2Abi, address: portalAddress, functionName: 'proofSubmitters', - args: [withdrawal.withdrawalHash, 0n], + args: [withdrawal.withdrawalHash, numProofSubmitters - 1n], }).catch(() => withdrawal.sender) const [disputeGameResult, checkWithdrawalResult, finalizedResult] = @@ -238,6 +245,7 @@ export async function getWithdrawalStatus< if (error.cause instanceof ContractFunctionRevertedError) { const errorMessage = error.cause.data?.args?.[0] if ( + errorMessage === 'OptimismPortal: invalid game type' || errorMessage === 'OptimismPortal: withdrawal has not been proven yet' || errorMessage === 'OptimismPortal: withdrawal has not been proven by proof submitter address yet' @@ -251,6 +259,9 @@ export async function getWithdrawalStatus< errorMessage === 'OptimismPortal: output proposal in air-gap' ) return 'waiting-to-finalize' + + if (error.cause.data?.errorName === 'InvalidGameType') + return 'ready-to-prove' } throw checkWithdrawalResult.reason } diff --git a/src/op-stack/actions/proveWithdrawal.test.ts b/src/op-stack/actions/proveWithdrawal.test.ts index 95dff29968..7cae530483 100644 --- a/src/op-stack/actions/proveWithdrawal.test.ts +++ b/src/op-stack/actions/proveWithdrawal.test.ts @@ -138,7 +138,7 @@ test('error: small gas', async () => { gas: 69n, }), ).rejects.toThrowErrorMatchingInlineSnapshot(` - [TransactionExecutionError: The amount of gas (69) provided for the transaction is too low. + [ContractFunctionExecutionError: The amount of gas (69) provided for the transaction is too low. Request Arguments: chain: Ethereum (Local) (id: 1) @@ -146,7 +146,14 @@ test('error: small gas', async () => { to: 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed data: 0x4870496f00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000011b1000000000000000000000000000000000000000000000000000000000000000092822c772346d9c8ad1c28885a736de9189a4523e0c79639831a4eed651d837f04091eb8ad4eb056aff2749c1d17b1ed1a0cdcd44f9d7a539ffd56e4b2b4e1f867319b70138527b1087a535099cf8a4db4692ca7cee16b7a3ebd950408ed610a00000000000000000000000000000000000000000000000000000000000003800001000000000000000000000000000000000000000000000000000000002d49000000000000000000000000420000000000000000000000000000000000000700000000000000000000000025ace71c97b33cc4729cf772ae268934f7ab5fa1000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000004638800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001a4d764ad0b0001000000000000000000000000000000000000000000000000000000002d49000000000000000000000000420000000000000000000000000000000000001000000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be1000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000bcce5f55dfda11600e48e91598ad0f8645466142000000000000000000000000bcce5f55dfda11600e48e91598ad0f8645466142000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000076000000000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000000000000000000000000000214f90211a07dd255038ced20e27bd9c823d53dff05dab9b56f47efec4d1373c6af4fef5989a0a72a37936fd968f361c541a4d5374d3862f0c3e6125b095158982b8eca440da0a0bb0cf61ec3b7954fa2f09263e712f1b9ed681dcab015932294512ad8b288b90da0d91dbd89baf8206e4952fa0f183cb76fee20ad8ab6d0c200053c4ed3e64d1b32a0ab94dcdd454eb74ece1d1fc6bee90a2cc9468a9be3500d774328cced5dd136dca0fa51148073d2fd37bea62e78e31d2f29f95bacdd72ee987ecbbce8fa98433681a0a7ad5e83e3e1b8d2e85dc249a99fbcff1673cf55c9915c879ac139ac0bf26dd5a0c5766a7cdac0498fd865d735ab9085a3a4163e2338c422e33c4a6b4d1ad0e9afa073b391a00e484b6729f77c11aa424511c8618c34a23dddbfc0fe0265dc4eb4cfa01b50892e8e4ecbfd5bc0cf53604f6f23cd706dc79719972d62be28e627ae287ca05658e252128bfb8ff743644e9610400bebc0264a5a1511469e9d35088c601437a06b5301e3158ca1288125e03b486e99d23baaa5706858ed39488854e197de81bba098a631fd53cccaaafc3b2e217a85cc4243fec1de269dab5135dede898a56993da0b72713ea8fc5cf977ed19945cd080171f40ea428fe438b99ffa70e086f2e38a1a0be5a3eadca25a2ed6a9b09f8d4203ad9bf76627a241756240cb241a5d7bfd422a0453403dd41482ab5064f94fa2d2e96182fb8b3b9f85473d300689c63f46f2441800000000000000000000000000000000000000000000000000000000000000000000000000000000000000214f90211a0a0ab3e1601d6549af5c32a5c38fef42f0866f24fc16ce70f5d6119733b32ce1ba0f92fcd0021c63ab9136e6df54db3ab735dc267ca8e5725c1c127c107c0d3fc83a0efeb2ad3839ca38f72fbb877c158ade23d7d62b49971abbbe874ae4d44298cdba0d5435ee90a9a1f4c369a0228fcef2d70f8e45d11bdb7bd7b823eb2e265824ac1a033a919415c4532290ca370f71492f352c9c729e4012955d461d525cb8b0faa3ca05cc9620184e8a396cacd0c4f76d25fd4ef80831b3717bfe1d89a0753c544a46fa059baaf0e765b6ff0c9488f7afa06071b9eda81a13b2db2762287c26623f8b27ca04735c39e8cd3d267e6e91f867c2b7120c86ff0c98c4cccd67dfa634f0870f6eda02a80ec76fee519323b8b553e071480d28ca693f95a21b82a48c70adc8155dcbba07809ffaff9ca0875ef1f6e1f84584e2fc79fc2054a47c150aea03f02dabf5cfaa00775eb64cd0add1462a1d0d762424f60fd5faed324f51d48d709ed564cc6d494a011c3b1c19b83e86d587900cfe3a3e2d8534a5c705bad96e68ef3ca0126cbe6a0a0459db754e27d481108d0bfe242f492f06e317437700d860d8a171b961acb3be1a058c7be7e1965ecae30b844bf8676adc851d6af299c6bccaf0856bec60776b01fa05a5ddb72a2a98858c0b4120d728947537bdcc9ab061c4b8da0f684576822bebca07e38f091de1b9e0fcf00f9fdfdec18eb34bfd3996c88329c4cd2d916dbf29cdc800000000000000000000000000000000000000000000000000000000000000000000000000000000000000214f90211a091e1c27400a43c5a5c197c19c9f9762fa99615f751f093ec268dcde5a0e46363a07c4dff1acc35fa31805556cda36b9618263faecf49a5b287b97fe39a13408c8da03c37d2c5a2f388350546e74c4593236849543f6476aa70f034d88ecc43e1d190a0abaf9651fa71053aa953bdc895c75969f82ed3569d9f001a7f7be66a92b1e6c9a04dfc96da68c1d49908f89f5a9bed4f65c514d1e2193ff1126f9700952e4daceda0ceb6d263009c644f0a348d951e12185bafe238e985432fb5a0deb94fa9a3b2b3a0eb493209507df91c53c45366178061b03226000cf2a8c4ef88fc4e809ae82cd0a064006be53d6f88a198080f749ffb1d6743843c63d3e6f6e395f103c572c73796a0466c8bea652668720b53de631bc1d16935bfaa85c730f6f7d19fcbe4704ab047a0c2792da5608db91851be4114546902cb4cbebea053665b1329c1e73f24e72d48a05fdd0ade55a0571d508274576bcd7a2ced913e77534ff267b3e60368b2ee95c5a0b574398c5e6640048b26a7ca2298105f027dd3561652a1f1fa9ba1c01ed0057fa0d1a98317c3dee409a6178771fc67378e3a14197f4f9f9e5aed1c6b05584d3f48a0e9abf8d9df852a45a5310777b46fbdfa0876e83063a94bc91096c4d5bb8385dba0f831723d52c0b60b61bb830c9a33c9f9b2d83830c3ed570e5da44ae6ee80a953a0333636ac068b435c011fd4e7d30dc52a8bbaf8e9d861a95eee4d3e512ff839c58000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b4f901b1a0e480ad00d97a48b6ecdbf027135399615123578f3de7e572259000b946f4c87080a09d2298b1328a8afd6b47f0bd57a1332011ab02614a86ef6b544baf61e425ba9ea05713276bc96f85c79bb9f4e4ef517d5bafe56db6775fd27f757981fe94846aa4a02f787118beba540f07c1fd3b488628ee0fa47694aa5eb1d86405ff25b3d6f66b80a09a628c00eebfe343a8f4a7072aa6ee968eea22a6dde4ac3a29d65bdaae854758a01ffd70ab795cbc879376990fad07f95ec2bf6dc9a51ae3603bfd5f321dc7474aa0cf82883dc01744467fa15bec5689b559b70aa63c6d341548676605e927a102cba0fdb90a7114f2137e15ac8915bf54727cd5d0dead26962eefe4ab2499ec6b5c65a00909bea4f700704cda454c330e2a88f73ebd6a7d7e8fef4204397e154953de99a0bbab7f75e0804aaee0f2761a49579f08820eb074f5ff9320ff5f48383975079880a0b3663141987995925fed9ef86f8fc02a44a42136645714891831ccdf1e08c68ea00a23286f92dbcd146255c6c2cc880990cbd694894653701169c07f6098d9573da0d8a58420dc5d85d4150c2bc6fcae28eb3a843d92aaba1274161e12554c389b8d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000022e19f20418ffb24ba711dfecd671b4054aa2e87efe3d10484b88078ceef79373c6001000000000000000000000000000000000000000000000000000000000000 gas: 69 - + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: proveWithdrawalTransaction((uint256 nonce, address sender, address target, uint256 value, uint256 gasLimit, bytes data), uint256 _l2OutputIndex, (bytes32 version, bytes32 stateRoot, bytes32 messagePasserStorageRoot, bytes32 latestBlockhash), bytes[] _withdrawalProof) + args: ({"data":"0xd764ad0b0001000000000000000000000000000000000000000000000000000000002d49000000000000000000000000420000000000000000000000000000000000001000000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be1000000000000000000000000000000000000000000000000002e2f6e5e148000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000bcce5f55dfda11600e48e91598ad0f8645466142000000000000000000000000bcce5f55dfda11600e48e91598ad0f8645466142000000000000000000000000000000000000000000000000002e2f6e5e1480000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","gasLimit":"287624","nonce":"1766847064778384329583297500742918515827483896875618958121606201292631369","sender":"0x4200000000000000000000000000000000000007","target":"0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1","value":"13000000000000000"}, 4529, {"latestBlockhash":"0x67319b70138527b1087a535099cf8a4db4692ca7cee16b7a3ebd950408ed610a","messagePasserStorageRoot":"0x04091eb8ad4eb056aff2749c1d17b1ed1a0cdcd44f9d7a539ffd56e4b2b4e1f8","stateRoot":"0x92822c772346d9c8ad1c28885a736de9189a4523e0c79639831a4eed651d837f","version":"0x0000000000000000000000000000000000000000000000000000000000000000"}, ["0xf90211a07dd255038ced20e27bd9c823d53dff05dab9b56f47efec4d1373c6af4fef5989a0a72a37936fd968f361c541a4d5374d3862f0c3e6125b095158982b8eca440da0a0bb0cf61ec3b7954fa2f09263e712f1b9ed681dcab015932294512ad8b288b90da0d91dbd89baf8206e4952fa0f183cb76fee20ad8ab6d0c200053c4ed3e64d1b32a0ab94dcdd454eb74ece1d1fc6bee90a2cc9468a9be3500d774328cced5dd136dca0fa51148073d2fd37bea62e78e31d2f29f95bacdd72ee987ecbbce8fa98433681a0a7ad5e83e3e1b8d2e85dc249a99fbcff1673cf55c9915c879ac139ac0bf26dd5a0c5766a7cdac0498fd865d735ab9085a3a4163e2338c422e33c4a6b4d1ad0e9afa073b391a00e484b6729f77c11aa424511c8618c34a23dddbfc0fe0265dc4eb4cfa01b50892e8e4ecbfd5bc0cf53604f6f23cd706dc79719972d62be28e627ae287ca05658e252128bfb8ff743644e9610400bebc0264a5a1511469e9d35088c601437a06b5301e3158ca1288125e03b486e99d23baaa5706858ed39488854e197de81bba098a631fd53cccaaafc3b2e217a85cc4243fec1de269dab5135dede898a56993da0b72713ea8fc5cf977ed19945cd080171f40ea428fe438b99ffa70e086f2e38a1a0be5a3eadca25a2ed6a9b09f8d4203ad9bf76627a241756240cb241a5d7bfd422a0453403dd41482ab5064f94fa2d2e96182fb8b3b9f85473d300689c63f46f244180","0xf90211a0a0ab3e1601d6549af5c32a5c38fef42f0866f24fc16ce70f5d6119733b32ce1ba0f92fcd0021c63ab9136e6df54db3ab735dc267ca8e5725c1c127c107c0d3fc83a0efeb2ad3839ca38f72fbb877c158ade23d7d62b49971abbbe874ae4d44298cdba0d5435ee90a9a1f4c369a0228fcef2d70f8e45d11bdb7bd7b823eb2e265824ac1a033a919415c4532290ca370f71492f352c9c729e4012955d461d525cb8b0faa3ca05cc9620184e8a396cacd0c4f76d25fd4ef80831b3717bfe1d89a0753c544a46fa059baaf0e765b6ff0c9488f7afa06071b9eda81a13b2db2762287c26623f8b27ca04735c39e8cd3d267e6e91f867c2b7120c86ff0c98c4cccd67dfa634f0870f6eda02a80ec76fee519323b8b553e071480d28ca693f95a21b82a48c70adc8155dcbba07809ffaff9ca0875ef1f6e1f84584e2fc79fc2054a47c150aea03f02dabf5cfaa00775eb64cd0add1462a1d0d762424f60fd5faed324f51d48d709ed564cc6d494a011c3b1c19b83e86d587900cfe3a3e2d8534a5c705bad96e68ef3ca0126cbe6a0a0459db754e27d481108d0bfe242f492f06e317437700d860d8a171b961acb3be1a058c7be7e1965ecae30b844bf8676adc851d6af299c6bccaf0856bec60776b01fa05a5ddb72a2a98858c0b4120d728947537bdcc9ab061c4b8da0f684576822bebca07e38f091de1b9e0fcf00f9fdfdec18eb34bfd3996c88329c4cd2d916dbf29cdc80","0xf90211a091e1c27400a43c5a5c197c19c9f9762fa99615f751f093ec268dcde5a0e46363a07c4dff1acc35fa31805556cda36b9618263faecf49a5b287b97fe39a13408c8da03c37d2c5a2f388350546e74c4593236849543f6476aa70f034d88ecc43e1d190a0abaf9651fa71053aa953bdc895c75969f82ed3569d9f001a7f7be66a92b1e6c9a04dfc96da68c1d49908f89f5a9bed4f65c514d1e2193ff1126f9700952e4daceda0ceb6d263009c644f0a348d951e12185bafe238e985432fb5a0deb94fa9a3b2b3a0eb493209507df91c53c45366178061b03226000cf2a8c4ef88fc4e809ae82cd0a064006be53d6f88a198080f749ffb1d6743843c63d3e6f6e395f103c572c73796a0466c8bea652668720b53de631bc1d16935bfaa85c730f6f7d19fcbe4704ab047a0c2792da5608db91851be4114546902cb4cbebea053665b1329c1e73f24e72d48a05fdd0ade55a0571d508274576bcd7a2ced913e77534ff267b3e60368b2ee95c5a0b574398c5e6640048b26a7ca2298105f027dd3561652a1f1fa9ba1c01ed0057fa0d1a98317c3dee409a6178771fc67378e3a14197f4f9f9e5aed1c6b05584d3f48a0e9abf8d9df852a45a5310777b46fbdfa0876e83063a94bc91096c4d5bb8385dba0f831723d52c0b60b61bb830c9a33c9f9b2d83830c3ed570e5da44ae6ee80a953a0333636ac068b435c011fd4e7d30dc52a8bbaf8e9d861a95eee4d3e512ff839c580","0xf901b1a0e480ad00d97a48b6ecdbf027135399615123578f3de7e572259000b946f4c87080a09d2298b1328a8afd6b47f0bd57a1332011ab02614a86ef6b544baf61e425ba9ea05713276bc96f85c79bb9f4e4ef517d5bafe56db6775fd27f757981fe94846aa4a02f787118beba540f07c1fd3b488628ee0fa47694aa5eb1d86405ff25b3d6f66b80a09a628c00eebfe343a8f4a7072aa6ee968eea22a6dde4ac3a29d65bdaae854758a01ffd70ab795cbc879376990fad07f95ec2bf6dc9a51ae3603bfd5f321dc7474aa0cf82883dc01744467fa15bec5689b559b70aa63c6d341548676605e927a102cba0fdb90a7114f2137e15ac8915bf54727cd5d0dead26962eefe4ab2499ec6b5c65a00909bea4f700704cda454c330e2a88f73ebd6a7d7e8fef4204397e154953de99a0bbab7f75e0804aaee0f2761a49579f08820eb074f5ff9320ff5f48383975079880a0b3663141987995925fed9ef86f8fc02a44a42136645714891831ccdf1e08c68ea00a23286f92dbcd146255c6c2cc880990cbd694894653701169c07f6098d9573da0d8a58420dc5d85d4150c2bc6fcae28eb3a843d92aaba1274161e12554c389b8d80","0xe19f20418ffb24ba711dfecd671b4054aa2e87efe3d10484b88078ceef79373c6001"]) + sender: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 + + Docs: https://viem.sh/docs/contract/writeContract Details: intrinsic gas too low Version: viem@x.y.z] `) diff --git a/src/package.json b/src/package.json index 07db113af9..ef265fcdc7 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "viem", "description": "TypeScript Interface for Ethereum", - "version": "2.19.2", + "version": "2.20.1", "type": "module", "main": "./_cjs/index.js", "module": "./_esm/index.js", @@ -70,6 +70,11 @@ "import": "./_esm/experimental/erc7739/index.js", "default": "./_cjs/experimental/erc7739/index.js" }, + "./linea": { + "types": "./_types/linea/index.d.ts", + "import": "./_esm/linea/index.js", + "default": "./_cjs/linea/index.js" + }, "./node": { "types": "./_types/node/index.d.ts", "import": "./_esm/node/index.js", diff --git a/src/types/chain.ts b/src/types/chain.ts index 4eeb91d269..3caeef921b 100644 --- a/src/types/chain.ts +++ b/src/types/chain.ts @@ -58,7 +58,18 @@ export type Chain< sourceId?: number | undefined /** Flag for test networks */ testnet?: boolean | undefined +} & ChainConfig +///////////////////////////////////////////////////////////////////// +// Config +///////////////////////////////////////////////////////////////////// + +export type ChainConfig< + formatters extends ChainFormatters | undefined = ChainFormatters | undefined, + custom extends Record | undefined = + | Record + | undefined, +> = { /** Custom chain data. */ custom?: custom | undefined /** Modifies how fees are derived. */ @@ -70,7 +81,51 @@ export type Chain< } ///////////////////////////////////////////////////////////////////// -// Config +// Fees +///////////////////////////////////////////////////////////////////// + +export type ChainFeesFnParameters< + formatters extends ChainFormatters | undefined = ChainFormatters | undefined, +> = { + /** The latest block. */ + block: Prettify< + FormattedBlock & { formatters: formatters }> + > + client: Client + /** + * A transaction request. This value will be undefined if the caller + * is outside of a transaction request context (e.g. a direct call to + * the `estimateFeesPerGas` Action). + */ + request?: + | PrepareTransactionRequestParameters< + Omit & { formatters: formatters }, + Account | undefined, + undefined + > + | undefined +} + +export type ChainEstimateFeesPerGasFnParameters< + formatters extends ChainFormatters | undefined = ChainFormatters | undefined, +> = { + /** A function to multiply the base fee based on the `baseFeeMultiplier` value. */ + multiply: (x: bigint) => bigint + /** The type of fees to return. */ + type: FeeValuesType +} & ChainFeesFnParameters + +export type ChainEstimateFeesPerGasFn< + formatters extends ChainFormatters | undefined = ChainFormatters | undefined, +> = ( + args: ChainEstimateFeesPerGasFnParameters, +) => Promise + +export type ChainMaxPriorityFeePerGasFn< + formatters extends ChainFormatters | undefined = ChainFormatters | undefined, +> = ( + args: ChainFeesFnParameters, +) => Promise | bigint | null export type ChainFees< formatters extends ChainFormatters | undefined = ChainFormatters | undefined, @@ -90,23 +145,27 @@ export type ChainFees< * * Overrides the return value in the [`estimateMaxPriorityFeePerGas` Action](/docs/actions/public/estimateMaxPriorityFeePerGas). */ + maxPriorityFeePerGas?: + | bigint + | ChainMaxPriorityFeePerGasFn + | undefined + /** @deprecated Use `maxPriorityFeePerGas` instead. */ defaultPriorityFee?: | bigint - | ((args: ChainFeesFnParameters) => Promise | bigint) + | ChainMaxPriorityFeePerGasFn | undefined /** * Allows customization of fee per gas values (e.g. `maxFeePerGas`/`maxPriorityFeePerGas`). * * Overrides the return value in the [`estimateFeesPerGas` Action](/docs/actions/public/estimateFeesPerGas). */ - estimateFeesPerGas?: - | bigint - | (( - args: ChainEstimateFeesPerGasFnParameters, - ) => Promise) - | undefined + estimateFeesPerGas?: ChainEstimateFeesPerGasFn | undefined } +///////////////////////////////////////////////////////////////////// +// Formatters +///////////////////////////////////////////////////////////////////// + export type ChainFormatters = { /** Modifies how the Block structure is formatted & typed. */ block?: ChainFormatter<'block'> | undefined @@ -123,6 +182,10 @@ export type ChainFormatter = { type: type } +///////////////////////////////////////////////////////////////////// +// Serializers +///////////////////////////////////////////////////////////////////// + export type ChainSerializers< formatters extends ChainFormatters | undefined = undefined, /// @@ -140,42 +203,9 @@ export type ChainSerializers< | undefined } -///////////////////////////////////////////////////////////////////// -// Parameters - -export type ChainFeesFnParameters< - formatters extends ChainFormatters | undefined = ChainFormatters | undefined, -> = { - /** The latest block. */ - block: Prettify< - FormattedBlock & { formatters: formatters }> - > - client: Client - /** - * A transaction request. This value will be undefined if the caller - * is outside of a transaction request context (e.g. a direct call to - * the `estimateFeesPerGas` Action). - */ - request?: - | PrepareTransactionRequestParameters< - Omit & { formatters: formatters }, - Account | undefined, - undefined - > - | undefined -} - -export type ChainEstimateFeesPerGasFnParameters< - formatters extends ChainFormatters | undefined = ChainFormatters | undefined, -> = { - /** A function to multiply the base fee based on the `baseFeeMultiplier` value. */ - multiply: (x: bigint) => bigint - /** The type of fees to return. */ - type: FeeValuesType -} & ChainFeesFnParameters - ///////////////////////////////////////////////////////////////////// // Utils +///////////////////////////////////////////////////////////////////// export type ExtractChainFormatterExclude< chain extends Chain | undefined, @@ -228,6 +258,7 @@ export type GetChainParameter< ///////////////////////////////////////////////////////////////////// // Constants +///////////////////////////////////////////////////////////////////// type ChainBlockExplorer = { name: string diff --git a/src/types/eip1193.ts b/src/types/eip1193.ts index bfdd6c0e7a..d45779a1b3 100644 --- a/src/types/eip1193.ts +++ b/src/types/eip1193.ts @@ -207,18 +207,15 @@ export type WalletSendCallsParameters< quantity extends Quantity | bigint = Quantity, > = [ { - calls: OneOf< - | { - to: Address - data?: Hex | undefined - value?: quantity | undefined - } - | { - data: Hex - } - >[] + calls: readonly { + chainId?: chainId | undefined + to?: Address | undefined + data?: Hex | undefined + value?: quantity | undefined + }[] capabilities?: capabilities | undefined - chainId: chainId + /** @deprecated Use `chainId` on `calls` instead. */ + chainId?: chainId | undefined from: Address version: string }, @@ -1335,7 +1332,7 @@ export type TestRpcSchema = [ * @link https://ganache.dev/#evm_setAccountBalance */ { - Method: `evm_setAccountBalance` + Method: 'evm_setAccountBalance' Parameters: [ /** The address of the target account. */ address: Address, @@ -1344,12 +1341,26 @@ export type TestRpcSchema = [ ] ReturnType: void }, + /** + * @description Modifies the bytecode stored at an account's address. + * @link https://ganache.dev/#evm_setAccountCode + */ + { + Method: 'evm_setAccountCode' + Parameters: [ + /** The address of the contract. */ + address: Address, + /** Data bytecode. */ + data: string, + ] + ReturnType: void + }, /** * @description Enables or disables, based on the single boolean argument, the automatic mining of new blocks with each new transaction submitted to the network. * @link https://hardhat.org/hardhat-network/docs/reference#evm_setautomine */ { - Method: `evm_setAutomine` + Method: 'evm_setAutomine' Parameters: [boolean] ReturnType: void }, @@ -1367,7 +1378,7 @@ export type TestRpcSchema = [ * @link https://github.com/trufflesuite/ganache/blob/ef1858d5d6f27e4baeb75cccd57fb3dc77a45ae8/src/chains/ethereum/ethereum/RPC-METHODS.md#evm_increasetime */ { - Method: `evm_increaseTime` + Method: 'evm_increaseTime' Parameters: [seconds: Quantity] ReturnType: Quantity }, diff --git a/src/utils/transaction/serializeTransaction.test.ts b/src/utils/transaction/serializeTransaction.test.ts index 240dcd4b00..2dcb5943b1 100644 --- a/src/utils/transaction/serializeTransaction.test.ts +++ b/src/utils/transaction/serializeTransaction.test.ts @@ -65,7 +65,7 @@ describe('eip7702', () => { const serialized = serializeTransaction(baseEip7702) assertType(serialized) expect(serialized).toMatchInlineSnapshot( - `"0x04f8e5018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85b0a940000000000000000000000000000000000000000c14501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, + `"0x04f8e3018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85a0a9400000000000000000000000000000000000000004501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, ) expect(parseTransaction(serialized)).toEqual({ ...baseEip7702, @@ -81,7 +81,7 @@ describe('eip7702', () => { yParity: 1, }), ).toMatchInlineSnapshot( - `"0x04f90128018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85b0a940000000000000000000000000000000000000000c14501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe01a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, + `"0x04f90126018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85a0a9400000000000000000000000000000000000000004501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe01a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, ) expect( serializeTransaction( @@ -94,7 +94,7 @@ describe('eip7702', () => { }, ), ).toMatchInlineSnapshot( - `"0x04f90128018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85b0a940000000000000000000000000000000000000000c14501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe80a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, + `"0x04f90126018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85a0a9400000000000000000000000000000000000000004501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe80a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, ) expect( serializeTransaction( @@ -107,7 +107,7 @@ describe('eip7702', () => { }, ), ).toMatchInlineSnapshot( - `"0x04f90128018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85b0a940000000000000000000000000000000000000000c14501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe80a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, + `"0x04f90126018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85a0a9400000000000000000000000000000000000000004501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe80a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, ) expect( serializeTransaction( @@ -120,7 +120,7 @@ describe('eip7702', () => { }, ), ).toMatchInlineSnapshot( - `"0x04f90128018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8bcf85d0194fba3912ca04dd458c843e2ee08967fc04f3579c2c38201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85b0a940000000000000000000000000000000000000000c14501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe01a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, + `"0x04f90126018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0f8baf85c0194fba3912ca04dd458c843e2ee08967fc04f3579c28201a480a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fef85a0a9400000000000000000000000000000000000000004501a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe01a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"`, ) }) }) diff --git a/src/zksync/accounts/toMultisigSmartAccount.test.ts b/src/zksync/accounts/toMultisigSmartAccount.test.ts new file mode 100644 index 0000000000..873f6595d5 --- /dev/null +++ b/src/zksync/accounts/toMultisigSmartAccount.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from 'vitest' + +import { accounts, typedData } from '~test/src/constants.js' + +import { parseEther, parseGwei } from '../../utils/index.js' +import { toMultisigSmartAccount } from './toMultisigSmartAccount.js' + +test('default', () => { + expect( + toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: [accounts[0].privateKey, accounts[1].privateKey], + }), + ).toMatchInlineSnapshot(` + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "experimental_signAuthorization": undefined, + "nonceManager": undefined, + "sign": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "smartAccountZksync", + "type": "local", + } + `) +}) + +test('sign', async () => { + const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: [accounts[0].privateKey, accounts[1].privateKey], + }) + expect( + await account.sign({ + hash: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', + }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`, + ) +}) + +test('sign message', async () => { + const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: [accounts[0].privateKey, accounts[1].privateKey], + }) + expect( + await account.signMessage({ message: 'hello world' }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`, + ) +}) + +test('sign transaction', async () => { + const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: [accounts[0].privateKey, accounts[1].privateKey], + }) + expect( + await account.signTransaction({ + chainId: 1, + maxFeePerGas: parseGwei('20'), + gas: 21000n, + to: accounts[1].address, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot( + `"0x71f8cc80808504a817c8008252089470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000000180800194f39fd6e51aad88f6f4ce6ab8827279cfffb9226682c350c0b882f40a2d2ae9638056cafbe9083c7125edc8555e0e715db0984dd859a5c6dfac5720f36fd0b32bef4d6d75c62f220e59c5fb60c244ca3b361e750985ee5c3a09311c4e5f2cafb92b15ff828d4f8fb34cd3058355428586539495fd0075919cb3cd0d0d33b1e617948f2938f0be2895e4eb4a58a4bcd1a57874ed2c2d235424cf03271bc0"`, + ) +}) + +test('sign typed data', async () => { + const account = toMultisigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKeys: [accounts[0].privateKey, accounts[1].privateKey], + }) + expect( + await account.signTypedData({ ...typedData.basic, primaryType: 'Mail' }), + ).toMatchInlineSnapshot( + `"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b7f65cd2428f35d93b843da90d299ccabe834c0068b0d4035112ad1345abf962b579854cad5911d725fc1000e6e4ebfb4c41e233f66153857b5c017cb3115879a1c"`, + ) +}) diff --git a/src/zksync/accounts/toMultisigSmartAccount.ts b/src/zksync/accounts/toMultisigSmartAccount.ts new file mode 100644 index 0000000000..4a1d329497 --- /dev/null +++ b/src/zksync/accounts/toMultisigSmartAccount.ts @@ -0,0 +1,37 @@ +import type { Address } from 'abitype' + +import { sign } from '../../accounts/utils/sign.js' +import type { Hex } from '../../types/misc.js' +import { concatHex } from '../../utils/index.js' +import type { ZksyncSmartAccount } from '../types/account.js' +import { toSmartAccount } from './toSmartAccount.js' + +export type ToMultisigSmartAccountParameters = { + /** Address of the deployed Account's Contract implementation. */ + address: Address + /** Array of Private Keys belonging to the owners. */ + privateKeys: readonly Hex[] +} + +/** + * Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) + * from a Contract Address and an array of Private Keys belonging to the owners. + */ +export function toMultisigSmartAccount( + parameters: ToMultisigSmartAccountParameters, +): ZksyncSmartAccount { + const { address, privateKeys } = parameters + + return toSmartAccount({ + address, + async sign({ hash }) { + return concatHex( + await Promise.all( + privateKeys.map((privateKey) => + sign({ hash, privateKey, to: 'hex' }), + ), + ), + ) + }, + }) +} diff --git a/src/zksync/accounts/toSinglesigSmartAccount.test.ts b/src/zksync/accounts/toSinglesigSmartAccount.test.ts new file mode 100644 index 0000000000..ff020eb48a --- /dev/null +++ b/src/zksync/accounts/toSinglesigSmartAccount.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from 'vitest' + +import { accounts, typedData } from '~test/src/constants.js' + +import { parseEther, parseGwei } from '../../utils/index.js' +import { toSinglesigSmartAccount } from './toSinglesigSmartAccount.js' + +test('default', () => { + expect( + toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: accounts[0].privateKey, + }), + ).toMatchInlineSnapshot(` + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "experimental_signAuthorization": undefined, + "nonceManager": undefined, + "sign": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "smartAccountZksync", + "type": "local", + } + `) +}) + +test('sign', async () => { + const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: accounts[0].privateKey, + }) + expect( + await account.sign({ + hash: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', + }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"`, + ) +}) + +test('sign message', async () => { + const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: accounts[0].privateKey, + }) + expect( + await account.signMessage({ message: 'hello world' }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b"`, + ) +}) + +test('sign transaction', async () => { + const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: accounts[0].privateKey, + }) + expect( + await account.signTransaction({ + chainId: 1, + maxFeePerGas: parseGwei('20'), + gas: 21000n, + to: accounts[1].address, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot( + `"0x71f88b80808504a817c8008252089470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000000180800194f39fd6e51aad88f6f4ce6ab8827279cfffb9226682c350c0b841f40a2d2ae9638056cafbe9083c7125edc8555e0e715db0984dd859a5c6dfac5720f36fd0b32bef4d6d75c62f220e59c5fb60c244ca3b361e750985ee5c3a09311cc0"`, + ) +}) + +test('sign typed data', async () => { + const account = toSinglesigSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + privateKey: accounts[0].privateKey, + }) + expect( + await account.signTypedData({ ...typedData.basic, primaryType: 'Mail' }), + ).toMatchInlineSnapshot( + `"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b"`, + ) +}) diff --git a/src/zksync/accounts/toSinglesigSmartAccount.ts b/src/zksync/accounts/toSinglesigSmartAccount.ts new file mode 100644 index 0000000000..9c053727ee --- /dev/null +++ b/src/zksync/accounts/toSinglesigSmartAccount.ts @@ -0,0 +1,30 @@ +import type { Address } from 'abitype' + +import { sign } from '../../accounts/utils/sign.js' +import type { Hex } from '../../types/misc.js' +import type { ZksyncSmartAccount } from '../types/account.js' +import { toSmartAccount } from './toSmartAccount.js' + +export type ToSinglesigSmartAccountParameters = { + /** Address of the deployed Account's Contract implementation. */ + address: Address + /** Private Key of the owner. */ + privateKey: Hex +} + +/** + * Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) + * from a Contract Address and a Private Key belonging to the owner. + */ +export function toSinglesigSmartAccount( + parameters: ToSinglesigSmartAccountParameters, +): ZksyncSmartAccount { + const { address, privateKey } = parameters + + return toSmartAccount({ + address, + async sign({ hash }) { + return sign({ hash, privateKey, to: 'hex' }) + }, + }) +} diff --git a/src/zksync/accounts/toSmartAccount.test.ts b/src/zksync/accounts/toSmartAccount.test.ts new file mode 100644 index 0000000000..23cc09bc3d --- /dev/null +++ b/src/zksync/accounts/toSmartAccount.test.ts @@ -0,0 +1,94 @@ +import { expect, test } from 'vitest' + +import { accounts, typedData } from '~test/src/constants.js' + +import { sign as sign_ } from '../../accounts/index.js' +import type { Hex } from '../../types/misc.js' +import { concatHex, parseEther, parseGwei } from '../../utils/index.js' +import { toSmartAccount } from './toSmartAccount.js' + +async function sign({ hash }: { hash: Hex }) { + const privateKeys = [accounts[0].privateKey, accounts[1].privateKey] + return concatHex( + await Promise.all( + privateKeys.map((privateKey) => sign_({ hash, privateKey, to: 'hex' })), + ), + ) +} + +test('default', () => { + expect( + toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + sign, + }), + ).toMatchInlineSnapshot(` + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "experimental_signAuthorization": undefined, + "nonceManager": undefined, + "sign": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "smartAccountZksync", + "type": "local", + } + `) +}) + +test('sign', async () => { + const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + sign, + }) + expect( + await account.sign({ + hash: '0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68', + }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`, + ) +}) + +test('sign message', async () => { + const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + sign, + }) + expect( + await account.signMessage({ message: 'hello world' }), + ).toMatchInlineSnapshot( + `"0xa461f509887bd19e312c0c58467ce8ff8e300d3c1a90b608a760c5b80318eaf15fe57c96f9175d6cd4daad4663763baa7e78836e067d0163e9a2ccf2ff753f5b1b02de21de49d98cf93ef790a262702dcc711b3f2ce0a971e3b50caea43cfc07cb34eabfd4d39eff886015fb2c42ec4265aeaf5ee34a78c75309fdc1165cb659521c"`, + ) +}) + +test('sign transaction', async () => { + const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + sign, + }) + expect( + await account.signTransaction({ + chainId: 1, + maxFeePerGas: parseGwei('20'), + gas: 21000n, + to: accounts[1].address, + value: parseEther('1'), + }), + ).toMatchInlineSnapshot( + `"0x71f8cc80808504a817c8008252089470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000000180800194f39fd6e51aad88f6f4ce6ab8827279cfffb9226682c350c0b882f40a2d2ae9638056cafbe9083c7125edc8555e0e715db0984dd859a5c6dfac5720f36fd0b32bef4d6d75c62f220e59c5fb60c244ca3b361e750985ee5c3a09311c4e5f2cafb92b15ff828d4f8fb34cd3058355428586539495fd0075919cb3cd0d0d33b1e617948f2938f0be2895e4eb4a58a4bcd1a57874ed2c2d235424cf03271bc0"`, + ) +}) + +test('sign typed data', async () => { + const account = toSmartAccount({ + address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + sign, + }) + expect( + await account.signTypedData({ ...typedData.basic, primaryType: 'Mail' }), + ).toMatchInlineSnapshot( + `"0x32f3d5975ba38d6c2fba9b95d5cbed1febaa68003d3d588d51f2de522ad54117760cfc249470a75232552e43991f53953a3d74edf6944553c6bef2469bb9e5921b7f65cd2428f35d93b843da90d299ccabe834c0068b0d4035112ad1345abf962b579854cad5911d725fc1000e6e4ebfb4c41e233f66153857b5c017cb3115879a1c"`, + ) +}) diff --git a/src/zksync/accounts/toSmartAccount.ts b/src/zksync/accounts/toSmartAccount.ts new file mode 100644 index 0000000000..092e42c0f3 --- /dev/null +++ b/src/zksync/accounts/toSmartAccount.ts @@ -0,0 +1,63 @@ +import type { Address } from 'abitype' + +import { toAccount } from '../../accounts/toAccount.js' +import type { ErrorType } from '../../errors/utils.js' +import type { Hash, Hex } from '../../types/misc.js' +import { keccak256 } from '../../utils/index.js' +import { hashMessage } from '../../utils/signature/hashMessage.js' +import { hashTypedData } from '../../utils/signature/hashTypedData.js' +import { serializeTransaction } from '../serializers.js' +import type { ZksyncSmartAccount } from '../types/account.js' +import type { ZksyncTransactionSerializableEIP712 } from '../types/transaction.js' + +export type ToSmartAccountParameters = { + /** Address of the deployed Account's Contract implementation. */ + address: Address + /** Function to sign a hash. */ + sign: (parameters: { hash: Hash }) => Promise +} + +export type ToSmartAccountErrorType = ErrorType + +/** + * Creates a [ZKsync Smart Account](https://docs.zksync.io/build/developer-reference/account-abstraction/building-smart-accounts) + * from a Contract Address and a custom sign function. + */ +export function toSmartAccount( + parameters: ToSmartAccountParameters, +): ZksyncSmartAccount { + const { address, sign } = parameters + + const account = toAccount({ + address, + sign, + async signMessage({ message }) { + return sign({ + hash: hashMessage(message), + }) + }, + async signTransaction(transaction) { + const signableTransaction = { + ...transaction, + from: this.address!, + } as ZksyncTransactionSerializableEIP712 + + return serializeTransaction({ + ...signableTransaction, + customSignature: await sign({ + hash: keccak256(serializeTransaction(signableTransaction)), + }), + }) + }, + async signTypedData(typedData) { + return sign({ + hash: hashTypedData(typedData), + }) + }, + }) + + return { + ...account, + source: 'smartAccountZksync', + } as ZksyncSmartAccount +} diff --git a/src/zksync/actions/signEip712Transaction.test.ts b/src/zksync/actions/signEip712Transaction.test.ts index fba555ebd0..045c8fd74a 100644 --- a/src/zksync/actions/signEip712Transaction.test.ts +++ b/src/zksync/actions/signEip712Transaction.test.ts @@ -25,7 +25,7 @@ test('default', async () => { ...base, }), ).toMatchInlineSnapshot( - `"0x71f8c880808080808000820144808082014494000000000000000000000000000000000000000082c350c0b841edc8fb1e839969b2072865653798be4b4e6ea7181ec97c5e32867bad5838224e1d247fb2f2f07c1bc70bf789d43404771ac496f4fc57c5e6398c1fa6fe6f62861cf85b94fd9ae5ebb0f6656f4b77a0e99dcbc5138d54b0bab8448c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"`, + `"0x71f8c880808080808000820144808082014494000000000000000000000000000000000000000082c350c0b841bb509f381d29a038bd2f700bd6a1f1138edfd7a3cf7234c13a03b01a023a30aa53e6bd5e6a50fdcdcf74587c9395b8a314690abbc85aadab5ebcb7678994eacf1bf85b94fd9ae5ebb0f6656f4b77a0e99dcbc5138d54b0bab8448c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"`, ) }) diff --git a/src/zksync/actions/signTransaction.test.ts b/src/zksync/actions/signTransaction.test.ts index db5561eb4e..cfd6372ef8 100644 --- a/src/zksync/actions/signTransaction.test.ts +++ b/src/zksync/actions/signTransaction.test.ts @@ -30,7 +30,7 @@ test('eip712', async () => { ...eip712, }), ).toMatchInlineSnapshot( - `"0x71f8c880808080808000820144808082014494000000000000000000000000000000000000000082c350c0b841edc8fb1e839969b2072865653798be4b4e6ea7181ec97c5e32867bad5838224e1d247fb2f2f07c1bc70bf789d43404771ac496f4fc57c5e6398c1fa6fe6f62861cf85b94fd9ae5ebb0f6656f4b77a0e99dcbc5138d54b0bab8448c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"`, + `"0x71f8c880808080808000820144808082014494000000000000000000000000000000000000000082c350c0b841bb509f381d29a038bd2f700bd6a1f1138edfd7a3cf7234c13a03b01a023a30aa53e6bd5e6a50fdcdcf74587c9395b8a314690abbc85aadab5ebcb7678994eacf1bf85b94fd9ae5ebb0f6656f4b77a0e99dcbc5138d54b0bab8448c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"`, ) }) diff --git a/src/zksync/formatters.test.ts b/src/zksync/formatters.test.ts index f8e0fe6ed4..2b4208b32f 100644 --- a/src/zksync/formatters.test.ts +++ b/src/zksync/formatters.test.ts @@ -321,7 +321,7 @@ describe('block', () => { "hash": "0x51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f", "l1BatchNumber": 1n, "l1BatchTimestamp": 1676384542n, - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x000000200004000800000100000000004001800000004000008008000400000008000000000000011400000000100000200000000010000000000000000000000001000000200400000021200000420200010000800000000000000000000000000000000200000000040000010808000000008000004001000000000000004000000810000400000000000000000000000001000000800000001000208000000080a0000001000000000000800500030440000010040000002000010000000080000020008100000000200000040000085000000552000000010000000020004802000000100000000400000000000010000080000000000200000010004000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", @@ -1397,7 +1397,7 @@ describe('transaction receipt', () => { "transactionLogIndex": 9, }, ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logsBloom": "0x00000020000400000000000000000000400100000000000000800000040000000000000000000000100000000010000020000000001000000000000000000000000000000020040000002100000042000001000000000000000000000000000000000000020000000000000001000800000000800000400000000000000000400000081000000000000000000000000000000100000080000000100020800000008080000001000000000000800400030440000010000000002000010000000080000020008100000000200000040000085000000552000000010000000020000802000000100000000000000000000010000080000000000200000010004000", "root": "0x51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f", "status": "success", "to": "0x0000000000000000000000000000000000008006", diff --git a/src/zksync/index.ts b/src/zksync/index.ts index 1130eb820b..08d9df95cd 100644 --- a/src/zksync/index.ts +++ b/src/zksync/index.ts @@ -1,4 +1,18 @@ // biome-ignore lint/performance/noBarrelFile: entrypoint module +export { + type ToSmartAccountErrorType, + type ToSmartAccountParameters, + toSmartAccount, +} from './accounts/toSmartAccount.js' +export { + type ToMultisigSmartAccountParameters, + toMultisigSmartAccount, +} from './accounts/toMultisigSmartAccount.js' +export { + type ToSinglesigSmartAccountParameters, + toSinglesigSmartAccount, +} from './accounts/toSinglesigSmartAccount.js' + export { type DeployContractErrorType, type DeployContractParameters, @@ -131,6 +145,7 @@ export { export { serializeTransaction } from './serializers.js' +export type { ZksyncSmartAccount } from './types/account.js' export type { /** @deprecated Use `ZksyncBlock` instead */ ZksyncBlock as ZkSyncBlock, diff --git a/src/zksync/types/account.ts b/src/zksync/types/account.ts new file mode 100644 index 0000000000..2c880ffa78 --- /dev/null +++ b/src/zksync/types/account.ts @@ -0,0 +1,5 @@ +import type { CustomSource, LocalAccount } from '../../accounts/types.js' + +export type ZksyncSmartAccount = LocalAccount<'smartAccountZksync'> & { + sign: NonNullable +} diff --git a/src/zksync/utils/getEip712Domain.test.ts b/src/zksync/utils/getEip712Domain.test.ts index bd4e87dbc4..0b5f67c93d 100644 --- a/src/zksync/utils/getEip712Domain.test.ts +++ b/src/zksync/utils/getEip712Domain.test.ts @@ -39,7 +39,7 @@ test('default', () => { { "domain": { "chainId": 324, - "name": "zksync", + "name": "zkSync", "version": "2", }, "message": { diff --git a/src/zksync/utils/getEip712Domain.ts b/src/zksync/utils/getEip712Domain.ts index 239703e82b..77e7d53d3a 100644 --- a/src/zksync/utils/getEip712Domain.ts +++ b/src/zksync/utils/getEip712Domain.ts @@ -21,7 +21,7 @@ export const getEip712Domain: EIP712DomainFn< return { domain: { - name: 'zksync', + name: 'zkSync', version: '2', chainId: transaction.chainId, }, diff --git a/test/src/constants.ts b/test/src/constants.ts index 58eff9bcb3..e15f76e48d 100644 --- a/test/src/constants.ts +++ b/test/src/constants.ts @@ -42,6 +42,8 @@ export const accounts = [ { address: '0xa0ee7a142d267c1f36714e4a8f75612f20a79720', balance: 10000000000000000000000n, + privateKey: + '0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6', }, ] as const