From 96a840babbbb1cb90dec0208bbf193260d45002d Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 12 Sep 2024 15:24:37 +0100 Subject: [PATCH] chore: add timers --- CHANGELOG.md | 2 +- README.md | 4 +- bun.lockb | Bin 355911 -> 355911 bytes package.json | 6 +- .../sdk}/__contracts/abi/EntryPointABI.ts | 0 .../sdk}/__contracts/abi/K1ValidatorAbi.ts | 0 .../__contracts/abi/K1ValidatorFactoryAbi.ts | 0 .../sdk}/__contracts/abi/NexusAbi.ts | 0 .../__contracts/abi/UniActionPolicyAbi.ts | 0 .../sdk}/__contracts/abi/index.ts | 0 .../sdk}/__contracts/addresses.ts | 0 {src => packages/sdk}/__contracts/index.ts | 5 +- packages/sdk/account/index.ts | 4 + .../sdk}/account/signers/local-account.ts | 0 .../sdk}/account/signers/wallet-client.ts | 0 packages/sdk/account/toNexusAccount.test.ts | 115 +++ .../sdk/account/toNexusAccount.ts | 128 ++- .../sdk}/account/utils/AccountNotFound.ts | 0 .../sdk}/account/utils/Constants.ts | 56 +- .../sdk}/account/utils/EthersSigner.ts | 2 +- .../sdk}/account/utils/Helpers.ts | 0 .../sdk}/account/utils/HttpRequests.ts | 2 +- {src => packages/sdk}/account/utils/Logger.ts | 0 {src => packages/sdk}/account/utils/Types.ts | 351 ++++--- {src => packages/sdk}/account/utils/Utils.ts | 7 +- .../sdk}/account/utils/convertSigner.ts | 4 +- .../sdk/account}/utils/getAAError.ts | 5 +- .../sdk}/account/utils/getChain.ts | 4 +- {src => packages/sdk}/account/utils/index.ts | 0 packages/sdk/account/utils/utils.test.ts | 58 ++ .../clients/decorators/erc7579/accountId.ts | 0 .../erc7579/erc7579.actions.test.ts | 116 +++ .../decorators/erc7579/getActiveHook.ts | 55 + .../erc7579/getFallbackBySelector.ts | 74 ++ .../erc7579/getInstalledExecutors.ts | 80 ++ .../erc7579/getInstalledValidators.ts | 80 ++ .../sdk}/clients/decorators/erc7579/index.ts | 64 +- .../decorators/erc7579/installModule.ts | 24 +- .../decorators/erc7579/installModules.ts | 0 .../decorators/erc7579/isModuleInstalled.ts | 28 +- .../erc7579/supportsExecutionMode.ts | 0 .../decorators/erc7579/supportsModule.ts | 0 .../decorators/erc7579/uninstallFallback.ts | 93 ++ .../decorators/erc7579/uninstallModule.ts | 17 +- .../decorators/erc7579/uninstallModules.ts | 12 +- .../smartAccount/erc7579.actions.test.ts | 139 +++ .../clients/decorators/smartAccount/index.ts | 8 +- .../smartAccount/sendTransaction.ts | 0 .../decorators/smartAccount/signMessage.ts | 0 .../decorators/smartAccount/signTypedData.ts | 0 .../decorators/smartAccount/writeContract.ts | 0 .../sdk/clients/toBicoBundlerClient.test.ts | 85 ++ packages/sdk/clients/toBicoBundlerClient.ts | 36 + packages/sdk/clients/toBicoPaymasterClient.ts | 33 + packages/sdk/clients/toNexusClient.test.ts | 105 ++ .../sdk/clients/toNexusClient.ts | 117 +-- {src => packages/sdk}/index.ts | 1 - .../sdk}/modules/base/BaseExecutionModule.ts | 7 +- .../sdk}/modules/base/BaseModule.ts | 18 +- .../sdk}/modules/base/BaseValidationModule.ts | 2 +- .../sdk}/modules/executors/OwnableExecutor.ts | 95 +- {src => packages/sdk}/modules/index.ts | 0 .../modules/interfaces/IExecutorModule.ts | 0 .../modules/interfaces/IValidationModule.ts | 0 .../sdk/modules}/smart.sessions.test.ts | 92 +- .../sdk}/modules/smartSessions.ts | 0 .../sdk}/modules/utils/Constants.ts | 0 {src => packages/sdk}/modules/utils/Helper.ts | 2 +- {src => packages/sdk}/modules/utils/Types.ts | 25 - {src => packages/sdk}/modules/utils/Uid.ts | 0 .../modules/validators/K1ValidatorModule.ts | 6 +- .../modules/validators/OwnableValidator.ts | 0 .../modules/validators/ValidationModule.ts | 10 +- .../modules/validators/k1Validator.test.ts | 127 +++ {tests/src => packages/tests}/README.md | 0 .../__contracts/abi/BiconomyMetaFactoryAbi.ts | 0 .../tests}/__contracts/abi/BootstrapAbi.ts | 0 .../tests}/__contracts/abi/BootstrapLibAbi.ts | 0 .../tests}/__contracts/abi/CounterAbi.ts | 0 .../tests}/__contracts/abi/MockExecutorAbi.ts | 0 .../tests}/__contracts/abi/MockHandlerAbi.ts | 0 .../tests}/__contracts/abi/MockHookAbi.ts | 0 .../tests}/__contracts/abi/MockRegistryAbi.ts | 0 .../tests}/__contracts/abi/MockTokenAbi.ts | 0 .../__contracts/abi/MockValidatorAbi.ts | 0 .../__contracts/abi/NexusAccountFactoryAbi.ts | 0 .../tests}/__contracts/abi/StakeableAbi.ts | 0 .../tests}/__contracts/abi/index.ts | 0 .../tests}/__contracts/mockAddresses.ts | 0 {tests/src => packages/tests}/callDatas.ts | 0 {tests/src => packages/tests}/executables.ts | 0 {tests/src => packages/tests}/globalSetup.ts | 1 + {tests => packages/tests}/playground.test.ts | 133 +-- {tests/src => packages/tests}/testSetup.ts | 10 +- {tests/src => packages/tests}/testUtils.ts | 105 +- {tests => packages/tests}/vitest.config.ts | 6 +- scripts/fetch:deployment.ts | 6 +- scripts/send:userOp.ts | 56 +- scripts/viem:bundler.ts | 137 +++ src/account/index.ts | 7 - src/bundler/Bundler.ts | 374 ------- src/bundler/index.ts | 8 - src/bundler/interfaces/IBundler.ts | 22 - src/bundler/utils/Constants.ts | 98 -- src/bundler/utils/HelperFunction.ts | 68 -- src/bundler/utils/Types.ts | 151 --- src/bundler/utils/Utils.ts | 53 - src/clients/biconomy.test.ts | 147 --- src/paymaster/Paymaster.ts | 410 -------- src/paymaster/index.ts | 7 - src/paymaster/interfaces/IHybridPaymaster.ts | 29 - src/paymaster/interfaces/IPaymaster.ts | 12 - src/paymaster/utils/Constants.ts | 12 - src/paymaster/utils/Helpers.ts | 7 - src/paymaster/utils/Types.ts | 123 --- tests/account.read.test.ts | 936 ------------------ tests/account.write.test.ts | 227 ----- tests/biconomy.test.ts | 148 --- tests/modules.k1Validator.write.test.ts | 161 --- tests/modules.ownableExecutor.read.test.ts | 112 --- tests/modules.ownableExecutor.write.test.ts | 371 ------- ...les.ownableValidator.install.write.test.ts | 371 ------- ...s.ownableValidator.uninstall.write.test.ts | 371 ------- tsconfig/tsconfig.cjs.json | 7 +- tsconfig/tsconfig.esm.json | 7 +- tsconfig/tsconfig.json | 12 +- tsconfig/tsconfig.types.json | 7 +- 127 files changed, 2104 insertions(+), 4912 deletions(-) rename {src => packages/sdk}/__contracts/abi/EntryPointABI.ts (100%) rename {src => packages/sdk}/__contracts/abi/K1ValidatorAbi.ts (100%) rename {src => packages/sdk}/__contracts/abi/K1ValidatorFactoryAbi.ts (100%) rename {src => packages/sdk}/__contracts/abi/NexusAbi.ts (100%) rename {src => packages/sdk}/__contracts/abi/UniActionPolicyAbi.ts (100%) rename {src => packages/sdk}/__contracts/abi/index.ts (100%) rename {src => packages/sdk}/__contracts/addresses.ts (100%) rename {src => packages/sdk}/__contracts/index.ts (85%) create mode 100644 packages/sdk/account/index.ts rename {src => packages/sdk}/account/signers/local-account.ts (100%) rename {src => packages/sdk}/account/signers/wallet-client.ts (100%) create mode 100644 packages/sdk/account/toNexusAccount.test.ts rename src/account/Nexus.ts => packages/sdk/account/toNexusAccount.ts (70%) rename {src => packages/sdk}/account/utils/AccountNotFound.ts (100%) rename {src => packages/sdk}/account/utils/Constants.ts (72%) rename {src => packages/sdk}/account/utils/EthersSigner.ts (94%) rename {src => packages/sdk}/account/utils/Helpers.ts (100%) rename {src => packages/sdk}/account/utils/HttpRequests.ts (97%) rename {src => packages/sdk}/account/utils/Logger.ts (100%) rename {src => packages/sdk}/account/utils/Types.ts (76%) rename {src => packages/sdk}/account/utils/Utils.ts (97%) rename {src => packages/sdk}/account/utils/convertSigner.ts (96%) rename {src/bundler => packages/sdk/account}/utils/getAAError.ts (93%) rename {src => packages/sdk}/account/utils/getChain.ts (95%) rename {src => packages/sdk}/account/utils/index.ts (100%) create mode 100644 packages/sdk/account/utils/utils.test.ts rename {src => packages/sdk}/clients/decorators/erc7579/accountId.ts (100%) create mode 100644 packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts create mode 100644 packages/sdk/clients/decorators/erc7579/getActiveHook.ts create mode 100644 packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts create mode 100644 packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts create mode 100644 packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts rename {src => packages/sdk}/clients/decorators/erc7579/index.ts (56%) rename {src => packages/sdk}/clients/decorators/erc7579/installModule.ts (82%) rename {src => packages/sdk}/clients/decorators/erc7579/installModules.ts (100%) rename {src => packages/sdk}/clients/decorators/erc7579/isModuleInstalled.ts (82%) rename {src => packages/sdk}/clients/decorators/erc7579/supportsExecutionMode.ts (100%) rename {src => packages/sdk}/clients/decorators/erc7579/supportsModule.ts (100%) create mode 100644 packages/sdk/clients/decorators/erc7579/uninstallFallback.ts rename {src => packages/sdk}/clients/decorators/erc7579/uninstallModule.ts (87%) rename {src => packages/sdk}/clients/decorators/erc7579/uninstallModules.ts (92%) create mode 100644 packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts rename {src => packages/sdk}/clients/decorators/smartAccount/index.ts (97%) rename {src => packages/sdk}/clients/decorators/smartAccount/sendTransaction.ts (100%) rename {src => packages/sdk}/clients/decorators/smartAccount/signMessage.ts (100%) rename {src => packages/sdk}/clients/decorators/smartAccount/signTypedData.ts (100%) rename {src => packages/sdk}/clients/decorators/smartAccount/writeContract.ts (100%) create mode 100644 packages/sdk/clients/toBicoBundlerClient.test.ts create mode 100644 packages/sdk/clients/toBicoBundlerClient.ts create mode 100644 packages/sdk/clients/toBicoPaymasterClient.ts create mode 100644 packages/sdk/clients/toNexusClient.test.ts rename src/clients/biconomy.ts => packages/sdk/clients/toNexusClient.ts (60%) rename {src => packages/sdk}/index.ts (65%) rename {src => packages/sdk}/modules/base/BaseExecutionModule.ts (54%) rename {src => packages/sdk}/modules/base/BaseModule.ts (91%) rename {src => packages/sdk}/modules/base/BaseValidationModule.ts (97%) rename {src => packages/sdk}/modules/executors/OwnableExecutor.ts (64%) rename {src => packages/sdk}/modules/index.ts (100%) rename {src => packages/sdk}/modules/interfaces/IExecutorModule.ts (100%) rename {src => packages/sdk}/modules/interfaces/IValidationModule.ts (100%) rename {tests => packages/sdk/modules}/smart.sessions.test.ts (63%) rename {src => packages/sdk}/modules/smartSessions.ts (100%) rename {src => packages/sdk}/modules/utils/Constants.ts (100%) rename {src => packages/sdk}/modules/utils/Helper.ts (99%) rename {src => packages/sdk}/modules/utils/Types.ts (91%) rename {src => packages/sdk}/modules/utils/Uid.ts (100%) rename {src => packages/sdk}/modules/validators/K1ValidatorModule.ts (83%) rename {src => packages/sdk}/modules/validators/OwnableValidator.ts (100%) rename {src => packages/sdk}/modules/validators/ValidationModule.ts (81%) create mode 100644 packages/sdk/modules/validators/k1Validator.test.ts rename {tests/src => packages/tests}/README.md (100%) rename {tests/src => packages/tests}/__contracts/abi/BiconomyMetaFactoryAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/BootstrapAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/BootstrapLibAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/CounterAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockExecutorAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockHandlerAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockHookAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockRegistryAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockTokenAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/MockValidatorAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/NexusAccountFactoryAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/StakeableAbi.ts (100%) rename {tests/src => packages/tests}/__contracts/abi/index.ts (100%) rename {tests/src => packages/tests}/__contracts/mockAddresses.ts (100%) rename {tests/src => packages/tests}/callDatas.ts (100%) rename {tests/src => packages/tests}/executables.ts (100%) rename {tests/src => packages/tests}/globalSetup.ts (97%) rename {tests => packages/tests}/playground.test.ts (55%) rename {tests/src => packages/tests}/testSetup.ts (85%) rename {tests/src => packages/tests}/testUtils.ts (86%) rename {tests => packages/tests}/vitest.config.ts (75%) create mode 100644 scripts/viem:bundler.ts delete mode 100644 src/account/index.ts delete mode 100644 src/bundler/Bundler.ts delete mode 100644 src/bundler/index.ts delete mode 100644 src/bundler/interfaces/IBundler.ts delete mode 100644 src/bundler/utils/Constants.ts delete mode 100644 src/bundler/utils/HelperFunction.ts delete mode 100644 src/bundler/utils/Types.ts delete mode 100644 src/bundler/utils/Utils.ts delete mode 100644 src/clients/biconomy.test.ts delete mode 100644 src/paymaster/Paymaster.ts delete mode 100644 src/paymaster/index.ts delete mode 100644 src/paymaster/interfaces/IHybridPaymaster.ts delete mode 100644 src/paymaster/interfaces/IPaymaster.ts delete mode 100644 src/paymaster/utils/Constants.ts delete mode 100644 src/paymaster/utils/Helpers.ts delete mode 100644 src/paymaster/utils/Types.ts delete mode 100644 tests/account.read.test.ts delete mode 100644 tests/account.write.test.ts delete mode 100644 tests/biconomy.test.ts delete mode 100644 tests/modules.k1Validator.write.test.ts delete mode 100644 tests/modules.ownableExecutor.read.test.ts delete mode 100644 tests/modules.ownableExecutor.write.test.ts delete mode 100644 tests/modules.ownableValidator.install.write.test.ts delete mode 100644 tests/modules.ownableValidator.uninstall.write.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e751d84a3..91f29092e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,7 +164,7 @@ Particle Auth Fix - E2E tests for session validation modules ([4ad7ea7](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/4ad7ea7f8eb6a28854dcce83834b2b7ff9ad3287)) - Added [TSDoc](https://bcnmy.github.io/biconomy-client-sdk) ([638dae](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/638daee0ed6924f67c5151a2d0e5a02d32e4bf23)) - Make txs more typesafe and default with valueOrData ([b1e5b5e](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/b1e5b5e02ab3a7fb99faa1d45b55e3cbe8d6bc93)) -- Added createSmartAccountClient alias ([232472](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/232472c788bed0619cf6295ade076b6ec3a9e0f8)) +- Added toNexusClient alias ([232472](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/232472c788bed0619cf6295ade076b6ec3a9e0f8)) - Improve dx of using paymster to build userOps ([bb54888](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/bb548884e76a94a20329e49b18994503de9e3888)) - Add ethers v6 signer support ([9727fd](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/9727fd51e47d62904399d17d48f5c9d6b9e591e5)) - Improved dx of using gas payments with erc20 ([741806](https://github.com/bcnmy/biconomy-client-sdk/pull/401/commits/741806da68457eba262e1a49be77fcc24360ba36)) diff --git a/README.md b/README.md index 88e5fabf8..0dd615ed6 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ bun i ``` ```typescript -import { createSmartAccountClient } from "@biconomy/account"; +import { toNexusClient } from "@biconomy/account"; -const smartAccount = await createSmartAccountClient({ +const smartAccount = await toNexusClient({ signer: viemWalletOrEthersSigner, bundlerUrl: "", // From dashboard.biconomy.io paymasterUrl: "", // From dashboard.biconomy.io diff --git a/bun.lockb b/bun.lockb index ea3542efc275a8597035cc8072173366368ba081..ac873fff02edd5e6e781831f3487a983a56bac62 100755 GIT binary patch delta 1169 zcmcIhe{54#6n^*J`_c|*hoi04!DyElh;)T^S+|9y{BS~Hs0>}qj0tO{%G8aT4l7Lt z!$ch2RvB-4h)4lTt%(|kBfFT6EYpN-T^2JE|B=Zs#Q3A2HF0BN6wmFLS^VFVeEH7( z&Ufy)@4VqjMnBC%eqx;Hlk#Dlgu6cV?hfcI_KVGa< zgo08ox*U?Elcct+JjW3_$Bn^#Im^7y>uYQ|5b#UtF~zJ6VUr1)`bHFsLPC8%ElQdy zG;CYz#c0*oxdTVvF<+R`y1p1(epPel^-nJCzSa8Z=|sa&kzC)O_RssCs{iQa<7-$_ z?X{Y zOu785Q%zU8WpJw7eQtTwsYbA21}(0f1sjy|Qc0>*$1B&%fl@W%bt}n#h3lp2_6B#K z_PLK!wfOzgG(zz0+-l~SU_Ofw{0BnR|GdY4-Q&B+A_U|9Bw3ibvXXpN`2XDdt%=LK z$YR$<@4+f-efU&N*>breeR5{YK+~qW&w7uP-T3r-r8(69{6MPpY__JDrmh9^>zh-t zvK@mj4ozJ5*fW>IhRm1a@K`%B!`B_8hWEA;BY}r7C2@GAgFFp;BSZ(|5fl>e`B46G7_Ce!@VVLGMFyNNrvh4_^xBJ@cr6TxG=HIzY*dm%<#G%x{{FFeE-`ts5G2KjUkeV1mYaxeZ*-*3~_fs z2=5t$@W&AgO1V_jK0wUhsll9kPErpgG2R`cgN(#^ZJe%^$%k+_LGM?@q&Pp7py%}( zr`_qWyChzBfgUlygpQR!V3b;*x}O@LN@TnA%tXqdQO{P8F6bVm4?uMhD}b9LR10VH z>_K_h0S!gWEKfLK#~3w3aXyx_qqIsGmo8aK9>#$p{$pGg-nS>Gm$+_ZwpJe+G7uXK zrKuGP(zF2FX&T^r#%R_^LgHm6{?d2UZzCa&2O}Xi{)3){@)wNf;UYHDq`2%YFN7gF zz)ZT3-{;-mvbVV;;4E`N?FMBzWTN=Gi&0hzS2`JoV3bwEY@t#J8>3MqWsPvLFP5^xZo3Rgn>scU4CIndqD{ckjxb4*K)lqF zFdMDQg5eF1qqUm2t&KKi>v*hioNX%DB{gSksFA`Zrzo@}L5;@jIlRpDrH`GN-~FHG zf1Z0@o_lWpu+%>+y>2qTyT$g+xa*}S9HTv}FIPWW>`_U76m`#wet&NBMX54kI9{|^ zvC)$y2zo)-(EzFey)MqE56@^XXae}7pz)w|HZFHo)iu?)gsR$Y4Yh(GOh5jm;Q@tF zfjr?~;zIh3yW)nkHPwyF4>#@HlXBvw%J%7*)Jw{#w)ak#UUNToAlNecZJwk5Mg7pj z4=!u@(!RGo_Vn%hjx@%tFFCq(_firni=>GqCu1%pdE9-fD|c{C!=ve$cKM=x$?hDL zxGzhNIqmA7;2^7Eqk?>4RknJGSrAr0gyh6c*D| zp=!ck0Qc_zJn!$>mF>q7;oml`_IQ?$~DG}H}aC`hY(V5D?_@%c_LLNt*Z1Ke=9> z2ttPK$Fa3*UWp_lH;u$_IxUOgh2qZ^dO3 diff --git a/package.json b/package.json index 2eee0c79b..0c08fc6c4 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,9 @@ "dev": "bun link && concurrently \"bun run esm:watch\" \"bun run cjs:watch\" \"bun run esm:watch:aliases\" \"bun run cjs:watch:aliases\"", "build": "bun run clean && bun run build:cjs && bun run build:esm && bun run build:types", "clean": "rimraf ./dist/_esm ./dist/_cjs ./dist/_types ./dist/tsconfig", - "test": "vitest -c ./tests/vitest.config.ts", + "test": "vitest -c ./packages/tests/vitest.config.ts", "test:watch": "bun run test dev", - "playground": "RUN_PLAYGROUND=true vitest -c ./tests/vitest.config.ts -t=playground", + "playground": "RUN_PLAYGROUND=true vitest -c ./packages/tests/vitest.config.ts -t=playground", "playground:watch": "RUN_PLAYGROUND=true bun run test -t=playground --watch", "size": "size-limit", "docs": "typedoc --tsconfig ./tsconfig/tsconfig.esm.json", @@ -109,7 +109,7 @@ "typedoc": "^0.25.9", "vitest": "^1.3.1", "yargs": "^17.7.2", - "viem": "^2.21.2" + "viem": "2.21.6" }, "peerDependencies": { "typescript": "^5" diff --git a/src/__contracts/abi/EntryPointABI.ts b/packages/sdk/__contracts/abi/EntryPointABI.ts similarity index 100% rename from src/__contracts/abi/EntryPointABI.ts rename to packages/sdk/__contracts/abi/EntryPointABI.ts diff --git a/src/__contracts/abi/K1ValidatorAbi.ts b/packages/sdk/__contracts/abi/K1ValidatorAbi.ts similarity index 100% rename from src/__contracts/abi/K1ValidatorAbi.ts rename to packages/sdk/__contracts/abi/K1ValidatorAbi.ts diff --git a/src/__contracts/abi/K1ValidatorFactoryAbi.ts b/packages/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts similarity index 100% rename from src/__contracts/abi/K1ValidatorFactoryAbi.ts rename to packages/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts diff --git a/src/__contracts/abi/NexusAbi.ts b/packages/sdk/__contracts/abi/NexusAbi.ts similarity index 100% rename from src/__contracts/abi/NexusAbi.ts rename to packages/sdk/__contracts/abi/NexusAbi.ts diff --git a/src/__contracts/abi/UniActionPolicyAbi.ts b/packages/sdk/__contracts/abi/UniActionPolicyAbi.ts similarity index 100% rename from src/__contracts/abi/UniActionPolicyAbi.ts rename to packages/sdk/__contracts/abi/UniActionPolicyAbi.ts diff --git a/src/__contracts/abi/index.ts b/packages/sdk/__contracts/abi/index.ts similarity index 100% rename from src/__contracts/abi/index.ts rename to packages/sdk/__contracts/abi/index.ts diff --git a/src/__contracts/addresses.ts b/packages/sdk/__contracts/addresses.ts similarity index 100% rename from src/__contracts/addresses.ts rename to packages/sdk/__contracts/addresses.ts diff --git a/src/__contracts/index.ts b/packages/sdk/__contracts/index.ts similarity index 85% rename from src/__contracts/index.ts rename to packages/sdk/__contracts/index.ts index eae2c5a65..38595002c 100644 --- a/src/__contracts/index.ts +++ b/packages/sdk/__contracts/index.ts @@ -1,14 +1,13 @@ import type { Hex } from "viem" +import { entryPoint07Address } from "viem/account-abstraction" import { EntrypointAbi, K1ValidatorAbi, K1ValidatorFactoryAbi } from "./abi" import addresses from "./addresses" export const ENTRYPOINT_SIMULATIONS: Hex = "0x74Cb5e4eE81b86e70f9045036a1C5477de69eE87" -export const ENTRYPOINT_ADDRESS: Hex = - "0x0000000071727De22E5E9d8BAf0edAc6f37da032" const entryPoint = { - address: ENTRYPOINT_ADDRESS, + address: entryPoint07Address, abi: EntrypointAbi } as const diff --git a/packages/sdk/account/index.ts b/packages/sdk/account/index.ts new file mode 100644 index 000000000..a7d2a4d2f --- /dev/null +++ b/packages/sdk/account/index.ts @@ -0,0 +1,4 @@ +export * from "./utils/index.js" +export * from "./signers/local-account.js" +export * from "./signers/wallet-client.js" +export * from "./toNexusAccount.js" diff --git a/src/account/signers/local-account.ts b/packages/sdk/account/signers/local-account.ts similarity index 100% rename from src/account/signers/local-account.ts rename to packages/sdk/account/signers/local-account.ts diff --git a/src/account/signers/wallet-client.ts b/packages/sdk/account/signers/wallet-client.ts similarity index 100% rename from src/account/signers/wallet-client.ts rename to packages/sdk/account/signers/wallet-client.ts diff --git a/packages/sdk/account/toNexusAccount.test.ts b/packages/sdk/account/toNexusAccount.test.ts new file mode 100644 index 000000000..1e9494dd4 --- /dev/null +++ b/packages/sdk/account/toNexusAccount.test.ts @@ -0,0 +1,115 @@ +import { http, type Account, type Address, type Chain, isHex } from "viem" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../tests/testSetup" +import { + getBalance, + getTestAccount, + killNetwork, + toTestClient, + topUp +} from "../../tests/testUtils" +import type { MasterClient, NetworkConfig } from "../../tests/testUtils" +import { type NexusAccount, toNexusAccount } from "./toNexusAccount" +import type { UserOperationStruct } from "./utils/Types" + +describe("nexus.account", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let nexusAccountAddress: Address + let nexusAccount: NexusAccount + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + testClient = toTestClient(chain, getTestAccount(0)) + + nexusAccount = await toNexusAccount({ + owner: account, + chain, + transport: http() + }) + + nexusAccountAddress = await nexusAccount.getCounterFactualAddress() + }) + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test.concurrent("should have 4337 account actions", async () => { + const [ + counterfactualAddress, + userOpHash, + address, + factoryArgs, + stubSignature, + signedMessage, + nonce, + initCode, + isDeployed, + encodedExecute, + encodedExecuteBatch + ] = await Promise.all([ + nexusAccount.getCounterFactualAddress(), + nexusAccount.getUserOpHash({ + sender: account.address, + nonce: 0n, + data: "0x", + signature: "0x", + verificationGasLimit: 1n, + preVerificationGas: 1n, + callData: "0x", + callGasLimit: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n + } as UserOperationStruct), + nexusAccount.getAddress(), + nexusAccount.getFactoryArgs(), + nexusAccount.getStubSignature(), + nexusAccount.signMessage({ message: "hello" }), + nexusAccount.getNonce(), + nexusAccount.getInitCode(), + nexusAccount.isDeployed(), + nexusAccount.encodeExecute({ to: account.address, value: 100n }), + nexusAccount.encodeExecuteBatch([{ to: account.address, value: 100n }]) + ]) + + expect(counterfactualAddress).toBe( + "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" + ) + expect(isHex(userOpHash)).toBe(true) + expect(address).toBe("0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54") + expect(factoryArgs.factory).toBe( + "0x8025afaD10209b8bEF3A3C94684AaE4D309c9996" + ) + expect(factoryArgs.factoryData).toBe( + "0x0d51f0b7000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + expect(stubSignature).toBe( + "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000d98238BBAeA4f91683d250003799EAd31d7F5c55000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000" + ) + expect(signedMessage).toBe( + "0x0000000000000000000000008025afad10209b8bef3a3c94684aae4d309c99960000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000a40d51f0b7000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000008025afad10209b8bef3a3c94684aae4d309c99960000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000a40d51f0b7000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055d98238bbaea4f91683d250003799ead31d7f5c55f16ea9a3478698f695fd1401bfe27e9e4a7e8e3da94aa72b021125e31fa899cc573c48ea3fe1d4ab61a9db10c19032026e3ed2dbccba5a178235ac27f94504311c000000000000000000000064926492649264926492649264926492649264926492649264926492649264926492649264926492649264926492649264926492649264926492649264926492" + ) + expect(nonce).toBe( + 22906337356820620590513465594309079979684970955157942146586636189696n + ) + expect(initCode).toBe( + "0x8025afaD10209b8bEF3A3C94684AaE4D309c99960d51f0b7000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + expect(isDeployed).toBe(false) + expect(encodedExecute).toBe( + "0xe9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000034f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000064000000000000000000000000" + ) + expect(encodedExecuteBatch).toBe( + "0xe9ae5c530100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + ) + }) +}) diff --git a/src/account/Nexus.ts b/packages/sdk/account/toNexusAccount.ts similarity index 70% rename from src/account/Nexus.ts rename to packages/sdk/account/toNexusAccount.ts index fccaab29c..b5ee25b0d 100644 --- a/src/account/Nexus.ts +++ b/packages/sdk/account/toNexusAccount.ts @@ -1,5 +1,4 @@ import { - http, type AbiParameter, type Account, type Address, @@ -27,27 +26,29 @@ import { walletActions } from "viem" import { - type BundlerClientConfig, type SmartAccount, type SmartAccountImplementation, type UserOperation, - createBundlerClient, + entryPoint07Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction" import { parseAccount } from "viem/accounts" import contracts from "../__contracts" import { EntrypointAbi, K1ValidatorFactoryAbi } from "../__contracts/abi" -import { EXECUTE_BATCH, EXECUTE_SINGLE } from "../bundler/utils/Constants" +import type { Call, GetNonceArgs, UserOperationStruct } from "./utils/Types" + import { - type GetNonceArgs, + EXECUTE_BATCH, + EXECUTE_SINGLE, MAGIC_BYTES, - MODE_VALIDATION, - type UserOperationStruct, - WalletClientSigner -} from "../index" + MODE_VALIDATION +} from "./utils/Constants" + +import type { BaseExecutionModule } from "../modules/base/BaseExecutionModule" import type { BaseValidationModule } from "../modules/base/BaseValidationModule" import { K1ValidatorModule } from "../modules/validators/K1ValidatorModule" +import { WalletClientSigner } from "./signers/wallet-client" import { packUserOp } from "./utils/Utils" export type ToNexusSmartAccountParameters = { @@ -56,6 +57,7 @@ export type ToNexusSmartAccountParameters = { owner: Account | Address index?: bigint | undefined activeModule?: BaseValidationModule + exectutorModule?: BaseExecutionModule factoryAddress?: Address k1ValidatorAddress?: Address } & Prettify< @@ -71,7 +73,9 @@ export type ToNexusSmartAccountParameters = { > > -export type Nexus = Prettify> +export type NexusAccount = Prettify< + SmartAccount +> export type NexusSmartAccountImplementation = SmartAccountImplementation< typeof EntrypointAbi, @@ -88,15 +92,23 @@ export type NexusSmartAccountImplementation = SmartAccountImplementation< } > -export type Call = { - to: Hex - data?: Hex | undefined - value?: bigint | undefined -} - +/** + * Parameters for creating a Nexus Smart Account + * @typedef {Object} ToNexusSmartAccountParameters + * @property {Chain} chain - The blockchain network + * @property {ClientConfig["transport"]} transport - The transport configuration + * @property {Account | Address} owner - The owner account or address + * @property {bigint} [index] - Optional index for the account + * @property {BaseValidationModule} [activeModule] - Optional active validation module + * @property {BaseExecutionModule} [exectutorModule] - Optional executor module + * @property {Address} [factoryAddress] - Optional factory address + * @property {Address} [k1ValidatorAddress] - Optional K1 validator address + * @property {string} [key] - Optional key for the wallet client + * @property {string} [name] - Optional name for the wallet client + */ export const toNexusAccount = async ( parameters: ToNexusSmartAccountParameters -): Promise => { +): Promise => { const { chain, transport, @@ -140,9 +152,9 @@ export const toNexusAccount = async ( activeModule ?? new K1ValidatorModule( { - moduleAddress: k1ValidatorAddress, + address: k1ValidatorAddress, type: "validator", - data: signerAddress, + context: signerAddress, additionalContext: "0x" }, moduleSigner @@ -151,34 +163,53 @@ export const toNexusAccount = async ( let _accountAddress: Address const getAddress = async () => { if (_accountAddress) return _accountAddress - _accountAddress = await masterClient.readContract({ + _accountAddress = (await masterClient.readContract({ address: factoryAddress, abi: K1ValidatorFactoryAbi, functionName: "computeAccountAddress", args: [signerAddress, index, [], 0] - }) + })) as Address return _accountAddress } + + /** + * Gets the counterfactual address of the account + * @returns {Promise
} A promise that resolves to the counterfactual address + * @throws {Error} If unable to get the counterfactual address + */ const getCounterFactualAddress = async (): Promise
=> { try { await entryPointContract.simulate.getSenderAddress([getInitCode()]) - } catch (e) { - if (e.cause?.data?.errorName === "SenderAddressResult") { - _accountAddress = e.cause.data.args[0] as Address + } catch (e: any) { + if (e?.cause?.data?.errorName === "SenderAddressResult") { + _accountAddress = e?.cause.data.args[0] as Address return _accountAddress } } throw new Error("Failed to get counterfactual account address") } + /** + * Gets the init code for the account + * @returns {Hex} The init code as a hexadecimal string + */ const getInitCode = () => concatHex([factoryAddress, factoryData]) + /** + * Checks if the account is deployed + * @returns {Promise} A promise that resolves to true if the account is deployed, false otherwise + */ const isDeployed = async (): Promise => { const address = await getCounterFactualAddress() const contractCode = await masterClient.getCode({ address }) return (contractCode?.length ?? 0) > 2 } + /** + * Calculates the hash of a user operation + * @param {Partial} userOp - The user operation + * @returns {Promise} A promise that resolves to the hash of the user operation + */ const getUserOpHash = async ( userOp: Partial ): Promise => { @@ -191,7 +222,16 @@ export const toNexusAccount = async ( return keccak256(enc) } - const encodeExecuteBatch = async (calls: readonly Call[]): Promise => { + /** + * Encodes a batch of calls for execution + * @param {readonly Call[]} calls - An array of calls to encode + * @param {Hex} [mode=EXECUTE_BATCH] - The execution mode + * @returns {Promise} A promise that resolves to the encoded calls + */ + const encodeExecuteBatch = async ( + calls: readonly Call[], + mode = EXECUTE_BATCH + ): Promise => { const executionAbiParams: AbiParameter = { type: "tuple[]", components: [ @@ -216,20 +256,25 @@ export const toNexusAccount = async ( "function execute(bytes32 mode, bytes calldata executionCalldata) external" ]), functionName: "execute", - args: [EXECUTE_BATCH, executionCalldataPrep] + args: [mode, executionCalldataPrep] }) } - const encodeExecute = async (call: Call): Promise => { - const mode = EXECUTE_SINGLE + /** + * Encodes a single call for execution + * @param {Call} call - The call to encode + * @param {Hex} [mode=EXECUTE_SINGLE] - The execution mode + * @returns {Promise} A promise that resolves to the encoded call + */ + const encodeExecute = async ( + call: Call, + mode = EXECUTE_SINGLE + ): Promise => { const executionCalldata = encodePacked( ["address", "uint256", "bytes"], - [ - call.to as Hex, - BigInt(call.value ?? 0n), - (call.data as Hex) ?? ("0x" as Hex) - ] + [call.to as Hex, BigInt(call.value ?? 0n), (call.data ?? "0x") as Hex] ) + return encodeFunctionData({ abi: parseAbi([ "function execute(bytes32 mode, bytes calldata executionCalldata) external" @@ -239,6 +284,11 @@ export const toNexusAccount = async ( }) } + /** + * Gets the nonce for the account + * @param {GetNonceArgs} [args] - Optional arguments for getting the nonce + * @returns {Promise} A promise that resolves to the nonce + */ const getNonce = async ({ validationMode: _validationMode = MODE_VALIDATION, nonceOptions @@ -249,10 +299,10 @@ export const toNexusAccount = async ( _validationMode = nonceOptions.validationMode } try { - const key = concat([ + const key: string = concat([ "0x000000", _validationMode, - defaultedActiveModule.moduleAddress + defaultedActiveModule.address ]) const accountAddress = await getAddress() return await entryPointContract.read.getNonce([ @@ -264,6 +314,12 @@ export const toNexusAccount = async ( } } + /** + * Signs a message + * @param {Object} params - The parameters for signing + * @param {SignableMessage} params.message - The message to sign + * @returns {Promise} A promise that resolves to the signature + */ const signMessage = async ({ message }: { message: SignableMessage }): Promise => { @@ -338,7 +394,7 @@ export const toNexusAccount = async ( const userOperation = { ...userOpWithoutSender, sender: address } const hash = getUserOperationHash({ chainId, - entryPointAddress: contracts.entryPoint.address, + entryPointAddress: entryPoint07Address, entryPointVersion: "0.7", userOperation }) diff --git a/src/account/utils/AccountNotFound.ts b/packages/sdk/account/utils/AccountNotFound.ts similarity index 100% rename from src/account/utils/AccountNotFound.ts rename to packages/sdk/account/utils/AccountNotFound.ts diff --git a/src/account/utils/Constants.ts b/packages/sdk/account/utils/Constants.ts similarity index 72% rename from src/account/utils/Constants.ts rename to packages/sdk/account/utils/Constants.ts index cdca61c40..fb51d8958 100644 --- a/src/account/utils/Constants.ts +++ b/packages/sdk/account/utils/Constants.ts @@ -1,4 +1,4 @@ -import { type Hex, keccak256, toHex } from "viem" +import { type Hex, concat, keccak256, pad, toHex } from "viem" export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000" export const MAGIC_BYTES = "0x6492649264926492649264926492649264926492649264926492649264926492" @@ -80,3 +80,57 @@ export const MOCK_MULTI_MODULE_ADDRESS = "0x9C992f91E7Cd4697B81E137007f446E826b8378b" export const MODULE_TYPE_MULTI = 0 export const eip1271MagicValue: Hex = "0x1626ba7e" + +export const EXECUTE_SINGLE = concat([ + CALLTYPE_SINGLE, + EXECTYPE_DEFAULT, + MODE_DEFAULT, + UNUSED, + MODE_PAYLOAD +]) + +export const EXECUTE_BATCH = concat([ + CALLTYPE_BATCH, + EXECTYPE_DEFAULT, + MODE_DEFAULT, + UNUSED, + MODE_PAYLOAD +]) + +export const ACCOUNT_MODES = { + DEFAULT_SINGLE: concat([ + pad(EXECTYPE_DEFAULT, { size: 1 }), + pad(CALLTYPE_SINGLE, { size: 1 }), + pad(UNUSED, { size: 4 }), + pad(MODE_DEFAULT, { size: 4 }), + pad(MODE_PAYLOAD, { size: 22 }) + ]), + DEFAULT_BATCH: concat([ + pad(EXECTYPE_DEFAULT, { size: 1 }), + pad(CALLTYPE_BATCH, { size: 1 }), + pad(UNUSED, { size: 4 }), + pad(MODE_DEFAULT, { size: 4 }), + pad(MODE_PAYLOAD, { size: 22 }) + ]), + TRY_BATCH: concat([ + pad(EXECTYPE_TRY, { size: 1 }), + pad(CALLTYPE_BATCH, { size: 1 }), + pad(UNUSED, { size: 4 }), + pad(MODE_DEFAULT, { size: 4 }), + pad(MODE_PAYLOAD, { size: 22 }) + ]), + TRY_SINGLE: concat([ + pad(EXECTYPE_TRY, { size: 1 }), + pad(CALLTYPE_SINGLE, { size: 1 }), + pad(UNUSED, { size: 4 }), + pad(MODE_DEFAULT, { size: 4 }), + pad(MODE_PAYLOAD, { size: 22 }) + ]), + DELEGATE_SINGLE: concat([ + pad(EXECTYPE_DELEGATE, { size: 1 }), + pad(CALLTYPE_SINGLE, { size: 1 }), + pad(UNUSED, { size: 4 }), + pad(MODE_DEFAULT, { size: 4 }), + pad(MODE_PAYLOAD, { size: 22 }) + ]) +} diff --git a/src/account/utils/EthersSigner.ts b/packages/sdk/account/utils/EthersSigner.ts similarity index 94% rename from src/account/utils/EthersSigner.ts rename to packages/sdk/account/utils/EthersSigner.ts index 8af82cb10..bed5708bd 100644 --- a/src/account/utils/EthersSigner.ts +++ b/packages/sdk/account/utils/EthersSigner.ts @@ -1,5 +1,5 @@ import type { Hex, SignableMessage } from "viem" -import type { LightSigner, SmartAccountSigner } from "../utils/Types.js" +import type { LightSigner, SmartAccountSigner } from "./Types.js" export class EthersSigner implements SmartAccountSigner diff --git a/src/account/utils/Helpers.ts b/packages/sdk/account/utils/Helpers.ts similarity index 100% rename from src/account/utils/Helpers.ts rename to packages/sdk/account/utils/Helpers.ts diff --git a/src/account/utils/HttpRequests.ts b/packages/sdk/account/utils/HttpRequests.ts similarity index 97% rename from src/account/utils/HttpRequests.ts rename to packages/sdk/account/utils/HttpRequests.ts index 0fcc51025..474b12e70 100644 --- a/src/account/utils/HttpRequests.ts +++ b/packages/sdk/account/utils/HttpRequests.ts @@ -1,6 +1,6 @@ -import { getAAError } from "../../bundler/utils/getAAError.js" import { Logger } from "./Logger.js" import type { Service } from "./Types.js" +import { getAAError } from "./getAAError.js" export enum HttpMethod { Get = "get", diff --git a/src/account/utils/Logger.ts b/packages/sdk/account/utils/Logger.ts similarity index 100% rename from src/account/utils/Logger.ts rename to packages/sdk/account/utils/Logger.ts diff --git a/src/account/utils/Types.ts b/packages/sdk/account/utils/Types.ts similarity index 76% rename from src/account/utils/Types.ts rename to packages/sdk/account/utils/Types.ts index 44b5f5546..e6ebda18d 100644 --- a/src/account/utils/Types.ts +++ b/packages/sdk/account/utils/Types.ts @@ -3,31 +3,188 @@ import type { Chain, Hash, Hex, + Log, PrivateKeyAccount, - PublicClient, SignTypedDataParameters, SignableMessage, TypedData, TypedDataDefinition, WalletClient } from "viem" -import type { IBundler } from "../../bundler" -import type { Module, ModuleInfo, ModuleType } from "../../modules" +import type { ModuleInfo, ModuleType } from "../../modules" import type { BaseValidationModule } from "../../modules/base/BaseValidationModule" -import type { - FeeQuotesOrDataDto, - IPaymaster, - PaymasterFeeQuote, - SmartAccountData, - SponsorUserOperationDto -} from "../../paymaster" -import type { MODE_MODULE_ENABLE, MODE_VALIDATION } from "../utils/Constants" +import type { MODE_MODULE_ENABLE, MODE_VALIDATION } from "./Constants" export type EntryPointAddresses = Record export type BiconomyFactories = Record export type EntryPointAddressesByVersion = Record export type BiconomyFactoriesByVersion = Record +export type UserOpGasResponse = { + preVerificationGas: bigint + verificationGasLimit: bigint + callGasLimit: bigint + paymasterVerificationGasLimit?: bigint + paymasterPostOpGasLimit?: bigint +} +export type TStatus = "success" | "reverted" + +export type UserOpReceiptTransaction = { + transactionHash: Hex + transactionIndex: bigint + blockHash: Hash + blockNumber: bigint + from: Address + to: Address | null + cumulativeGasUsed: bigint + status: TStatus + gasUsed: bigint + contractAddress: Address | null + logsBloom: Hex + effectiveGasPrice: bigint +} + +export type UserOpReceipt = { + userOpHash: Hash + entryPoint: Address + sender: Address + nonce: bigint + paymaster?: Address + actualGasUsed: bigint + actualGasCost: bigint + success: boolean + reason?: string + receipt: UserOpReceiptTransaction + logs: Log[] +} + +export type UserOpResponse = { + userOpHash: Hash + wait(_confirmations?: number): Promise +} + +export type GetUserOperationGasPriceReturnType = { + slow: { + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + } + standard: { + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + } + fast: { + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + } +} + +export type BundlerEstimateUserOpGasResponse = { + preVerificationGas: Hex + verificationGasLimit: Hex + callGasLimit?: Hex | null + paymasterVerificationGasLimit?: Hex | null + paymasterPostOpGasLimit?: Hex | null +} + +export type JsonRpcError = { + code: string + message: string + data: any +} + +export type GetUserOpByHashResponse = { + jsonrpc: string + id: number + result: UserOpByHashResponse + error?: JsonRpcError +} + +export type UserOpStatus = { + state: string // for now // could be an enum + transactionHash?: string + userOperationReceipt?: UserOpReceipt +} + +export type UserOpByHashResponse = UserOperationStruct & { + transactionHash: string + blockNumber: number + blockHash: string + entryPoint: string +} + +export interface IBundler { + estimateUserOpGas( + _userOp: Partial, + stateOverrideSet?: StateOverrideSet + ): Promise + sendUserOp(_userOp: UserOperationStruct): Promise + getUserOpReceipt(_userOpHash: string): Promise + getUserOpByHash(_userOpHash: string): Promise + getGasFeeValues(): Promise + getUserOpStatus(_userOpHash: string): Promise + getBundlerUrl(): string +} + +export type PaymasterAndDataResponse = { + paymasterAndData: Hex + /* Gas overhead of this UserOperation */ + preVerificationGas: number + /* Actual gas used by the validation of this UserOperation */ + verificationGasLimit: number + /* Value used by inner account execution */ + callGasLimit: number +} + +export interface IPaymaster { + // Implementing class may add extra parameter (for example paymasterServiceData with it's own type) in below function signature + getPaymasterAndData( + _userOp: Partial + ): Promise + getDummyPaymasterAndData( + _userOp: Partial + ): Promise +} +export type PaymasterFeeQuote = { + /** symbol: Token symbol */ + symbol: string + /** tokenAddress: Token address */ + tokenAddress: string + /** decimal: Token decimal */ + decimal: number + logoUrl?: string + /** maxGasFee: in wei */ + maxGasFee: number + /** maxGasFee: in dollars */ + maxGasFeeUSD?: number + usdPayment?: number + /** The premium paid on the token */ + premiumPercentage: number + /** validUntil: Unix timestamp */ + validUntil?: number +} + +export type SponsorUserOperationDto = { + /** mode: sponsored or erc20 */ + mode: "SPONSORED" | "ERC20" + /** Always recommended, especially when using token paymaster */ + calculateGasLimits?: boolean + /** Expiry duration in seconds */ + expiryDuration?: number + /** Webhooks to be fired after user op is sent */ + webhookData?: Record + /** Smart account meta data */ + smartAccountInfo?: SmartAccountData + /** the fee-paying token address */ + feeTokenAddress?: string +} + +export type SmartAccountData = { + /** name: Name of the smart account */ + name: string + /** version: Version of the smart account */ + version: string +} + export type SmartAccountConfig = { /** entryPointAddress: address of the entry point */ entryPointAddress: Address @@ -252,6 +409,24 @@ export type InitilizationData = { signerAddress?: string } +export type FeeQuotesOrDataDto = { + /** mode: sponsored or erc20 */ + mode?: "SPONSORED" | "ERC20" + /** Expiry duration in seconds */ + expiryDuration?: number + /** Always recommended, especially when using token paymaster */ + calculateGasLimits?: boolean + /** List of tokens to be used for fee quotes, if ommitted fees for all supported will be returned */ + tokenList?: string[] + /** preferredToken: Can be ommitted to return all quotes */ + preferredToken?: string + /** Webhooks to be fired after user op is sent */ + // biome-ignore lint/suspicious/noExplicitAny: + webhookData?: Record + /** Smart account meta data */ + smartAccountInfo?: SmartAccountData +} + export type PaymasterUserOperationDto = SponsorUserOperationDto & FeeQuotesOrDataDto & { /** mode: sponsored or erc20 */ @@ -343,9 +518,6 @@ export type ValueOrData = RequireAtLeastOne< }, "value" | "data" > -export type Transaction = { - to: string -} & ValueOrData export type SupportedToken = Omit< PaymasterFeeQuote, @@ -470,149 +642,6 @@ export type BaseSmartContractAccountProps = accountAddress?: Address } -export interface ISmartContractAccount< - TSigner extends SmartAccountSigner = SmartAccountSigner -> { - /** - * The RPC provider the account uses to make RPC calls - */ - publicClient: PublicClient - - /** - * @returns the init code for the account - */ - getInitCode(): Promise - - /** - * This is useful for estimating gas costs. It should return a signature that doesn't cause the account to revert - * when validation is run during estimation. - * - * @returns a dummy signature that doesn't cause the account to revert during estimation - */ - getDummySignature(): Hex - - /** - * Encodes a call to the account's execute function. - * - * @param target - the address receiving the call data - * @param value - optionally the amount of native token to send - * @param data - the call data or "0x" if empty - */ - encodeExecute(transaction: Transaction, useExecutor: boolean): Promise - - /** - * Encodes a batch of transactions to the account's batch execute function. - * NOTE: not all accounts support batching. - * @param txs - An Array of objects containing the target, value, and data for each transaction - * @returns the encoded callData for a UserOperation - */ - encodeBatchExecute(txs: BatchUserOperationCallData): Promise - - /** - * @returns the nonce of the account - */ - getNonce( - validationMode?: typeof MODE_VALIDATION | typeof MODE_MODULE_ENABLE - ): Promise - - /** - * If your account handles 1271 signatures of personal_sign differently - * than it does UserOperations, you can implement two different approaches to signing - * - * @param uoHash -- The hash of the UserOperation to sign - * @returns the signature of the UserOperation - */ - signUserOperationHash(uoHash: Hash): Promise - - /** - * Returns a signed and prefixed message. - * - * @param msg - the message to sign - * @returns the signature of the message - */ - signMessage(msg: string | Uint8Array | Hex): Promise - - /** - * Signs a typed data object as per ERC-712 - * - * @param params - {@link SignTypedDataParams} - * @returns the signed hash for the message passed - */ - signTypedData(params: SignTypedDataParams): Promise - - /** - * If the account is not deployed, it will sign the message and then wrap it in 6492 format - * - * @param msg - the message to sign - * @returns ths signature wrapped in 6492 format - */ - signMessageWith6492(msg: string | Uint8Array | Hex): Promise - - /** - * If the account is not deployed, it will sign the typed data blob and then wrap it in 6492 format - * - * @param params - {@link SignTypedDataParams} - * @returns the signed hash for the params passed in wrapped in 6492 format - */ - signTypedDataWith6492(params: SignTypedDataParams): Promise - - /** - * @returns the address of the account - */ - getAddress(): Promise
- - /** - * @returns the current account signer instance that the smart account client - * operations are being signed with. - * - * The signer is expected to be the owner or one of the owners of the account - * for the signatures to be valid for the acting account. - */ - getSigner(): TSigner - - /** - * @returns the address of the factory contract for the smart account - */ - getFactoryAddress(): Address - - /** - * @returns the address of the entry point contract for the smart account - */ - getEntryPointAddress(): Address - - /** - * Allows you to add additional functionality and utility methods to this account - * via a decorator pattern. - * - * NOTE: this method does not allow you to override existing methods on the account. - * - * @example - * ```ts - * const account = new BaseSmartCobntractAccount(...).extend((account) => ({ - * readAccountState: async (...args) => { - * return this.rpcProvider.readContract({ - * address: await this.getAddress(), - * abi: ThisContractsAbi - * args: args - * }); - * } - * })); - * - * account.debugSendUserOperation(...); - * ``` - * - * @param extendFn -- this function gives you access to the created account instance and returns an object - * with the extension methods - * @returns -- the account with the extension methods added - */ - extend: (extendFn: (self: this) => R) => this & R - - encodeUpgradeToAndCall: ( - upgradeToImplAddress: Address, - upgradeToInitData: Hex - ) => Promise -} - export type TransferOwnershipCompatibleModule = | "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e" | "0x000000824dc138db84FD9109fc154bdad332Aa8E" @@ -646,3 +675,11 @@ export type GetNonceArgs = { validationMode?: "0x00" | "0x01" nonceOptions?: NonceOptions } +export type Call = { + to: Hex + data?: Hex | undefined + value?: bigint | undefined +} + +export type PartiallyOptional = Omit & + Partial> diff --git a/src/account/utils/Utils.ts b/packages/sdk/account/utils/Utils.ts similarity index 97% rename from src/account/utils/Utils.ts rename to packages/sdk/account/utils/Utils.ts index 843d66c73..b00c4b1a5 100644 --- a/src/account/utils/Utils.ts +++ b/packages/sdk/account/utils/Utils.ts @@ -12,12 +12,9 @@ import { toBytes, toHex } from "viem" -import type { UserOperationStruct } from "../../account" -import { - MOCK_MULTI_MODULE_ADDRESS, - MODULE_ENABLE_MODE_TYPE_HASH -} from "../../account" +import { MOCK_MULTI_MODULE_ADDRESS, MODULE_ENABLE_MODE_TYPE_HASH } from ".." import { type ModuleType, moduleTypeIds } from "../../modules/utils/Types" +import type { UserOperationStruct } from "./Types" /** * pack the userOperation diff --git a/src/account/utils/convertSigner.ts b/packages/sdk/account/utils/convertSigner.ts similarity index 96% rename from src/account/utils/convertSigner.ts rename to packages/sdk/account/utils/convertSigner.ts index 3dd8bfd59..ec169c5ad 100644 --- a/src/account/utils/convertSigner.ts +++ b/packages/sdk/account/utils/convertSigner.ts @@ -4,8 +4,8 @@ import { type WalletClient, createWalletClient } from "viem" -import { ERROR_MESSAGES, WalletClientSigner } from "../../account" -import type { Signer, SmartAccountSigner, SupportedSigner } from "../../account" +import { ERROR_MESSAGES, WalletClientSigner } from "../index.js" +import type { Signer, SmartAccountSigner, SupportedSigner } from "../index.js" import { EthersSigner } from "./EthersSigner.js" interface SmartAccountResult { diff --git a/src/bundler/utils/getAAError.ts b/packages/sdk/account/utils/getAAError.ts similarity index 93% rename from src/bundler/utils/getAAError.ts rename to packages/sdk/account/utils/getAAError.ts index 2f0425b3c..8be4806c0 100644 --- a/src/bundler/utils/getAAError.ts +++ b/packages/sdk/account/utils/getAAError.ts @@ -1,6 +1,5 @@ import { BaseError } from "viem" -import type { Service } from "../../account" -import { SDK_VERSION } from "./Constants" +import type { Service } from ".." export type KnownError = { name: string regex: string @@ -47,7 +46,7 @@ type AccountAbstractionErrorParams = { class AccountAbstractionError extends BaseError { override name = "AccountAbstractionError" - override version = `@biconomy/account@${SDK_VERSION}` + override version = "@biconomy/sdk" constructor(title: string, params: AccountAbstractionErrorParams = {}) { super(title, params) diff --git a/src/account/utils/getChain.ts b/packages/sdk/account/utils/getChain.ts similarity index 95% rename from src/account/utils/getChain.ts rename to packages/sdk/account/utils/getChain.ts index ede2ea4db..991a3f35d 100644 --- a/src/account/utils/getChain.ts +++ b/packages/sdk/account/utils/getChain.ts @@ -64,7 +64,7 @@ type StringOrStrings = string | string[] * * @example * - * import { getCustomChain, createSmartAccountClient } from "@biconomy/account" + * import { getCustomChain, toNexusClient } from "@biconomy/account" * * const customChain = getCustomChain( * "My Custom Chain", @@ -80,7 +80,7 @@ type StringOrStrings = string | string[] * transport: http() * }) * - * const smartAccountCustomChain = await createSmartAccountClient({ + * const smartAccountCustomChain = await toNexusClient({ * signer: walletClientWithCustomChain, * bundlerUrl, * customChain diff --git a/src/account/utils/index.ts b/packages/sdk/account/utils/index.ts similarity index 100% rename from src/account/utils/index.ts rename to packages/sdk/account/utils/index.ts diff --git a/packages/sdk/account/utils/utils.test.ts b/packages/sdk/account/utils/utils.test.ts new file mode 100644 index 000000000..b65e73c6c --- /dev/null +++ b/packages/sdk/account/utils/utils.test.ts @@ -0,0 +1,58 @@ +import { ParamType, ethers } from "ethers" +import { type AbiParameter, encodeAbiParameters } from "viem" +import { describe, expect, test } from "vitest" + +describe("utils", () => { + test.concurrent( + "should have consistent behaviour between ethers.AbiCoder.defaultAbiCoder() and viem.encodeAbiParameters()", + async () => { + const expectedResult = + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + + const Executions = ParamType.from({ + type: "tuple(address,uint256,bytes)[]", + baseType: "tuple", + name: "executions", + arrayLength: null, + components: [ + { name: "target", type: "address" }, + { name: "value", type: "uint256" }, + { name: "callData", type: "bytes" } + ] + }) + + const viemExecutions: AbiParameter = { + type: "tuple[]", + components: [ + { name: "target", type: "address" }, + { name: "value", type: "uint256" }, + { name: "callData", type: "bytes" } + ] + } + + const txs = [ + { + target: "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + callData: "0x", + value: 1n + }, + { + target: "0x90F79bf6EB2c4f870365E785982E1f101E93b906", + callData: "0x", + value: 1n + } + ] + + const executionCalldataPrepWithEthers = + ethers.AbiCoder.defaultAbiCoder().encode([Executions], [txs]) + + const executionCalldataPrepWithViem = encodeAbiParameters( + [viemExecutions], + [txs] + ) + + expect(executionCalldataPrepWithEthers).toBe(expectedResult) + expect(executionCalldataPrepWithViem).toBe(expectedResult) + } + ) +}) diff --git a/src/clients/decorators/erc7579/accountId.ts b/packages/sdk/clients/decorators/erc7579/accountId.ts similarity index 100% rename from src/clients/decorators/erc7579/accountId.ts rename to packages/sdk/clients/decorators/erc7579/accountId.ts diff --git a/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts b/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts new file mode 100644 index 000000000..aa9e014ee --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts @@ -0,0 +1,116 @@ +import { textSpanOverlapsWith } from "typescript" +import { http, type Account, type Address, type Chain, isHex } from "viem" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../../../tests/testSetup" +import { + type MasterClient, + type NetworkConfig, + fundAndDeploy, + getTestAccount, + killNetwork, + toTestClient +} from "../../../../tests/testUtils" +import contracts from "../../../__contracts" +import { type NexusClient, toNexusClient } from "../../toNexusClient" + +describe("erc7579.decorators", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let nexusClient: NexusClient + let nexusAccountAddress: Address + let recipient: Account + let recipientAddress: Address + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + recipient = getTestAccount(1) + recipientAddress = recipient.address + testClient = toTestClient(chain, getTestAccount(0)) + + nexusClient = await toNexusClient({ + owner: account, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + await fundAndDeploy(testClient, [nexusClient]) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test.concurrent("should test read methods", async () => { + const [ + installedValidators, + installedExecutors, + activeHook, + fallbackSelector, + supportsValidator, + supportsDelegateCall, + isK1ValidatorInstalled + ] = await Promise.all([ + nexusClient.getInstalledValidators({}), + nexusClient.getInstalledExecutors({}), + nexusClient.getActiveHook({}), + nexusClient.getFallbackBySelector({ selector: "0xcb5baf0f" }), + nexusClient.supportsModule({ type: "validator" }), + nexusClient.supportsExecutionMode({ type: "delegatecall" }), + nexusClient.isModuleInstalled({ + module: { + type: "validator", + address: contracts.k1Validator.address, + context: "0x" + } + }) + ]) + + expect(installedExecutors[0].length).toBeTypeOf("number") + expect(installedValidators[0]).toEqual([contracts.k1Validator.address]) + expect(isHex(activeHook)).toBe(true) + expect(fallbackSelector.length).toBeTypeOf("number") + expect(supportsValidator).toBe(true) + expect(supportsDelegateCall).toBe(true) + expect(isK1ValidatorInstalled).toBe(true) + }) + + test.skip("should uninstall a module", async () => { + const gas = await testClient.estimateFeesPerGas() + + const hash = await nexusClient.uninstallModule({ + ...gas, + module: { + type: "validator", + address: contracts.k1Validator.address, + context: "0x" + } + }) + + const { success } = await nexusClient.waitForUserOperationReceipt({ hash }) + expect(success).toBe(true) + }) + + test.skip("should install a module", async () => { + const hash = await nexusClient.installModule({ + module: { + type: "validator", + address: contracts.k1Validator.address, + context: "0x" + } + }) + + const { success } = await nexusClient.waitForUserOperationReceipt({ hash }) + expect(success).toBe(true) + }) +}) diff --git a/packages/sdk/clients/decorators/erc7579/getActiveHook.ts b/packages/sdk/clients/decorators/erc7579/getActiveHook.ts new file mode 100644 index 000000000..0e4dbb8bc --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/getActiveHook.ts @@ -0,0 +1,55 @@ +import type { Chain, Client, Hex, Transport } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +export type GetActiveHookParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter + +export async function getActiveHook< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: GetActiveHookParameters +): Promise { + const { account: account_ = client.account } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + return getAction( + publicClient, + readContract, + "readContract" + )({ + address: account.address, + abi: [ + { + inputs: [], + name: "getActiveHook", + outputs: [ + { + internalType: "address", + name: "hook", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "getActiveHook" + }) as Promise +} diff --git a/packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts b/packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts new file mode 100644 index 000000000..1257e2d52 --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts @@ -0,0 +1,74 @@ +import type { Chain, Client, Hex, Transport } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { GENERIC_FALLBACK_SELECTOR } from "../../../account/utils/Constants" + +export type GetFallbackBySelectorParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & + Partial<{ + selector?: Hex + }> + +export async function getFallbackBySelector< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: GetFallbackBySelectorParameters +): Promise<[Hex, Hex]> { + const { + account: account_ = client.account, + selector = GENERIC_FALLBACK_SELECTOR + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + return getAction( + publicClient, + readContract, + "readContract" + )({ + address: account.address, + abi: [ + { + inputs: [ + { + internalType: "bytes4", + name: "selector", + type: "bytes4" + } + ], + name: "getFallbackHandlerBySelector", + outputs: [ + { + internalType: "CallType", + name: "", + type: "bytes1" + }, + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "getFallbackHandlerBySelector", + args: [selector] + }) as Promise<[Hex, Hex]> +} diff --git a/packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts b/packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts new file mode 100644 index 000000000..af1e634e0 --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts @@ -0,0 +1,80 @@ +import type { Chain, Client, Hex, Transport } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { SENTINEL_ADDRESS } from "../../../account/utils/Constants" + +export type GetInstalledExecutorsParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + pageSize?: bigint + cursor?: Hex +} + +export async function getInstalledExecutors< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: GetInstalledExecutorsParameters +): Promise { + const { + account: account_ = client.account, + pageSize = 100n, + cursor = SENTINEL_ADDRESS + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + return getAction( + publicClient, + readContract, + "readContract" + )({ + address: account.address, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "cursor", + type: "address" + }, + { + internalType: "uint256", + name: "size", + type: "uint256" + } + ], + name: "getExecutorsPaginated", + outputs: [ + { + internalType: "address[]", + name: "array", + type: "address[]" + }, + { + internalType: "address", + name: "next", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "getExecutorsPaginated", + args: [cursor, pageSize] + }) as Promise +} diff --git a/packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts b/packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts new file mode 100644 index 000000000..07cebce68 --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts @@ -0,0 +1,80 @@ +import type { Chain, Client, Hex, Transport } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { SENTINEL_ADDRESS } from "../../../account/utils/Constants" + +export type GetInstalledValidatorsParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + pageSize?: bigint + cursor?: Hex +} + +export async function getInstalledValidators< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: GetInstalledValidatorsParameters +): Promise { + const { + account: account_ = client.account, + pageSize = 100n, + cursor = SENTINEL_ADDRESS + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + return getAction( + publicClient, + readContract, + "readContract" + )({ + address: account.address, + abi: [ + { + inputs: [ + { + internalType: "address", + name: "cursor", + type: "address" + }, + { + internalType: "uint256", + name: "size", + type: "uint256" + } + ], + name: "getValidatorsPaginated", + outputs: [ + { + internalType: "address[]", + name: "array", + type: "address[]" + }, + { + internalType: "address", + name: "next", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } + ], + functionName: "getValidatorsPaginated", + args: [cursor, pageSize] + }) as Promise +} diff --git a/src/clients/decorators/erc7579/index.ts b/packages/sdk/clients/decorators/erc7579/index.ts similarity index 56% rename from src/clients/decorators/erc7579/index.ts rename to packages/sdk/clients/decorators/erc7579/index.ts index 458b4774e..4d883440d 100644 --- a/src/clients/decorators/erc7579/index.ts +++ b/packages/sdk/clients/decorators/erc7579/index.ts @@ -1,9 +1,23 @@ -import type { Chain, Client, Hash, Transport } from "viem" +import type { Address, Chain, Client, Hash, Hex, Transport } from "viem" import type { GetSmartAccountParameter, SmartAccount } from "viem/account-abstraction" +import type { SafeHookType } from "../../../modules/utils/Types.js" import { accountId } from "./accountId.js" +import { type GetActiveHookParameters, getActiveHook } from "./getActiveHook.js" +import { + type GetFallbackBySelectorParameters, + getFallbackBySelector +} from "./getFallbackBySelector.js" +import { + type GetInstalledExecutorsParameters, + getInstalledExecutors +} from "./getInstalledExecutors.js" +import { + type GetInstalledValidatorsParameters, + getInstalledValidators +} from "./getInstalledValidators.js" import { type InstallModuleParameters, installModule } from "./installModule.js" import { type InstallModulesParameters, @@ -53,6 +67,16 @@ export type Erc7579Actions = { uninstallModules: ( args: UninstallModulesParameters ) => Promise + getInstalledValidators: ( + args: GetInstalledValidatorsParameters + ) => Promise + getInstalledExecutors: ( + args: GetInstalledExecutorsParameters + ) => Promise + getActiveHook: (args: GetActiveHookParameters) => Promise + getFallbackBySelector: ( + args: GetFallbackBySelectorParameters + ) => Promise<[Hex, Hex]> } export type { @@ -63,7 +87,10 @@ export type { SupportsExecutionModeParameters, ModuleType, SupportsModuleParameters, - UninstallModuleParameters + UninstallModuleParameters, + GetInstalledValidatorsParameters, + GetInstalledExecutorsParameters, + GetActiveHookParameters } export { @@ -74,7 +101,11 @@ export { supportsExecutionMode, supportsModule, uninstallModule, - uninstallModules + uninstallModules, + getInstalledValidators, + getInstalledExecutors, + getActiveHook, + getFallbackBySelector } export function erc7579Actions() { @@ -88,6 +119,31 @@ export function erc7579Actions() { supportsExecutionMode: (args) => supportsExecutionMode(client, args), supportsModule: (args) => supportsModule(client, args), uninstallModule: (args) => uninstallModule(client, args), - uninstallModules: (args) => uninstallModules(client, args) + uninstallModules: (args) => uninstallModules(client, args), + getInstalledValidators: (args) => getInstalledValidators(client, args), + getInstalledExecutors: (args) => getInstalledExecutors(client, args), + getActiveHook: (args) => getActiveHook(client, args), + getFallbackBySelector: (args) => getFallbackBySelector(client, args) }) } + +export type Module = { + address: Address + context: Hex + additionalContext?: Hex + type: ModuleType + + /* ---- kernel module params ---- */ + // these param needed for installing validator, executor, fallback handler + hook?: Address + /* ---- end kernel module params ---- */ + + /* ---- safe module params ---- */ + // these two params needed for installing hooks + hookType?: SafeHookType + selector?: Hex + + // these two params needed for installing fallback handlers + functionSig?: Hex + callType?: CallType +} diff --git a/src/clients/decorators/erc7579/installModule.ts b/packages/sdk/clients/decorators/erc7579/installModule.ts similarity index 82% rename from src/clients/decorators/erc7579/installModule.ts rename to packages/sdk/clients/decorators/erc7579/installModule.ts index 974aa721d..55ab308bb 100644 --- a/src/clients/decorators/erc7579/installModule.ts +++ b/packages/sdk/clients/decorators/erc7579/installModule.ts @@ -1,25 +1,18 @@ -import { - type Address, - type Client, - type Hex, - encodeFunctionData, - getAddress -} from "viem" +import { type Client, type Hex, encodeFunctionData, getAddress } from "viem" import { type GetSmartAccountParameter, type SmartAccount, sendUserOperation } from "viem/account-abstraction" import { getAction, parseAccount } from "viem/utils" +import type { Module } from "." import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { type ModuleType, parseModuleTypeId } from "./supportsModule" +import { parseModuleTypeId } from "./supportsModule" export type InstallModuleParameters< TSmartAccount extends SmartAccount | undefined > = GetSmartAccountParameter & { - type: ModuleType - address: Address - context: Hex + module: Module maxFeePerGas?: bigint maxPriorityFeePerGas?: bigint nonce?: bigint @@ -36,8 +29,7 @@ export async function installModule< maxFeePerGas, maxPriorityFeePerGas, nonce, - address, - context + module: { type, address, context } } = parameters if (!account_) { @@ -81,11 +73,7 @@ export async function installModule< } ], functionName: "installModule", - args: [ - parseModuleTypeId(parameters.type), - getAddress(address), - context - ] + args: [parseModuleTypeId(type), getAddress(address), context] }) } ], diff --git a/src/clients/decorators/erc7579/installModules.ts b/packages/sdk/clients/decorators/erc7579/installModules.ts similarity index 100% rename from src/clients/decorators/erc7579/installModules.ts rename to packages/sdk/clients/decorators/erc7579/installModules.ts diff --git a/src/clients/decorators/erc7579/isModuleInstalled.ts b/packages/sdk/clients/decorators/erc7579/isModuleInstalled.ts similarity index 82% rename from src/clients/decorators/erc7579/isModuleInstalled.ts rename to packages/sdk/clients/decorators/erc7579/isModuleInstalled.ts index 7262d286e..06f8b4f25 100644 --- a/src/clients/decorators/erc7579/isModuleInstalled.ts +++ b/packages/sdk/clients/decorators/erc7579/isModuleInstalled.ts @@ -1,9 +1,7 @@ import { - type Address, type Chain, type Client, ContractFunctionExecutionError, - type Hex, type Transport, decodeFunctionResult, encodeFunctionData, @@ -15,15 +13,14 @@ import type { } from "viem/account-abstraction" import { call, readContract } from "viem/actions" import { getAction, parseAccount } from "viem/utils" +import type { Module } from "." import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { type ModuleType, parseModuleTypeId } from "./supportsModule" +import { parseModuleTypeId } from "./supportsModule" export type IsModuleInstalledParameters< TSmartAccount extends SmartAccount | undefined > = GetSmartAccountParameter & { - type: ModuleType - address: Address - context: Hex + module: Module } export async function isModuleInstalled< @@ -32,7 +29,10 @@ export async function isModuleInstalled< client: Client, parameters: IsModuleInstalledParameters ): Promise { - const { account: account_ = client.account, address, context } = parameters + const { + account: account_ = client.account, + module: { address, context, type } + } = parameters if (!account_) { throw new AccountNotFoundError({ @@ -72,16 +72,16 @@ export async function isModuleInstalled< ] as const try { - return await getAction( + return (await getAction( publicClient, readContract, "readContract" )({ abi, functionName: "isModuleInstalled", - args: [parseModuleTypeId(parameters.type), getAddress(address), context], + args: [parseModuleTypeId(type), getAddress(address), context], address: account.address - }) + })) as unknown as Promise } catch (error) { if (error instanceof ContractFunctionExecutionError) { const { factory, factoryData } = await account.getFactoryArgs() @@ -97,11 +97,7 @@ export async function isModuleInstalled< data: encodeFunctionData({ abi, functionName: "isModuleInstalled", - args: [ - parseModuleTypeId(parameters.type), - getAddress(address), - context - ] + args: [parseModuleTypeId(type), getAddress(address), context] }) }) @@ -113,7 +109,7 @@ export async function isModuleInstalled< abi, functionName: "isModuleInstalled", data: result.data - }) + }) as unknown as Promise } throw error diff --git a/src/clients/decorators/erc7579/supportsExecutionMode.ts b/packages/sdk/clients/decorators/erc7579/supportsExecutionMode.ts similarity index 100% rename from src/clients/decorators/erc7579/supportsExecutionMode.ts rename to packages/sdk/clients/decorators/erc7579/supportsExecutionMode.ts diff --git a/src/clients/decorators/erc7579/supportsModule.ts b/packages/sdk/clients/decorators/erc7579/supportsModule.ts similarity index 100% rename from src/clients/decorators/erc7579/supportsModule.ts rename to packages/sdk/clients/decorators/erc7579/supportsModule.ts diff --git a/packages/sdk/clients/decorators/erc7579/uninstallFallback.ts b/packages/sdk/clients/decorators/erc7579/uninstallFallback.ts new file mode 100644 index 000000000..5ebf06ce9 --- /dev/null +++ b/packages/sdk/clients/decorators/erc7579/uninstallFallback.ts @@ -0,0 +1,93 @@ +import { + type Chain, + type Client, + type Hex, + type Transport, + encodeFunctionData, + getAddress +} from "viem" +import { + type GetSmartAccountParameter, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { getAction } from "viem/utils" +import { parseAccount } from "viem/utils" +import type { Module } from "." +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { parseModuleTypeId } from "./supportsModule" + +export type UninstallFallbackParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + module: Module + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + nonce?: bigint +} + +export async function uninstallFallback< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: UninstallFallbackParameters +): Promise { + const { + account: account_ = client.account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + module: { address, context, type } + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + return getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + calls: [ + { + to: account.address, + value: BigInt(0), + data: encodeFunctionData({ + abi: [ + { + name: "uninstallFallback", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "deInitData" + } + ], + outputs: [] + } + ], + functionName: "uninstallFallback", + args: [parseModuleTypeId(type), getAddress(address), context] + }) + } + ], + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + account: account + }) +} diff --git a/src/clients/decorators/erc7579/uninstallModule.ts b/packages/sdk/clients/decorators/erc7579/uninstallModule.ts similarity index 87% rename from src/clients/decorators/erc7579/uninstallModule.ts rename to packages/sdk/clients/decorators/erc7579/uninstallModule.ts index 8737d257f..e8975be85 100644 --- a/src/clients/decorators/erc7579/uninstallModule.ts +++ b/packages/sdk/clients/decorators/erc7579/uninstallModule.ts @@ -1,5 +1,4 @@ import { - type Address, type Chain, type Client, type Hex, @@ -14,15 +13,14 @@ import { } from "viem/account-abstraction" import { getAction } from "viem/utils" import { parseAccount } from "viem/utils" +import type { Module } from "." import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { type ModuleType, parseModuleTypeId } from "./supportsModule" +import { parseModuleTypeId } from "./supportsModule" export type UninstallModuleParameters< TSmartAccount extends SmartAccount | undefined > = GetSmartAccountParameter & { - type: ModuleType - address: Address - context: Hex + module: Module maxFeePerGas?: bigint maxPriorityFeePerGas?: bigint nonce?: bigint @@ -39,8 +37,7 @@ export async function uninstallModule< maxFeePerGas, maxPriorityFeePerGas, nonce, - address, - context + module: { address, context, type } } = parameters if (!account_) { @@ -84,11 +81,7 @@ export async function uninstallModule< } ], functionName: "uninstallModule", - args: [ - parseModuleTypeId(parameters.type), - getAddress(address), - context - ] + args: [parseModuleTypeId(type), getAddress(address), context] }) } ], diff --git a/src/clients/decorators/erc7579/uninstallModules.ts b/packages/sdk/clients/decorators/erc7579/uninstallModules.ts similarity index 92% rename from src/clients/decorators/erc7579/uninstallModules.ts rename to packages/sdk/clients/decorators/erc7579/uninstallModules.ts index 34aa73843..93731d32f 100644 --- a/src/clients/decorators/erc7579/uninstallModules.ts +++ b/packages/sdk/clients/decorators/erc7579/uninstallModules.ts @@ -1,5 +1,4 @@ import { - type Address, type Chain, type Client, type Hex, @@ -14,19 +13,14 @@ import { } from "viem/account-abstraction" import { getAction } from "viem/utils" import { parseAccount } from "viem/utils" +import type { Module } from "." import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { type ModuleType, parseModuleTypeId } from "./supportsModule" +import { parseModuleTypeId } from "./supportsModule" export type UninstallModulesParameters< TSmartAccount extends SmartAccount | undefined > = GetSmartAccountParameter & { - modules: [ - { - type: ModuleType - address: Address - context: Hex - } - ] + modules: Module[] maxFeePerGas?: bigint maxPriorityFeePerGas?: bigint nonce?: bigint diff --git a/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts b/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts new file mode 100644 index 000000000..1deb2a3dc --- /dev/null +++ b/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts @@ -0,0 +1,139 @@ +import { http, type Account, type Address, type Chain, isHex } from "viem" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { CounterAbi } from "../../../../tests/__contracts/abi" +import { mockAddresses } from "../../../../tests/__contracts/mockAddresses" +import { toNetwork } from "../../../../tests/testSetup" +import { + type MasterClient, + type NetworkConfig, + fundAndDeploy, + getBalance, + getTestAccount, + killNetwork, + toTestClient +} from "../../../../tests/testUtils" +import { type NexusClient, toNexusClient } from "../../toNexusClient" + +describe("account.decorators", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let nexusClient: NexusClient + let nexusAccountAddress: Address + let recipient: Account + let recipientAddress: Address + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + recipient = getTestAccount(1) + recipientAddress = recipient.address + testClient = toTestClient(chain, getTestAccount(0)) + + nexusClient = await toNexusClient({ + owner: account, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + await fundAndDeploy(testClient, [nexusClient]) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test.concurrent("should sign a message", async () => { + const signedMessage = await nexusClient.signMessage({ message: "hello" }) + + expect(signedMessage).toEqual( + "0xd98238bbaea4f91683d250003799ead31d7f5c55f16ea9a3478698f695fd1401bfe27e9e4a7e8e3da94aa72b021125e31fa899cc573c48ea3fe1d4ab61a9db10c19032026e3ed2dbccba5a178235ac27f94504311c" + ) + }) + + test.concurrent("should currently fail to sign with typed data", async () => { + expect( + nexusClient.signTypedData({ + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" } + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" } + ] + }, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + contents: "Hello, Bob!" + } + }) + ).rejects.toThrow() + }) + + test("should send a user operation using sendTransaction", async () => { + const balanceBefore = await getBalance(testClient, recipientAddress) + const hash = await nexusClient.sendTransaction({ + calls: [ + { + to: recipientAddress, + value: 1n + } + ] + }) + const { status } = await testClient.waitForTransactionReceipt({ hash }) + const balanceAfter = await getBalance(testClient, recipientAddress) + expect(status).toBe("success") + expect(balanceAfter - balanceBefore).toBe(1n) + }) + + test("should write to a contract", async () => { + const counterValueBefore = await testClient.readContract({ + abi: CounterAbi, + functionName: "getNumber", + address: mockAddresses.Counter + }) + + expect(counterValueBefore).toBe(0n) + const hash = await nexusClient.writeContract({ + abi: CounterAbi, + functionName: "incrementNumber", + address: mockAddresses.Counter, + chain + }) + const { status } = await testClient.waitForTransactionReceipt({ hash }) + const counterValueAfter = await testClient.readContract({ + abi: CounterAbi, + functionName: "getNumber", + address: mockAddresses.Counter + }) + + expect(status).toBe("success") + expect(counterValueAfter).toBe(1n) + }) +}) diff --git a/src/clients/decorators/smartAccount/index.ts b/packages/sdk/clients/decorators/smartAccount/index.ts similarity index 97% rename from src/clients/decorators/smartAccount/index.ts rename to packages/sdk/clients/decorators/smartAccount/index.ts index adafbf72b..d0bbb611d 100644 --- a/src/clients/decorators/smartAccount/index.ts +++ b/packages/sdk/clients/decorators/smartAccount/index.ts @@ -11,10 +11,10 @@ import type { WriteContractParameters } from "viem" import type { SmartAccount } from "viem/account-abstraction" -import { sendTransaction } from "../../actions/smartAccount/sendTransaction" -import { signMessage } from "../../actions/smartAccount/signMessage" -import { signTypedData } from "../../actions/smartAccount/signTypedData" -import { writeContract } from "../../actions/smartAccount/writeContract" +import { sendTransaction } from "./sendTransaction" +import { signMessage } from "./signMessage" +import { signTypedData } from "./signTypedData" +import { writeContract } from "./writeContract" export type SmartAccountActions< TChain extends Chain | undefined = Chain | undefined, diff --git a/src/clients/decorators/smartAccount/sendTransaction.ts b/packages/sdk/clients/decorators/smartAccount/sendTransaction.ts similarity index 100% rename from src/clients/decorators/smartAccount/sendTransaction.ts rename to packages/sdk/clients/decorators/smartAccount/sendTransaction.ts diff --git a/src/clients/decorators/smartAccount/signMessage.ts b/packages/sdk/clients/decorators/smartAccount/signMessage.ts similarity index 100% rename from src/clients/decorators/smartAccount/signMessage.ts rename to packages/sdk/clients/decorators/smartAccount/signMessage.ts diff --git a/src/clients/decorators/smartAccount/signTypedData.ts b/packages/sdk/clients/decorators/smartAccount/signTypedData.ts similarity index 100% rename from src/clients/decorators/smartAccount/signTypedData.ts rename to packages/sdk/clients/decorators/smartAccount/signTypedData.ts diff --git a/src/clients/decorators/smartAccount/writeContract.ts b/packages/sdk/clients/decorators/smartAccount/writeContract.ts similarity index 100% rename from src/clients/decorators/smartAccount/writeContract.ts rename to packages/sdk/clients/decorators/smartAccount/writeContract.ts diff --git a/packages/sdk/clients/toBicoBundlerClient.test.ts b/packages/sdk/clients/toBicoBundlerClient.test.ts new file mode 100644 index 000000000..51b7160bf --- /dev/null +++ b/packages/sdk/clients/toBicoBundlerClient.test.ts @@ -0,0 +1,85 @@ +import { http, type Account, type Address, type Chain, isHex } from "viem" +import type { BundlerClient } from "viem/account-abstraction" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../tests/testSetup" +import { + getTestAccount, + killNetwork, + toTestClient, + topUp +} from "../../tests/testUtils" +import type { MasterClient, NetworkConfig } from "../../tests/testUtils" +import contracts from "../__contracts" +import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" +import { toBicoBundlerClient } from "./toBicoBundlerClient" + +describe("bico.bundler", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let nexusAccountAddress: Address + let bicoBundler: BundlerClient + let nexusAccount: NexusAccount + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + testClient = toTestClient(chain, getTestAccount(0)) + + nexusAccount = await toNexusAccount({ + owner: account, + chain, + transport: http() + }) + + bicoBundler = toBicoBundlerClient({ bundlerUrl, account: nexusAccount }) + nexusAccountAddress = await nexusAccount.getCounterFactualAddress() + await topUp(testClient, nexusAccountAddress) + }) + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test.concurrent("should have 4337 bundler actions", async () => { + const [chainId, supportedEntrypoints, preparedUserOp] = await Promise.all([ + bicoBundler.getChainId(), + bicoBundler.getSupportedEntryPoints(), + bicoBundler.prepareUserOperation({ + sender: account.address, + nonce: 0n, + data: "0x", + signature: "0x", + verificationGasLimit: 1n, + preVerificationGas: 1n, + callData: "0x", + callGasLimit: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + account: nexusAccount + }) + ]) + expect(chainId).toEqual(chain.id) + expect(supportedEntrypoints).to.include(contracts.entryPoint.address) + expect(preparedUserOp).toHaveProperty("signature") + }) + + test("should send a user operation and get the receipt", async () => { + const calls = [{ to: account.address, value: 1n }] + // Must find gas fees before sending the user operation + const gas = await testClient.estimateFeesPerGas() + const hash = await bicoBundler.sendUserOperation({ + ...gas, + calls, + account: nexusAccount + }) + const receipt = await bicoBundler.waitForUserOperationReceipt({ hash }) + expect(receipt.success).toBeTruthy() + }) +}) diff --git a/packages/sdk/clients/toBicoBundlerClient.ts b/packages/sdk/clients/toBicoBundlerClient.ts new file mode 100644 index 000000000..7d0ec12df --- /dev/null +++ b/packages/sdk/clients/toBicoBundlerClient.ts @@ -0,0 +1,36 @@ +import { http, type OneOf, type Transport } from "viem" +import { + type BundlerClient, + type BundlerClientConfig, + createBundlerClient +} from "viem/account-abstraction" + +type BicoBundlerClientConfig = Omit & + OneOf< + | { + transport?: Transport + } + | { + bundlerUrl: string + } + | { + chainId: number + apiKey?: string + } + > + +export const toBicoBundlerClient = ( + parameters: BicoBundlerClientConfig +): BundlerClient => + createBundlerClient({ + ...parameters, + transport: + parameters.transport ?? parameters.bundlerUrl + ? http(parameters.bundlerUrl) + : http( + `https://bundler.biconomy.io/api/v2/${parameters.chainId}/${ + parameters.apiKey ?? + "nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14" + }` + ) + }) diff --git a/packages/sdk/clients/toBicoPaymasterClient.ts b/packages/sdk/clients/toBicoPaymasterClient.ts new file mode 100644 index 000000000..5e2263709 --- /dev/null +++ b/packages/sdk/clients/toBicoPaymasterClient.ts @@ -0,0 +1,33 @@ +import { http, type OneOf, type Transport } from "viem" +import { + type PaymasterClient, + type PaymasterClientConfig, + createPaymasterClient +} from "viem/account-abstraction" + +type BicoPaymasterClientConfig = Omit & + OneOf< + | { + transport?: Transport + } + | { + paymasterUrl: string + } + | { + chainId: number + apiKey: string + } + > + +export const toBicoPaymasterClient = ( + parameters: BicoPaymasterClientConfig +): PaymasterClient => + createPaymasterClient({ + ...parameters, + transport: + parameters.transport ?? parameters.paymasterUrl + ? http(parameters.paymasterUrl) + : http( + `https://paymaster.biconomy.io/api/v2/${parameters.chainId}/${parameters.apiKey}` + ) + }) diff --git a/packages/sdk/clients/toNexusClient.test.ts b/packages/sdk/clients/toNexusClient.test.ts new file mode 100644 index 000000000..a89a000df --- /dev/null +++ b/packages/sdk/clients/toNexusClient.test.ts @@ -0,0 +1,105 @@ +import { http, type Account, type Address, type Chain, isHex } from "viem" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../tests/testSetup" +import { + getBalance, + getTestAccount, + killNetwork, + toTestClient +} from "../../tests/testUtils" +import { + type MasterClient, + type NetworkConfig, + fundAndDeploy +} from "../../tests/testUtils" +import { addresses } from "../__contracts/addresses" +import { type NexusClient, toNexusClient } from "./toNexusClient" + +describe("nexus.client", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let recipientAccount: Account + let recipientAddress: Address + let nexusClient: NexusClient + let nexusAccountAddress: Address + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + recipientAccount = getTestAccount(1) + recipientAddress = recipientAccount.address + + testClient = toTestClient(chain, getTestAccount(0)) + + nexusClient = await toNexusClient({ + owner: account, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + await fundAndDeploy(testClient, [nexusClient]) + }) + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test("should have attached erc757 actions", async () => { + const [ + accountId, + isModuleInstalled, + supportsExecutionMode, + supportsModule + ] = await Promise.all([ + nexusClient.accountId(), + nexusClient.isModuleInstalled({ + module: { + type: "validator", + address: addresses.K1Validator, + context: "0x" + } + }), + nexusClient.supportsExecutionMode({ + type: "delegatecall" + }), + nexusClient.supportsModule({ + type: "validator" + }) + ]) + expect(accountId).toBe("biconomy.nexus.1.0.0-beta") + expect(isModuleInstalled).toBe(true) + expect(supportsExecutionMode).toBe(true) + expect(supportsModule).toBe(true) + }) + + test("should send eth twice", async () => { + const balanceBefore = await getBalance(testClient, recipientAddress) + const tx = { to: recipientAddress, value: 1n } + const hash = await nexusClient.sendTransaction({ calls: [tx, tx] }) + const { success } = await nexusClient.waitForUserOperationReceipt({ hash }) + const balanceAfter = await getBalance(testClient, recipientAddress) + expect(success).toBe(true) + expect(balanceAfter - balanceBefore).toBe(2n) + }) + + test.skip("should uninstall modules", async () => { + const result = await nexusClient.uninstallModules({ + modules: [ + { + type: "validator", + address: addresses.K1Validator, + context: "0x" + } + ] + }) + }) +}) diff --git a/src/clients/biconomy.ts b/packages/sdk/clients/toNexusClient.ts similarity index 60% rename from src/clients/biconomy.ts rename to packages/sdk/clients/toNexusClient.ts index 7054f3ce8..63e071737 100644 --- a/src/clients/biconomy.ts +++ b/packages/sdk/clients/toNexusClient.ts @@ -20,8 +20,9 @@ import { createBundlerClient } from "viem/account-abstraction" import contracts from "../__contracts" -import { type Call, type Nexus, toNexusAccount } from "../account/Nexus" +import type { Call } from "../account" +import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" import type { BaseValidationModule } from "../modules/base/BaseValidationModule" import { type Erc7579Actions, erc7579Actions } from "./decorators/erc7579" import { @@ -33,10 +34,10 @@ export type SendTransactionParameters = { calls: Call | Call[] } -export type BiconomyClient< +export type NexusClient< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends Nexus | undefined = Nexus | undefined, + account extends NexusAccount | undefined = NexusAccount | undefined, client extends Client | undefined = Client | undefined, rpcSchema extends RpcSchema | undefined = undefined > = Prettify< @@ -44,10 +45,10 @@ export type BiconomyClient< ClientConfig, "cacheTime" | "chain" | "key" | "name" | "pollingInterval" | "rpcSchema" > & - BundlerActions & - Erc7579Actions & - SmartAccountActions & { - account: Nexus + BundlerActions & + Erc7579Actions & + SmartAccountActions & { + account: NexusAccount client?: client | Client | undefined bundlerTransport?: BundlerClientConfig["transport"] paymaster?: BundlerClientConfig["paymaster"] | undefined @@ -56,7 +57,7 @@ export type BiconomyClient< } > -export type BiconomyClientConfig< +export type NexusClientConfig< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined, @@ -72,56 +73,56 @@ export type BiconomyClientConfig< | "name" | "pollingInterval" | "rpcSchema" - > -> & { - /** RPC URL. */ - transport: transport - /** Bundler URL. */ - bundlerTransport: transport - /** Client that points to an Execution RPC URL. */ - client?: client | Client | undefined - /** Paymaster configuration. */ - paymaster?: - | 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 - /** User Operation configuration. */ - userOperation?: - | { - /** Prepares fee properties for the User Operation request. */ - estimateFeesPerGas?: - | ((parameters: { - account: account | SmartAccount - bundlerClient: Client - userOperation: UserOperationRequest - }) => Promise>) - | undefined - } - | undefined - /** Owner of the account. */ - owner: Address | Account - /** Index of the account. */ - index?: bigint - /** Active module of the account. */ - activeModule?: BaseValidationModule - /** Factory address of the account. */ - factoryAddress?: Address - /** Owner module */ - k1ValidatorAddress?: Address -} + > & { + /** RPC URL. */ + transport: transport + /** Bundler URL. */ + bundlerTransport: transport + /** Client that points to an Execution RPC URL. */ + client?: client | Client | undefined + /** Paymaster configuration. */ + paymaster?: + | 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 + /** User Operation configuration. */ + userOperation?: + | { + /** Prepares fee properties for the User Operation request. */ + estimateFeesPerGas?: + | ((parameters: { + account: account | SmartAccount + bundlerClient: Client + userOperation: UserOperationRequest + }) => Promise>) + | undefined + } + | undefined + /** Owner of the account. */ + owner: Address | Account + /** Index of the account. */ + index?: bigint + /** Active module of the account. */ + activeModule?: BaseValidationModule + /** Factory address of the account. */ + factoryAddress?: Address + /** Owner module */ + k1ValidatorAddress?: Address + } +> -export async function createBiconomyClient( - parameters: BiconomyClientConfig -): Promise { +export async function toNexusClient( + parameters: NexusClientConfig +): Promise { const { client: client_, chain = parameters.chain ?? client_?.chain, @@ -179,5 +180,5 @@ export async function createBiconomyClient( .extend(erc7579Actions()) .extend(smartAccountActions()) - return bundler as unknown as BiconomyClient + return bundler as unknown as NexusClient } diff --git a/src/index.ts b/packages/sdk/index.ts similarity index 65% rename from src/index.ts rename to packages/sdk/index.ts index 903f1f5b4..9c227a1c4 100644 --- a/src/index.ts +++ b/packages/sdk/index.ts @@ -1,3 +1,2 @@ export * from "./account" -export * from "./paymaster" export * from "./modules" diff --git a/src/modules/base/BaseExecutionModule.ts b/packages/sdk/modules/base/BaseExecutionModule.ts similarity index 54% rename from src/modules/base/BaseExecutionModule.ts rename to packages/sdk/modules/base/BaseExecutionModule.ts index f63140753..678a7aa01 100644 --- a/src/modules/base/BaseExecutionModule.ts +++ b/packages/sdk/modules/base/BaseExecutionModule.ts @@ -1,11 +1,10 @@ -import type { Address } from "viem" -import type { UserOpReceipt } from "../../bundler/index.js" -import { BaseModule } from "../base/BaseModule.js" +import type { Address, Hash } from "viem" import type { Execution } from "../utils/Types.js" +import { BaseModule } from "./BaseModule.js" export abstract class BaseExecutionModule extends BaseModule { abstract execute( execution: Execution | Execution[], ownedAccountAddress?: Address - ): Promise + ): Promise } diff --git a/src/modules/base/BaseModule.ts b/packages/sdk/modules/base/BaseModule.ts similarity index 91% rename from src/modules/base/BaseModule.ts rename to packages/sdk/modules/base/BaseModule.ts index 20eb272fc..21f9a8915 100644 --- a/src/modules/base/BaseModule.ts +++ b/packages/sdk/modules/base/BaseModule.ts @@ -1,16 +1,16 @@ import { type Address, type Hex, encodeFunctionData, parseAbi } from "viem" import contracts from "../../__contracts/index.js" import type { SmartAccountSigner } from "../../account/index.js" +import type { Module } from "../../clients/decorators/erc7579/index.js" import { - type Module, type ModuleType, type ModuleVersion, moduleTypeIds } from "../utils/Types.js" export abstract class BaseModule { - moduleAddress: Address - data: Hex + address: Address + context: Hex additionalContext: Hex type: ModuleType hook?: Address @@ -19,8 +19,8 @@ export abstract class BaseModule { signer: SmartAccountSigner constructor(module: Module, signer: SmartAccountSigner) { - this.moduleAddress = module.moduleAddress - this.data = module.data ?? "0x" + this.address = module.address + this.context = module.context ?? "0x" this.additionalContext = module.additionalContext ?? "0x" this.hook = module.hook this.type = module.type @@ -35,8 +35,8 @@ export abstract class BaseModule { functionName: "installModule", args: [ BigInt(moduleTypeIds[this.type]), - this.moduleAddress, - this.data ?? "0x" + this.address, + this.context ?? "0x" ] }) @@ -51,7 +51,7 @@ export abstract class BaseModule { functionName: "uninstallModule", args: [ BigInt(moduleTypeIds[this.type]), - this.moduleAddress, + this.address, uninstallData ?? "0x" ] }) @@ -93,7 +93,7 @@ export abstract class BaseModule { } public getAddress(): Hex { - return this.moduleAddress + return this.address } public getVersion(): string { diff --git a/src/modules/base/BaseValidationModule.ts b/packages/sdk/modules/base/BaseValidationModule.ts similarity index 97% rename from src/modules/base/BaseValidationModule.ts rename to packages/sdk/modules/base/BaseValidationModule.ts index 67f5af00d..2d7625a45 100644 --- a/src/modules/base/BaseValidationModule.ts +++ b/packages/sdk/modules/base/BaseValidationModule.ts @@ -1,6 +1,6 @@ import { type Hex, getAddress } from "viem" import type { SmartAccountSigner } from "../../account/index.js" -import { BaseModule } from "../base/BaseModule.js" +import { BaseModule } from "./BaseModule.js" export abstract class BaseValidationModule extends BaseModule { public getSigner(): SmartAccountSigner { diff --git a/src/modules/executors/OwnableExecutor.ts b/packages/sdk/modules/executors/OwnableExecutor.ts similarity index 64% rename from src/modules/executors/OwnableExecutor.ts rename to packages/sdk/modules/executors/OwnableExecutor.ts index 76be22bb8..36678981c 100644 --- a/src/modules/executors/OwnableExecutor.ts +++ b/packages/sdk/modules/executors/OwnableExecutor.ts @@ -1,58 +1,66 @@ import { type Address, + type Hash, type Hex, + type PublicClient, + type WalletClient, encodeAbiParameters, encodeFunctionData, encodePacked, getAddress, parseAbi } from "viem" -import { SENTINEL_ADDRESS } from "../../account" -import type { NexusSmartAccount } from "../../account/NexusSmartAccount" -import type { UserOpReceipt } from "../../bundler" +import { SENTINEL_ADDRESS, WalletClientSigner } from "../../account" +import type { Module } from "../../clients/decorators/erc7579" +import type { NexusClient } from "../../clients/toNexusClient" import { BaseExecutionModule } from "../base/BaseExecutionModule" -import type { Execution, Module } from "../utils/Types" +import type { Execution } from "../utils/Types" export class OwnableExecutorModule extends BaseExecutionModule { - smartAccount!: NexusSmartAccount + public nexusClient: NexusClient public owners: Address[] - private address: Address + public override address: Address public constructor( module: Module, - smartAccount: NexusSmartAccount, + nexusClient: NexusClient, owners: Address[], address: Address ) { - super(module, smartAccount.getSigner()) - this.smartAccount = smartAccount + super( + module, + new WalletClientSigner(nexusClient.account.client as WalletClient, "viem") + ) + this.nexusClient = nexusClient this.owners = owners - this.data = module.data ?? "0x" + this.context = module.context ?? "0x" this.address = address } public static async create( - smartAccount: NexusSmartAccount, + nexusClient: NexusClient, address: Address, - data?: Hex + context?: Hex ): Promise { const module: Module = { - moduleAddress: address, + address: address, type: "executor", - data: data ?? "0x", + context: context ?? "0x", additionalContext: "0x" } - const owners = await smartAccount.publicClient.readContract({ + const owners = await ( + nexusClient.account.client as PublicClient + ).readContract({ address, abi: parseAbi([ "function getOwners(address account) external view returns (address[])" ]), functionName: "getOwners", - args: [await smartAccount.getAddress()] + args: [await nexusClient.account.getAddress()] }) return new OwnableExecutorModule( module, - smartAccount, + nexusClient, owners as Address[], address ) @@ -61,7 +69,7 @@ export class OwnableExecutorModule extends BaseExecutionModule { public async execute( execution: Execution | Execution[], accountAddress?: Address - ): Promise { + ): Promise { let calldata: Hex if (Array.isArray(execution)) { calldata = encodeFunctionData({ @@ -70,7 +78,7 @@ export class OwnableExecutorModule extends BaseExecutionModule { "function executeBatchOnOwnedAccount(address ownedAccount, bytes callData)" ]), args: [ - accountAddress ?? (await this.smartAccount.getAddress()), + accountAddress ?? (await this.nexusClient.account.getAddress()), encodeAbiParameters( [ { @@ -103,7 +111,7 @@ export class OwnableExecutorModule extends BaseExecutionModule { "function executeOnOwnedAccount(address ownedAccount, bytes callData)" ]), args: [ - accountAddress ?? (await this.smartAccount.getAddress()), + accountAddress ?? (await this.nexusClient.account.getAddress()), encodePacked( ["address", "uint256", "bytes"], [ @@ -115,34 +123,23 @@ export class OwnableExecutorModule extends BaseExecutionModule { ] }) } - const response = await this.smartAccount.sendTransaction({ - to: this.moduleAddress, - data: calldata, - value: 0n + return this.nexusClient.sendTransaction({ + calls: [{ to: this.address, data: calldata, value: 0n }] }) - const receipt = await response.wait() - return receipt } - public async addOwner(newOwner: Address): Promise { + public async addOwner(newOwner: Address) { const callData = encodeFunctionData({ functionName: "addOwner", abi: parseAbi(["function addOwner(address owner)"]), args: [newOwner] }) - const response = await this.smartAccount.sendTransaction({ - to: this.moduleAddress, - data: callData, - value: 0n + return this.nexusClient.sendTransaction({ + calls: [{ to: this.address, data: callData, value: 0n }] }) - const receipt = await response.wait() - if (receipt.success) { - this.owners.push(newOwner) - } - return receipt } - public async removeOwner(ownerToRemove: Address): Promise { + public async removeOwner(ownerToRemove: Address) { const owners = await this.getOwners(this.address) let prevOwner: Address @@ -165,30 +162,30 @@ export class OwnableExecutorModule extends BaseExecutionModule { args: [prevOwner, ownerToRemove] }) - const response = await this.smartAccount.sendTransaction({ - to: this.moduleAddress, - data: calldata, - value: 0n + return this.nexusClient.sendTransaction({ + calls: [ + { + to: this.address, + data: calldata, + value: 0n + } + ] }) - - const receipt = await response.wait() - if (receipt.success) { - this.owners = this.owners.filter((o: Address) => o !== ownerToRemove) - } - return receipt } public async getOwners( moduleAddress: Address, accountAddress?: Address ): Promise { - const owners = await this.smartAccount.publicClient.readContract({ + const owners = await ( + this.nexusClient.account.client as PublicClient + ).readContract({ address: moduleAddress, abi: parseAbi([ "function getOwners(address account) external view returns (address[])" ]), functionName: "getOwners", - args: [accountAddress ?? (await this.smartAccount.getAddress())] + args: [accountAddress ?? (await this.nexusClient.account.getAddress())] }) return owners as Address[] diff --git a/src/modules/index.ts b/packages/sdk/modules/index.ts similarity index 100% rename from src/modules/index.ts rename to packages/sdk/modules/index.ts diff --git a/src/modules/interfaces/IExecutorModule.ts b/packages/sdk/modules/interfaces/IExecutorModule.ts similarity index 100% rename from src/modules/interfaces/IExecutorModule.ts rename to packages/sdk/modules/interfaces/IExecutorModule.ts diff --git a/src/modules/interfaces/IValidationModule.ts b/packages/sdk/modules/interfaces/IValidationModule.ts similarity index 100% rename from src/modules/interfaces/IValidationModule.ts rename to packages/sdk/modules/interfaces/IValidationModule.ts diff --git a/tests/smart.sessions.test.ts b/packages/sdk/modules/smart.sessions.test.ts similarity index 63% rename from tests/smart.sessions.test.ts rename to packages/sdk/modules/smart.sessions.test.ts index ce60eaf02..2496a0071 100644 --- a/tests/smart.sessions.test.ts +++ b/packages/sdk/modules/smart.sessions.test.ts @@ -1,100 +1,60 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient, - pad, - toBytes, - toHex -} from "viem" +import { http, type Account, type Address, type Chain, pad, toHex } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { parseReferenceValue } from "../src" -import { - type NexusSmartAccount, - createSmartAccountClient -} from "../src/account" -import policies, { - ParamCondition, - type ActionConfig -} from "../src/modules/smartSessions" -import { TEST_CONTRACTS } from "./src/callDatas" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" +import { parseReferenceValue } from ".." +import { TEST_CONTRACTS } from "../../tests/callDatas" +import { toNetwork } from "../../tests/testSetup" import { + fundAndDeploy, getTestAccount, killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" + toTestClient +} from "../../tests/testUtils" +import type { MasterClient, NetworkConfig } from "../../tests/testUtils" +import { type NexusClient, toNexusClient } from "../clients/toNexusClient" +import policies, { ParamCondition } from "./smartSessions" describe("smart.sessions", () => { let network: NetworkConfig - // Nexus Config let chain: Chain let bundlerUrl: string - let walletClient: WalletClient // Test utils let testClient: MasterClient let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex + let nexusClient: NexusClient + let nexusAccountAddress: Address + let recipient: Account + let recipientAddress: Address beforeAll(async () => { - network = (await toNetwork(NETWORK_TYPE)) as NetworkConfig + network = await toNetwork() chain = network.chain bundlerUrl = network.bundlerUrl - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - + recipient = getTestAccount(1) + recipientAddress = recipient.address testClient = toTestClient(chain, getTestAccount(0)) - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain + nexusClient = await toNexusClient({ + owner: account, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) }) - smartAccountAddress = await smartAccount.getAddress() + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + await fundAndDeploy(testClient, [nexusClient]) }) + afterAll(async () => { await killNetwork([network?.rpcPort, network?.bundlerPort]) }) - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).toBeTruthy() - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - test("should have smart account bytecode", async () => { const bytecodes = await Promise.all( [TEST_CONTRACTS.SmartSession, TEST_CONTRACTS.UniActionPolicy].map( - (address) => testClient.getBytecode(address) + (address) => testClient.getCode(address) ) ) expect(bytecodes.every((bytecode) => !!bytecode?.length)).toBeTruthy() diff --git a/src/modules/smartSessions.ts b/packages/sdk/modules/smartSessions.ts similarity index 100% rename from src/modules/smartSessions.ts rename to packages/sdk/modules/smartSessions.ts diff --git a/src/modules/utils/Constants.ts b/packages/sdk/modules/utils/Constants.ts similarity index 100% rename from src/modules/utils/Constants.ts rename to packages/sdk/modules/utils/Constants.ts diff --git a/src/modules/utils/Helper.ts b/packages/sdk/modules/utils/Helper.ts similarity index 99% rename from src/modules/utils/Helper.ts rename to packages/sdk/modules/utils/Helper.ts index a6250108c..8975e154f 100644 --- a/src/modules/utils/Helper.ts +++ b/packages/sdk/modules/utils/Helper.ts @@ -14,7 +14,7 @@ import { ERROR_MESSAGES, type UserOperationStruct, getChain -} from "../../account" +} from "../../account/index.js" import type { ChainInfo, SignerData diff --git a/src/modules/utils/Types.ts b/packages/sdk/modules/utils/Types.ts similarity index 91% rename from src/modules/utils/Types.ts rename to packages/sdk/modules/utils/Types.ts index ada55804c..7a56b5f4e 100644 --- a/src/modules/utils/Types.ts +++ b/packages/sdk/modules/utils/Types.ts @@ -1,6 +1,5 @@ import type { Address, Chain, Hex } from "viem" import type { - CallType, SimulationType, SmartAccountSigner, SupportedSigner, @@ -203,30 +202,6 @@ export enum SafeHookType { SIG = 1 } -export type Module = { - moduleAddress: Address - data?: Hex - additionalContext?: Hex - type: ModuleType - - /* ---- kernel module params ---- */ - // these param needed for installing validator, executor, fallback handler - hook?: Address - /* ---- end kernel module params ---- */ - - /* ---- safe module params ---- */ - - // these two params needed for installing hooks - hookType?: SafeHookType - selector?: Hex - - // these two params needed for installing fallback handlers - functionSig?: Hex - callType?: CallType - - /* ---- end safe module params ---- */ -} - export type ModuleType = "validator" | "executor" | "fallback" | "hook" type ModuleTypeIds = { diff --git a/src/modules/utils/Uid.ts b/packages/sdk/modules/utils/Uid.ts similarity index 100% rename from src/modules/utils/Uid.ts rename to packages/sdk/modules/utils/Uid.ts diff --git a/src/modules/validators/K1ValidatorModule.ts b/packages/sdk/modules/validators/K1ValidatorModule.ts similarity index 83% rename from src/modules/validators/K1ValidatorModule.ts rename to packages/sdk/modules/validators/K1ValidatorModule.ts index 03a655da5..3c51e6b9b 100644 --- a/src/modules/validators/K1ValidatorModule.ts +++ b/packages/sdk/modules/validators/K1ValidatorModule.ts @@ -1,7 +1,7 @@ import addresses from "../../__contracts/addresses.js" import type { SmartAccountSigner } from "../../account/index.js" +import type { Module } from "../../clients/decorators/erc7579/index.js" import { BaseValidationModule } from "../base/BaseValidationModule.js" -import type { Module } from "../utils/Types.js" export class K1ValidatorModule extends BaseValidationModule { // biome-ignore lint/complexity/noUselessConstructor: @@ -14,9 +14,9 @@ export class K1ValidatorModule extends BaseValidationModule { k1ValidatorAddress = addresses.K1Validator ): Promise { const module: Module = { - moduleAddress: k1ValidatorAddress, + address: k1ValidatorAddress, type: "validator", - data: await signer.getAddress(), + context: await signer.getAddress(), additionalContext: "0x" } const instance = new K1ValidatorModule(module, signer) diff --git a/src/modules/validators/OwnableValidator.ts b/packages/sdk/modules/validators/OwnableValidator.ts similarity index 100% rename from src/modules/validators/OwnableValidator.ts rename to packages/sdk/modules/validators/OwnableValidator.ts diff --git a/src/modules/validators/ValidationModule.ts b/packages/sdk/modules/validators/ValidationModule.ts similarity index 81% rename from src/modules/validators/ValidationModule.ts rename to packages/sdk/modules/validators/ValidationModule.ts index 8390243a0..e75c627bc 100644 --- a/src/modules/validators/ValidationModule.ts +++ b/packages/sdk/modules/validators/ValidationModule.ts @@ -1,7 +1,7 @@ import type { Address, Hex } from "viem" import type { SmartAccountSigner } from "../../account/index.js" +import type { Module } from "../../clients/decorators/erc7579/index.js" import { BaseValidationModule } from "../base/BaseValidationModule.js" -import type { Module } from "../utils/Types.js" export class ValidationModule extends BaseValidationModule { private constructor(moduleConfig: Module, signer: SmartAccountSigner) { @@ -10,13 +10,13 @@ export class ValidationModule extends BaseValidationModule { public static async create( signer: SmartAccountSigner, - moduleAddress: Address, - data: Hex + address: Address, + context: Hex ): Promise { const module: Module = { - moduleAddress, + address, type: "validator", - data, + context, additionalContext: "0x" } const instance = new ValidationModule(module, signer) diff --git a/packages/sdk/modules/validators/k1Validator.test.ts b/packages/sdk/modules/validators/k1Validator.test.ts new file mode 100644 index 000000000..2873dfa9f --- /dev/null +++ b/packages/sdk/modules/validators/k1Validator.test.ts @@ -0,0 +1,127 @@ +import { http, type Account, type Address, type Chain } from "viem" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../../tests/testSetup" +import { + fundAndDeploy, + getBalance, + getTestAccount, + killNetwork, + toTestClient +} from "../../../tests/testUtils" +import type { MasterClient, NetworkConfig } from "../../../tests/testUtils" +import addresses from "../../__contracts/addresses" +import { type NexusClient, toNexusClient } from "../../clients/toNexusClient" + +describe("modules.k1Validator.write", () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let account: Account + let nexusClient: NexusClient + let nexusAccountAddress: Address + let recipient: Account + let recipientAddress: Address + + beforeAll(async () => { + network = await toNetwork() + + chain = network.chain + bundlerUrl = network.bundlerUrl + account = getTestAccount(0) + recipient = getTestAccount(1) + recipientAddress = recipient.address + + testClient = toTestClient(chain, getTestAccount(0)) + + nexusClient = await toNexusClient({ + owner: account, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + await fundAndDeploy(testClient, [nexusClient]) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test.skip("should send eth", async () => { + const balanceBefore = await getBalance(testClient, recipientAddress) + const hash = await nexusClient.sendUserOperation({ + calls: [ + { + to: recipientAddress, + value: 1n + } + ] + }) + const { success } = await nexusClient.waitForUserOperationReceipt({ hash }) + const balanceAfter = await getBalance(testClient, recipientAddress) + expect(success).toBe(true) + expect(balanceAfter - balanceBefore).toBe(1n) + }) + + test.skip("should install k1 validator with 1 owner", async () => { + const isInstalledBefore = await nexusClient.isModuleInstalled({ + module: { + type: "validator", + address: addresses.K1Validator, + context: "0x" + } + }) + + console.log({ isInstalledBefore }) + + if (!isInstalledBefore) { + const hash = await nexusClient.installModule({ + module: { + address: addresses.K1Validator, + type: "validator", + context: "0x" + } + }) + + const { success: installSuccess } = + await nexusClient.waitForUserOperationReceipt({ hash }) + expect(installSuccess).toBe(true) + + const hashUninstall = await nexusClient.uninstallModule({ + module: { + address: addresses.K1Validator, + type: "validator", + context: "0x" + } + }) + + const { success: uninstallSuccess } = + await nexusClient.waitForUserOperationReceipt({ hash: hashUninstall }) + expect(uninstallSuccess).toBe(true) + } else { + // Uninstall + + const byteCode = await testClient.getCode({ + address: addresses.K1Validator + }) + console.log({ byteCode }) + + const hash = await nexusClient.uninstallModule({ + module: { + address: addresses.K1Validator, + type: "validator", + context: "0x" + } + }) + const { success } = await nexusClient.waitForUserOperationReceipt({ + hash + }) + expect(success).toBe(true) + } + // Get installed modules + }) +}) diff --git a/tests/src/README.md b/packages/tests/README.md similarity index 100% rename from tests/src/README.md rename to packages/tests/README.md diff --git a/tests/src/__contracts/abi/BiconomyMetaFactoryAbi.ts b/packages/tests/__contracts/abi/BiconomyMetaFactoryAbi.ts similarity index 100% rename from tests/src/__contracts/abi/BiconomyMetaFactoryAbi.ts rename to packages/tests/__contracts/abi/BiconomyMetaFactoryAbi.ts diff --git a/tests/src/__contracts/abi/BootstrapAbi.ts b/packages/tests/__contracts/abi/BootstrapAbi.ts similarity index 100% rename from tests/src/__contracts/abi/BootstrapAbi.ts rename to packages/tests/__contracts/abi/BootstrapAbi.ts diff --git a/tests/src/__contracts/abi/BootstrapLibAbi.ts b/packages/tests/__contracts/abi/BootstrapLibAbi.ts similarity index 100% rename from tests/src/__contracts/abi/BootstrapLibAbi.ts rename to packages/tests/__contracts/abi/BootstrapLibAbi.ts diff --git a/tests/src/__contracts/abi/CounterAbi.ts b/packages/tests/__contracts/abi/CounterAbi.ts similarity index 100% rename from tests/src/__contracts/abi/CounterAbi.ts rename to packages/tests/__contracts/abi/CounterAbi.ts diff --git a/tests/src/__contracts/abi/MockExecutorAbi.ts b/packages/tests/__contracts/abi/MockExecutorAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockExecutorAbi.ts rename to packages/tests/__contracts/abi/MockExecutorAbi.ts diff --git a/tests/src/__contracts/abi/MockHandlerAbi.ts b/packages/tests/__contracts/abi/MockHandlerAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockHandlerAbi.ts rename to packages/tests/__contracts/abi/MockHandlerAbi.ts diff --git a/tests/src/__contracts/abi/MockHookAbi.ts b/packages/tests/__contracts/abi/MockHookAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockHookAbi.ts rename to packages/tests/__contracts/abi/MockHookAbi.ts diff --git a/tests/src/__contracts/abi/MockRegistryAbi.ts b/packages/tests/__contracts/abi/MockRegistryAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockRegistryAbi.ts rename to packages/tests/__contracts/abi/MockRegistryAbi.ts diff --git a/tests/src/__contracts/abi/MockTokenAbi.ts b/packages/tests/__contracts/abi/MockTokenAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockTokenAbi.ts rename to packages/tests/__contracts/abi/MockTokenAbi.ts diff --git a/tests/src/__contracts/abi/MockValidatorAbi.ts b/packages/tests/__contracts/abi/MockValidatorAbi.ts similarity index 100% rename from tests/src/__contracts/abi/MockValidatorAbi.ts rename to packages/tests/__contracts/abi/MockValidatorAbi.ts diff --git a/tests/src/__contracts/abi/NexusAccountFactoryAbi.ts b/packages/tests/__contracts/abi/NexusAccountFactoryAbi.ts similarity index 100% rename from tests/src/__contracts/abi/NexusAccountFactoryAbi.ts rename to packages/tests/__contracts/abi/NexusAccountFactoryAbi.ts diff --git a/tests/src/__contracts/abi/StakeableAbi.ts b/packages/tests/__contracts/abi/StakeableAbi.ts similarity index 100% rename from tests/src/__contracts/abi/StakeableAbi.ts rename to packages/tests/__contracts/abi/StakeableAbi.ts diff --git a/tests/src/__contracts/abi/index.ts b/packages/tests/__contracts/abi/index.ts similarity index 100% rename from tests/src/__contracts/abi/index.ts rename to packages/tests/__contracts/abi/index.ts diff --git a/tests/src/__contracts/mockAddresses.ts b/packages/tests/__contracts/mockAddresses.ts similarity index 100% rename from tests/src/__contracts/mockAddresses.ts rename to packages/tests/__contracts/mockAddresses.ts diff --git a/tests/src/callDatas.ts b/packages/tests/callDatas.ts similarity index 100% rename from tests/src/callDatas.ts rename to packages/tests/callDatas.ts diff --git a/tests/src/executables.ts b/packages/tests/executables.ts similarity index 100% rename from tests/src/executables.ts rename to packages/tests/executables.ts diff --git a/tests/src/globalSetup.ts b/packages/tests/globalSetup.ts similarity index 97% rename from tests/src/globalSetup.ts rename to packages/tests/globalSetup.ts index b3e12288c..ac4620817 100644 --- a/tests/src/globalSetup.ts +++ b/packages/tests/globalSetup.ts @@ -5,6 +5,7 @@ import { } from "./testUtils" let globalConfig: NetworkConfigWithBundler +// @ts-ignore export const setup = async ({ provide }) => { globalConfig = await initLocalhostNetwork() const { bundlerInstance, instance, ...serializeableConfig } = globalConfig diff --git a/tests/playground.test.ts b/packages/tests/playground.test.ts similarity index 55% rename from tests/playground.test.ts rename to packages/tests/playground.test.ts index 51a8ca59c..edbaa155c 100644 --- a/tests/playground.test.ts +++ b/packages/tests/playground.test.ts @@ -1,35 +1,25 @@ import { http, + type Address, type Chain, - type Hex, type PrivateKeyAccount, type PublicClient, type WalletClient, createPublicClient, createWalletClient } from "viem" -import { createPaymasterClient } from "viem/account-abstraction" -import { beforeAll, expect, test } from "vitest" -import { createPaymaster } from "../src" -import { - type NexusSmartAccount, - createSmartAccountClient -} from "../src/account" -import { - type TestFileNetworkType, - describeWithPlaygroundGuard, - toNetwork -} from "./src/testSetup" -import type { NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "PUBLIC_TESTNET" +import { beforeAll, describe, expect, test } from "vitest" +import { toBicoPaymasterClient } from "../sdk/clients/toBicoPaymasterClient" +import { type NexusClient, toNexusClient } from "../sdk/clients/toNexusClient" +import { playgroundTrue, toNetwork } from "./testSetup" +import type { NetworkConfig } from "./testUtils" // Remove the following lines to use the default factory and validator addresses // These are relevant only for now on base sopelia chain and are likely to change const k1ValidatorAddress = "0x663E709f60477f07885230E213b8149a7027239B" const factoryAddress = "0x887Ca6FaFD62737D0E79A2b8Da41f0B15A864778" -describeWithPlaygroundGuard("playground", () => { +describe.skipIf(!playgroundTrue)("playground", () => { let network: NetworkConfig // Nexus Config let chain: Chain @@ -40,17 +30,20 @@ describeWithPlaygroundGuard("playground", () => { // Test utils let publicClient: PublicClient // testClient not available on public testnets let account: PrivateKeyAccount - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex + let recipientAddress: Address + let nexusClient: NexusClient + let nexusAccountAddress: Address beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) + network = await toNetwork("PUBLIC_TESTNET") chain = network.chain bundlerUrl = network.bundlerUrl paymasterUrl = network.paymasterUrl account = network.account as PrivateKeyAccount + recipientAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik.eth + walletClient = createWalletClient({ account, chain, @@ -61,24 +54,14 @@ describeWithPlaygroundGuard("playground", () => { chain, transport: http() }) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain, - k1ValidatorAddress, - factoryAddress - }) - - smartAccountAddress = await smartAccount.getAddress() }) test("should have factory and k1Validator deployed", async () => { const byteCodes = await Promise.all([ - publicClient.getBytecode({ + publicClient.getCode({ address: k1ValidatorAddress }), - publicClient.getBytecode({ + publicClient.getCode({ address: factoryAddress }) ]) @@ -87,20 +70,19 @@ describeWithPlaygroundGuard("playground", () => { }) test("should init the smart account", async () => { - smartAccount = await createSmartAccountClient({ - signer: walletClient, + nexusClient = await toNexusClient({ + owner: account, chain, - bundlerUrl, - // Remove the following lines to use the default factory and validator addresses - // These are relevant only for now on sopelia chain and are likely to change + transport: http(), + bundlerTransport: http(bundlerUrl), k1ValidatorAddress, factoryAddress }) }) test("should log relevant addresses", async () => { - smartAccountAddress = await smartAccount.getAddress() - console.log({ smartAccountAddress }) + nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() + console.log({ nexusAccountAddress }) }) test("should check balances and top up relevant addresses", async () => { @@ -109,10 +91,9 @@ describeWithPlaygroundGuard("playground", () => { address: account.address }), publicClient.getBalance({ - address: smartAccountAddress + address: nexusAccountAddress }) ]) - console.log({ ownerBalance, smartAccountBalance }) const balancesAreOfCorrectType = [ownerBalance, smartAccountBalance].every( (balance) => typeof balance === "bigint" @@ -121,7 +102,7 @@ describeWithPlaygroundGuard("playground", () => { const hash = await walletClient.sendTransaction({ chain, account, - to: smartAccountAddress, + to: nexusAccountAddress, value: 1000000000000000000n }) const receipt = await publicClient.waitForTransactionReceipt({ hash }) @@ -132,27 +113,21 @@ describeWithPlaygroundGuard("playground", () => { test("should send some native token", async () => { const balanceBefore = await publicClient.getBalance({ - address: account.address + address: recipientAddress }) - - const { wait } = await smartAccount.sendTransaction({ - to: account.address, - data: "0x", - value: 1n + const hash = await nexusClient.sendTransaction({ + calls: [ + { + to: recipientAddress, + value: 1n + } + ] }) - - const { - success, - receipt: { transactionHash } - } = await wait() - expect(success).toBeTruthy() - - console.log({ transactionHash }) - + const { status } = await publicClient.waitForTransactionReceipt({ hash }) const balanceAfter = await publicClient.getBalance({ - address: account.address + address: recipientAddress }) - + expect(status).toBe("success") expect(balanceAfter - balanceBefore).toBe(1n) }) @@ -162,36 +137,26 @@ describeWithPlaygroundGuard("playground", () => { return } - const paymasterClient = createPaymasterClient({ - transport: http(paymasterUrl) - }) - - console.log({ paymasterClient }) - - const smartAccount = await createSmartAccountClient({ - signer: walletClient, + nexusClient = await toNexusClient({ + owner: account, chain, - paymasterUrl, - bundlerUrl, - // Remove the following lines to use the default factory and validator addresses - // These are relevant only for now on sopelia chain and are likely to change + transport: http(), + bundlerTransport: http(bundlerUrl), k1ValidatorAddress, - factoryAddress + factoryAddress, + paymaster: toBicoPaymasterClient({ + paymasterUrl + }) }) - expect(async () => - smartAccount.sendTransaction( - { - to: account.address, - data: "0x", - value: 1n - }, - { - paymasterServiceData: { - mode: "SPONSORED" + nexusClient.sendTransaction({ + calls: [ + { + to: account.address, + value: 1n } - } - ) + ] + }) ).rejects.toThrow("Error in generating paymasterAndData") }) }) diff --git a/tests/src/testSetup.ts b/packages/tests/testSetup.ts similarity index 85% rename from tests/src/testSetup.ts rename to packages/tests/testSetup.ts index e6ad9a1a6..dd9813f80 100644 --- a/tests/src/testSetup.ts +++ b/packages/tests/testSetup.ts @@ -1,4 +1,4 @@ -import { describe, inject, test } from "vitest" +import { inject, test } from "vitest" import { type FundedTestClients, type NetworkConfig, @@ -55,9 +55,5 @@ export const toNetwork = async ( ? initLocalhostNetwork() : initTestnetNetwork()) -export const describeWithPlaygroundGuard = - process.env.RUN_PLAYGROUND === "true" ? describe : describe.skip - -export const describeWithPaymasterGuard = process.env.PAYMASTER_URL - ? describe - : describe.skip +export const playgroundTrue = process.env.RUN_PLAYGROUND === "true" +export const paymasterTruthy = !!process.env.PAYMASTER_URL diff --git a/tests/src/testUtils.ts b/packages/tests/testUtils.ts similarity index 86% rename from tests/src/testUtils.ts rename to packages/tests/testUtils.ts index 4463d4c7d..c5f58fa90 100644 --- a/tests/src/testUtils.ts +++ b/packages/tests/testUtils.ts @@ -1,5 +1,6 @@ import { config } from "dotenv" import getPort from "get-port" +// @ts-ignore import { alto, anvil } from "prool/instances" import { http, @@ -15,24 +16,25 @@ import { parseAbi, parseAbiParameters, publicActions, - walletActions + walletActions, + zeroAddress } from "viem" +import { createBundlerClient } from "viem/account-abstraction" import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts" +import contracts from "../sdk/__contracts" import { type EIP712DomainReturn, - type NexusSmartAccount, - createSmartAccountClient -} from "../../src" -import contracts from "../../src/__contracts" -import { getChain, getCustomChain } from "../../src/account/utils" -import { Logger } from "../../src/account/utils/Logger" -import { createBundler } from "../../src/bundler" + getChain, + getCustomChain +} from "../sdk/account/utils" +import { Logger } from "../sdk/account/utils/Logger" +import { type NexusClient, toNexusClient } from "../sdk/clients/toNexusClient" import { ENTRY_POINT_SIMULATIONS_CREATECALL, ENTRY_POINT_V07_CREATECALL, TEST_CONTRACTS } from "./callDatas" -import { clean, deploy, init } from "./executables" +import * as hardhatExec from "./executables" config() @@ -176,14 +178,14 @@ export const ensureBundlerIsReady = async ( bundlerUrl: string, chain: Chain ) => { - const bundler = await createBundler({ + const bundler = await createBundlerClient({ chain, - bundlerUrl + transport: http(bundlerUrl) }) while (true) { try { - await bundler.getGasFeeValues() + await bundler.getChainId() return } catch { await new Promise((resolve) => setTimeout(resolve, 1000)) @@ -204,11 +206,13 @@ export const toConfiguredAnvil = async ({ await instance.start() console.log("") console.log(`configuring module bytecode on http://localhost:${rpcPort}`) + // Set code with a test client await deployContracts(rpcPort) - await init() - await clean() + // Hardhat deployment + await hardhatExec.init() + await hardhatExec.clean() console.log(`deploying nexus contracts to http://localhost:${rpcPort}`) - await deploy(rpcPort) + await hardhatExec.deploy(rpcPort) console.log("deployment complete") console.log("") return instance @@ -233,12 +237,11 @@ export const initBundlerInstance = async ({ const bundlerInstance = await toBundlerInstance({ rpcUrl, bundlerPort }) return { bundlerInstance, bundlerUrl, bundlerPort } } - -export const checkBalance = ( +export const getBalance = ( testClient: MasterClient, owner: Hex, tokenAddress?: Hex -) => { +): Promise => { if (!tokenAddress) { return testClient.getBalance({ address: owner }) } @@ -249,7 +252,7 @@ export const checkBalance = ( ]), functionName: "balanceOf", args: [owner] - }) + }) as Promise } export const nonZeroBalance = async ( @@ -257,7 +260,7 @@ export const nonZeroBalance = async ( address: Hex, tokenAddress?: Hex ) => { - const balance = await checkBalance(testClient, address, tokenAddress) + const balance = await getBalance(testClient, address, tokenAddress) if (balance > BigInt(0)) return throw new Error( `Insufficient balance ${ @@ -288,21 +291,15 @@ export const toFundedTestClients = async ({ const testClient = toTestClient(chain, getTestAccount()) - const smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - const recipientSmartAccount = await createSmartAccountClient({ - signer: recipientWalletClient, - bundlerUrl, + const nexus = await toNexusClient({ + owner: account, + transport: http(), + bundlerTransport: http(bundlerUrl), chain }) - const smartAccountAddress = await smartAccount.getAddress() - const recipientSmartAccountAddress = await recipientSmartAccount.getAddress() - await fundAndDeploy(testClient, [smartAccount, recipientSmartAccount]) + const smartAccountAddress = await nexus.account.getAddress() + await fundAndDeploy(testClient, [nexus]) return { account, @@ -310,37 +307,45 @@ export const toFundedTestClients = async ({ walletClient, recipientWalletClient, testClient, - smartAccount, - recipientSmartAccount, - smartAccountAddress, - recipientSmartAccountAddress + nexus, + smartAccountAddress } } export const fundAndDeploy = async ( testClient: MasterClient, - smartAccounts: NexusSmartAccount[] -) => - Promise.all( - smartAccounts.map((smartAccount) => - fundAndDeploySingleAccount(testClient, smartAccount) + nexusClients: NexusClient[] +) => { + return Promise.all( + nexusClients.map((nexusClient) => + fundAndDeploySingleAccount(testClient, nexusClient) ) ) +} export const fundAndDeploySingleAccount = async ( testClient: MasterClient, - smartAccount: NexusSmartAccount + nexusClient: NexusClient ) => { try { - const accountAddress = await smartAccount.getAddress() + const accountAddress = await nexusClient.account.getAddress() await topUp(testClient, accountAddress) - const { wait } = await smartAccount.deploy() - const { success } = await wait() - if (!success) { + const hash = await nexusClient.sendUserOperation({ + calls: [ + { + to: zeroAddress, + value: 1n + } + ] + }) + const receipt = await nexusClient.waitForUserOperationReceipt({ hash }) + if (!receipt.success) { throw new Error("Failed to deploy smart account") } + return receipt } catch (e) { Logger.error(`Error initializing smart account: ${e}`) + return Promise.resolve() } } @@ -363,7 +368,7 @@ export const topUp = async ( amount = 100000000000000000000n, token?: Hex ) => { - const balanceOfRecipient = await checkBalance(testClient, recipient, token) + const balanceOfRecipient = await getBalance(testClient, recipient, token) if (balanceOfRecipient > amount) { Logger.log( @@ -385,13 +390,13 @@ export const topUp = async ( functionName: "transfer", args: [recipient, amount] }) - await testClient.waitForTransactionReceipt({ hash }) + return await testClient.waitForTransactionReceipt({ hash }) } const hash = await testClient.sendTransaction({ to: recipient, value: amount }) - return testClient.waitForTransactionReceipt({ hash }) + return await testClient.waitForTransactionReceipt({ hash }) } // Returns the encoded EIP-712 domain struct fields. @@ -478,7 +483,7 @@ export const byteCodeDeployer = async ( chain: fetchChain, transport: http() }) - return publicClient.getBytecode({ address }) + return publicClient.getCode({ address }) }) )) as Hex[] diff --git a/tests/vitest.config.ts b/packages/tests/vitest.config.ts similarity index 75% rename from tests/vitest.config.ts rename to packages/tests/vitest.config.ts index e32940803..91ec68f49 100644 --- a/tests/vitest.config.ts +++ b/packages/tests/vitest.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ "**/*.test.ts", "**/test/**" ], - include: ["src/**/*.ts"], + include: ["./packages/tests/**/*.test.ts", "./packages/sdk/**/*.test.ts"], thresholds: { lines: 80, functions: 50, @@ -25,8 +25,8 @@ export default defineConfig({ statements: 80 } }, - include: ["tests/**/*.test.ts", "src/**/*.test.ts"], - globalSetup: join(__dirname, "src/globalSetup.ts"), + include: ["./packages/tests/**/*.test.ts", "./packages/sdk/**/*.test.ts"], + globalSetup: join(__dirname, "globalSetup.ts"), environment: "node", testTimeout: 60_000, hookTimeout: 60_000 diff --git a/scripts/fetch:deployment.ts b/scripts/fetch:deployment.ts index 1ea138b5a..bc3aba731 100644 --- a/scripts/fetch:deployment.ts +++ b/scripts/fetch:deployment.ts @@ -52,7 +52,7 @@ export const getDeployments = async () => { const tsAbiPath = isForCore ? `${__dirname}/../src/__contracts/abi/${name}Abi.ts` - : `${__dirname}/../tests/src/__contracts/abi/${name}Abi.ts` + : `${__dirname}/../tests/__contracts/abi/${name}Abi.ts` fs.writeFileSync(tsAbiPath, tsAbiContent) @@ -76,12 +76,12 @@ export const getDeployments = async () => { const abiIndexPath = `${__dirname}/../src/__contracts/abi/index.ts` fs.writeFileSync(abiIndexPath, abiIndexContent) - const testAbiIndexPath = `${__dirname}/../tests/src/__contracts/abi/index.ts` + const testAbiIndexPath = `${__dirname}/../tests/__contracts/abi/index.ts` fs.writeFileSync(testAbiIndexPath, testAbiIndexContent) // Write addresses to src folder const writeAddressesPath = `${__dirname}/../src/__contracts/addresses.ts` - const writeAddressesPathTest = `${__dirname}/../tests/src/__contracts/mockAddresses.ts` + const writeAddressesPathTest = `${__dirname}/../tests/__contracts/mockAddresses.ts` const addressesContent = `// The contents of this folder is auto-generated. Please do not edit as your changes are likely to be overwritten\n import type { Hex } from "viem"\nexport const addresses: Record = ${JSON.stringify( diff --git a/scripts/send:userOp.ts b/scripts/send:userOp.ts index badbbfdd9..e8442945d 100644 --- a/scripts/send:userOp.ts +++ b/scripts/send:userOp.ts @@ -1,10 +1,7 @@ -import { http, createWalletClient, parseEther } from "viem" +import { http, type PublicClient, parseEther } from "viem" import { privateKeyToAccount } from "viem/accounts" -import { - createK1ValidatorModule, - createSmartAccountClient, - getChain -} from "../src" +import { getChain } from "../packages/sdk/account/utils/getChain" +import { toNexusClient } from "../packages/sdk/clients/toNexusClient" const k1ValidatorAddress = "0x663E709f60477f07885230E213b8149a7027239B" const factoryAddress = "0x887Ca6FaFD62737D0E79A2b8Da41f0B15A864778" @@ -51,37 +48,40 @@ const account = privateKeyToAccount(`0x${privateKey}`) const accountTwo = privateKeyToAccount(`0x${privateKeyTwo}`) const recipient = accountTwo.address -const [walletClient] = [ - createWalletClient({ - account, - chain, - transport: http() - }) -] - -const smartAccount = await createSmartAccountClient({ +const nexusClient = await toNexusClient({ + owner: account, chain, - signer: walletClient, - bundlerUrl, + transport: http(), + bundlerTransport: http(bundlerUrl), k1ValidatorAddress, factoryAddress }) -const sendUserOperation = async () => { - const transaction = { - to: recipient, // NFT address - data: "0x", - value: parseEther("0.0001") - } +const main = async () => { console.log( "Your smart account will be deployed at address, make sure it has some funds to pay for user ops: ", - await smartAccount.getAddress() + await nexusClient.account.getAddress() ) - const response = await smartAccount.sendTransaction([transaction]) + const hash = await nexusClient.sendTransaction({ + calls: [ + { + to: recipient, + value: parseEther("0.0001") + } + ] + }) - const receipt = await response.wait() - console.log("Receipt: ", receipt) + const receipt = await ( + nexusClient.account.client as PublicClient + ).waitForTransactionReceipt({ hash }) } -sendUserOperation() +main() + .then(() => { + process.exit(0) + }) + .catch((error) => { + console.error(error) + process.exitCode = 1 + }) diff --git a/scripts/viem:bundler.ts b/scripts/viem:bundler.ts new file mode 100644 index 000000000..c8b488c3d --- /dev/null +++ b/scripts/viem:bundler.ts @@ -0,0 +1,137 @@ +import { http, type PublicClient, createPublicClient, parseEther } from "viem" +import { privateKeyToAccount } from "viem/accounts" +import { toNexusAccount } from "../packages/sdk/account/toNexusAccount" +import { getChain } from "../packages/sdk/account/utils/getChain" +import { toBicoBundlerClient } from "../packages/sdk/clients/toBicoBundlerClient" + +const k1ValidatorAddress = "0x663E709f60477f07885230E213b8149a7027239B" +const factoryAddress = "0x887Ca6FaFD62737D0E79A2b8Da41f0B15A864778" + +const GAS_ESTIMATE = 1.25 + +const safeMultiplier = (bI: bigint, multiplier: number): bigint => + BigInt(Math.round(Number(bI) * multiplier)) + +export const getEnvVars = () => { + return { + bundlerUrl: process.env.BUNDLER_URL || "", + privateKey: process.env.E2E_PRIVATE_KEY_ONE || "", + privateKeyTwo: process.env.E2E_PRIVATE_KEY_TWO || "", + paymasterUrl: process.env.PAYMASTER_URL || "", + chainId: process.env.CHAIN_ID || "0" + } +} + +export const getConfig = () => { + const { + paymasterUrl, + bundlerUrl, + chainId: chainIdFromEnv, + privateKey, + privateKeyTwo + } = getEnvVars() + + const chains = [Number.parseInt(chainIdFromEnv)] + const chainId = chains[0] + const chain = getChain(chainId) + + return { + chain, + chainId, + paymasterUrl, + bundlerUrl, + privateKey, + privateKeyTwo + } +} + +const { chain, privateKey, privateKeyTwo, bundlerUrl } = getConfig() + +if ([chain, privateKey, privateKeyTwo, bundlerUrl].every(Boolean) !== true) + throw new Error("Missing env vars") + +const account = privateKeyToAccount(`0x${privateKey}`) +const accountTwo = privateKeyToAccount(`0x${privateKeyTwo}`) +const recipient = accountTwo.address + +const publicClient = createPublicClient({ + chain, + transport: http() +}) + +const main = async () => { + const nexusAccount = await toNexusAccount({ + owner: account, + chain, + transport: http(), + k1ValidatorAddress, + factoryAddress + }) + + const bicoBundler = toBicoBundlerClient({ + bundlerUrl, + account: nexusAccount, + userOperation: { + estimateFeesPerGas: async (parameters) => { + const feeData = await ( + parameters?.account?.client as PublicClient + )?.estimateFeesPerGas?.() + const gas = { + maxFeePerGas: safeMultiplier(feeData.maxFeePerGas, GAS_ESTIMATE), + maxPriorityFeePerGas: safeMultiplier( + feeData.maxPriorityFeePerGas, + GAS_ESTIMATE + ) + } + return gas + } + } + }) + + const usesAltoBundler = process.env.BUNDLER_URL?.includes("pimlico") + console.time("read methods") + const results = await Promise.allSettled([ + bicoBundler.getChainId(), + bicoBundler.getSupportedEntryPoints(), + bicoBundler.prepareUserOperation({ + sender: account.address, + nonce: 0n, + data: "0x", + signature: "0x", + verificationGasLimit: 1n, + preVerificationGas: 1n, + callData: "0x", + callGasLimit: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + account: nexusAccount + }) + ]) + console.timeEnd("read methods") + console.log( + `${results}: running the ${usesAltoBundler ? "Alto" : "Bico"} bundler` + ) + + console.time("write methods") + const hash = await bicoBundler.sendUserOperation({ + calls: [ + { + to: account.address, + value: 1n + } + ], + account: nexusAccount + }) + const userOpReceipt = await bicoBundler.waitForUserOperationReceipt({ hash }) + console.timeEnd("write methods") + console.log({ userOpReceipt, hash }) +} + +main() + .then(() => { + process.exit(0) + }) + .catch((error) => { + console.error(error) + process.exitCode = 1 + }) diff --git a/src/account/index.ts b/src/account/index.ts deleted file mode 100644 index 77a915ada..000000000 --- a/src/account/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { NexusSmartAccountConfig } from "./utils/Types.js" - -export * from "./utils/index.js" -export * from "./signers/local-account.js" -export * from "./signers/wallet-client.js" - -export type SmartWalletConfig = NexusSmartAccountConfig diff --git a/src/bundler/Bundler.ts b/src/bundler/Bundler.ts deleted file mode 100644 index ceb2cc0ef..000000000 --- a/src/bundler/Bundler.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { http, type Hash, type PublicClient, createPublicClient } from "viem" -import contracts from "../__contracts/index.js" -import type { UserOperationStruct } from "../account" -import { HttpMethod, isNullOrUndefined, sendRequest } from "../account" -import type { IBundler } from "./interfaces/IBundler.js" -import { - UserOpReceiptIntervals, - UserOpReceiptMaxDurationIntervals, - UserOpWaitForTxHashIntervals, - UserOpWaitForTxHashMaxDurationIntervals -} from "./utils/Constants.js" -import { - decodeUserOperationError, - getTimestampInSeconds -} from "./utils/HelperFunction.js" -import type { - BundlerConfig, - BundlerConfigWithChainId, - BundlerEstimateUserOpGasResponse, - GetUserOpByHashResponse, - GetUserOperationGasPriceReturnType, - GetUserOperationReceiptResponse, - GetUserOperationStatusResponse, - UserOpByHashResponse, - UserOpGasResponse, - UserOpReceipt, - UserOpResponse, - UserOpStatus -} from "./utils/Types.js" -import { deepHexlify } from "./utils/Utils.js" - -/** - * This class implements IBundler interface. - * Implementation sends UserOperation to a bundler URL as per ERC4337 standard. - * Checkout the proposal for more details on Bundlers. - */ -export class Bundler implements IBundler { - private bundlerConfig: BundlerConfigWithChainId - - // eslint-disable-next-line no-unused-vars - UserOpReceiptIntervals!: { [key in number]?: number } - - UserOpWaitForTxHashIntervals!: { [key in number]?: number } - - UserOpReceiptMaxDurationIntervals!: { [key in number]?: number } - - UserOpWaitForTxHashMaxDurationIntervals!: { [key in number]?: number } - - private publicClient: PublicClient - - constructor(bundlerConfig: BundlerConfig) { - const parsedChainId: number = bundlerConfig.chain.id - // || extractChainIdFromBundlerUrl(bundlerConfig.bundlerUrl) - this.bundlerConfig = { ...bundlerConfig, chainId: parsedChainId } - - this.publicClient = createPublicClient({ - chain: bundlerConfig.chain, - transport: http() - }) - - this.UserOpReceiptIntervals = { - ...UserOpReceiptIntervals, - ...bundlerConfig.userOpReceiptIntervals - } - - this.UserOpWaitForTxHashIntervals = { - ...UserOpWaitForTxHashIntervals, - ...bundlerConfig.userOpWaitForTxHashIntervals - } - - this.UserOpReceiptMaxDurationIntervals = { - ...UserOpReceiptMaxDurationIntervals, - ...bundlerConfig.userOpReceiptMaxDurationIntervals - } - - this.UserOpWaitForTxHashMaxDurationIntervals = { - ...UserOpWaitForTxHashMaxDurationIntervals, - ...bundlerConfig.userOpWaitForTxHashMaxDurationIntervals - } - - this.bundlerConfig.entryPointAddress = - bundlerConfig.entryPointAddress || contracts.entryPoint.address - } - - public getBundlerUrl(): string { - return `${this.bundlerConfig.bundlerUrl}` - } - - /** - * @param userOpHash - * @description This function will fetch gasPrices from bundler - * @returns Promise - */ - async estimateUserOpGas( - _userOp: UserOperationStruct - ): Promise { - const bundlerUrl = this.getBundlerUrl() - - const response: { - result: BundlerEstimateUserOpGasResponse - error: { message: string } - } = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: "eth_estimateUserOperationGas", - params: [deepHexlify(_userOp), this.bundlerConfig.entryPointAddress], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - const userOpGasResponse = response - for (const key in userOpGasResponse.result) { - if ( - userOpGasResponse.result[key as keyof UserOpGasResponse] === null || - userOpGasResponse.result[key as keyof UserOpGasResponse] === undefined - ) { - throw new Error(`Got undefined ${key} from bundler`) - } - } - - if (isNullOrUndefined(response.result)) { - const decodedError = decodeUserOperationError( - JSON.stringify(response?.error?.message) - ) - throw new Error(`Error from Bundler: ${decodedError}`) - } - - return { - preVerificationGas: BigInt(response.result.preVerificationGas || 0), - verificationGasLimit: BigInt(response.result.verificationGasLimit || 0), - callGasLimit: BigInt(response.result.callGasLimit || 0), - paymasterVerificationGasLimit: response.result - .paymasterVerificationGasLimit - ? BigInt(response.result.paymasterVerificationGasLimit) - : undefined, - paymasterPostOpGasLimit: response.result.paymasterPostOpGasLimit - ? BigInt(response.result.paymasterPostOpGasLimit) - : undefined - } - } - - /** - * - * @param userOp - * @description This function will send signed userOp to bundler to get mined on chain - * @returns Promise - */ - async sendUserOp(_userOp: UserOperationStruct): Promise { - const chainId = this.bundlerConfig.chainId - - const params = [deepHexlify(_userOp), this.bundlerConfig.entryPointAddress] - const bundlerUrl = this.getBundlerUrl() - const sendUserOperationResponse: { - result: Hash - error: { message: string } - } = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: "eth_sendUserOperation", - params: params, - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - - if (isNullOrUndefined(sendUserOperationResponse.result)) { - throw new Error(sendUserOperationResponse.error.message) - } - - return { - userOpHash: sendUserOperationResponse.result, - wait: (confirmations?: number): Promise => { - // Note: maxDuration can be defined per chainId - const maxDuration = - this.UserOpReceiptMaxDurationIntervals[chainId] || 50000 // default 50 seconds - let totalDuration = 0 - - return new Promise((resolve, reject) => { - const intervalValue = this.UserOpReceiptIntervals[chainId] || 5000 // default 5 seconds - const intervalId = setInterval(async () => { - try { - const userOpResponse = await this.getUserOpReceipt( - sendUserOperationResponse.result - ) - if (userOpResponse?.receipt?.blockNumber) { - if (confirmations) { - const latestBlock = await this.publicClient.getBlockNumber() - const confirmedBlocks = - BigInt(latestBlock) - - BigInt(userOpResponse.receipt.blockNumber) - if (confirmations >= confirmedBlocks) { - clearInterval(intervalId) - resolve(userOpResponse) - return - } - } else { - clearInterval(intervalId) - resolve(userOpResponse) - return - } - } - } catch (error) { - clearInterval(intervalId) - reject(error) - return - } - - totalDuration += intervalValue - if (totalDuration >= maxDuration) { - clearInterval(intervalId) - reject( - new Error( - `Exceeded maximum duration (${ - maxDuration / 1000 - } sec) waiting to get receipt for userOpHash ${ - sendUserOperationResponse.result - }. Try getting the receipt manually using eth_getUserOperationReceipt rpc method on bundler` - ) - ) - } - }, intervalValue) - }) - } - } - } - - /** - * - * @param userOpHash - * @description This function will return userOpReceipt for a given userOpHash - * @returns Promise - */ - async getUserOpReceipt(userOpHash: string): Promise { - const bundlerUrl = this.getBundlerUrl() - const response: GetUserOperationReceiptResponse = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: "eth_getUserOperationReceipt", - params: [userOpHash], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - - if (isNullOrUndefined(response.result)) { - throw new Error(response?.error?.message) - } - - const userOpReceipt: UserOpReceipt = response.result - return userOpReceipt - } - - /** - * - * @param userOpHash - * @description This function will return userOpReceipt for a given userOpHash - * @returns Promise - */ - async getUserOpStatus(userOpHash: string): Promise { - const bundlerUrl = this.getBundlerUrl() - const response: GetUserOperationStatusResponse = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: "biconomy_getUserOperationStatus", - params: [userOpHash], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - - if (isNullOrUndefined(response.result)) { - throw new Error(response?.error?.message) - } - - const userOpStatus: UserOpStatus = response.result - return userOpStatus - } - - /** - * - * @param userOpHash - * @description this function will return UserOpByHashResponse for given UserOpHash - * @returns Promise - */ - async getUserOpByHash(userOpHash: string): Promise { - const bundlerUrl = this.getBundlerUrl() - const response: GetUserOpByHashResponse = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: "eth_getUserOperationByHash", - params: [userOpHash], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - - if (isNullOrUndefined(response.result)) { - throw new Error(response?.error?.message) - } - - const userOpByHashResponse: UserOpByHashResponse = response.result - return userOpByHashResponse - } - - /** - * @description This function will return the gas fee values - */ - async getGasFeeValues(): Promise { - const bundlerUrl = this.getBundlerUrl() - const response: { - result: GetUserOperationGasPriceReturnType - error: Error - } = await sendRequest( - { - url: bundlerUrl, - method: HttpMethod.Post, - body: { - method: process.env.BUNDLER_URL?.includes("pimlico") - ? "pimlico_getUserOperationGasPrice" - : "biconomy_getGasFeeValues", - params: [], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Bundler" - ) - - if (isNullOrUndefined(response.result)) { - throw new Error(response?.error?.message) - } - - return { - slow: { - maxFeePerGas: BigInt(response.result.slow.maxFeePerGas), - maxPriorityFeePerGas: BigInt(response.result.slow.maxPriorityFeePerGas) - }, - standard: { - maxFeePerGas: BigInt(response.result.standard.maxFeePerGas), - maxPriorityFeePerGas: BigInt( - response.result.standard.maxPriorityFeePerGas - ) - }, - fast: { - maxFeePerGas: BigInt(response.result.fast.maxFeePerGas), - maxPriorityFeePerGas: BigInt(response.result.fast.maxPriorityFeePerGas) - } - } - } - - public static async create(config: BundlerConfig): Promise { - return new Bundler(config) - } -} diff --git a/src/bundler/index.ts b/src/bundler/index.ts deleted file mode 100644 index 5986083f4..000000000 --- a/src/bundler/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Bundler } from "./Bundler.js" - -export * from "./interfaces/IBundler.js" -export * from "./Bundler.js" -export * from "./utils/Utils.js" -export * from "./utils/Types.js" - -export const createBundler = Bundler.create diff --git a/src/bundler/interfaces/IBundler.ts b/src/bundler/interfaces/IBundler.ts deleted file mode 100644 index bb465ee15..000000000 --- a/src/bundler/interfaces/IBundler.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { StateOverrideSet, UserOperationStruct } from "../../account" -import type { - GetUserOperationGasPriceReturnType, - UserOpByHashResponse, - UserOpGasResponse, - UserOpReceipt, - UserOpResponse, - UserOpStatus -} from "../utils/Types.js" - -export interface IBundler { - estimateUserOpGas( - _userOp: Partial, - stateOverrideSet?: StateOverrideSet - ): Promise - sendUserOp(_userOp: UserOperationStruct): Promise - getUserOpReceipt(_userOpHash: string): Promise - getUserOpByHash(_userOpHash: string): Promise - getGasFeeValues(): Promise - getUserOpStatus(_userOpHash: string): Promise - getBundlerUrl(): string -} diff --git a/src/bundler/utils/Constants.ts b/src/bundler/utils/Constants.ts deleted file mode 100644 index c615713ab..000000000 --- a/src/bundler/utils/Constants.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { concat, pad } from "viem" - -// define mode and exec type enums -export const CALLTYPE_SINGLE = "0x00" // 1 byte -export const CALLTYPE_BATCH = "0x01" // 1 byte -export const EXECTYPE_DEFAULT = "0x00" // 1 byte -export const EXECTYPE_TRY = "0x01" // 1 byte -export const EXECTYPE_DELEGATE = "0xFF" // 1 byte -export const MODE_DEFAULT = "0x00000000" // 4 bytes -export const UNUSED = "0x00000000" // 4 bytes -export const MODE_PAYLOAD = "0x00000000000000000000000000000000000000000000" // 22 bytes -export const ERC1271_MAGICVALUE = "0x1626ba7e" -export const ERC1271_INVALID = "0xffffffff" -export const GENERIC_FALLBACK_SELECTOR = "0xcb5baf0f" - -export const UserOpReceiptIntervals: { [key in number]?: number } = { - [1]: 10000 -} - -// Note: Default value is 500(0.5sec) -export const UserOpWaitForTxHashIntervals: { [key in number]?: number } = { - [1]: 1000 -} - -// Note: Default value is 30000 (30sec) -export const UserOpReceiptMaxDurationIntervals: { [key in number]?: number } = { - [1]: 300000, - [11155111]: 50000, - [137]: 60000, - [56]: 50000, - [97]: 50000, - [421613]: 50000, - [42161]: 50000, - [59140]: 50000 // linea testnet -} - -// Note: Default value is 20000 (20sec) -export const UserOpWaitForTxHashMaxDurationIntervals: { - [key in number]?: number -} = { - [1]: 20000 -} - -export const SDK_VERSION = "4.4.5" - -export const EXECUTE_SINGLE = concat([ - CALLTYPE_SINGLE, - EXECTYPE_DEFAULT, - MODE_DEFAULT, - UNUSED, - MODE_PAYLOAD -]) - -export const EXECUTE_BATCH = concat([ - CALLTYPE_BATCH, - EXECTYPE_DEFAULT, - MODE_DEFAULT, - UNUSED, - MODE_PAYLOAD -]) - -export const ACCOUNT_MODES = { - DEFAULT_SINGLE: concat([ - pad(EXECTYPE_DEFAULT, { size: 1 }), - pad(CALLTYPE_SINGLE, { size: 1 }), - pad(UNUSED, { size: 4 }), - pad(MODE_DEFAULT, { size: 4 }), - pad(MODE_PAYLOAD, { size: 22 }) - ]), - DEFAULT_BATCH: concat([ - pad(EXECTYPE_DEFAULT, { size: 1 }), - pad(CALLTYPE_BATCH, { size: 1 }), - pad(UNUSED, { size: 4 }), - pad(MODE_DEFAULT, { size: 4 }), - pad(MODE_PAYLOAD, { size: 22 }) - ]), - TRY_BATCH: concat([ - pad(EXECTYPE_TRY, { size: 1 }), - pad(CALLTYPE_BATCH, { size: 1 }), - pad(UNUSED, { size: 4 }), - pad(MODE_DEFAULT, { size: 4 }), - pad(MODE_PAYLOAD, { size: 22 }) - ]), - TRY_SINGLE: concat([ - pad(EXECTYPE_TRY, { size: 1 }), - pad(CALLTYPE_SINGLE, { size: 1 }), - pad(UNUSED, { size: 4 }), - pad(MODE_DEFAULT, { size: 4 }), - pad(MODE_PAYLOAD, { size: 22 }) - ]), - DELEGATE_SINGLE: concat([ - pad(EXECTYPE_DELEGATE, { size: 1 }), - pad(CALLTYPE_SINGLE, { size: 1 }), - pad(UNUSED, { size: 4 }), - pad(MODE_DEFAULT, { size: 4 }), - pad(MODE_PAYLOAD, { size: 22 }) - ]) -} diff --git a/src/bundler/utils/HelperFunction.ts b/src/bundler/utils/HelperFunction.ts deleted file mode 100644 index cf96b536c..000000000 --- a/src/bundler/utils/HelperFunction.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Will convert the userOp hex, bigInt and number values to hex strings -// export const transformUserOP = ( -// userOp: UserOperationStruct -// ): UserOperationStruct => { -// try { -// const userOperation = { ...userOp } -// const keys: (keyof UserOperationStruct)[] = [ -// "nonce", -// "callGasLimit", -// "verificationGasLimit", -// "preVerificationGas", -// "maxFeePerGas", -// "maxPriorityFeePerGas" -// ] -// for (const key of keys) { -// if (userOperation[key] && userOperation[key] !== "0x") { -// userOperation[key] = `0x${BigInt(userOp[key] as BigNumberish).toString( -// 16 -// )}` as `0x${string}` -// } -// } -// return userOperation -// } catch (error) { -// throw `Failed to transform user operation: ${error}` -// } -// } - -/** - * @description this function will return current timestamp in seconds - * @returns Number - */ -export const getTimestampInSeconds = (): number => { - return Math.floor(Date.now() / 1000) -} - -export function decodeUserOperationError(errorFromBundler: string) { - const prefix = "UserOperation reverted during simulation with reason: " - if (errorFromBundler.includes(prefix)) { - const errorCode = errorFromBundler - .slice(prefix.length) - .trim() - .replace(/"/g, "") - return decodeErrorCode(errorCode) - } - return errorFromBundler // Return original error if it doesn't match the expected format -} - -function decodeErrorCode(errorCode: string) { - const errorMap: { [key: string]: string } = { - "0xe7190273": - "NotSortedAndUnique: The owners array must contain unique addresses.", - "0xf91bd6f1000000000000000000000000da6959da394b1bddb068923a9a214dc0cd193d2e": - "NotInitialized: The module is not initialized on this smart account.", - "0xaabd5a09": - "InvalidThreshold: The threshold must be greater than or equal to the number of owners.", - "0x71448bfe000000000000000000000000bf2137a23f439ca5aa4360cc6970d70b24d07ea2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0": - "WrongContractSignatureFormat", - "0x40d3d1a40000000000000000000000004d8249d21c9553b1bd23cabf611011376dd3416a": - "LinkedList_EntryAlreadyInList", - "0x40d3d1a40000000000000000000000004b8306128aed3d49a9d17b99bf8082d4e406fa1f": - "LinkedList_EntryAlreadyInList", - "0x40d3d1a4000000000000000000000000d98238bbaea4f91683d250003799ead31d7f5c55": - "Error: Custom error message about the K1Validator contract" - // Add more error codes and their corresponding human-readable messages here - } - const decodedError = errorMap[errorCode] || errorCode - return `User operation reverted during simulation with reason: ${decodedError}` -} diff --git a/src/bundler/utils/Types.ts b/src/bundler/utils/Types.ts deleted file mode 100644 index 53688a28d..000000000 --- a/src/bundler/utils/Types.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { Address, Chain, Hash, Hex, Log } from "viem" -import type { UserOperationStruct } from "../../account" - -export type BundlerConfig = { - bundlerUrl: string - entryPointAddress?: string - chain: Chain - // eslint-disable-next-line no-unused-vars - userOpReceiptIntervals?: { [key in number]?: number } - userOpWaitForTxHashIntervals?: { [key in number]?: number } - userOpReceiptMaxDurationIntervals?: { [key in number]?: number } - userOpWaitForTxHashMaxDurationIntervals?: { [key in number]?: number } -} -export type BundlerConfigWithChainId = BundlerConfig & { chainId: number } - -export type TStatus = "success" | "reverted" - -export type UserOpReceiptTransaction = { - transactionHash: Hex - transactionIndex: bigint - blockHash: Hash - blockNumber: bigint - from: Address - to: Address | null - cumulativeGasUsed: bigint - status: TStatus - gasUsed: bigint - contractAddress: Address | null - logsBloom: Hex - effectiveGasPrice: bigint -} - -export type UserOpReceipt = { - userOpHash: Hash - entryPoint: Address - sender: Address - nonce: bigint - paymaster?: Address - actualGasUsed: bigint - actualGasCost: bigint - success: boolean - reason?: string - receipt: UserOpReceiptTransaction - logs: Log[] -} - -// review -export type UserOpStatus = { - state: string // for now // could be an enum - transactionHash?: string - userOperationReceipt?: UserOpReceipt -} - -// Converted to JsonRpcResponse with strict type -export type GetUserOperationReceiptResponse = { - jsonrpc: string - id: number - result: UserOpReceipt - error?: JsonRpcError -} - -export type GetUserOperationStatusResponse = { - jsonrpc: string - id: number - result: UserOpStatus - error?: JsonRpcError -} - -// Converted to JsonRpcResponse with strict type -export type SendUserOpResponse = { - jsonrpc: string - id: number - result: string - error?: JsonRpcError -} - -export type UserOpResponse = { - userOpHash: Hash - wait(_confirmations?: number): Promise -} - -// Converted to JsonRpcResponse with strict type -export type EstimateUserOpGasResponse = { - jsonrpc: string - id: number - result: UserOpGasResponse - error?: JsonRpcError -} - -export type UserOpGasResponse = { - preVerificationGas: bigint - verificationGasLimit: bigint - callGasLimit: bigint - paymasterVerificationGasLimit?: bigint - paymasterPostOpGasLimit?: bigint -} - -// Converted to JsonRpcResponse with strict type -export type GetUserOpByHashResponse = { - jsonrpc: string - id: number - result: UserOpByHashResponse - error?: JsonRpcError -} - -export type UserOpByHashResponse = UserOperationStruct & { - transactionHash: string - blockNumber: number - blockHash: string - entryPoint: string -} -/* eslint-disable @typescript-eslint/no-explicit-any */ -export type JsonRpcError = { - code: string - message: string - data: any -} - -export type GetGasFeeValuesResponse = { - jsonrpc: string - id: number - result: GasFeeValues - error?: JsonRpcError -} -export type GasFeeValues = { - maxPriorityFeePerGas: bigint - maxFeePerGas: bigint -} - -export type GetUserOperationGasPriceReturnType = { - slow: { - maxFeePerGas: bigint - maxPriorityFeePerGas: bigint - } - standard: { - maxFeePerGas: bigint - maxPriorityFeePerGas: bigint - } - fast: { - maxFeePerGas: bigint - maxPriorityFeePerGas: bigint - } -} - -export type BundlerEstimateUserOpGasResponse = { - preVerificationGas: Hex - verificationGasLimit: Hex - callGasLimit?: Hex | null - paymasterVerificationGasLimit?: Hex | null - paymasterPostOpGasLimit?: Hex | null -} diff --git a/src/bundler/utils/Utils.ts b/src/bundler/utils/Utils.ts deleted file mode 100644 index a5971ff93..000000000 --- a/src/bundler/utils/Utils.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { toHex } from "viem" - -export const extractChainIdFromBundlerUrl = (url: string): number => { - try { - const regex = /\/api\/v[2-3]\/(\d+)\/[a-zA-Z0-9.-]+$/ - // biome-ignore lint/style/noNonNullAssertion: - const match = regex.exec(url)! - return Number.parseInt(match[1]) - } catch (error) { - throw new Error("Invalid chain id") - } -} - -export const extractChainIdFromPaymasterUrl = (url: string): number => { - try { - const regex = /\/api\/v\d+\/(\d+)\// - const match = regex.exec(url) - if (!match) { - throw new Error("Invalid URL format") - } - return Number.parseInt(match[1]) - } catch (error) { - throw new Error("Invalid chain id") - } -} - -export function deepHexlify(obj: any): any { - if (typeof obj === "function") { - return undefined - } - if (obj == null || typeof obj === "string" || typeof obj === "boolean") { - return obj - } - - if (typeof obj === "bigint") { - return toHex(obj) - } - - if (obj._isBigNumber != null || typeof obj !== "object") { - return toHex(obj).replace(/^0x0/, "0x") - } - if (Array.isArray(obj)) { - return obj.map((member) => deepHexlify(member)) - } - return Object.keys(obj).reduce( - // biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type - (set: any, key: string) => { - set[key] = deepHexlify(obj[key]) - return set - }, - {} - ) -} diff --git a/src/clients/biconomy.test.ts b/src/clients/biconomy.test.ts deleted file mode 100644 index 9ab6c4524..000000000 --- a/src/clients/biconomy.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { - http, - type Account, - type Address, - type Chain, - type WalletClient, - createWalletClient, - isHex -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../tests/src/testSetup" -import { - type MasterClient, - type NetworkConfig, - getTestAccount, - killNetwork, - toTestClient -} from "../../tests/src/testUtils" -import contracts from "../__contracts" -import { type BiconomyClient, createBiconomyClient } from "./biconomy" - -describe("biconomy.argle", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let biconomyAccountAddress: Address - let biconomy: BiconomyClient - - beforeAll(async () => { - network = await toNetwork() - - chain = network.chain - bundlerUrl = network.bundlerUrl - account = getTestAccount(0) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - biconomy = await createBiconomyClient({ - owner: account, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - biconomyAccountAddress = await biconomy.account.getCounterFactualAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should check balances and top up relevant addresses", async () => { - const [ownerBalance, smartAccountBalance] = await Promise.all([ - testClient.getBalance({ - address: account.address - }), - testClient.getBalance({ - address: biconomyAccountAddress - }) - ]) - const balancesAreOfCorrectType = [ownerBalance, smartAccountBalance].every( - (balance) => typeof balance === "bigint" - ) - - if (smartAccountBalance < 100000000000000000n) { - const hash = await walletClient.sendTransaction({ - chain, - account, - to: biconomyAccountAddress, - value: 100000000000000000n - }) - await testClient.waitForTransactionReceipt({ hash }) - } - - const smartAccountBalanceAfterTopUp = await testClient.getBalance({ - address: biconomyAccountAddress - }) - - expect(balancesAreOfCorrectType).toBeTruthy() - }) - - test("should have a working bundler client", async () => { - const chainId = await biconomy.getChainId() - expect(chainId).toEqual(chain.id) - - const supportedEntrypoints = await biconomy.getSupportedEntryPoints() - expect(supportedEntrypoints).to.include(contracts.entryPoint.address) - }) - - test("should send a user operation", async () => { - const calls = [{ to: account.address, value: 1n }] - const feeData = await testClient.estimateFeesPerGas() - - const gas = { - maxFeePerGas: feeData.maxFeePerGas * 2n, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n - } - - const hash = await biconomy.sendUserOperation({ calls, ...gas }) - const receipt = await biconomy.waitForUserOperationReceipt({ hash }) - - expect(receipt.success).toEqual(true) - }) - - test("should send a userop using the biconomy stack", async () => { - const calls = [{ to: account.address, value: 1n }] - - const hash = await biconomy.sendTransaction({ calls }) - const receipt = await biconomy.waitForUserOperationReceipt({ hash }) - - console.log({ receipt }) - - expect(receipt.success).toEqual(true) - }) - - test("should call some erc7579 actions", async () => { - expect(biconomy.isModuleInstalled).toBeTruthy() - const supportsExecutionMode = await biconomy.supportsExecutionMode({ - type: "delegatecall", - // biome-ignore lint/style/noNonNullAssertion: - account: biconomy.account! - }) - console.log({ supportsExecutionMode }) - }) - - test("should produce the same results", async () => { - const nexusInitCode = biconomy.account.getInitCode() - expect(nexusInitCode).toBeTruthy() - - const nexusFactoryData = biconomy.account.factoryData - expect(isHex(nexusFactoryData)).toBeTruthy() - - const nexusIsDeployed = await biconomy.account.isDeployed() - expect(nexusIsDeployed).toBeTruthy() - }) -}) diff --git a/src/paymaster/Paymaster.ts b/src/paymaster/Paymaster.ts deleted file mode 100644 index f833ceee3..000000000 --- a/src/paymaster/Paymaster.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { encodeFunctionData, parseAbi } from "viem" -import { - type BiconomyTokenPaymasterRequest, - HttpMethod, - Logger, - type Transaction, - type UserOperationStruct, - sendRequest -} from "../account/index.js" -import { deepHexlify } from "../bundler/index.js" -import type { IHybridPaymaster } from "./interfaces/IHybridPaymaster.js" -import { ADDRESS_ZERO, ERC20_ABI, MAX_UINT256 } from "./utils/Constants.js" -import { getTimestampInSeconds } from "./utils/Helpers.js" -import type { - FeeQuotesOrDataDto, - FeeQuotesOrDataResponse, - Hex, - JsonRpcResponse, - PaymasterAndDataResponse, - PaymasterConfig, - PaymasterFeeQuote, - SponsorUserOperationDto -} from "./utils/Types.js" - -const defaultPaymasterConfig: PaymasterConfig = { - paymasterUrl: "", - strictMode: false // Set your desired default value for strictMode here -} -/** - * @dev Hybrid - Generic Gas Abstraction paymaster - */ -export class Paymaster implements IHybridPaymaster { - paymasterConfig: PaymasterConfig - - constructor(config: PaymasterConfig) { - const mergedConfig: PaymasterConfig = { - ...defaultPaymasterConfig, - ...config - } - this.paymasterConfig = mergedConfig - } - - /** - * @dev Prepares the user operation by resolving properties and converting certain values to hexadecimal format. - * @param userOp The partial user operation. - * @returns A Promise that resolves to the prepared partial user operation. - */ - // private async prepareUserOperation( - // userOp: Partial - // ): Promise> { - // const userOperation = { ...userOp } - // try { - // const keys1: (keyof UserOperationStruct)[] = [ - // "nonce", - // "maxFeePerGas", - // "maxPriorityFeePerGas" - // ] - // for (const key of keys1) { - // if (userOperation[key] && userOperation[key] !== "0x") { - // userOperation[key] = `0x${BigInt( - // userOp[key] as BigNumberish - // ).toString(16)}` as `0x${string}` - // } - // } - // const keys2: (keyof UserOperationStruct)[] = [ - // "callGasLimit", - // "verificationGasLimit", - // "preVerificationGas" - // ] - // for (const key of keys2) { - // if (userOperation[key] && userOperation[key] !== "0x") { - // userOperation[key] = BigInt( - // userOp[key] as BigNumberish - // ).toString() as `0x${string}` - // } - // } - // } catch (error) { - // throw `Failed to transform user operation: ${error}` - // } - // userOperation.signature = userOp.signature || "0x" - // userOperation.paymasterAndData = userOp.paymasterAndData || "0x" - // return userOperation - // } - - /** - * @dev Builds a token approval transaction for the Biconomy token paymaster. - * @param tokenPaymasterRequest The token paymaster request data. This will include information about chosen feeQuote, spender address and optional flag to provide maxApproval - * @param provider Optional provider object. - * @returns A Promise that resolves to the built transaction object. - */ - async buildTokenApprovalTransaction( - tokenPaymasterRequest: BiconomyTokenPaymasterRequest - ): Promise { - const feeTokenAddress: string = tokenPaymasterRequest.feeQuote.tokenAddress - - const spender = tokenPaymasterRequest.spender - - // logging provider object isProvider - // Logger.log("provider object passed - is provider", provider?._isProvider); - - // TODO move below notes to separate method - // Note: should also check in caller if the approval is already given, if yes return object with address or data 0 - // Note: we would need userOp here to get the account/owner info to check allowance - - let requiredApproval = BigInt(0) - - if ( - tokenPaymasterRequest.maxApproval && - tokenPaymasterRequest.maxApproval === true - ) { - requiredApproval = BigInt(MAX_UINT256) - } else { - requiredApproval = BigInt( - Math.ceil( - tokenPaymasterRequest.feeQuote.maxGasFee * - 10 ** tokenPaymasterRequest.feeQuote.decimal - ) - ) - } - - try { - const parsedAbi = parseAbi(ERC20_ABI) - const data = encodeFunctionData({ - abi: parsedAbi, - functionName: "approve", - args: [spender, requiredApproval] - }) - - // TODO? - // Note: For some tokens we may need to set allowance to 0 first so that would return batch of transactions and changes the return type to Transaction[] - // In that case we would return two objects in an array, first of them being.. - /* - { - to: erc20.address, - value: ethers.BigNumber.from(0), - data: erc20.interface.encodeFunctionData('approve', [spender, BigNumber.from("0")]) - } - */ - - // const zeroValue: ethers.BigNumber = ethers.BigNumber.from(0); - // const value: BigNumberish | undefined = zeroValue as any; - return { - to: feeTokenAddress, - value: "0x00", - data: data - } - } catch (error) { - throw new Error("Failed to encode function data") - } - } - - /** - * @dev Retrieves paymaster fee quotes or data based on the provided user operation and paymaster service data. - * @param userOp The partial user operation. - * @param paymasterServiceData The paymaster service data containing token information and sponsorship details. Devs can send just the preferred token or array of token addresses in case of mode "ERC20" and sartAccountInfo in case of "sponsored" mode. - * @returns A Promise that resolves to the fee quotes or data response. - */ - async getPaymasterFeeQuotesOrData( - userOp: Partial, - paymasterServiceData: FeeQuotesOrDataDto - ): Promise { - // const userOp = await this.prepareUserOperation(_userOp) - - let mode: "SPONSORED" | "ERC20" | null = null - let expiryDuration: number | null = null - const calculateGasLimits = paymasterServiceData.calculateGasLimits ?? true - let preferredToken: string | null = null - let feeTokensArray: string[] = [] - // could make below null - let smartAccountInfo = { - name: "BICONOMY", - version: "2.0.0" - } - let webhookData: Record | null = null - - if (paymasterServiceData.mode) { - mode = paymasterServiceData.mode - // Validation on the mode passed / define allowed enums - } - - if (paymasterServiceData.expiryDuration) { - expiryDuration = paymasterServiceData.expiryDuration - } - - preferredToken = paymasterServiceData?.preferredToken - ? paymasterServiceData?.preferredToken - : preferredToken - - feeTokensArray = ( - paymasterServiceData?.tokenList?.length !== 0 - ? paymasterServiceData?.tokenList - : feeTokensArray - ) as string[] - - webhookData = paymasterServiceData?.webhookData ?? webhookData - - smartAccountInfo = - paymasterServiceData?.smartAccountInfo ?? smartAccountInfo - - try { - const response: JsonRpcResponse = await sendRequest( - { - url: `${this.paymasterConfig.paymasterUrl}`, - method: HttpMethod.Post, - body: { - method: "pm_getFeeQuoteOrData", - params: [ - userOp, - { - ...(mode !== null && { mode }), - calculateGasLimits: calculateGasLimits, - ...(expiryDuration !== null && { expiryDuration }), - tokenInfo: { - tokenList: feeTokensArray, - ...(preferredToken !== null && { preferredToken }) - }, - sponsorshipInfo: { - ...(webhookData !== null && { webhookData }), - smartAccountInfo: smartAccountInfo - } - } - ], // As per current API - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Paymaster" - ) - - if (response?.result) { - if (response.result.mode === "ERC20") { - const feeQuotesResponse: Array = - response.result.feeQuotes - const paymasterAddress: Hex = response.result.paymasterAddress - // check all objects iterate and populate below calculation for all tokens - return { - feeQuotes: feeQuotesResponse, - tokenPaymasterAddress: paymasterAddress - } - } - if (response.result.mode === "SPONSORED") { - const paymasterAndData: Hex = response.result.paymasterAndData - const preVerificationGas = response.result.preVerificationGas - const verificationGasLimit = response.result.verificationGasLimit - const callGasLimit = response.result.callGasLimit - return { - paymasterAndData: paymasterAndData, - preVerificationGas: preVerificationGas, - verificationGasLimit: verificationGasLimit, - callGasLimit: callGasLimit - } - } - const errorObject = { - code: 417, - message: - "Expectation Failed: Invalid mode in Paymaster service response" - } - throw errorObject - } - } catch (error: any) { - Logger.error( - "Failed to fetch Fee Quotes or Paymaster data - reason: ", - JSON.stringify(error) - ) - // Note: we may not throw if we include strictMode off and return paymasterData '0x'. - if ( - !this.paymasterConfig.strictMode && - paymasterServiceData.mode === "SPONSORED" && - (error?.message.includes("Smart contract data not found") || - error?.message.includes("No policies were set")) - // can also check based on error.code being -32xxx - ) { - Logger.warn( - `Strict mode is ${this.paymasterConfig.strictMode}. sending paymasterAndData 0x` - ) - return { - paymasterAndData: "0x", - // send below values same as userOp gasLimits - preVerificationGas: userOp.preVerificationGas, - verificationGasLimit: userOp.verificationGasLimit, - callGasLimit: userOp.callGasLimit - } - } - throw error - } - throw new Error("Failed to fetch feeQuote or paymaster data") - } - - /** - * @dev Retrieves the paymaster and data based on the provided user operation and paymaster service data. - * @param userOp The partial user operation. - * @param paymasterServiceData Optional paymaster service data. - * @returns A Promise that resolves to the paymaster and data string. - */ - async getPaymasterAndData( - userOp: Partial, - paymasterServiceData?: SponsorUserOperationDto // mode is necessary. partial context of token paymaster or verifying - ): Promise { - // const userOp = await this.prepareUserOperation(_userOp) - - if (paymasterServiceData?.mode === undefined) { - throw new Error("mode is required in paymasterServiceData") - } - - const mode = paymasterServiceData.mode - - const calculateGasLimits = paymasterServiceData.calculateGasLimits ?? true - - let tokenInfo: Record | null = null - // could make below null - let smartAccountInfo = { - name: "BICONOMY", - version: "2.0.0" - } - let webhookData: Record | null = null - let expiryDuration: number | null = null - - if (mode === "ERC20") { - if ( - !paymasterServiceData?.feeTokenAddress && - paymasterServiceData?.feeTokenAddress === ADDRESS_ZERO - ) { - throw new Error("feeTokenAddress is required and should be non-zero") - } - tokenInfo = { - feeTokenAddress: paymasterServiceData.feeTokenAddress - } - } - - webhookData = paymasterServiceData?.webhookData ?? webhookData - smartAccountInfo = - paymasterServiceData?.smartAccountInfo ?? smartAccountInfo - expiryDuration = paymasterServiceData?.expiryDuration ?? expiryDuration - - // Note: The idea is before calling this below rpc, userOp values presense and types should be in accordance with how we call eth_estimateUseropGas on the bundler - - const hexlifiedUserOp = deepHexlify(userOp) - - try { - const response: JsonRpcResponse = await sendRequest( - { - url: `${this.paymasterConfig.paymasterUrl}`, - method: HttpMethod.Post, - body: { - method: "pm_sponsorUserOperation", - params: [ - hexlifiedUserOp, - { - mode: mode, - calculateGasLimits: calculateGasLimits, - ...(expiryDuration !== null && { expiryDuration }), - ...(tokenInfo !== null && { tokenInfo }), - sponsorshipInfo: { - ...(webhookData !== null && { webhookData }), - smartAccountInfo: smartAccountInfo - } - } - ], - id: getTimestampInSeconds(), - jsonrpc: "2.0" - } - }, - "Paymaster" - ) - - if (response?.result) { - const paymasterAndData = response.result.paymasterAndData - const preVerificationGas = - response.result.preVerificationGas ?? userOp.preVerificationGas - const verificationGasLimit = - response.result.verificationGasLimit ?? userOp.verificationGasLimit - const callGasLimit = response.result.callGasLimit ?? userOp.callGasLimit - return { - paymasterAndData: paymasterAndData, - preVerificationGas: preVerificationGas, - verificationGasLimit: verificationGasLimit, - callGasLimit: callGasLimit - } - } - // biome-ignore lint/suspicious/noExplicitAny: caught error is any - } catch (error: any) { - Logger.error( - "Error in generating paymasterAndData - reason: ", - JSON.stringify(error) - ) - throw error - } - throw new Error("Error in generating paymasterAndData") - } - - /** - * - * @param userOp user operation - * @param paymasterServiceData optional extra information to be passed to paymaster service - * @returns "0x" - */ - async getDummyPaymasterAndData( - _userOp: Partial, - _paymasterServiceData?: SponsorUserOperationDto // mode is necessary. partial context of token paymaster or verifying - ): Promise { - return "0x" - } - - public static async create(config: PaymasterConfig): Promise { - return new Paymaster(config) - } -} - -export const toPaymaster = Paymaster.create -export default toPaymaster diff --git a/src/paymaster/index.ts b/src/paymaster/index.ts deleted file mode 100644 index 70cbb99d8..000000000 --- a/src/paymaster/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Paymaster } from "./Paymaster.js" -export * from "./interfaces/IPaymaster.js" -export * from "./interfaces/IHybridPaymaster.js" -export * from "./utils/Types.js" -export * from "./Paymaster.js" - -export const createPaymaster = Paymaster.create diff --git a/src/paymaster/interfaces/IHybridPaymaster.ts b/src/paymaster/interfaces/IHybridPaymaster.ts deleted file mode 100644 index 104029e0a..000000000 --- a/src/paymaster/interfaces/IHybridPaymaster.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { UserOperationStruct } from "../../account" -import type { - BiconomyTokenPaymasterRequest, - Transaction -} from "../../account/utils/Types.js" -import type { - FeeQuotesOrDataDto, - FeeQuotesOrDataResponse, - PaymasterAndDataResponse -} from "../utils/Types.js" -import type { IPaymaster } from "./IPaymaster.js" - -export interface IHybridPaymaster extends IPaymaster { - getPaymasterAndData( - _userOp: Partial, - _paymasterServiceData?: T - ): Promise - getDummyPaymasterAndData( - _userOp: Partial, - _paymasterServiceData?: T - ): Promise - buildTokenApprovalTransaction( - _tokenPaymasterRequest: BiconomyTokenPaymasterRequest - ): Promise - getPaymasterFeeQuotesOrData( - _userOp: Partial, - _paymasterServiceData: FeeQuotesOrDataDto - ): Promise -} diff --git a/src/paymaster/interfaces/IPaymaster.ts b/src/paymaster/interfaces/IPaymaster.ts deleted file mode 100644 index 3aee820b2..000000000 --- a/src/paymaster/interfaces/IPaymaster.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { UserOperationStruct } from "../../account" -import type { PaymasterAndDataResponse } from "../utils/Types.js" - -export interface IPaymaster { - // Implementing class may add extra parameter (for example paymasterServiceData with it's own type) in below function signature - getPaymasterAndData( - _userOp: Partial - ): Promise - getDummyPaymasterAndData( - _userOp: Partial - ): Promise -} diff --git a/src/paymaster/utils/Constants.ts b/src/paymaster/utils/Constants.ts deleted file mode 100644 index a148d17ad..000000000 --- a/src/paymaster/utils/Constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const MAX_UINT256 = - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" -export const ADDRESS_ZERO = "0x0000000000000000000000000000000000000000" -// export const ERC20_PAYMASTER_ADDRESS = '0xE9f6Ffc87cac92bc94f704AE017e85cB83DBe4EC' // likely to be same address on all chains - -export const ERC20_ABI = [ - "function transfer(address to, uint256 value) external returns (bool)", - "function transferFrom(address from, address to, uint256 value) external returns (bool)", - "function approve(address spender, uint256 value) external returns (bool)", - "function allowance(address owner, address spender) external view returns (uint256)", - "function balanceOf(address owner) external view returns (uint256)" -] diff --git a/src/paymaster/utils/Helpers.ts b/src/paymaster/utils/Helpers.ts deleted file mode 100644 index d9aeceea9..000000000 --- a/src/paymaster/utils/Helpers.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @description this function will return current timestamp in seconds - * @returns Number - */ -export const getTimestampInSeconds = (): number => { - return Math.floor(Date.now() / 1000) -} diff --git a/src/paymaster/utils/Types.ts b/src/paymaster/utils/Types.ts deleted file mode 100644 index 25326220f..000000000 --- a/src/paymaster/utils/Types.ts +++ /dev/null @@ -1,123 +0,0 @@ -export type Hex = `0x${string}` -import type { BigNumberish } from "../../account" -import type { JsonRpcError } from "../../bundler/utils/Types" - -export type PaymasterServiceErrorResponse = { - jsonrpc: string - id: number - error: JsonRpcError -} - -export type JsonRpcResponse = { - jsonrpc: string - id: number - // biome-ignore lint/suspicious/noExplicitAny: - result?: any - error?: JsonRpcError -} - -export type PaymasterConfig = { - paymasterUrl: string - strictMode?: boolean -} - -export type SponsorUserOperationDto = { - /** mode: sponsored or erc20 */ - mode: "SPONSORED" | "ERC20" - /** Always recommended, especially when using token paymaster */ - calculateGasLimits?: boolean - /** Expiry duration in seconds */ - expiryDuration?: number - /** Webhooks to be fired after user op is sent */ - webhookData?: Record - /** Smart account meta data */ - smartAccountInfo?: SmartAccountData - /** the fee-paying token address */ - feeTokenAddress?: string -} - -export type FeeQuotesOrDataDto = { - /** mode: sponsored or erc20 */ - mode?: "SPONSORED" | "ERC20" - /** Expiry duration in seconds */ - expiryDuration?: number - /** Always recommended, especially when using token paymaster */ - calculateGasLimits?: boolean - /** List of tokens to be used for fee quotes, if ommitted fees for all supported will be returned */ - tokenList?: string[] - /** preferredToken: Can be ommitted to return all quotes */ - preferredToken?: string - /** Webhooks to be fired after user op is sent */ - // biome-ignore lint/suspicious/noExplicitAny: - webhookData?: Record - /** Smart account meta data */ - smartAccountInfo?: SmartAccountData -} - -export type FeeQuoteParams = { - tokenList?: string[] - preferredToken?: string -} - -export type FeeTokenInfo = { - feeTokenAddress: string -} - -export type SponsorpshipInfo = { - /** Webhooks to be fired after user op is sent */ - // biome-ignore lint/suspicious/noExplicitAny: - webhookData?: Record - /** Smart account meta data */ - smartAccountInfo: SmartAccountData -} - -export type SmartAccountData = { - /** name: Name of the smart account */ - name: string - /** version: Version of the smart account */ - version: string -} - -export type PaymasterFeeQuote = { - /** symbol: Token symbol */ - symbol: string - /** tokenAddress: Token address */ - tokenAddress: string - /** decimal: Token decimal */ - decimal: number - logoUrl?: string - /** maxGasFee: in wei */ - maxGasFee: number - /** maxGasFee: in dollars */ - maxGasFeeUSD?: number - usdPayment?: number - /** The premium paid on the token */ - premiumPercentage: number - /** validUntil: Unix timestamp */ - validUntil?: number -} - -export type FeeQuotesOrDataResponse = { - /** Array of results from the paymaster */ - feeQuotes?: PaymasterFeeQuote[] - /** Normally set to the spender in the proceeding call to send the tx */ - tokenPaymasterAddress?: Hex - /** Relevant Data returned from the paymaster */ - paymasterAndData?: Uint8Array | Hex - /* Gas overhead of this UserOperation */ - preVerificationGas?: BigNumberish - /* Actual gas used by the validation of this UserOperation */ - verificationGasLimit?: BigNumberish - /* Value used by inner account execution */ - callGasLimit?: BigNumberish -} - -export type PaymasterAndDataResponse = { - paymasterAndData: Hex - /* Gas overhead of this UserOperation */ - preVerificationGas: number - /* Actual gas used by the validation of this UserOperation */ - verificationGasLimit: number - /* Value used by inner account execution */ - callGasLimit: number -} diff --git a/tests/account.read.test.ts b/tests/account.read.test.ts deleted file mode 100644 index 1533c32e2..000000000 --- a/tests/account.read.test.ts +++ /dev/null @@ -1,936 +0,0 @@ -import { JsonRpcProvider, ParamType, Wallet, ethers } from "ethers" -import { - http, - type AbiParameter, - type Account, - type Chain, - type Hex, - type WalletClient, - concat, - createPublicClient, - createWalletClient, - encodeAbiParameters, - encodeFunctionData, - encodePacked, - getContract, - hashMessage, - keccak256, - parseAbiParameters, - toBytes, - toHex -} from "viem" -import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" -import { baseSepolia } from "viem/chains" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { K1ValidatorFactoryAbi, NexusAbi } from "../src/__contracts/abi" -import addresses from "../src/__contracts/addresses" -import { - ERROR_MESSAGES, - NATIVE_TOKEN_ALIAS, - type NexusSmartAccount, - type SupportedSigner, - type Transaction, - createSmartAccountClient, - eip1271MagicValue, - getChain, - makeInstallDataAndHash -} from "../src/account" -import { CounterAbi } from "./src/__contracts/abi" -import mockAddresses from "./src/__contracts/mockAddresses" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - checkBalance, - getAccountDomainStructFields, - getBundlerUrl, - getTestAccount, - killNetwork, - pKey, - toTestClient, - topUp -} from "./src/testUtils" -import type { - MasterClient, - NetworkConfig, - NetworkConfigWithBundler -} from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "COMMON_LOCALHOST" - -describe("account.read", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = (await toNetwork(NETWORK_TYPE)) as NetworkConfig - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test.skip("should estimate gas for minting an NFT", async () => { - const encodedCall = encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - const transaction = { - to: mockAddresses.Counter, - data: encodedCall - } - const results = await Promise.all([ - smartAccount.getGasEstimate([transaction]), - smartAccount.getGasEstimate([transaction, transaction]) - ]) - - const increasingGasExpenditure = results.every( - (result, i) => result > (results[i - 1] ?? 0) - ) - - expect(increasingGasExpenditure).toBeTruthy() - }, 60000) - - test.skip("should throw if PrivateKeyAccount is used as signer and rpcUrl is not provided", async () => { - const createSmartAccount = createSmartAccountClient({ - chain, - signer: account as SupportedSigner, - bundlerUrl - }) - - await expect(createSmartAccount).rejects.toThrow( - ERROR_MESSAGES.MISSING_RPC_URL - ) - }, 50000) - - test.skip("should get all modules", async () => { - const modules = smartAccount.getInstalledModules() - if (await smartAccount.isAccountDeployed()) { - expect(modules).resolves - } else { - expect(modules).rejects.toThrow("Account is not deployed") - } - }, 30000) - - test.skip("should check if module is enabled on the smart account", async () => { - const isEnabled = smartAccount.isModuleInstalled({ - type: "validator", - moduleAddress: addresses.K1Validator - }) - if (await smartAccount.isAccountDeployed()) { - expect(isEnabled).resolves.toBeTruthy() - } else { - expect(isEnabled).rejects.toThrow("Account is not deployed") - } - }, 30000) - - test.skip("enable mode", async () => { - const result = makeInstallDataAndHash(account.address, [ - { - moduleType: "validator", - config: account.address - } - ]) - expect(result).toBeTruthy() - }, 30000) - - test.skip("should create a smartAccountClient from an ethers signer", async () => { - const ethersProvider = new JsonRpcProvider(chain.rpcUrls.default.http[0]) - const ethersSigner = new Wallet(pKey, ethersProvider) - - const smartAccount = await createSmartAccountClient({ - chain, - signer: ethersSigner, - bundlerUrl, - rpcUrl: chain.rpcUrls.default.http[0] - }) - }) - - test.skip("should pickup the rpcUrl from viem wallet and ethers", async () => { - const newRpcUrl = "http://localhost:8545" - const defaultRpcUrl = chain.rpcUrls.default.http[0] //http://127.0.0.1:8545" - - const ethersProvider = new JsonRpcProvider(newRpcUrl) - const ethersSignerWithNewRpcUrl = new Wallet(pKey, ethersProvider) - - const originalEthersProvider = new JsonRpcProvider( - chain.rpcUrls.default.http[0] - ) - const ethersSigner = new Wallet(pKey, originalEthersProvider) - - const walletClientWithNewRpcUrl = createWalletClient({ - account, - chain, - transport: http(newRpcUrl) - }) - const [ - smartAccountFromEthersWithNewRpc, - smartAccountFromViemWithNewRpc, - smartAccountFromEthersWithOldRpc, - smartAccountFromViemWithOldRpc - ] = await Promise.all([ - createSmartAccountClient({ - chain, - signer: ethersSignerWithNewRpcUrl, - bundlerUrl: getBundlerUrl(1337), - rpcUrl: newRpcUrl - }), - createSmartAccountClient({ - chain, - signer: walletClientWithNewRpcUrl, - bundlerUrl: getBundlerUrl(1337), - rpcUrl: newRpcUrl - }), - createSmartAccountClient({ - chain, - signer: ethersSigner, - bundlerUrl: getBundlerUrl(1337), - rpcUrl: chain.rpcUrls.default.http[0] - }), - createSmartAccountClient({ - chain, - signer: walletClient, - bundlerUrl: getBundlerUrl(1337), - rpcUrl: chain.rpcUrls.default.http[0] - }) - ]) - - const [ - smartAccountFromEthersWithNewRpcAddress, - smartAccountFromViemWithNewRpcAddress, - smartAccountFromEthersWithOldRpcAddress, - smartAccountFromViemWithOldRpcAddress - ] = await Promise.all([ - smartAccountFromEthersWithNewRpc.getAccountAddress(), - smartAccountFromViemWithNewRpc.getAccountAddress(), - smartAccountFromEthersWithOldRpc.getAccountAddress(), - smartAccountFromViemWithOldRpc.getAccountAddress() - ]) - - expect( - [ - smartAccountFromEthersWithNewRpcAddress, - smartAccountFromViemWithNewRpcAddress, - smartAccountFromEthersWithOldRpcAddress, - smartAccountFromViemWithOldRpcAddress - ].every(Boolean) - ).toBeTruthy() - - expect(smartAccountFromEthersWithNewRpc.publicClient.transport.url).toBe( - newRpcUrl - ) - expect(smartAccountFromViemWithNewRpc.publicClient.transport.url).toBe( - newRpcUrl - ) - expect(smartAccountFromEthersWithOldRpc.publicClient.transport.url).toBe( - defaultRpcUrl - ) - expect(smartAccountFromViemWithOldRpc.publicClient.transport.url).toBe( - defaultRpcUrl - ) - }) - - test.skip("should read estimated user op gas values", async () => { - const tx = { - to: recipientAccount.address, - data: "0x" - } - - const userOp = await smartAccount.buildUserOp([tx]) - - const estimatedGas = await smartAccount.estimateUserOpGas(userOp) - expect(estimatedGas.maxFeePerGas).toBeTruthy() - expect(estimatedGas.maxPriorityFeePerGas).toBeTruthy() - expect(estimatedGas.verificationGasLimit).toBeTruthy() - expect(estimatedGas.callGasLimit).toBeTruthy() - expect(estimatedGas.preVerificationGas).toBeTruthy() - }, 30000) - - test.skip("should have an active validation module", async () => { - const module = smartAccount.activeValidationModule - expect(module).toBeTruthy() - }) - - // @note Ignored untill we implement Paymaster - // test.skip( - // "should create a smart account with paymaster by creating instance", - // async () => { - // const paymaster = new Paymaster({ paymasterUrl }) - - // const smartAccount = await createSmartAccountClient({ - // signer: walletClient, - // bundlerUrl, - // paymaster - // }) - // expect(smartAccount.paymaster).not.toBeNull() - // expect(smartAccount.paymaster).not.toBeUndefined() - // } - // ) - - test.skip("should fail to create a smartAccountClient from a walletClient without an account", async () => { - const viemWalletNoAccount = createWalletClient({ - transport: http(chain.rpcUrls.default.http[0]) - }) - - expect(async () => - createSmartAccountClient({ - chain, - signer: viemWalletNoAccount, - bundlerUrl, - rpcUrl: chain.rpcUrls.default.http[0] - }) - ).rejects.toThrow("Cannot consume a viem wallet without an account") - }) - - // test.skip( - // "should create a smart account with paymaster with an api key", - // async () => { - // const paymaster = smartAccount.paymaster - // expect(paymaster).not.toBeNull() - // expect(paymaster).not.toBeUndefined() - // } - // ) - - // test.skip("should not throw and error, chain ids match", async () => { - // const mockBundlerUrl = - // "https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44" - // const mockPaymasterUrl = - // "https://paymaster.biconomy.io/api/v1/84532/-RObQRX9ei.fc6918eb-c582-4417-9d5a-0507b17cfe71" - - // const config: NexusSmartAccountConfig = { - // signer: walletClient, - // bundlerUrl: mockBundlerUrl, - // paymasterUrl: mockPaymasterUrl - // } - - // await expect( - // compareChainIds(walletClient, config, false) - // ).resolves.not.toThrow() - // }) - - // test.skip( - // "should throw and error, bundlerUrl chain id and paymaster url chain id does not match with validation module", - // async () => { - // const mockPaymasterUrl = - // "https://paymaster.biconomy.io/api/v1/1337/-RObQRX9ei.fc6918eb-c582-4417-9d5a-0507b17cfe71" - - // const k1ValidationModule = await createK1ValidatorModule( - // smartAccount.getSigner() - // ) - - // const config: NexusSmartAccountConfig = { - // chain, - // defaultValidationModule: k1ValidationModule, - // activeValidationModule: k1ValidationModule, - // bundlerUrl, - // paymasterUrl: mockPaymasterUrl - // } - - // } - // ) - - // test.skip( - // "should throw and error, signer has chain id (56) and paymasterUrl has chain id (11155111)", - // async () => { - // const mockPaymasterUrl = - // "https://paymaster.biconomy.io/api/v1/11155111/-RObQRX9ei.fc6918eb-c582-4417-9d5a-0507b17cfe71" - - // const walletClientBsc = createWalletClient({ - // account: walletClient.account, - // chain: bsc, - // transport: http(bsc.rpcUrls.default.http[0]) - // }) - - // const config: NexusSmartAccountConfig = { - // chain, - // signer: walletClientBsc, - // bundlerUrl, - // paymasterUrl: mockPaymasterUrl - // } - - // } - // ) - - test.skip("should return chain object for chain id 1", async () => { - const chainId = 1 - const chain = getChain(chainId) - expect(chain.id).toBe(chainId) - }) - - test.skip("should have correct fields", async () => { - const chainId = 1 - const chain = getChain(chainId) - ;[ - "blockExplorers", - "contracts", - "fees", - "formatters", - "id", - "name", - "nativeCurrency", - "rpcUrls", - "serializers" - ].every((field) => { - expect(chain).toHaveProperty(field) - }) - }) - - test.skip("should throw an error, chain id not found", async () => { - const chainId = 0 - expect(() => getChain(chainId)).toThrow(ERROR_MESSAGES.CHAIN_NOT_FOUND) - }) - - test.skip("should have matching counterFactual address from the contracts with smartAccount.getAddress()", async () => { - const client = createWalletClient({ - account, - chain, - transport: http() - }) - - const smartAccount = await createSmartAccountClient({ - chain, - signer: client, - bundlerUrl - }) - - const smartAccountAddressFromSDK = await smartAccount.getAccountAddress() - - const publicClient = createPublicClient({ - chain, - transport: http() - }) - - const factoryContract = getContract({ - address: addresses.K1ValidatorFactory, - abi: K1ValidatorFactoryAbi, - client: { public: publicClient, wallet: client } - }) - - const smartAccountAddressFromContracts = - await factoryContract.read.computeAccountAddress([ - await smartAccount.getSmartAccountOwner().getAddress(), - BigInt(0), - [], - 0 - ]) - - expect(smartAccountAddressFromSDK).toBe(smartAccountAddressFromContracts) - }) - - test.skip("should be deployed to counterfactual address", async () => { - const accountAddress = await smartAccount.getAccountAddress() - const byteCode = await testClient.getBytecode({ - address: accountAddress as Hex - }) - if (await smartAccount.isAccountDeployed()) { - expect(byteCode?.length).toBeGreaterThan(2) - } else { - expect(byteCode?.length).toBe(undefined) - } - }, 10000) - - test.skip("should check if ecdsaOwnershipModule is enabled", async () => { - const ecdsaOwnershipModule = addresses.K1Validator - - expect(ecdsaOwnershipModule).toBe( - smartAccount.activeValidationModule.getAddress() - ) - }) - - test.skip("should fail to deploy a smart account if no native token balance or paymaster", async () => { - const newPrivateKey = generatePrivateKey() - const newAccount = privateKeyToAccount(newPrivateKey) - - const newViemWallet = createWalletClient({ - account: newAccount, - chain, - transport: http() - }) - - const smartAccount = await createSmartAccountClient({ - chain, - signer: newViemWallet, - bundlerUrl - }) - - expect(async () => smartAccount.deploy()).rejects.toThrow( - ERROR_MESSAGES.NO_NATIVE_TOKEN_BALANCE_DURING_DEPLOY - ) - }) - - test.skip("should fail to deploy a smart account if already deployed", async () => { - if (await smartAccount.isAccountDeployed()) { - expect(async () => smartAccount.deploy()).rejects.toThrow( - ERROR_MESSAGES.ACCOUNT_ALREADY_DEPLOYED - ) - } else { - expect(smartAccount.deploy()).resolves - } - }, 60000) - - test.skip("should fetch balances for smartAccount", async () => { - const token = "0x69835C1f31ed0721A05d5711C1d669C10802a3E1" - const tokenBalanceBefore = await checkBalance( - testClient, - smartAccountAddress, - token - ) - const [tokenBalanceFromSmartAccount] = await smartAccount.getBalances([ - token - ]) - - expect(tokenBalanceBefore).toBe(tokenBalanceFromSmartAccount.amount) - }) - - test.skip("should error if no recipient exists", async () => { - const token: Hex = "0x69835C1f31ed0721A05d5711C1d669C10802a3E1" - - const txs = [ - { address: token, amount: BigInt(1), recipient: account.address }, - { address: NATIVE_TOKEN_ALIAS, amount: BigInt(1) } - ] - - expect(async () => smartAccount.withdraw(txs)).rejects.toThrow( - ERROR_MESSAGES.NO_RECIPIENT - ) - }) - - test.skip("should error when withdraw all of native token is attempted without an amount explicitly set", async () => { - expect(async () => - smartAccount.withdraw(null, account.address) - ).rejects.toThrow(ERROR_MESSAGES.NATIVE_TOKEN_WITHDRAWAL_WITHOUT_AMOUNT) - }, 6000) - - test.skip("should check native token balance and more token info for smartAccount", async () => { - const [ethBalanceFromSmartAccount] = await smartAccount.getBalances() - - expect(ethBalanceFromSmartAccount.amount).toBeGreaterThan(0n) - expect(ethBalanceFromSmartAccount.address).toBe(NATIVE_TOKEN_ALIAS) - expect(ethBalanceFromSmartAccount.chainId).toBe(chain.id) - expect(ethBalanceFromSmartAccount.decimals).toBe(18) - }, 60000) - - // @note Skip until we implement the Paymaster - // test.skip( - // "should check balance of supported token", - // async () => { - // const tokens = await smartAccount.getSupportedTokens() - // const [firstToken] = tokens - - // expect(tokens.length).toBeGreaterThan(0) - // expect(tokens[0]).toHaveProperty("balance") - // expect(firstToken.balance.amount).toBeGreaterThanOrEqual(0n) - // }, - // 60000 - // ) - - // @note Nexus SA signature needs to contain the validator module address in the first 20 bytes - test.skip("should test isValidSignature PersonalSign to be valid", async () => { - if (await smartAccount.isAccountDeployed()) { - const data = hashMessage("0x1234") - - // Define constants as per the original Solidity function - const DOMAIN_NAME = "Nexus" - const DOMAIN_VERSION = "1.0.0-beta" - const DOMAIN_TYPEHASH = - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - const PARENT_TYPEHASH = "PersonalSign(bytes prefixed)" - const chainId = baseSepolia.id - - // Calculate the domain separator - const domainSeparator = keccak256( - encodeAbiParameters( - parseAbiParameters("bytes32, bytes32, bytes32, uint256, address"), - [ - keccak256(toBytes(DOMAIN_TYPEHASH)), - keccak256(toBytes(DOMAIN_NAME)), - keccak256(toBytes(DOMAIN_VERSION)), - BigInt(chainId), - smartAccountAddress - ] - ) - ) - - // Calculate the parent struct hash - const parentStructHash = keccak256( - encodeAbiParameters(parseAbiParameters("bytes32, bytes32"), [ - keccak256(toBytes(PARENT_TYPEHASH)), - hashMessage(data) - ]) - ) - - // Calculate the final hash - const resultHash: Hex = keccak256( - concat(["0x1901", domainSeparator, parentStructHash]) - ) - - const signature = await smartAccount.signMessage(resultHash) - - const contractResponse = await testClient.readContract({ - address: await smartAccount.getAddress(), - abi: NexusAbi, - functionName: "isValidSignature", - args: [hashMessage(data), signature] - }) - - const viemResponse = await testClient.verifyMessage({ - address: smartAccountAddress, - message: data, - signature - }) - - expect(contractResponse).toBe(eip1271MagicValue) - expect(viemResponse).toBe(true) - } - }) - - test.skip("should test isValidSignature EIP712Sign to be valid", async () => { - if (await smartAccount.isAccountDeployed()) { - const data = keccak256("0x1234") - - // Define constants as per the original Solidity function - const DOMAIN_NAME = "Nexus" - const DOMAIN_VERSION = "1.0.0-beta" - const DOMAIN_TYPEHASH = - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - const PARENT_TYPEHASH = - "TypedDataSign(Contents contents,bytes1 fields,string name,string version,uint256 chainId,address verifyingContract,bytes32 salt,uint256[] extensions) Contents(bytes32 stuff)" - const chainId = baseSepolia.id - - // Calculate the domain separator - const domainSeparator = keccak256( - encodeAbiParameters( - parseAbiParameters("bytes32, bytes32, bytes32, uint256, address"), - [ - keccak256(toBytes(DOMAIN_TYPEHASH)), - keccak256(toBytes(DOMAIN_NAME)), - keccak256(toBytes(DOMAIN_VERSION)), - BigInt(chainId), - smartAccountAddress - ] - ) - ) - - const encodedAccountDomainStructFields = - await getAccountDomainStructFields(testClient, smartAccountAddress) - - // Calculate the parent struct hash - const parentStructHash = keccak256( - encodePacked( - ["bytes", "bytes"], - [ - encodeAbiParameters(parseAbiParameters("bytes32, bytes32"), [ - keccak256(toBytes(PARENT_TYPEHASH)), - hashMessage(data) - ]), - encodedAccountDomainStructFields - ] - ) - ) - - const dataToSign: Hex = keccak256( - concat(["0x1901" as Hex, domainSeparator, parentStructHash]) - ) - - let signature = await smartAccount.signMessage(dataToSign) - const contentsType: Hex = toHex("Contents(bytes32 stuff)") - signature = encodePacked( - ["bytes", "bytes", "bytes", "bytes", "uint"], - [ - signature, - domainSeparator, - hashMessage(data), - contentsType, - BigInt(contentsType.length) - ] - ) - - const finalSignature = encodePacked( - ["address", "bytes"], - [smartAccount.activeValidationModule.moduleAddress, signature] - ) - - const contents = keccak256( - encodePacked( - ["bytes", "bytes", "bytes"], - ["0x1901", domainSeparator, hashMessage(data)] - ) - ) - - const contractResponse = await testClient.readContract({ - address: await smartAccount.getAddress(), - abi: NexusAbi, - functionName: "isValidSignature", - args: [contents, finalSignature] - }) - - const viemResponse = await testClient.verifyMessage({ - address: smartAccountAddress, - message: data, - signature: finalSignature - }) - - expect(contractResponse).toBe(eip1271MagicValue) - expect(viemResponse).toBe(true) - } - }) - - test("should have consistent behaviour between ethers.AbiCoder.defaultAbiCoder() and viem.encodeAbiParameters()", async () => { - const expectedResult = - "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b906000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - - const Executions = ParamType.from({ - type: "tuple(address,uint256,bytes)[]", - baseType: "tuple", - name: "executions", - arrayLength: null, - components: [ - { name: "target", type: "address" }, - { name: "value", type: "uint256" }, - { name: "callData", type: "bytes" } - ] - }) - - const viemExecutions: AbiParameter = { - type: "tuple[]", - components: [ - { name: "target", type: "address" }, - { name: "value", type: "uint256" }, - { name: "callData", type: "bytes" } - ] - } - - const txs = [ - { - target: "0x90F79bf6EB2c4f870365E785982E1f101E93b906", - callData: "0x", - value: 1n - }, - { - target: "0x90F79bf6EB2c4f870365E785982E1f101E93b906", - callData: "0x", - value: 1n - } - ] - - const executionCalldataPrepWithEthers = - ethers.AbiCoder.defaultAbiCoder().encode([Executions], [txs]) - - const executionCalldataPrepWithViem = encodeAbiParameters( - [viemExecutions], - [txs] - ) - - expect(executionCalldataPrepWithEthers).toBe(expectedResult) - expect(executionCalldataPrepWithViem).toBe(expectedResult) - }) - - // test.skip("should call isValidSignature for deployed smart account", async () => { - // const smartAccount = await createSmartAccountClient({ - // signer: walletClient, - // bundlerUrl - // }) - - // const message = "hello world" - // const signature = await smartAccount.signMessage(message) - - // const isVerified = await publicClient.readContract({ - // address: await smartAccount.getAddress(), - // abi: NexusAccountAbi, - // functionName: "isValidSignature", - // args: [hashMessage(message), signature] - // }) - - // expect(isVerified).toBe(eip1271MagicValue) - // }) - - // test.skip("should verifySignature of not deployed", async () => { - // const undeployedSmartAccount = await createSmartAccountClient({ - // signer: walletClient, - // bundlerUrl, - // index: 99n - // }) - // const isDeployed = await undeployedSmartAccount.isAccountDeployed() - // if (!isDeployed) { - // const message = "hello world" - - // const signature = await smartAccount.signMessage(message) - // // OR - // // const signature = await smartAccount.signMessageWith6492(message) - - // const isVerified = await publicClient.readContract({ - // address: await smartAccount.getAddress(), - // abi: NexusAccountAbi, - // functionName: "isValidSignature", - // args: [hashMessage(message), signature] - // }) - - // expect(isVerified).toBe(eip1271MagicValue) - // } - // }) - - // test.skip("should verifySignature using viem", async () => { - // const isDeployed = await smartAccount.isAccountDeployed() - // if (isDeployed) { - // const message = "0x123" - - // const signature = await smartAccount.signMessage(message) - - // console.log(signature, 'signature'); - // console.log(hashMessage(message), 'hashMessage(message)'); - - // // const isVerified = await verifyMessage(publicClient, { - // // address: await smartAccount.getAddress(), - // // message, - // // signature, - // // }) - - // const isVerified = await publicClient.readContract({ - // address: await smartAccount.getAddress(), - // abi: NexusAccountAbi, - // functionName: "isValidSignature", - // args: [hashMessage(message), signature] - // }) - - // console.log(isVerified, "isVerified"); - - // expect(isVerified).toBe(eip1271MagicValue) - // } - // }) - - // @note Removed untill we implement the Bundler (Pimlico's bundler does no behave as expected in this test) - // test.skip( - // "should simulate a user operation execution, expecting to fail", - // async () => { - // const smartAccount = await createSmartAccountClient({ - // signer: walletClient, - // bundlerUrl - // }) - - // const balances = await smartAccount.getBalances() - // expect(balances[0].amount).toBeGreaterThan(0n) - - // const encodedCall = encodeFunctionData({ - // abi: parseAbi(["function deposit()"]), - // functionName: "deposit" - // }) - - // const amoyTestContract = "0x59Dbe91FBa486CA10E4ad589688Fe547a48bd62A" - - // // fail if value is not bigger than 1 - // // the contract call requires a deposit of at least 1 wei - // const tx1 = { - // to: amoyTestContract as Hex, - // data: encodedCall, - // value: 0 - // } - // const tx2 = { - // to: amoyTestContract as Hex, - // data: encodedCall, - // value: 2 - // } - - // await expect(smartAccount.buildUserOp([tx1, tx2])).rejects.toThrow() - // } - // ) - - // @note Removed untill we implement the Bundler (Pimlico's bundler does no behave as expected in this test) - // test.skip( - // "should simulate a user operation execution, expecting to pass execution", - // async () => { - // const smartAccount = await createSmartAccountClient({ - // signer: walletClient, - // bundlerUrl - // }) - - // const balances = await smartAccount.getBalances() - // expect(balances[0].amount).toBeGreaterThan(0n) - - // const encodedCall = encodeFunctionData({ - // abi: parseAbi(["function deposit()"]), - // functionName: "deposit" - // }) - - // const amoyTestContract = "0x59Dbe91FBa486CA10E4ad589688Fe547a48bd62A" - - // // fail if value is not bigger than 1 - // // the contract call requires a deposit of at least 1 wei - // const tx1 = { - // to: amoyTestContract as Hex, - // data: encodedCall, - // value: 2 - // } - // const tx2 = { - // to: amoyTestContract as Hex, - // data: encodedCall, - // value: 2 - // } - - // await expect(smartAccount.buildUserOp([tx1, tx2])).resolves.toBeTruthy() - // } - // ) - - // test.skip("Should verify supported modes", async () => { - // expect( - // await smartAccount.supportsExecutionMode(ACCOUNT_MODES.DEFAULT_SINGLE) - // ).to.be.true - // expect( - // await smartAccount.supportsExecutionMode(ACCOUNT_MODES.DEFAULT_BATCH) - // ).to.be.true - // expect(await smartAccount.supportsExecutionMode(ACCOUNT_MODES.TRY_BATCH)).to - // .be.true - // expect(await smartAccount.supportsExecutionMode(ACCOUNT_MODES.TRY_SINGLE)) - // .to.be.true - - // expect( - // await smartAccount.supportsExecutionMode(ACCOUNT_MODES.DELEGATE_SINGLE) - // ).to.be.false - // }) -}) diff --git a/tests/account.write.test.ts b/tests/account.write.test.ts deleted file mode 100644 index 065ff4dec..000000000 --- a/tests/account.write.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("account.write", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - test("should send eth twice", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction([tx, tx]) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(2n) - }) - - // test("install a mock Hook module", async () => { - // const isSupported = await smartAccount.supportsModule(ModuleType.Hook) - // console.log(isSupported, "is supported") - - // const isInstalledBefore = await smartAccount.isModuleInstalled( - // ModuleType.Hook, - // MOCK_HOOK - // ) - // console.log(isInstalledBefore, "is installed before") - - // const userOpReceipt = await smartAccount.installModule(MOCK_HOOK, ModuleType.Hook) - // console.log(userOpReceipt, "user op receipt") - - // const isInstalled = await smartAccount.isModuleInstalled( - // ModuleType.Hook, - // MOCK_HOOK - // ) - - // expect(userOpReceipt.success).toBe(true) - // expect(isInstalled).toBeTruthy() - // }, 60000) - - // test("get active hook", async () => { - // const activeHook: Address = await smartAccount.getActiveHook() - // console.log(activeHook, "active hook") - // expect(activeHook).toBe(MOCK_HOOK) - // }, 60000) - - // test("uninstall hook module", async () => { - // const prevAddress: Hex = "0x0000000000000000000000000000000000000001" - // const deInitData = encodeAbiParameters( - // [ - // { name: "prev", type: "address" }, - // { name: "disableModuleData", type: "bytes" } - // ], - // [prevAddress, toHex(stringToBytes(""))] - // ) - // const userOpReceipt = await smartAccount.uninstallModule(MOCK_HOOK, ModuleType.Hook, deInitData) - - // const isInstalled = await smartAccount.isModuleInstalled( - // ModuleType.Hook, - // MOCK_HOOK - // ) - - // expect(userOpReceipt.success).toBe(true) - // expect(isInstalled).toBeFalsy() - // expect(userOpReceipt).toBeTruthy() - // }, 60000) - - // test("install a fallback handler Hook module", async () => { - // const isSupported = await smartAccount.supportsModule(ModuleType.Fallback) - // console.log(isSupported, "is supported") - - // const isInstalledBefore = await smartAccount.isModuleInstalled( - // ModuleType.Fallback, - // MOCK_FALLBACK_HANDLER, - // ethers.AbiCoder.defaultAbiCoder().encode( - // ["bytes4"], - // [GENERIC_FALLBACK_SELECTOR as Hex] - // ) as Hex - // ) - // console.log(isInstalledBefore, "is installed before") - - // const userOpReceipt = await smartAccount.installModule(MOCK_FALLBACK_HANDLER, ModuleType.Fallback, ethers.AbiCoder.defaultAbiCoder().encode( - // ["bytes4"], - // [GENERIC_FALLBACK_SELECTOR as Hex] - // ) as Hex) - - // const isInstalled = await smartAccount.isModuleInstalled( - // ModuleType.Fallback, - // MOCK_FALLBACK_HANDLER, - // ethers.AbiCoder.defaultAbiCoder().encode( - // ["bytes4"], - // [GENERIC_FALLBACK_SELECTOR as Hex] - // ) as Hex - // ) - - // expect(userOpReceipt.success).toBe(true) - // expect(isInstalled).toBeTruthy() - // }, 60000) - - // test("uninstall handler module", async () => { - // const prevAddress: Hex = "0x0000000000000000000000000000000000000001" - // const deInitData = ethers.AbiCoder.defaultAbiCoder().encode( - // ["bytes4"], - // [GENERIC_FALLBACK_SELECTOR as Hex] - // ) as Hex - // const userOpReceipt = await smartAccount.uninstallModule( - // MOCK_FALLBACK_HANDLER, - // ModuleType.Fallback, - // deInitData - // ) - - // const isInstalled = await smartAccount.isModuleInstalled( - // ModuleType.Fallback, - // MOCK_FALLBACK_HANDLER, - // ethers.AbiCoder.defaultAbiCoder().encode( - // ["bytes4"], - // [GENERIC_FALLBACK_SELECTOR as Hex] - // ) as Hex - // ) - - // expect(userOpReceipt.success).toBe(true) - // expect(isInstalled).toBeFalsy() - // expect(userOpReceipt).toBeTruthy() - // }, 60000) -}) diff --git a/tests/biconomy.test.ts b/tests/biconomy.test.ts deleted file mode 100644 index a6250a0c7..000000000 --- a/tests/biconomy.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - http, - type Account, - type Address, - type Chain, - type WalletClient, - createWalletClient, - isHex -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import contracts from "../src/__contracts" -import { - type BiconomyClient, - createBiconomyClient -} from "../src/clients/biconomy" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { getTestAccount, killNetwork, toTestClient } from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("biconomy", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let biconomyAccountAddress: Address - let biconomy: BiconomyClient - - beforeAll(async () => { - network = (await toNetwork(NETWORK_TYPE)) as NetworkConfig - - chain = network.chain - bundlerUrl = network.bundlerUrl - account = getTestAccount(0) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - biconomy = await createBiconomyClient({ - owner: account, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - biconomyAccountAddress = await biconomy.account.getCounterFactualAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should check balances and top up relevant addresses", async () => { - const [ownerBalance, smartAccountBalance] = await Promise.all([ - testClient.getBalance({ - address: account.address - }), - testClient.getBalance({ - address: biconomyAccountAddress - }) - ]) - const balancesAreOfCorrectType = [ownerBalance, smartAccountBalance].every( - (balance) => typeof balance === "bigint" - ) - - if (smartAccountBalance < 100000000000000000n) { - const hash = await walletClient.sendTransaction({ - chain, - account, - to: biconomyAccountAddress, - value: 100000000000000000n - }) - await testClient.waitForTransactionReceipt({ hash }) - } - - const smartAccountBalanceAfterTopUp = await testClient.getBalance({ - address: biconomyAccountAddress - }) - - expect(balancesAreOfCorrectType).toBeTruthy() - }) - - test("should have a working bundler client", async () => { - const chainId = await biconomy.getChainId() - expect(chainId).toEqual(chain.id) - - const supportedEntrypoints = await biconomy.getSupportedEntryPoints() - expect(supportedEntrypoints).to.include(contracts.entryPoint.address) - }) - - test("should send a user operation", async () => { - const calls = [{ to: account.address, value: 1n }] - const feeData = await testClient.estimateFeesPerGas() - - const gas = { - maxFeePerGas: feeData.maxFeePerGas * 2n, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n - } - - const hash = await biconomy.sendUserOperation({ calls, ...gas }) - const receipt = await biconomy.waitForUserOperationReceipt({ hash }) - - expect(receipt.success).toBeTruthy() - }) - - test("should send a userop using the biconomy stack", async () => { - const calls = [{ to: account.address, value: 1n }] - - const hash = await biconomy.sendTransaction({ calls }) - - const receipt = await testClient.waitForTransactionReceipt({ hash }) - - console.log({ receipt }) - - expect(receipt.status).toEqual("success") - }) - - test("should call some erc7579 actions", async () => { - expect(biconomy.isModuleInstalled).toBeTruthy() - const supportsExecutionMode = await biconomy.supportsExecutionMode({ - type: "delegatecall", - // biome-ignore lint/style/noNonNullAssertion: - account: biconomy.account! - }) - console.log({ supportsExecutionMode }) - }) - - test("should produce the same results", async () => { - const nexusInitCode = biconomy.account.getInitCode() - expect(nexusInitCode).toBeTruthy() - - const nexusFactoryData = biconomy.account.factoryData - expect(isHex(nexusFactoryData)).toBeTruthy() - - const nexusIsDeployed = await biconomy.account.isDeployed() - expect(nexusIsDeployed).toBeTruthy() - }) -}) diff --git a/tests/modules.k1Validator.write.test.ts b/tests/modules.k1Validator.write.test.ts deleted file mode 100644 index 7995e8456..000000000 --- a/tests/modules.k1Validator.write.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient, - encodeFunctionData, - encodePacked -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import addresses from "../src/__contracts/addresses" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { CounterAbi } from "./src/__contracts/abi" -import { mockAddresses } from "./src/__contracts/mockAddresses" -import { OWNABLE_VALIDATOR } from "./src/callDatas" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("modules.k1Validator.write", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - test("should install k1 Validator with 1 owner", async () => { - const isInstalledBefore = await smartAccount.isModuleInstalled({ - type: "validator", - moduleAddress: addresses.K1Validator - }) - - if (!isInstalledBefore) { - const { wait } = await smartAccount.installModule({ - moduleAddress: addresses.K1Validator, - type: "validator", - data: encodePacked(["address"], [await smartAccount.getAddress()]) - }) - - const { success: installSuccess } = await wait() - expect(installSuccess).toBe(true) - - const { wait: uninstallWait } = await smartAccount.uninstallModule({ - moduleAddress: addresses.K1Validator, - type: "validator", - data: encodePacked(["address"], [await smartAccount.getAddress()]) - }) - const { success: uninstallSuccess } = await uninstallWait() - expect(uninstallSuccess).toBe(true) - } - }, 60000) - - test.skip("should have the Ownable Validator Module installed", async () => { - const isInstalled = await smartAccount.isModuleInstalled({ - type: "validator", - moduleAddress: OWNABLE_VALIDATOR - }) - expect(isInstalled).toBeTruthy() - }, 60000) - - test("should perform a contract interaction", async () => { - const encodedCall = encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - - const transaction = { - to: mockAddresses.Counter, - data: encodedCall - } - - const response = await smartAccount.sendTransaction([transaction]) - const receipt = await response.wait() - - expect(receipt.success).toBe(true) - }, 60000) -}) diff --git a/tests/modules.ownableExecutor.read.test.ts b/tests/modules.ownableExecutor.read.test.ts deleted file mode 100644 index 0b0262d47..000000000 --- a/tests/modules.ownableExecutor.read.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("modules.ownable.executor.read", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - // test.skip("should initialize Ownable Executor Module with correct owners", async () => { - // const ownableExecutorModule = await createOwnableExecutorModule( - // smartAccount, - // OWNABLE_EXECUTOR - // ) - // const owners = await ownableExecutorModule.getOwners(OWNABLE_EXECUTOR) - // expect(owners).toStrictEqual(ownableExecutorModule.owners) - // }) -}) diff --git a/tests/modules.ownableExecutor.write.test.ts b/tests/modules.ownableExecutor.write.test.ts deleted file mode 100644 index 415221823..000000000 --- a/tests/modules.ownableExecutor.write.test.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("modules.ownable.executor.write", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - // test.skip("install Ownable Executor", async () => { - // let isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // const receipt = await smartAccount.installModule({ - // moduleAddress: ownableExecutorModule.moduleAddress, - // type: ownableExecutorModule.type, - // data: ownableExecutorModule.data - // }) - - // smartAccount.setActiveExecutionModule(ownableExecutorModule) - - // expect(receipt.success).toBe(true) - // } - // }, 60000) - - // test.skip("uninstall Ownable Executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR) - - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (isInstalled) { - // await smartAccount2.uninstallModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // }, 60000) - - // test.skip("Ownable Executor Module should be installed", async () => { - // const isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - // console.log(isInstalled, "isInstalled") - // expect(isInstalled).toBeTruthy() - // }, 60000) - - // test.skip("should add an owner to the module", async () => { - // const ownersBefore = await ownableExecutorModule.getOwners() - // const isOwnerBefore = ownersBefore.includes(accountTwo.address) - - // if (isOwnerBefore) { - // console.log("Owner already exists in list, skipping test case ...") - // return - // } - - // const userOpReceipt = await ownableExecutorModule.addOwner( - // accountTwo.address - // ) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeTruthy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("EOA 2 can execute actions on behalf of SA 1", async () => { - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - // expect(isOwner).toBeTruthy() - - // const balanceBefore = await smartAccount.getBalances([token]) - // console.log("balanceBefore", balanceBefore) - - // const calldata = encodeFunctionData({ - // abi: parseAbi([ - // "function executeOnOwnedAccount(address ownedAccount, bytes callData)" - // ]), - // functionName: "executeOnOwnedAccount", - // args: [ - // await smartAccount.getAddress(), - // encodePacked( - // ["address", "uint256", "bytes"], - // [token, BigInt(Number(0)), transferEncodedCall] - // ) - // ] - // }) - - // // EOA 2 (walletClientTwo) executes an action on behalf of SA 1 which is owned by EOA 1 (walletClientOne) - // const txHash = await walletClientTwo.sendTransaction({ - // account: accountTwo, // Called by delegated EOA owner - // to: ownableExecutorModule.moduleAddress, - // data: calldata, - // value: 0n - // }) - - // const balanceAfter = await smartAccount.getBalances([token]) - // console.log("balanceAfter", balanceAfter) - - // expect(txHash).toBeTruthy() - // }, 60000) - - // test("SA 2 can execute actions on behalf of SA 1", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // to: token, - // data: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - // const receipt = await smartAccount2.sendTransactionWithExecutor([transferTransaction], await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("SA 2 can execute actions on behalf of SA 1 using module instance instead of smart account instance", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR, initData) - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule2.getOwners() - - // // check if SA 2 is as an owner of SA 1 - // const isOwner = owners.includes(await smartAccount2.getAddress()) - // if(!isOwner) { - // const userOpReceipt = await ownableExecutorModule2.addOwner( - // await smartAccount2.getAddress() - // ) - // expect(userOpReceipt.success).toBeTruthy() - // } - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule2) - // // SA 2 will execute the transferTransaction on behalf of SA 1 (smartAccount) - // const receipt = await ownableExecutorModule2.execute(transferTransaction, await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("should remove an owner from the module", async () => { - // const userOpReceipt = await ownableExecutorModule.removeOwner( - // accountTwo.address - // ) - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeFalsy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("should use rhinestone to call ownable executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - - // const ownableExecutorModule2 = getOwnableExecuter({ - // owner: await smartAccount2.getAddress(), - // }); - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // module: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // module: ownableExecutorModule2.module, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.initData - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // const execution = getExecuteOnOwnedAccountAction({ownedAccount: await smartAccount.getAddress(), execution: transferTransaction}) - // const receipt = await smartAccount2.sendTransaction([{to: execution.target, data: execution.callData, value: 0n}]); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // }, 60000) -}) diff --git a/tests/modules.ownableValidator.install.write.test.ts b/tests/modules.ownableValidator.install.write.test.ts deleted file mode 100644 index 680b4ca87..000000000 --- a/tests/modules.ownableValidator.install.write.test.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("modules.ownable.validator.install.write", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - // test.skip("install Ownable Executor", async () => { - // let isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // const receipt = await smartAccount.installModule({ - // moduleAddress: ownableExecutorModule.moduleAddress, - // type: ownableExecutorModule.type, - // data: ownableExecutorModule.data - // }) - - // smartAccount.setActiveExecutionModule(ownableExecutorModule) - - // expect(receipt.success).toBe(true) - // } - // }, 60000) - - // test.skip("uninstall Ownable Executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR) - - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (isInstalled) { - // await smartAccount2.uninstallModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // }, 60000) - - // test.skip("Ownable Executor Module should be installed", async () => { - // const isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - // console.log(isInstalled, "isInstalled") - // expect(isInstalled).toBeTruthy() - // }, 60000) - - // test.skip("should add an owner to the module", async () => { - // const ownersBefore = await ownableExecutorModule.getOwners() - // const isOwnerBefore = ownersBefore.includes(accountTwo.address) - - // if (isOwnerBefore) { - // console.log("Owner already exists in list, skipping test case ...") - // return - // } - - // const userOpReceipt = await ownableExecutorModule.addOwner( - // accountTwo.address - // ) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeTruthy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("EOA 2 can execute actions on behalf of SA 1", async () => { - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - // expect(isOwner).toBeTruthy() - - // const balanceBefore = await smartAccount.getBalances([token]) - // console.log("balanceBefore", balanceBefore) - - // const calldata = encodeFunctionData({ - // abi: parseAbi([ - // "function executeOnOwnedAccount(address ownedAccount, bytes callData)" - // ]), - // functionName: "executeOnOwnedAccount", - // args: [ - // await smartAccount.getAddress(), - // encodePacked( - // ["address", "uint256", "bytes"], - // [token, BigInt(Number(0)), transferEncodedCall] - // ) - // ] - // }) - - // // EOA 2 (walletClientTwo) executes an action on behalf of SA 1 which is owned by EOA 1 (walletClientOne) - // const txHash = await walletClientTwo.sendTransaction({ - // account: accountTwo, // Called by delegated EOA owner - // to: ownableExecutorModule.moduleAddress, - // data: calldata, - // value: 0n - // }) - - // const balanceAfter = await smartAccount.getBalances([token]) - // console.log("balanceAfter", balanceAfter) - - // expect(txHash).toBeTruthy() - // }, 60000) - - // test("SA 2 can execute actions on behalf of SA 1", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // to: token, - // data: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - // const receipt = await smartAccount2.sendTransactionWithExecutor([transferTransaction], await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("SA 2 can execute actions on behalf of SA 1 using module instance instead of smart account instance", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR, initData) - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule2.getOwners() - - // // check if SA 2 is as an owner of SA 1 - // const isOwner = owners.includes(await smartAccount2.getAddress()) - // if(!isOwner) { - // const userOpReceipt = await ownableExecutorModule2.addOwner( - // await smartAccount2.getAddress() - // ) - // expect(userOpReceipt.success).toBeTruthy() - // } - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule2) - // // SA 2 will execute the transferTransaction on behalf of SA 1 (smartAccount) - // const receipt = await ownableExecutorModule2.execute(transferTransaction, await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("should remove an owner from the module", async () => { - // const userOpReceipt = await ownableExecutorModule.removeOwner( - // accountTwo.address - // ) - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeFalsy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("should use rhinestone to call ownable executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - - // const ownableExecutorModule2 = getOwnableExecuter({ - // owner: await smartAccount2.getAddress(), - // }); - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // module: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // module: ownableExecutorModule2.module, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.initData - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // const execution = getExecuteOnOwnedAccountAction({ownedAccount: await smartAccount.getAddress(), execution: transferTransaction}) - // const receipt = await smartAccount2.sendTransaction([{to: execution.target, data: execution.callData, value: 0n}]); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // }, 60000) -}) diff --git a/tests/modules.ownableValidator.uninstall.write.test.ts b/tests/modules.ownableValidator.uninstall.write.test.ts deleted file mode 100644 index 09fbd4a78..000000000 --- a/tests/modules.ownableValidator.uninstall.write.test.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type WalletClient, - createWalletClient -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { - type NexusSmartAccount, - type Transaction, - createSmartAccountClient -} from "../src/account" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { - getTestAccount, - killNetwork, - toTestClient, - topUp -} from "./src/testUtils" -import type { MasterClient, NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST" - -describe("modules.ownable.validator.uninstall.write", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - - // Test utils - let testClient: MasterClient - let account: Account - let recipientAccount: Account - let smartAccount: NexusSmartAccount - let smartAccountAddress: Hex - - beforeAll(async () => { - network = await toNetwork(NETWORK_TYPE) - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = getTestAccount(0) - recipientAccount = getTestAccount(3) - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - testClient = toTestClient(chain, getTestAccount(0)) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - bundlerUrl, - chain - }) - - smartAccountAddress = await smartAccount.getAddress() - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should fund the smart account", async () => { - await topUp(testClient, smartAccountAddress) - const [balance] = await smartAccount.getBalances() - expect(balance.amount > 0) - }) - - test("should have account addresses", async () => { - const addresses = await Promise.all([ - account.address, - smartAccount.getAddress() - ]) - expect(addresses.every(Boolean)).to.be.true - expect(addresses).toStrictEqual([ - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "0xa3962DB24D3cAb711e18d5A508591C6dB82a0f54" // Sender smart account - ]) - }) - - test("should send eth", async () => { - const balanceBefore = await testClient.getBalance({ - address: recipientAccount.address - }) - const tx: Transaction = { - to: recipientAccount.address, - value: 1n - } - const { wait } = await smartAccount.sendTransaction(tx) - const { success } = await wait() - const balanceAfter = await testClient.getBalance({ - address: recipientAccount.address - }) - expect(success).toBe(true) - expect(balanceAfter - balanceBefore).toBe(1n) - }) - - // test.skip("install Ownable Executor", async () => { - // let isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // const receipt = await smartAccount.installModule({ - // moduleAddress: ownableExecutorModule.moduleAddress, - // type: ownableExecutorModule.type, - // data: ownableExecutorModule.data - // }) - - // smartAccount.setActiveExecutionModule(ownableExecutorModule) - - // expect(receipt.success).toBe(true) - // } - // }, 60000) - - // test.skip("uninstall Ownable Executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR) - - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (isInstalled) { - // await smartAccount2.uninstallModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // }, 60000) - - // test.skip("Ownable Executor Module should be installed", async () => { - // const isInstalled = await smartAccount.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - // console.log(isInstalled, "isInstalled") - // expect(isInstalled).toBeTruthy() - // }, 60000) - - // test.skip("should add an owner to the module", async () => { - // const ownersBefore = await ownableExecutorModule.getOwners() - // const isOwnerBefore = ownersBefore.includes(accountTwo.address) - - // if (isOwnerBefore) { - // console.log("Owner already exists in list, skipping test case ...") - // return - // } - - // const userOpReceipt = await ownableExecutorModule.addOwner( - // accountTwo.address - // ) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeTruthy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("EOA 2 can execute actions on behalf of SA 1", async () => { - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - // expect(isOwner).toBeTruthy() - - // const balanceBefore = await smartAccount.getBalances([token]) - // console.log("balanceBefore", balanceBefore) - - // const calldata = encodeFunctionData({ - // abi: parseAbi([ - // "function executeOnOwnedAccount(address ownedAccount, bytes callData)" - // ]), - // functionName: "executeOnOwnedAccount", - // args: [ - // await smartAccount.getAddress(), - // encodePacked( - // ["address", "uint256", "bytes"], - // [token, BigInt(Number(0)), transferEncodedCall] - // ) - // ] - // }) - - // // EOA 2 (walletClientTwo) executes an action on behalf of SA 1 which is owned by EOA 1 (walletClientOne) - // const txHash = await walletClientTwo.sendTransaction({ - // account: accountTwo, // Called by delegated EOA owner - // to: ownableExecutorModule.moduleAddress, - // data: calldata, - // value: 0n - // }) - - // const balanceAfter = await smartAccount.getBalances([token]) - // console.log("balanceAfter", balanceAfter) - - // expect(txHash).toBeTruthy() - // }, 60000) - - // test("SA 2 can execute actions on behalf of SA 1", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // to: token, - // data: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - // const receipt = await smartAccount2.sendTransactionWithExecutor([transferTransaction], await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("SA 2 can execute actions on behalf of SA 1 using module instance instead of smart account instance", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - // const ownableExecutorModule2 = await createOwnableExecutorModule(smartAccount2, OWNABLE_EXECUTOR, initData) - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // moduleAddress: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // moduleAddress: ownableExecutorModule2.moduleAddress, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.data - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const owners = await ownableExecutorModule2.getOwners() - - // // check if SA 2 is as an owner of SA 1 - // const isOwner = owners.includes(await smartAccount2.getAddress()) - // if(!isOwner) { - // const userOpReceipt = await ownableExecutorModule2.addOwner( - // await smartAccount2.getAddress() - // ) - // expect(userOpReceipt.success).toBeTruthy() - // } - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule2) - // // SA 2 will execute the transferTransaction on behalf of SA 1 (smartAccount) - // const receipt = await ownableExecutorModule2.execute(transferTransaction, await smartAccount.getAddress()); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // expect(receipt.success).toBe(true) - // }, 60000) - - // test.skip("should remove an owner from the module", async () => { - // const userOpReceipt = await ownableExecutorModule.removeOwner( - // accountTwo.address - // ) - // const owners = await ownableExecutorModule.getOwners() - // const isOwner = owners.includes(accountTwo.address) - - // expect(isOwner).toBeFalsy() - // expect(userOpReceipt.success).toBeTruthy() - // }, 60000) - - // test.skip("should use rhinestone to call ownable executor", async () => { - // const smartAccount2: NexusSmartAccount = await createSmartAccountClient({ - // signer: walletClientTwo, - // bundlerUrl - // }) - - // const initData = encodePacked( - // ["address"], - // [await smartAccount2.getAddress()] - // ) - - // const ownableExecutorModule2 = getOwnableExecuter({ - // owner: await smartAccount2.getAddress(), - // }); - - // // First, we need to install the OwnableExecutor module on SA 2 - // let isInstalled = await smartAccount2.isModuleInstalled({ - // type: 'executor', - // module: OWNABLE_EXECUTOR - // }) - - // if (!isInstalled) { - // await smartAccount2.installModule({ - // module: ownableExecutorModule2.module, - // type: ownableExecutorModule2.type, - // data: ownableExecutorModule2.initData - // }) - // } - - // smartAccount2.setActiveExecutionModule(ownableExecutorModule) - - // const valueToTransfer = parseEther("0.1") - // const recipient = accountTwo.address - // const transferEncodedCall = encodeFunctionData({ - // abi: parseAbi(["function transfer(address to, uint256 value)"]), - // functionName: "transfer", - // args: [recipient, valueToTransfer] - // }) - - // const transferTransaction = { - // target: token as `0x${string}`, - // callData: transferEncodedCall, - // value: 0n - // } - - // const execution = getExecuteOnOwnedAccountAction({ownedAccount: await smartAccount.getAddress(), execution: transferTransaction}) - // const receipt = await smartAccount2.sendTransaction([{to: execution.target, data: execution.callData, value: 0n}]); - // console.log(receipt, "receipt"); - - // expect(receipt.userOpHash).toBeTruthy() - // }, 60000) -}) diff --git a/tsconfig/tsconfig.cjs.json b/tsconfig/tsconfig.cjs.json index d5607ac12..95ca3b2df 100644 --- a/tsconfig/tsconfig.cjs.json +++ b/tsconfig/tsconfig.cjs.json @@ -6,5 +6,10 @@ "removeComments": true, "verbatimModuleSyntax": false, "noEmit": false + }, + "exclude": [ + "../packages/tests/**/*.*", + "../packages/sdk/**/*.test.*", + "../packages/sdk/**/*.spec.*", + ] } -} diff --git a/tsconfig/tsconfig.esm.json b/tsconfig/tsconfig.esm.json index 136a81fc1..85dcc3a03 100644 --- a/tsconfig/tsconfig.esm.json +++ b/tsconfig/tsconfig.esm.json @@ -4,5 +4,10 @@ "module": "es2015", "outDir": "../dist/_esm", "noEmit": false - } + }, + "exclude": [ + "../packages/tests/**/*.*", + "../packages/sdk/**/*.test.*", + "../packages/sdk/**/*.spec.*", + ] } diff --git a/tsconfig/tsconfig.json b/tsconfig/tsconfig.json index 2aa922c41..0612f8fe4 100644 --- a/tsconfig/tsconfig.json +++ b/tsconfig/tsconfig.json @@ -1,18 +1,16 @@ { "extends": "./tsconfig.base.json", "include": [ - "../src/" + "../packages/" ], "exclude": [ - "../src/**/*.spec.ts", - "../src/**/*.test.ts", - "../src/**/*.test-d.ts", - "../src/**/*.bench.ts", - "../tests/**/*.*" + "../packages/**/*.test.ts", + "../packages/**/*.test-d.ts", + "../packages/**/*.bench.ts", ], "compilerOptions": { "moduleResolution": "node", "sourceMap": true, - "rootDir": "../src" + "rootDir": "../packages" } } \ No newline at end of file diff --git a/tsconfig/tsconfig.types.json b/tsconfig/tsconfig.types.json index bb70b996d..a6b1ae518 100644 --- a/tsconfig/tsconfig.types.json +++ b/tsconfig/tsconfig.types.json @@ -8,5 +8,10 @@ "declaration": true, "declarationMap": true, "noEmit": false - } + }, + "exclude": [ + "../packages/tests/**/*.*", + "../packages/sdk/**/*.test.*", + "../packages/sdk/**/*.spec.*", + ] }