From a1fff6dd3d04e7897e39ee15cc5a092b9d41c154 Mon Sep 17 00:00:00 2001 From: joepegler Date: Tue, 3 Sep 2024 16:51:38 +0100 Subject: [PATCH 01/29] chore: fix nexus import (#567) --- .../actions/install-dependencies/action.yml | 4 - .size-limit.json | 11 +- bun.lockb | Bin 352526 -> 355911 bytes package.json | 8 +- src/account/Nexus.ts | 336 ++++++++++++++++++ src/account/NexusSmartAccount.ts | 25 +- src/account/utils/Constants.ts | 4 +- src/account/utils/Types.ts | 8 +- src/bundler/Bundler.ts | 1 + src/modules/validators/K1ValidatorModule.ts | 3 +- tests/modules.ownableExecutor.read.test.ts | 2 - tests/nexus.test.ts | 285 +++++++++++++++ tests/playground.test.ts | 8 + tests/src/executables.ts | 3 +- tests/src/testUtils.ts | 5 + 15 files changed, 670 insertions(+), 33 deletions(-) create mode 100644 src/account/Nexus.ts create mode 100644 tests/nexus.test.ts diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index 58896120..85ebbac6 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -10,10 +10,6 @@ runs: - name: Set up foundry uses: foundry-rs/foundry-toolchain@v1 - - name: Installs with yarn - shell: bash - run: yarn install - - name: Install dependencies shell: bash run: | diff --git a/.size-limit.json b/.size-limit.json index c261c336..35bf1e81 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,21 +2,14 @@ { "name": "core (esm)", "path": "./dist/_esm/index.js", - "limit": "180 kB", + "limit": "25 kB", "import": "*", "ignore": ["node:fs", "fs"] }, { "name": "core (cjs)", "path": "./dist/_cjs/index.js", - "limit": "180 kB", - "ignore": ["node:fs", "fs"] - }, - { - "name": "account (tree-shaking)", - "path": "./dist/_esm/index.js", - "limit": "180 kB", - "import": "{ createSmartAccountClient }", + "limit": "25 kB", "ignore": ["node:fs", "fs"] }, { diff --git a/bun.lockb b/bun.lockb index 3b66649cb71dcd1fe68551fa39b10f370cad36c5..ea3542efc275a8597035cc8072173366368ba081 100755 GIT binary patch delta 67682 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 PFTIC7<6=LXI)?rqw`{(1 delta 65978 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 +export const toNexus = ({ + chain, + transport, + owner, + index = 0n, + activeModule, + factoryAddress = contracts.k1ValidatorFactory.address, + k1ValidatorAddress = contracts.k1Validator.address +}: ToNexusSmartAccountParameters) => { + const masterClient = createWalletClient({ + account: parseAccount(owner), + chain, + transport + }) + .extend(walletActions) + .extend(publicActions) + + const signer = new WalletClientSigner(masterClient, "viem") + + const signerAddress = masterClient.account.address + + const entryPointContract = getContract({ + address: contracts.entryPoint.address, + abi: EntrypointAbi, + client: { + public: masterClient, + wallet: masterClient + } + }) + + const factoryData = encodeFunctionData({ + abi: K1ValidatorFactoryAbi, + functionName: "createAccount", + args: [signerAddress, index, [], 0] + }) + + const defaultedActiveModule = + activeModule ?? + new K1ValidatorModule( + { + moduleAddress: k1ValidatorAddress, + type: "validator", + data: signerAddress, + additionalContext: "0x" + }, + signer + ) + + let _accountAddress: Address + const getAddress = async () => { + if (_accountAddress) return _accountAddress + const fetchedAddress = await masterClient.readContract({ + address: factoryAddress, + abi: K1ValidatorFactoryAbi, + functionName: "computeAccountAddress", + args: [signerAddress, index, [], 0] + }) + _accountAddress = fetchedAddress as Address + return _accountAddress + } + const getCounterFactualAddress = async (): Promise
=> { + try { + // @ts-ignore + await entryPointContract.simulate.getSenderAddress([getInitCode()]) + } catch (e) { + 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") + } + + const getInitCode = () => concatHex([factoryAddress, factoryData]) + + const isDeployed = async (): Promise => { + const address = await getCounterFactualAddress() + const contractCode = await masterClient.getCode({ address }) + return (contractCode?.length ?? 0) > 2 + } + + const getUserOpHash = async ( + userOp: Partial + ): Promise => { + const packedUserOp = packUserOp(userOp) + const userOpHash = keccak256(packedUserOp as Hex) + const enc = encodeAbiParameters( + parseAbiParameters("bytes32, address, uint256"), + [userOpHash, contracts.entryPoint.address, BigInt(chain.id)] + ) + return keccak256(enc) + } + + const encodeExecuteBatch = async (calls: readonly Call[]): Promise => { + const executionAbiParams: AbiParameter = { + type: "tuple[]", + components: [ + { name: "target", type: "address" }, + { name: "value", type: "uint256" }, + { name: "callData", type: "bytes" } + ] + } + + const executions = calls.map((tx) => ({ + target: tx.to, + callData: tx.data ?? "0x", + value: BigInt(tx.value ?? 0n) + })) + + const executionCalldataPrep = encodeAbiParameters( + [executionAbiParams], + [executions] + ) + return encodeFunctionData({ + abi: parseAbi([ + "function execute(bytes32 mode, bytes calldata executionCalldata) external" + ]), + functionName: "execute", + args: [EXECUTE_BATCH, executionCalldataPrep] + }) + } + + const encodeExecute = async (call: Call): Promise => { + const mode = EXECUTE_SINGLE + const executionCalldata = encodePacked( + ["address", "uint256", "bytes"], + [ + call.to as Hex, + BigInt(call.value ?? 0n), + (call.data as Hex) ?? ("0x" as Hex) + ] + ) + return encodeFunctionData({ + abi: parseAbi([ + "function execute(bytes32 mode, bytes calldata executionCalldata) external" + ]), + functionName: "execute", + args: [mode, executionCalldata] + }) + } + + const getNonce = async ({ + key: _key, + validationMode: _validationMode = MODE_VALIDATION, + nonceOptions + }: GetNonceArgs = {}): Promise => { + if (nonceOptions) { + if (nonceOptions?.nonceOverride) return BigInt(nonceOptions.nonceOverride) + if (nonceOptions?.validationMode) + _validationMode = nonceOptions.validationMode + if (nonceOptions?.nonceKey) _key = nonceOptions.nonceKey + } + try { + const key = + _key ?? + concat([ + "0x000000", + _validationMode, + defaultedActiveModule.moduleAddress + ]) + const accountAddress = await getAddress() + return await entryPointContract.read.getNonce([ + accountAddress, + BigInt(key) + ]) + } catch (e) { + return BigInt(0) + } + } + + return toSmartAccount({ + client: masterClient, + entryPoint: { + abi: EntrypointAbi, + address: contracts.entryPoint.address, + version: "0.7" + }, + getAddress, + encodeCalls: (calls: readonly Call[]): Promise => { + return calls.length === 1 + ? encodeExecute(calls[0]) + : encodeExecuteBatch(calls) + }, + getFactoryArgs: async () => ({ factory: factoryAddress, factoryData }), + getStubSignature: async (): Promise => { + return defaultedActiveModule.getDummySignature() + }, + signMessage: async ({ + message + }: { message: SignableMessage }): Promise => { + const tempSignature = await defaultedActiveModule + .getSigner() + .signMessage(message) + + const signature = encodePacked( + ["address", "bytes"], + [defaultedActiveModule.getAddress(), tempSignature] + ) + + const erc6492Signature = concat([ + encodeAbiParameters( + [ + { + type: "address", + name: "create2Factory" + }, + { + type: "bytes", + name: "factoryCalldata" + }, + { + type: "bytes", + name: "originalERC1271Signature" + } + ], + [factoryAddress, factoryData, signature] + ), + MAGIC_BYTES + ]) + + const accountIsDeployed = await isDeployed() + return accountIsDeployed ? signature : erc6492Signature + }, + signTypedData: < + const typedData extends TypedData | Record, + primaryType extends keyof typedData | "EIP712Domain" = keyof typedData + >( + _: TypedDataDefinition + ): Promise => { + throw new Error("signTypedData not supported") + }, + signUserOperation: async ( + parameters: UnionPartialBy & { + chainId?: number | undefined + } + ): Promise => { + const { chainId = masterClient.chain.id, ...userOpWithoutSender } = + parameters + + const nonce = await getNonce() + + console.log("nexnus nonce", { nonce }) + const address = await getCounterFactualAddress() + const userOperation = { ...userOpWithoutSender, sender: address, nonce } + + const hash = getUserOperationHash({ + chainId, + entryPointAddress: contracts.entryPoint.address, + entryPointVersion: "0.7", + userOperation + }) + + const signature = await defaultedActiveModule.signUserOpHash(hash) + return signature + }, + getNonce: async (parameters?: { + key?: bigint | undefined + }): Promise => { + return getNonce() + }, + extend: { + getCounterFactualAddress, + isDeployed, + getInitCode, + encodeExecute, + encodeExecuteBatch, + getUserOpHash, + factoryData, + factoryAddress + } + }) +} diff --git a/src/account/NexusSmartAccount.ts b/src/account/NexusSmartAccount.ts index eb6c520f..1505d9f0 100644 --- a/src/account/NexusSmartAccount.ts +++ b/src/account/NexusSmartAccount.ts @@ -66,7 +66,8 @@ import { import { GENERIC_FALLBACK_SELECTOR, type MODE_MODULE_ENABLE, - MODE_VALIDATION + MODE_VALIDATION, + ModeType } from "./utils/Constants.js" import { ADDRESS_ZERO, @@ -1040,8 +1041,9 @@ export class NexusSmartAccount extends BaseSmartContractAccount { signature, ...userOpWithoutSignature }: Partial): Promise { - const userOperation = await this.signUserOp(userOpWithoutSignature) - return await this.sendSignedUserOp(userOperation) + const signedUserOp = await this.signUserOp(userOpWithoutSignature) + + return await this.sendSignedUserOp(signedUserOp) } /** @@ -1063,8 +1065,13 @@ export class NexusSmartAccount extends BaseSmartContractAccount { // "signature", // ] // this.validateUserOp(userOp, requiredFields) + if (!this.bundler) throw new Error("Bundler is not provided") - return await this.bundler.sendUserOp(userOp) + + console.log("sendSignedUserOp", { userOp }) + + return { wait: () => {}, hash: undefined } as unknown as UserOpResponse + // return await this.bundler.sendUserOp(userOp) } /** @@ -1118,7 +1125,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount { const feeData = await this.publicClient.estimateFeesPerGas() if (feeData.maxFeePerGas?.toString()) { finalUserOp.maxFeePerGas = feeData.maxFeePerGas - } else if (feeData.gasPrice?.toString()) { + } else if (feeData.gasPrice) { finalUserOp.maxFeePerGas = feeData.gasPrice } else { finalUserOp.maxFeePerGas = await this.publicClient.getGasPrice() @@ -1126,7 +1133,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount { if (feeData.maxPriorityFeePerGas?.toString()) { finalUserOp.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas - } else if (feeData.gasPrice?.toString()) { + } else if (feeData.gasPrice) { finalUserOp.maxPriorityFeePerGas = feeData.gasPrice ?? 0n } else { finalUserOp.maxPriorityFeePerGas = await this.publicClient.getGasPrice() @@ -1144,9 +1151,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount { return finalUserOp } - override async getNonce( - validationMode?: typeof MODE_VALIDATION | typeof MODE_MODULE_ENABLE - ): Promise { + override async getNonce(validationMode?: "0x00" | "0x01"): Promise { try { const vm = this.activeValidationModule.moduleAddress ?? @@ -1419,6 +1424,8 @@ export class NexusSmartAccount extends BaseSmartContractAccount { callData } + console.log({ nonceFromFetch }) + if (!(await this.isAccountDeployed())) { userOp.factory = this.factoryAddress } diff --git a/src/account/utils/Constants.ts b/src/account/utils/Constants.ts index 1f57e802..cdca61c4 100644 --- a/src/account/utils/Constants.ts +++ b/src/account/utils/Constants.ts @@ -70,8 +70,8 @@ export const GENERIC_FALLBACK_SELECTOR = "0xcb5baf0f" export const SENTINEL_ADDRESS: Hex = "0x0000000000000000000000000000000000000001" -export const MODE_VALIDATION = "0x00" as Hex -export const MODE_MODULE_ENABLE = "0x01" as Hex +export const MODE_VALIDATION = "0x00" +export const MODE_MODULE_ENABLE = "0x01" export const MODULE_ENABLE_MODE_TYPE_HASH = keccak256( toHex("ModuleEnableMode(address module, bytes32 initDataHash)") diff --git a/src/account/utils/Types.ts b/src/account/utils/Types.ts index 2474d1f3..44b5f554 100644 --- a/src/account/utils/Types.ts +++ b/src/account/utils/Types.ts @@ -12,7 +12,7 @@ import type { WalletClient } from "viem" import type { IBundler } from "../../bundler" -import type { ModuleInfo, ModuleType } from "../../modules" +import type { Module, ModuleInfo, ModuleType } from "../../modules" import type { BaseValidationModule } from "../../modules/base/BaseValidationModule" import type { FeeQuotesOrDataDto, @@ -640,3 +640,9 @@ export enum CallType { CALLTYPE_STATIC = "0xFE", CALLTYPE_DELEGATECALL = "0xFF" } + +export type GetNonceArgs = { + key?: bigint | undefined + validationMode?: "0x00" | "0x01" + nonceOptions?: NonceOptions +} diff --git a/src/bundler/Bundler.ts b/src/bundler/Bundler.ts index 0d312652..1a62fd8b 100644 --- a/src/bundler/Bundler.ts +++ b/src/bundler/Bundler.ts @@ -151,6 +151,7 @@ export class Bundler implements IBundler { */ async sendUserOp(_userOp: UserOperationStruct): Promise { const chainId = this.bundlerConfig.chainId + const params = [deepHexlify(_userOp), this.bundlerConfig.entryPointAddress] const bundlerUrl = this.getBundlerUrl() const sendUserOperationResponse: { diff --git a/src/modules/validators/K1ValidatorModule.ts b/src/modules/validators/K1ValidatorModule.ts index 19b26a28..03a655da 100644 --- a/src/modules/validators/K1ValidatorModule.ts +++ b/src/modules/validators/K1ValidatorModule.ts @@ -4,7 +4,8 @@ import { BaseValidationModule } from "../base/BaseValidationModule.js" import type { Module } from "../utils/Types.js" export class K1ValidatorModule extends BaseValidationModule { - private constructor(moduleConfig: Module, signer: SmartAccountSigner) { + // biome-ignore lint/complexity/noUselessConstructor: + public constructor(moduleConfig: Module, signer: SmartAccountSigner) { super(moduleConfig, signer) } diff --git a/tests/modules.ownableExecutor.read.test.ts b/tests/modules.ownableExecutor.read.test.ts index 042e12c5..0b0262d4 100644 --- a/tests/modules.ownableExecutor.read.test.ts +++ b/tests/modules.ownableExecutor.read.test.ts @@ -12,8 +12,6 @@ import { type Transaction, createSmartAccountClient } from "../src/account" -import { createOwnableExecutorModule } from "../src/modules" -import { OWNABLE_EXECUTOR } from "./src/callDatas" import { type TestFileNetworkType, toNetwork } from "./src/testSetup" import { getTestAccount, diff --git a/tests/nexus.test.ts b/tests/nexus.test.ts new file mode 100644 index 00000000..eb03332b --- /dev/null +++ b/tests/nexus.test.ts @@ -0,0 +1,285 @@ +import { + http, + type Account, + type Chain, + type Hex, + type Prettify, + type PrivateKeyAccount, + type PublicClient, + type WalletClient, + createPublicClient, + createWalletClient, + isHex +} from "viem" +import { + type BundlerClient, + createBundlerClient +} from "viem/account-abstraction" +import { afterAll, beforeAll, describe, expect, test } from "vitest" +import contracts from "../src/__contracts" +import { + type NexusSmartAccount, + createSmartAccountClient +} from "../src/account" +import { toNexus } from "../src/account/Nexus" +import { type TestFileNetworkType, toNetwork } from "./src/testSetup" +import { killNetwork } from "./src/testUtils" +import type { NetworkConfig } from "./src/testUtils" + +const NETWORK_TYPE: TestFileNetworkType = "PUBLIC_TESTNET" + +// 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" + +describe("nexus", () => { + let network: NetworkConfig + // Nexus Config + let chain: Chain + let bundlerUrl: string + let walletClient: WalletClient + let bundlerClient: BundlerClient + + // Test utils + let publicClient: PublicClient + let account: Account + let smartAccount: NexusSmartAccount + let nexus: Prettify>> + let smartAccountAddress: Hex + let nexusAccountAddress: Hex + + beforeAll(async () => { + network = (await toNetwork(NETWORK_TYPE)) as NetworkConfig + + chain = network.chain + bundlerUrl = network.bundlerUrl + + account = network.account as PrivateKeyAccount + + walletClient = createWalletClient({ + account, + chain, + transport: http() + }) + + publicClient = createPublicClient({ + chain, + transport: http() + }) + }) + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + test("should create a smart account", async () => { + const regularWalletClient = createWalletClient({ + account, + chain, + transport: http() + }) + + smartAccount = await createSmartAccountClient({ + signer: regularWalletClient, + bundlerUrl, + chain, + k1ValidatorAddress, + factoryAddress + }) + + nexus = await toNexus({ + owner: account, + chain, + transport: http(), + factoryAddress, + k1ValidatorAddress + }) + + smartAccountAddress = await smartAccount.getAddress() + nexusAccountAddress = await nexus.getAddress() + }) + + test("should produce the same results", async () => { + const [smartAccountAddress, nexusAddress] = await Promise.all([ + smartAccount.getAddress(), + nexus.getAddress() + ]) + expect(smartAccountAddress).toEqual(nexusAddress) + nexusAccountAddress = nexusAddress + + console.log({ smartAccountAddress }, account.address) + + const [initCode, nexusInitCode] = await Promise.all([ + smartAccount.getAccountInitCode(), + nexus.getInitCode() + ]) + + expect([initCode, nexusInitCode].every((code) => isHex(code))).toBeTruthy() + + const [smartAccountFactoryData, nexusFactoryData] = await Promise.all([ + smartAccount.getFactoryData(), + nexus.factoryData + ]) + expect(isHex(nexusFactoryData)).toBeTruthy() + + const [smartAccountIsDeployed, nexusIsDeployed] = await Promise.all([ + smartAccount.isAccountDeployed(), + nexus.isDeployed() + ]) + expect(smartAccountIsDeployed).toEqual(nexusIsDeployed) + }) + + test("should check balances and top up relevant addresses", async () => { + const [ownerBalance, smartAccountBalance] = await Promise.all([ + publicClient.getBalance({ + address: account.address + }), + publicClient.getBalance({ + address: smartAccountAddress + }) + ]) + const balancesAreOfCorrectType = [ownerBalance, smartAccountBalance].every( + (balance) => typeof balance === "bigint" + ) + + console.log({ ownerBalance, smartAccountBalance }) + if (smartAccountBalance < 1000000000000n) { + const hash = await walletClient.sendTransaction({ + chain, + account, + to: smartAccountAddress, + value: 1000000000000n + }) + const receipt = await publicClient.waitForTransactionReceipt({ hash }) + console.log({ receipt }) + } + expect(balancesAreOfCorrectType).toBeTruthy() + }) + + test("should have a working bundler client", async () => { + const bundlerClient = createBundlerClient({ + account: nexus, + chain, + transport: http(bundlerUrl) + }) + + const chainId = await bundlerClient.getChainId() + expect(chainId).toEqual(chain.id) + + const supportedEntrypoints = await bundlerClient.getSupportedEntryPoints() + expect(supportedEntrypoints).to.include(contracts.entryPoint.address) + }) + + test("should prepare a user operation", async () => { + const bundlerClient = createBundlerClient({ + account: nexus, + chain, + transport: http(bundlerUrl) + }) + + const isDeployed = await nexus.isDeployed() + + const calls = [{ to: account.address, value: 1n }] + const feeData = await publicClient.estimateFeesPerGas() + const gas = { + maxFeePerGas: feeData.maxFeePerGas * 2n, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n + } + + const UNDER_THE_HOOD = true + + if (UNDER_THE_HOOD) { + const preppedUserOp = await bundlerClient.prepareUserOperation({ + calls, + ...gas + }) + const signature = await nexus.signUserOperation(preppedUserOp) + const uO = { ...preppedUserOp, signature } + const gasEstimates = await bundlerClient.estimateUserOperationGas(uO) + const userOpWithGasEstimates = { ...uO, ...gasEstimates } + + const nexusNonce = await nexus.getNonce() + console.log({ nexusNonce }) + + const { wait } = await smartAccount.sendTransaction(calls) + // const { success } = await wait() + // expect(success).toBe(true) + + console.log({ userOpWithGasEstimates }) + + const hash = await bundlerClient.sendUserOperation(userOpWithGasEstimates) + + const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) + console.log({ receipt }) + } else { + const hash = await bundlerClient.sendUserOperation({ calls, ...gas }) + const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) + console.log({ receipt }) + } + + // const encodedCalls = await nexus.encodeCalls(calls) + // expect(isHex(encodedCalls)).toBeTruthy() + + // const preppedUserOperation = await bundlerClient.prepareUserOperation({ + // calls, + // ...feeData + // }) + + // // const gasPrice = await bundlerClient + + // const { + // factory, + // factoryData, + // paymasterPostOpGasLimit, + // paymasterVerificationGasLimit, + // ...rawUserOperation + // } = preppedUserOperation + + // const signature = await nexus.signUserOperation(preppedUserOperation) + + // const uO = { ...rawUserOperation, signature } + + // console.log({ uO }) + + // // const gasEstimates = + // // await bundlerClient.estimateUserOperationGas(preppedUserOperation) + + // // const userOpWithOld = await smartAccount.buildUserOp([]) + // // const signedUserOpWithOld = await smartAccount.signUserOp(userOpWithOld) + // // console.log({ signedUserOpWithOld }) + // const { wait } = await smartAccount.sendTransaction(calls) + // const { receipt: receiptOld } = await wait() + // console.log({ receiptOld }) + + // const receipt = await bundlerClient.sendUserOperation(uO) + // console.log({ receipt }) + + // const gasEstimates = await bundlerClient.prepareUserOperation({ + // // account: parseAccount(account), + // callData: "0x", + // maxFeePerGas: 1n, + // maxPriorityFeePerGas: 1n + // }) + + // console.log(gasEstimates) + + // const userOperation = await nexus.signUserOperation({ + // callData: encodedCalls, + // callGasLimit, + // maxFeePerGas, + // maxPriorityFeePerGas, + // nonce + // }) + + // estimateUserOperationGas: [Function: estimateUserOperationGas], + // getChainId: [Function: getChainId], + // getSupportedEntryPoints: [Function: getSupportedEntryPoints], + // getUserOperation: [Function: getUserOperation], + // getUserOperationReceipt: [Function: getUserOperationReceipt], + // prepareUserOperation: [Function: prepareUserOperation], + // sendUserOperation: [Function: sendUserOperation], + // waitForUserOperationReceipt: [Function: waitForUserOperationReceipt] + + // console.log(bundlerClient) + }) +}) diff --git a/tests/playground.test.ts b/tests/playground.test.ts index 1a689c39..51a8ca59 100644 --- a/tests/playground.test.ts +++ b/tests/playground.test.ts @@ -8,7 +8,9 @@ import { createPublicClient, createWalletClient } from "viem" +import { createPaymasterClient } from "viem/account-abstraction" import { beforeAll, expect, test } from "vitest" +import { createPaymaster } from "../src" import { type NexusSmartAccount, createSmartAccountClient @@ -160,6 +162,12 @@ describeWithPlaygroundGuard("playground", () => { return } + const paymasterClient = createPaymasterClient({ + transport: http(paymasterUrl) + }) + + console.log({ paymasterClient }) + const smartAccount = await createSmartAccountClient({ signer: walletClient, chain, diff --git a/tests/src/executables.ts b/tests/src/executables.ts index 40d5fdff..aae06221 100644 --- a/tests/src/executables.ts +++ b/tests/src/executables.ts @@ -2,7 +2,8 @@ import { execa } from "execa" const cwd = "./node_modules/nexus" -export const init = async () => await execa({ cwd })`yarn install` +export const init = async () => + await execa({ cwd })`yarn install --frozen-lockfile` export const cleanOne = async (rpcPort: number) => await execa({ cwd })`rm -rf ./deployments/anvil-${rpcPort}` diff --git a/tests/src/testUtils.ts b/tests/src/testUtils.ts index c2e4fcf5..4463d4c7 100644 --- a/tests/src/testUtils.ts +++ b/tests/src/testUtils.ts @@ -202,10 +202,15 @@ export const toConfiguredAnvil = async ({ // forkUrl: "https://base-sepolia.gateway.tenderly.co/2oxlNZ7oiNCUpXzrWFuIHx" }) await instance.start() + console.log("") + console.log(`configuring module bytecode on http://localhost:${rpcPort}`) await deployContracts(rpcPort) await init() await clean() + console.log(`deploying nexus contracts to http://localhost:${rpcPort}`) await deploy(rpcPort) + console.log("deployment complete") + console.log("") return instance } From b4e78d69119c2b464d66eb447f8d4f7e869f8ae2 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Wed, 11 Sep 2024 16:04:32 +0100 Subject: [PATCH 02/29] chore: refactor continued --- src/account/BaseSmartContractAccount.ts | 358 --- src/account/Nexus.ts | 185 +- src/account/NexusSmartAccount.ts | 2095 ----------------- src/account/index.ts | 4 - src/account/utils/AccountNotFound.ts | 17 + src/clients/biconomy.test.ts | 147 ++ src/clients/biconomy.ts | 183 ++ src/clients/decorators/erc7579/accountId.ts | 93 + src/clients/decorators/erc7579/index.ts | 93 + .../decorators/erc7579/installModule.ts | 97 + .../decorators/erc7579/installModules.ts | 93 + .../decorators/erc7579/isModuleInstalled.ts | 121 + .../erc7579/supportsExecutionMode.ts | 159 ++ .../decorators/erc7579/supportsModule.ts | 121 + .../decorators/erc7579/uninstallModule.ts | 100 + .../decorators/erc7579/uninstallModules.ts | 97 + src/clients/decorators/smartAccount/index.ts | 324 +++ .../smartAccount/sendTransaction.ts | 132 ++ .../decorators/smartAccount/signMessage.ts | 73 + .../decorators/smartAccount/signTypedData.ts | 157 ++ .../decorators/smartAccount/writeContract.ts | 70 + tests/biconomy.test.ts | 148 ++ tests/nexus.test.ts | 285 --- tests/src/testSetup.ts | 2 +- tests/vitest.config.ts | 2 +- tsconfig/tsconfig.json | 1 + 26 files changed, 2332 insertions(+), 2825 deletions(-) delete mode 100644 src/account/BaseSmartContractAccount.ts delete mode 100644 src/account/NexusSmartAccount.ts create mode 100644 src/account/utils/AccountNotFound.ts create mode 100644 src/clients/biconomy.test.ts create mode 100644 src/clients/biconomy.ts create mode 100644 src/clients/decorators/erc7579/accountId.ts create mode 100644 src/clients/decorators/erc7579/index.ts create mode 100644 src/clients/decorators/erc7579/installModule.ts create mode 100644 src/clients/decorators/erc7579/installModules.ts create mode 100644 src/clients/decorators/erc7579/isModuleInstalled.ts create mode 100644 src/clients/decorators/erc7579/supportsExecutionMode.ts create mode 100644 src/clients/decorators/erc7579/supportsModule.ts create mode 100644 src/clients/decorators/erc7579/uninstallModule.ts create mode 100644 src/clients/decorators/erc7579/uninstallModules.ts create mode 100644 src/clients/decorators/smartAccount/index.ts create mode 100644 src/clients/decorators/smartAccount/sendTransaction.ts create mode 100644 src/clients/decorators/smartAccount/signMessage.ts create mode 100644 src/clients/decorators/smartAccount/signTypedData.ts create mode 100644 src/clients/decorators/smartAccount/writeContract.ts create mode 100644 tests/biconomy.test.ts delete mode 100644 tests/nexus.test.ts diff --git a/src/account/BaseSmartContractAccount.ts b/src/account/BaseSmartContractAccount.ts deleted file mode 100644 index 6cc829f7..00000000 --- a/src/account/BaseSmartContractAccount.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { - http, - type Address, - type GetContractReturnType, - type Hash, - type Hex, - type PublicClient, - createPublicClient, - getContract, - trim -} from "viem" -import { EntrypointAbi } from "../__contracts/abi/EntryPointABI.js" -import contracts from "../__contracts/index.js" -import { Logger, type SmartAccountSigner } from "./index.js" -import type { MODE_MODULE_ENABLE, MODE_VALIDATION } from "./utils/Constants.js" -import type { - BaseSmartContractAccountProps, - BatchUserOperationCallData, - ISmartContractAccount, - SignTypedDataParams, - Transaction -} from "./utils/Types.js" -import { wrapSignatureWith6492 } from "./utils/Utils.js" - -export enum DeploymentState { - UNDEFINED = "0x0", - NOT_DEPLOYED = "0x1", - DEPLOYED = "0x2" -} - -export abstract class BaseSmartContractAccount< - TSigner extends SmartAccountSigner = SmartAccountSigner -> implements ISmartContractAccount -{ - protected factoryAddress: Address - - protected deploymentState: DeploymentState = DeploymentState.UNDEFINED - - protected accountAddress?: Address - - protected accountInitCode?: Hex - - protected signer: TSigner - - protected entryPoint: GetContractReturnType< - typeof contracts.entryPoint.abi, - PublicClient - > - - protected entryPointAddress: Address - - public publicClient: PublicClient - - constructor(params: BaseSmartContractAccountProps) { - this.entryPointAddress = - params.entryPointAddress ?? contracts.entryPoint.address - - this.publicClient = createPublicClient({ - chain: params.chain, - transport: http(params.rpcUrl) - }) - - this.accountAddress = params.accountAddress - this.factoryAddress = params.factoryAddress - this.signer = params.signer as TSigner - this.accountInitCode = params.initCode - - this.entryPoint = getContract({ - address: this.entryPointAddress, - abi: EntrypointAbi, - client: this.publicClient as PublicClient - }) - } - - //#region abstract-methods - - /** - * This method should return a signature that will not `revert` during validation. - * It does not have to pass validation, just not cause the contract to revert. - * This is required for gas estimation so that the gas estimate are accurate. - * - */ - abstract getDummySignature(): Hash - - /** - * this method should return the abi encoded function data for a call to your contract's `execute` method - * - * @param target -- equivalent to `to` in a normal transaction - * @param value -- equivalent to `value` in a normal transaction - * @param data -- equivalent to `data` in a normal transaction - * @returns abi encoded function data for a call to your contract's `execute` method - */ - abstract encodeExecute( - transaction: Transaction, - useExecutor: boolean - ): Promise - - /** - * this should return an ERC-191 compliant message and is used to sign UO Hashes - * - * @param msg -- the message to sign - */ - abstract signMessage(msg: string | Uint8Array): Promise - - /** - * this should return the init code that will be used to create an account if one does not exist. - * This is the concatenation of the account's factory address and the abi encoded function data of the account factory's `createAccount` method. - * https://github.com/eth-infinitism/account-abstraction/blob/abff2aca61a8f0934e533d0d352978055fddbd96/contracts/core/SenderCreator.sol#L12 - */ - protected abstract getAccountInitCode(): Promise - - //#endregion abstract-methods - - //#region optional-methods - - /** - * 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 - */ - async signUserOperationHash(uoHash: Hash): Promise { - return this.signMessage(uoHash) - } - - /** - * If your contract supports signing and verifying typed data, - * you should implement this method. - * - * @param _params -- Typed Data params to sign - */ - async signTypedData(_params: SignTypedDataParams): Promise<`0x${string}`> { - throw new Error("signTypedData not supported") - } - - /** - * This method should wrap the result of `signMessage` as per - * [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) - * - * @param msg -- the message to sign - */ - async signMessageWith6492(msg: string | Uint8Array): Promise<`0x${string}`> { - const [isDeployed, signature] = await Promise.all([ - this.isAccountDeployed(), - this.signMessage(msg) - ]) - - return this.create6492Signature(isDeployed, signature) - } - - /** - * Similar to the signMessageWith6492 method above, - * this method should wrap the result of `signTypedData` as per - * [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) - * - * @param params -- Typed Data params to sign - */ - async signTypedDataWith6492( - params: SignTypedDataParams - ): Promise<`0x${string}`> { - const [isDeployed, signature] = await Promise.all([ - this.isAccountDeployed(), - this.signTypedData(params) - ]) - - return this.create6492Signature(isDeployed, signature) - } - - /** - * Not all contracts support batch execution. - * If your contract does, this method should encode a list of - * transactions into the call data that will be passed to your - * contract's batch execution method. - * - * @param _txs -- the transactions to batch execute - */ - async encodeBatchExecute( - _txs: BatchUserOperationCallData - ): Promise<`0x${string}`> { - throw new Error("Batch execution not supported") - } - - /** - * If your contract supports UUPS, you can implement this method which can be - * used to upgrade the implementation of the account. - * - * @param upgradeToImplAddress -- the implementation address of the contract you want to upgrade to - * @param upgradeToInitData -- the initialization data required by that account - */ - encodeUpgradeToAndCall = async ( - _upgradeToImplAddress: Address, - _upgradeToInitData: Hex - ): Promise => { - throw new Error("Upgrade ToAndCall Not Supported") - } - //#endregion optional-methods - - // Extra implementations - abstract getNonce( - validationMode?: typeof MODE_VALIDATION | typeof MODE_MODULE_ENABLE - ): Promise - - private async _isDeployed(): Promise { - const contractCode = await this.publicClient.getBytecode({ - address: await this.getAddress() - }) - return (contractCode?.length ?? 0) > 2 - } - - async getInitCode(): Promise { - if (this.deploymentState === DeploymentState.DEPLOYED) { - return "0x" - } - - const isDeployed = await this._isDeployed() - - if (isDeployed) { - this.deploymentState = DeploymentState.DEPLOYED - return "0x" - } - - this.deploymentState = DeploymentState.NOT_DEPLOYED - - return this._getAccountInitCode() - } - - async getAddress(): Promise
{ - if (!this.accountAddress) { - const initCode = await this._getAccountInitCode() - Logger.log("[BaseSmartContractAccount](getAddress) initCode: ", initCode) - try { - await this.entryPoint.simulate.getSenderAddress([initCode]) - } catch (err: any) { - Logger.log( - "[BaseSmartContractAccount](getAddress) getSenderAddress err: ", - err - ) - - if (err.cause?.data?.errorName === "SenderAddressResult") { - this.accountAddress = err.cause.data.args[0] as Address - Logger.log( - "[BaseSmartContractAccount](getAddress) entryPoint.getSenderAddress result:", - this.accountAddress - ) - return this.accountAddress - } - - if (err.details === "Invalid URL") { - throw new Error("Invalid URL") - } - } - - throw new Error("Failed to get counterfactual account address") - } - - return this.accountAddress - } - - extend = (fn: (self: this) => R): this & R => { - const extended = fn(this) as any - // this should make it so extensions can't overwrite the base methods - for (const key in this) { - delete extended[key] - } - return Object.assign(this, extended) - } - - getSigner(): TSigner { - return this.signer - } - - getFactoryAddress(): Address { - return this.factoryAddress - } - - getEntryPointAddress(): Address { - return this.entryPointAddress - } - - async isAccountDeployed(): Promise { - return (await this.getDeploymentState()) === DeploymentState.DEPLOYED - } - - async getDeploymentState(): Promise { - if (this.deploymentState === DeploymentState.UNDEFINED) { - const initCode = await this.getInitCode() - return initCode === "0x" - ? DeploymentState.DEPLOYED - : DeploymentState.NOT_DEPLOYED - } - if (this.deploymentState === DeploymentState.NOT_DEPLOYED) { - if (await this._isDeployed()) { - this.deploymentState = DeploymentState.DEPLOYED - } - } - return this.deploymentState - } - /** - * https://eips.ethereum.org/EIPS/eip-4337#first-time-account-creation - * The initCode field (if non-zero length) is parsed as a 20-byte address, - * followed by calldata to pass to this address. - * The factory address is the first 40 char after the 0x, and the callData is the rest. - */ - protected async parseFactoryAddressFromAccountInitCode(): Promise< - [Address, Hex] - > { - const initCode = await this._getAccountInitCode() - const factoryAddress = `0x${initCode.substring(2, 42)}` as Address - const factoryCalldata = `0x${initCode.substring(42)}` as Hex - return [factoryAddress, factoryCalldata] - } - - protected async getImplementationAddress(): Promise<"0x0" | Address> { - const accountAddress = await this.getAddress() - - const storage = await this.publicClient.getStorageAt({ - address: accountAddress, - // This is the default slot for the implementation address for Proxies - slot: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" - }) - - if (storage == null) { - throw new Error( - "Failed to get storage slot 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" - ) - } - - return trim(storage) - } - - private async _getAccountInitCode(): Promise { - return this.accountInitCode ?? this.getAccountInitCode() - } - - private async create6492Signature( - isDeployed: boolean, - signature: Hash - ): Promise { - if (isDeployed) { - return signature - } - - const [factoryAddress, factoryCalldata] = - await this.parseFactoryAddressFromAccountInitCode() - - Logger.log( - `[BaseSmartContractAccount](create6492Signature)\ - factoryAddress: ${factoryAddress}, factoryCalldata: ${factoryCalldata}` - ) - - return wrapSignatureWith6492({ - factoryAddress, - factoryCalldata, - signature - }) - } -} diff --git a/src/account/Nexus.ts b/src/account/Nexus.ts index ce5a7941..fccaab29 100644 --- a/src/account/Nexus.ts +++ b/src/account/Nexus.ts @@ -1,4 +1,5 @@ import { + http, type AbiParameter, type Account, type Address, @@ -6,7 +7,9 @@ import { type ClientConfig, type Hex, type Prettify, + type RpcSchema, type SignableMessage, + type Transport, type TypedData, type TypedDataDefinition, type UnionPartialBy, @@ -24,7 +27,11 @@ import { walletActions } from "viem" import { + type BundlerClientConfig, + type SmartAccount, + type SmartAccountImplementation, type UserOperation, + createBundlerClient, getUserOperationHash, toSmartAccount } from "viem/account-abstraction" @@ -36,8 +43,6 @@ import { type GetNonceArgs, MAGIC_BYTES, MODE_VALIDATION, - type ModeType, - type NonceOptions, type UserOperationStruct, WalletClientSigner } from "../index" @@ -53,36 +58,69 @@ export type ToNexusSmartAccountParameters = { activeModule?: BaseValidationModule factoryAddress?: Address k1ValidatorAddress?: Address -} +} & Prettify< + Pick< + ClientConfig, + | "account" + | "cacheTime" + | "chain" + | "key" + | "name" + | "pollingInterval" + | "rpcSchema" + > +> + +export type Nexus = Prettify> + +export type NexusSmartAccountImplementation = SmartAccountImplementation< + typeof EntrypointAbi, + "0.7", + { + getCounterFactualAddress: () => Promise
+ isDeployed: () => Promise + getInitCode: () => Hex + encodeExecute: (call: Call) => Promise + encodeExecuteBatch: (calls: readonly Call[]) => Promise + getUserOpHash: (userOp: Partial) => Promise + factoryData: Hex + factoryAddress: Address + } +> -type Call = { +export type Call = { to: Hex data?: Hex | undefined value?: bigint | undefined } -export type Nexus = Prettify>> -export const toNexus = ({ - chain, - transport, - owner, - index = 0n, - activeModule, - factoryAddress = contracts.k1ValidatorFactory.address, - k1ValidatorAddress = contracts.k1Validator.address -}: ToNexusSmartAccountParameters) => { +export const toNexusAccount = async ( + parameters: ToNexusSmartAccountParameters +): Promise => { + const { + chain, + transport, + owner, + index = 0n, + activeModule, + factoryAddress = contracts.k1ValidatorFactory.address, + k1ValidatorAddress = contracts.k1Validator.address, + key = "nexus", + name = "Nexus" + } = parameters + const masterClient = createWalletClient({ account: parseAccount(owner), chain, - transport + transport, + key, + name }) .extend(walletActions) .extend(publicActions) - const signer = new WalletClientSigner(masterClient, "viem") - + const moduleSigner = new WalletClientSigner(masterClient, "viem") const signerAddress = masterClient.account.address - const entryPointContract = getContract({ address: contracts.entryPoint.address, abi: EntrypointAbi, @@ -107,24 +145,22 @@ export const toNexus = ({ data: signerAddress, additionalContext: "0x" }, - signer + moduleSigner ) let _accountAddress: Address const getAddress = async () => { if (_accountAddress) return _accountAddress - const fetchedAddress = await masterClient.readContract({ + _accountAddress = await masterClient.readContract({ address: factoryAddress, abi: K1ValidatorFactoryAbi, functionName: "computeAccountAddress", args: [signerAddress, index, [], 0] }) - _accountAddress = fetchedAddress as Address return _accountAddress } const getCounterFactualAddress = async (): Promise
=> { try { - // @ts-ignore await entryPointContract.simulate.getSenderAddress([getInitCode()]) } catch (e) { if (e.cause?.data?.errorName === "SenderAddressResult") { @@ -204,7 +240,6 @@ export const toNexus = ({ } const getNonce = async ({ - key: _key, validationMode: _validationMode = MODE_VALIDATION, nonceOptions }: GetNonceArgs = {}): Promise => { @@ -212,16 +247,13 @@ export const toNexus = ({ if (nonceOptions?.nonceOverride) return BigInt(nonceOptions.nonceOverride) if (nonceOptions?.validationMode) _validationMode = nonceOptions.validationMode - if (nonceOptions?.nonceKey) _key = nonceOptions.nonceKey } try { - const key = - _key ?? - concat([ - "0x000000", - _validationMode, - defaultedActiveModule.moduleAddress - ]) + const key = concat([ + "0x000000", + _validationMode, + defaultedActiveModule.moduleAddress + ]) const accountAddress = await getAddress() return await entryPointContract.read.getNonce([ accountAddress, @@ -232,6 +264,43 @@ export const toNexus = ({ } } + const signMessage = async ({ + message + }: { message: SignableMessage }): Promise => { + const tempSignature = await defaultedActiveModule + .getSigner() + .signMessage(message) + + const signature = encodePacked( + ["address", "bytes"], + [defaultedActiveModule.getAddress(), tempSignature] + ) + + const erc6492Signature = concat([ + encodeAbiParameters( + [ + { + type: "address", + name: "create2Factory" + }, + { + type: "bytes", + name: "factoryCalldata" + }, + { + type: "bytes", + name: "originalERC1271Signature" + } + ], + [factoryAddress, factoryData, signature] + ), + MAGIC_BYTES + ]) + + const accountIsDeployed = await isDeployed() + return accountIsDeployed ? signature : erc6492Signature + } + return toSmartAccount({ client: masterClient, entryPoint: { @@ -249,42 +318,7 @@ export const toNexus = ({ getStubSignature: async (): Promise => { return defaultedActiveModule.getDummySignature() }, - signMessage: async ({ - message - }: { message: SignableMessage }): Promise => { - const tempSignature = await defaultedActiveModule - .getSigner() - .signMessage(message) - - const signature = encodePacked( - ["address", "bytes"], - [defaultedActiveModule.getAddress(), tempSignature] - ) - - const erc6492Signature = concat([ - encodeAbiParameters( - [ - { - type: "address", - name: "create2Factory" - }, - { - type: "bytes", - name: "factoryCalldata" - }, - { - type: "bytes", - name: "originalERC1271Signature" - } - ], - [factoryAddress, factoryData, signature] - ), - MAGIC_BYTES - ]) - - const accountIsDeployed = await isDeployed() - return accountIsDeployed ? signature : erc6492Signature - }, + signMessage, signTypedData: < const typedData extends TypedData | Record, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData @@ -300,28 +334,17 @@ export const toNexus = ({ ): Promise => { const { chainId = masterClient.chain.id, ...userOpWithoutSender } = parameters - - const nonce = await getNonce() - - console.log("nexnus nonce", { nonce }) const address = await getCounterFactualAddress() - const userOperation = { ...userOpWithoutSender, sender: address, nonce } - + const userOperation = { ...userOpWithoutSender, sender: address } const hash = getUserOperationHash({ chainId, entryPointAddress: contracts.entryPoint.address, entryPointVersion: "0.7", userOperation }) - - const signature = await defaultedActiveModule.signUserOpHash(hash) - return signature - }, - getNonce: async (parameters?: { - key?: bigint | undefined - }): Promise => { - return getNonce() + return await defaultedActiveModule.signUserOpHash(hash) }, + getNonce, extend: { getCounterFactualAddress, isDeployed, diff --git a/src/account/NexusSmartAccount.ts b/src/account/NexusSmartAccount.ts deleted file mode 100644 index 1505d9f0..00000000 --- a/src/account/NexusSmartAccount.ts +++ /dev/null @@ -1,2095 +0,0 @@ -import { - type AbiParameter, - type Address, - type GetContractReturnType, - type Hash, - type Hex, - type PublicClient, - type WalletClient, - concat, - concatHex, - decodeFunctionData, - encodeAbiParameters, - encodeFunctionData, - encodePacked, - formatUnits, - getAddress, - getContract, - keccak256, - pad, - parseAbi, - parseAbiParameters, - toBytes, - toHex -} from "viem" -import contracts from "../__contracts" -import { NexusAbi } from "../__contracts/abi" -import { - Bundler, - type GetUserOperationGasPriceReturnType, - type UserOpReceipt, - type UserOpResponse -} from "../bundler/index.js" -import type { IBundler } from "../bundler/interfaces/IBundler.js" -import { EXECUTE_BATCH, EXECUTE_SINGLE } from "../bundler/utils/Constants.js" -import type { BaseExecutionModule } from "../modules/base/BaseExecutionModule.js" -import { BaseValidationModule } from "../modules/base/BaseValidationModule.js" -import { - type Execution, - type Module, - type ModuleInfo, - type SendUserOpParams, - createK1ValidatorModule, - createValidationModule, - moduleTypeIds -} from "../modules/index.js" -import type { K1ValidatorModule } from "../modules/validators/K1ValidatorModule.js" -import { - type FeeQuotesOrDataDto, - type FeeQuotesOrDataResponse, - type IHybridPaymaster, - type IPaymaster, - Paymaster, - type SponsorUserOperationDto -} from "../paymaster/index.js" -import { - BaseSmartContractAccount, - DeploymentState -} from "./BaseSmartContractAccount.js" -import { - Logger, - type SmartAccountSigner, - type StateOverrideSet, - type UserOperationStruct, - convertSigner -} from "./index.js" -import { - GENERIC_FALLBACK_SELECTOR, - type MODE_MODULE_ENABLE, - MODE_VALIDATION, - ModeType -} from "./utils/Constants.js" -import { - ADDRESS_ZERO, - ERC20_ABI, - ERROR_MESSAGES, - MAGIC_BYTES, - NATIVE_TOKEN_ALIAS, - SENTINEL_ADDRESS -} from "./utils/Constants.js" -import type { - BalancePayload, - BiconomyTokenPaymasterRequest, - BigNumberish, - BuildUserOpOptions, - CounterFactualAddressParam, - NexusSmartAccountConfig, - NexusSmartAccountConfigConstructorProps, - NonceOptions, - PaymasterUserOperationDto, - SupportedToken, - Transaction, - TransferOwnershipCompatibleModule, - WithdrawalRequest -} from "./utils/Types.js" -import { addressEquals, isNullOrUndefined, packUserOp } from "./utils/Utils.js" - -export class NexusSmartAccount extends BaseSmartContractAccount { - private index: bigint - - private chainId: number - - paymaster?: IPaymaster - - bundler?: IBundler - - private accountContract?: GetContractReturnType< - typeof NexusAbi, - PublicClient | WalletClient - > - - // private scanForUpgradedAccountsFromV1!: boolean - - // private maxIndexForScan!: bigint - - // Validation module responsible for account deployment initCode. This acts as a default authorization module. - defaultValidationModule!: BaseValidationModule - - // Deployed Smart Account can have more than one module enabled. When sending a transaction activeValidationModule is used to prepare and validate userOp signature. - activeValidationModule!: BaseValidationModule - - installedExecutors: BaseExecutionModule[] = [] - activeExecutionModule?: BaseExecutionModule - k1ValidatorAddress: Address - - private constructor( - readonly nexusSmartAccountConfig: NexusSmartAccountConfigConstructorProps - ) { - const resolvedEntryPointAddress = - (nexusSmartAccountConfig.entryPointAddress as Hex) ?? - contracts.entryPoint.address - - super({ - ...nexusSmartAccountConfig, - entryPointAddress: resolvedEntryPointAddress, - accountAddress: - (nexusSmartAccountConfig.accountAddress as Hex) ?? undefined, - factoryAddress: nexusSmartAccountConfig.factoryAddress - }) - - this.k1ValidatorAddress = nexusSmartAccountConfig.k1ValidatorAddress - const chain = nexusSmartAccountConfig.chain - - this.defaultValidationModule = - nexusSmartAccountConfig.defaultValidationModule - this.activeValidationModule = nexusSmartAccountConfig.activeValidationModule - - this.index = nexusSmartAccountConfig.index ?? 0n - this.chainId = chain.id - this.bundler = nexusSmartAccountConfig.bundler - - if (nexusSmartAccountConfig.paymasterUrl) { - this.paymaster = new Paymaster({ - paymasterUrl: nexusSmartAccountConfig.paymasterUrl - }) - } else { - this.paymaster = nexusSmartAccountConfig.paymaster - } - - this.bundler = nexusSmartAccountConfig.bundler - - // Added bang operator to avoid null check as the constructor have these params as optional - this.defaultValidationModule = - // biome-ignore lint/style/noNonNullAssertion: - nexusSmartAccountConfig.defaultValidationModule! - this.activeValidationModule = - // biome-ignore lint/style/noNonNullAssertion: - nexusSmartAccountConfig.activeValidationModule! - } - - /** - * Creates a new instance of NexusSmartAccount - * - * This method will create a NexusSmartAccount instance but will not deploy the Smart Account - * Deployment of the Smart Account will be donewith the first user operation. - * - * - Docs: https://docs.biconomy.io/account/integration#integration-1 - * - * @param nexusSmartAccountConfig - Configuration for initializing the NexusSmartAccount instance {@link NexusSmartAccountConfig}. - * @returns A promise that resolves to a new instance of NexusSmartAccount. - * @throws An error if something is wrong with the smart account instance creation. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient, NexusSmartAccount } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const bundlerUrl = "" // Retrieve bundler url from dashboard - * - * const smartAccountFromStaticCreate = await NexusSmartAccount.create({ signer, bundlerUrl }); - * - * // Is the same as... - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); - * - */ - public static async create( - nexusSmartAccountConfig: NexusSmartAccountConfig - ): Promise { - let resolvedSmartAccountSigner!: SmartAccountSigner - const defaultedRpcUrl = - nexusSmartAccountConfig.rpcUrl ?? - nexusSmartAccountConfig.chain.rpcUrls.default.http[0] - - const defaultedEntryPointAddress = - (nexusSmartAccountConfig.entryPointAddress ?? - contracts.entryPoint.address) as Hex - - // Signer needs to be initialised here before defaultValidationModule is set - if (nexusSmartAccountConfig.signer) { - const signerResult = await convertSigner( - nexusSmartAccountConfig.signer, - nexusSmartAccountConfig.rpcUrl - ) - resolvedSmartAccountSigner = signerResult.signer - } - - const bundler: IBundler = - nexusSmartAccountConfig.bundler ?? - new Bundler({ - // biome-ignore lint/style/noNonNullAssertion: always required - bundlerUrl: nexusSmartAccountConfig.bundlerUrl!, - chain: nexusSmartAccountConfig.chain - }) - - let defaultValidationModule = - nexusSmartAccountConfig.defaultValidationModule - - const k1ValidatorAddress = - nexusSmartAccountConfig.k1ValidatorAddress ?? - (contracts.k1Validator.address as Hex) - const factoryAddress = - nexusSmartAccountConfig.factoryAddress ?? - (contracts.k1ValidatorFactory.address as Hex) - - if (!defaultValidationModule) { - const newModule = await createK1ValidatorModule( - resolvedSmartAccountSigner, - k1ValidatorAddress - ) - defaultValidationModule = newModule as K1ValidatorModule - } - const activeValidationModule = - nexusSmartAccountConfig?.activeValidationModule ?? defaultValidationModule - if (!resolvedSmartAccountSigner) { - resolvedSmartAccountSigner = activeValidationModule.getSigner() - } - if (!resolvedSmartAccountSigner) { - throw new Error("signer required") - } - - const config: NexusSmartAccountConfigConstructorProps = { - ...nexusSmartAccountConfig, - factoryAddress, - k1ValidatorAddress, - defaultValidationModule, - activeValidationModule, - rpcUrl: defaultedRpcUrl, - bundler, - signer: resolvedSmartAccountSigner, - entryPointAddress: defaultedEntryPointAddress - } - - const smartAccount = new NexusSmartAccount(config) - await smartAccount.getDeploymentState() - return smartAccount - } - - override async getAddress(params?: CounterFactualAddressParam): Promise { - if (isNullOrUndefined(this.accountAddress)) { - const signerAddress = await this.signer.getAddress() - const index = params?.index ?? this.index - this.accountAddress = (await this.publicClient.readContract({ - address: this.factoryAddress, - abi: contracts.k1ValidatorFactory.abi, - functionName: "computeAccountAddress", - args: [signerAddress, index, [], 0] - })) as Hex - } - return this.accountAddress - } - public getAccountAddress = this.getAddress - - /** - * Returns an upper estimate for the gas spent on a specific user operation - * - * This method will fetch an approximate gas estimate for the user operation, given the current state of the network. - * It is regularly an overestimate, and the actual gas spent will likely be lower. - * It is unlikely to be an underestimate unless the network conditions rapidly change. - * - * @param transactions Array of {@link Transaction} to be sent. - * @param buildUseropDto {@link BuildUserOpOptions}. - * @returns Promise - The estimated gas cost in wei. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl, paymasterUrl }); // Retrieve bundler/paymaster url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const tx = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const amountInWei = await smartAccount.getGasEstimates([tx, tx], { - * paymasterServiceData: { - * mode: "SPONSORED", - * }, - * }); - * - * console.log(amountInWei.toString()); - * - */ - public async getGasEstimate( - transactions: Transaction[], - buildUseropDto?: BuildUserOpOptions - ): Promise { - const { - callGasLimit, - preVerificationGas, - verificationGasLimit, - maxFeePerGas - } = await this.buildUserOp(transactions, buildUseropDto) - - const _callGasLimit = BigInt(callGasLimit || 0) - const _preVerificationGas = BigInt(preVerificationGas || 0) - const _verificationGasLimit = BigInt(verificationGasLimit || 0) - const _maxFeePerGas = BigInt(maxFeePerGas || 0) - - if (!buildUseropDto?.paymasterServiceData?.mode) { - return ( - (_callGasLimit + _preVerificationGas + _verificationGasLimit) * - _maxFeePerGas - ) - } - return ( - (_callGasLimit + - BigInt(3) * _verificationGasLimit + - _preVerificationGas) * - _maxFeePerGas - ) - } - - /** - * Returns balances for the smartAccount instance. - * - * This method will fetch tokens info given an array of token addresses for the smartAccount instance. - * The balance of the native token will always be returned as the last element in the reponse array, with the address set to 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE. - * - * @param addresses - Optional. Array of asset addresses to fetch the balances of. If not provided, the method will return only the balance of the native token. - * @returns Promise> - An array of token balances (plus the native token balance) of the smartAccount instance. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const token = "0x747A4168DB14F57871fa8cda8B5455D8C2a8e90a"; - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); - * const [tokenBalanceFromSmartAccount, nativeTokenBalanceFromSmartAccount] = await smartAccount.getBalances([token]); - * - * console.log(tokenBalanceFromSmartAccount); - * // { - * // amount: 1000000000000000n, - * // decimals: 6, - * // address: "0x747A4168DB14F57871fa8cda8B5455D8C2a8e90a", - * // formattedAmount: "1000000", - * // chainId: 11155111 - * // } - * - * // or to get the nativeToken balance - * - * const [nativeTokenBalanceFromSmartAccount] = await smartAccount.getBalances(); - * - * console.log(nativeTokenBalanceFromSmartAccount); - * // { - * // amount: 1000000000000000n, - * // decimals: 18, - * // address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - * // formattedAmount: "1", - * // chainId: 11155111 - * // } - * - */ - public async getBalances( - addresses?: Array - ): Promise> { - const accountAddress = await this.getAddress() - const result: BalancePayload[] = [] - - if (addresses) { - const tokenContracts = addresses.map((address) => - getContract({ - address, - abi: parseAbi(ERC20_ABI), - client: this.publicClient - }) - ) - - const balancePromises = tokenContracts.map((tokenContract) => - tokenContract.read.balanceOf([accountAddress]) - ) as Promise[] - const decimalsPromises = tokenContracts.map((tokenContract) => - tokenContract.read.decimals() - ) as Promise[] - const [balances, decimalsPerToken] = await Promise.all([ - Promise.all(balancePromises), - Promise.all(decimalsPromises) - ]) - - balances.forEach((amount, index) => - result.push({ - amount, - decimals: decimalsPerToken[index], - address: addresses[index], - formattedAmount: formatUnits(amount, decimalsPerToken[index]), - chainId: this.chainId - }) - ) - } - - const balance = await this.publicClient.getBalance({ - address: accountAddress - }) - - result.push({ - amount: balance, - decimals: 18, - address: NATIVE_TOKEN_ALIAS, - formattedAmount: formatUnits(balance, 18), - chainId: this.chainId - }) - - return result - } - - /** - * Transfers funds from Smart Account to recipient (usually EOA) - * @param recipient - Address of the recipient - * @param withdrawalRequests - Array of withdrawal requests {@link WithdrawalRequest}. If withdrawal request is an empty array, it will transfer the balance of the native token. Using a paymaster will ensure no dust remains in the smart account. - * @param buildUseropDto - Optional. {@link BuildUserOpOptions} - * - * @returns Promise - An object containing the status of the transaction. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient, NATIVE_TOKEN_ALIAS } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonMumbai } from "viem/chains"; - * - * const token = "0x747A4168DB14F57871fa8cda8B5455D8C2a8e90a"; - * const signer = createWalletClient({ - * account, - * chain: polygonMumbai, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl, biconomyPaymasterApiKey }); - * - * const { wait } = await smartAccount.withdraw( - * [ - * { address: token }, // omit the amount to withdraw the full balance - * { address: NATIVE_TOKEN_ALIAS, amount: BigInt(1) } - * ], - * account.address, // Default recipient used if no recipient is present in the withdrawal request - * { - * paymasterServiceData: { mode: "SPONSORED" }, - * } - * ); - * - * // OR to withdraw all of the native token, leaving no dust in the smart account - * - * const { wait } = await smartAccount.withdraw([], account.address, { - * paymasterServiceData: { mode: "SPONSORED" }, - * }); - * - * const { success } = await wait(); - */ - public async withdraw( - withdrawalRequests?: WithdrawalRequest[] | null, - defaultRecipient?: Hex | null, - buildUseropDto?: BuildUserOpOptions - ): Promise { - const accountAddress = this.accountAddress ?? (await this.getAddress()) - - if ( - !defaultRecipient && - withdrawalRequests?.some(({ recipient }) => !recipient) - ) { - throw new Error(ERROR_MESSAGES.NO_RECIPIENT) - } - - // Remove the native token from the withdrawal requests - let tokenRequests = - withdrawalRequests?.filter( - ({ address }) => !addressEquals(address, NATIVE_TOKEN_ALIAS) - ) ?? [] - - // Check if the amount is not present in all withdrawal requests - const shouldFetchMaxBalances = tokenRequests.some(({ amount }) => !amount) - - // Get the balances of the tokens if the amount is not present in the withdrawal requests - if (shouldFetchMaxBalances) { - const balances = await this.getBalances( - tokenRequests.map(({ address }) => address) - ) - tokenRequests = tokenRequests.map(({ amount, address }, i) => ({ - address, - amount: amount ?? balances[i].amount - })) - } - - // Create the transactions - const txs: Transaction[] = tokenRequests.map( - ({ address, amount, recipient: recipientFromRequest }) => ({ - to: address, - data: encodeFunctionData({ - abi: parseAbi(ERC20_ABI), - functionName: "transfer", - args: [recipientFromRequest || defaultRecipient, amount] - }) - }) - ) - - // Check if eth alias is present in the original withdrawal requests - const nativeTokenRequest = withdrawalRequests?.find(({ address }) => - addressEquals(address, NATIVE_TOKEN_ALIAS) - ) - const hasNoRequests = !withdrawalRequests?.length - if (!!nativeTokenRequest || hasNoRequests) { - // Check that an amount is present in the withdrawal request, if no paymaster service data is present, as max amounts cannot be calculated without a paymaster. - if ( - !nativeTokenRequest?.amount && - !buildUseropDto?.paymasterServiceData?.mode - ) { - throw new Error(ERROR_MESSAGES.NATIVE_TOKEN_WITHDRAWAL_WITHOUT_AMOUNT) - } - - // get eth balance if not present in withdrawal requests - const nativeTokenAmountToWithdraw = - nativeTokenRequest?.amount ?? - (await this.publicClient.getBalance({ address: accountAddress })) - - txs.push({ - to: (nativeTokenRequest?.recipient ?? defaultRecipient) as Hex, - value: nativeTokenAmountToWithdraw - }) - } - - return this.sendTransaction(txs, buildUseropDto) - } - - async _getAccountContract(): Promise< - GetContractReturnType - > { - if (await this.isAccountDeployed()) { - if (!this.accountContract) { - this.accountContract = getContract({ - address: await this.getAddress(), - abi: NexusAbi, - client: this.publicClient as PublicClient - }) - } - return this.accountContract - } - throw new Error(ERROR_MESSAGES.ACCOUNT_NOT_DEPLOYED) - } - - isActiveValidationModuleDefined(): boolean { - if (!this.activeValidationModule) - throw new Error("Must provide an instance of active validation module.") - return true - } - - isDefaultValidationModuleDefined(): boolean { - if (!this.defaultValidationModule) - throw new Error("Must provide an instance of default validation module.") - return true - } - - /** - * Sets the active validation module on the NexusSmartAccount instance - * @param validationModule - BaseValidationModule instance - * - * @returns Promise - The BaseValidationModule instance. - */ - setActiveValidationModule( - validationModule: BaseValidationModule - ): BaseValidationModule { - if (validationModule instanceof BaseValidationModule) { - this.activeValidationModule = validationModule - } - return this.activeValidationModule - } - - /** - * Sets the active validation module on the NexusSmartAccount instance - * @param validationModuleAddress - Address of the validation module - * @param data - Initialization data for the validation module - * - * @returns Promise - The BaseValidationModule instance. - */ - async setActiveValidationModuleByAddress({ - validationModuleAddress, - data - }: { - validationModuleAddress: Address - data: Hex - }): Promise { - if (validationModuleAddress) { - this.activeValidationModule = await createValidationModule( - this.signer, - validationModuleAddress, - data - ) - return this.activeValidationModule - } - throw new Error("Validation module address is required") - } - - /** - * Sets the active executor module on the NexusSmartAccount instance - * @param executorModule - Address of the executor module - * @param data - Initialization data for the executor module - * - * @returns Promise - The BaseExecutionModule instance. - */ - setActiveExecutionModule( - executorModule: BaseExecutionModule - ): BaseExecutionModule { - this.activeExecutionModule = executorModule - return this.activeExecutionModule - } - - setDefaultValidationModule( - validationModule: BaseValidationModule - ): NexusSmartAccount { - if (validationModule instanceof BaseValidationModule) { - this.defaultValidationModule = validationModule - } - return this - } - - // async getV1AccountsUpgradedToV2( - // params: QueryParamsForAddressResolver - // ): Promise { - // const maxIndexForScan = params.maxIndexForScan ?? this.maxIndexForScan - - // const addressResolver = getContract({ - // address: ADDRESS_RESOLVER_ADDRESS, - // abi: AccountResolverAbi, - // client: { - // public: this.publicClient as PublicClient - // } - // }) - // // Note: depending on moduleAddress and moduleSetupData passed call this. otherwise could call resolveAddresses() - - // if (params.moduleAddress && params.moduleSetupData) { - // const result = await addressResolver.read.resolveAddressesFlexibleForV2([ - // params.eoaAddress, - // Number.parseInt(maxIndexForScan.toString()), // TODO: SHOULD BE A BIGINT BUT REQUIRED TO BE A NUMBER FOR THE CONTRACT - // params.moduleAddress, - // params.moduleSetupData - // ]) - - // const desiredV1Account = result.find( - // (smartAccountInfo: { - // factoryVersion: string - // currentVersion: string - // deploymentIndex: { toString: () => string } - // }) => - // smartAccountInfo.factoryVersion === "v1" && - // smartAccountInfo.currentVersion === "2.0.0" && - // smartAccountInfo.deploymentIndex === params.index - // ) - - // if (desiredV1Account) { - // const smartAccountAddress = desiredV1Account.accountAddress - // return smartAccountAddress - // } - // return ADDRESS_ZERO - // } - // return ADDRESS_ZERO - // } - - /** - * Return the value to put into the "initCode" field, if the account is not yet deployed. - * This value holds the "factory" address, followed by this account's information - */ - public override async getAccountInitCode(): Promise { - this.isDefaultValidationModuleDefined() - - if (await this.isAccountDeployed()) return "0x" - - const factoryData = (await this.getFactoryData()) as Hex - - return concatHex([this.factoryAddress, factoryData]) - } - - /** - * - * @param to { target } address of transaction - * @param value represents amount of native tokens - * @param data represent data associated with transaction - * @returns encoded data for execute function - */ - async encodeExecute(transaction: Transaction): Promise { - const mode = EXECUTE_SINGLE - const executionCalldata = encodePacked( - ["address", "uint256", "bytes"], - [ - transaction.to as Hex, - BigInt(transaction.value ?? 0n), - (transaction.data as Hex) ?? ("0x" as Hex) - ] - ) - return encodeFunctionData({ - abi: parseAbi([ - "function execute(bytes32 mode, bytes calldata executionCalldata) external" - ]), - functionName: "execute", - args: [mode, executionCalldata] - }) - } - - /** - * - * @param to { target } array of addresses in transaction - * @param value represents array of amount of native tokens associated with each transaction - * @param data represent array of data associated with each transaction - * @returns encoded data for executeBatch function - */ - async encodeExecuteBatch(transactions: Transaction[]): Promise { - const executionAbiParams: AbiParameter = { - type: "tuple[]", - components: [ - { name: "target", type: "address" }, - { name: "value", type: "uint256" }, - { name: "callData", type: "bytes" } - ] - } - - const executions = transactions.map((tx) => ({ - target: tx.to, - callData: tx.data ?? "0x", - value: BigInt(tx.value ?? 0n) - })) - - const executionCalldataPrep = encodeAbiParameters( - [executionAbiParams], - [executions] - ) - - return encodeFunctionData({ - abi: parseAbi([ - "function execute(bytes32 mode, bytes calldata executionCalldata) external" - ]), - functionName: "execute", - args: [EXECUTE_BATCH, executionCalldataPrep] - }) - } - - // dummy signature depends on the validation module supplied. - async getDummySignatures(): Promise { - // const params = { ...(this.sessionData ? this.sessionData : {}), ..._params } - this.isActiveValidationModuleDefined() - return this.activeValidationModule.getDummySignature() as Hex - } - - // TODO: review this - getDummySignature(): Hex { - throw new Error("Method not implemented! Call getDummySignatures instead.") - } - - // Might use provided paymaster instance to get dummy data (from pm service) - getDummyPaymasterData(): string { - return "0x" - } - - validateUserOp( - // userOp: Partial, - // requiredFields: UserOperationKey[] - ): boolean { - // console.log(userOp, "userOp"); - // for (const field of requiredFields) { - // if (isNullOrUndefined(userOp[field])) { - // throw new Error(`${String(field)} is missing in the UserOp`) - // } - // } - return true - } - - async signUserOp( - userOp: Partial - ): Promise { - this.isActiveValidationModuleDefined() - // TODO REMOVE COMMENT AND CHECK FOR PIMLICO USER OP FIELDS - // const requiredFields: UserOperationKey[] = [ - // "sender", - // "nonce", - // "callGasLimit", - // "signature", - // "maxFeePerGas", - // "maxPriorityFeePerGas", - // ] - // this.validateUserOp(userOp, requiredFields) - const userOpHash = await this.getUserOpHash(userOp) - - const eoaSignature = (await this.activeValidationModule.signUserOpHash( - userOpHash - )) as Hex - - userOp.signature = eoaSignature - return userOp as UserOperationStruct - } - - getSignatureWithModuleAddress( - moduleSignature: Hex, - moduleAddress?: Hex - ): Hex { - const moduleAddressToUse = - moduleAddress ?? (this.activeValidationModule.getAddress() as Hex) - return encodePacked( - ["address", "bytes"], - [moduleAddressToUse, moduleSignature] - ) - } - - private async getPaymasterFeeQuotesOrData( - userOp: Partial, - feeQuotesOrData: FeeQuotesOrDataDto - ): Promise { - const paymaster = this - .paymaster as IHybridPaymaster - const tokenList = feeQuotesOrData?.preferredToken - ? [feeQuotesOrData?.preferredToken] - : feeQuotesOrData?.tokenList?.length - ? feeQuotesOrData?.tokenList - : [] - return paymaster.getPaymasterFeeQuotesOrData(userOp, { - ...feeQuotesOrData, - tokenList - }) - } - - /** - * - * @description This function will retrieve fees from the paymaster in erc20 mode - * - * @param manyOrOneTransactions Array of {@link Transaction} to be batched and sent. Can also be a single {@link Transaction}. - * @param buildUseropDto {@link BuildUserOpOptions}. - * @returns Promise - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const transaction = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const feeQuotesResponse: FeeQuotesOrDataResponse = await smartAccount.getTokenFees(transaction, { paymasterServiceData: { mode: "ERC20" } }); - * - * const userSeletedFeeQuote = feeQuotesResponse.feeQuotes?.[0]; - * - * const { wait } = await smartAccount.sendTransaction(transaction, { - * paymasterServiceData: { - * mode: "ERC20", - * feeQuote: userSeletedFeeQuote, - * spender: feeQuotesResponse.tokenPaymasterAddress, - * }, - * }); - * - * const { success, receipt } = await wait(); - * - */ - public async getTokenFees( - manyOrOneTransactions: Transaction | Transaction[], - buildUseropDto: BuildUserOpOptions - ): Promise { - const txs = Array.isArray(manyOrOneTransactions) - ? manyOrOneTransactions - : [manyOrOneTransactions] - const userOp = await this.buildUserOp(txs, buildUseropDto) - if (!buildUseropDto.paymasterServiceData) - throw new Error("paymasterServiceData was not provided") - return this.getPaymasterFeeQuotesOrData( - userOp, - buildUseropDto.paymasterServiceData - ) - } - - /** - * - * @description This function will return an array of supported tokens from the erc20 paymaster associated with the Smart Account - * @returns Promise<{@link SupportedToken}> - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl, biconomyPaymasterApiKey }); // Retrieve bundler url from dashboard - * const tokens = await smartAccount.getSupportedTokens(); - * - * // [ - * // { - * // symbol: "USDC", - * // tokenAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", - * // decimal: 6, - * // logoUrl: "https://assets.coingecko.com/coins/images/279/large/usd-coin.png?1595353707", - * // premiumPercentage: 0.1, - * // } - * // ] - * - */ - public async getSupportedTokens(): Promise { - const feeQuotesResponse = await this.getTokenFees( - { - data: "0x", - value: BigInt(0), - to: await this.getAddress() - }, - { - paymasterServiceData: { mode: "ERC20" } - } - ) - - return await Promise.all( - (feeQuotesResponse?.feeQuotes ?? []).map(async (quote) => { - const [tokenBalance] = await this.getBalances([ - quote.tokenAddress as Hex - ]) - return { - symbol: quote.symbol, - tokenAddress: quote.tokenAddress, - decimal: quote.decimal, - logoUrl: quote.logoUrl, - premiumPercentage: quote.premiumPercentage, - balance: tokenBalance - } - }) - ) - } - - /** - * - * @param userOp - * @param params - * @description This function will take a user op as an input, sign it with the owner key, and send it to the bundler. - * @returns Promise - * Sends a user operation - * - * - Docs: https://docs.biconomy.io/account/methods#senduserop- - * - * @param userOp Partial<{@link UserOperationStruct}> the userOp params to be sent. - * @param params {@link SendUserOpParams}. - * @returns Promise<{@link Hash}> that you can use to track the user operation. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const transaction = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const userOp = await smartAccount.buildUserOp([transaction]); - * - * const { wait } = await smartAccount.sendUserOp(userOp); - * const { success, receipt } = await wait(); - * - */ - async sendUserOp({ - signature, - ...userOpWithoutSignature - }: Partial): Promise { - const signedUserOp = await this.signUserOp(userOpWithoutSignature) - - return await this.sendSignedUserOp(signedUserOp) - } - - /** - * - * @param userOp - The signed user operation to send - * @param simulationType - The type of simulation to perform ("validation" | "validation_and_execution") - * @description This function call will take 'signedUserOp' as input and send it to the bundler - * @returns - */ - async sendSignedUserOp(userOp: UserOperationStruct): Promise { - // TODO REMOVE COMMENT AND CHECK FOR PIMLICO USER OP FIELDS - // const requiredFields: UserOperationKey[] = [ - // "sender", - // "nonce", - // "verificationGasLimit", - // "preVerificationGas", - // "maxFeePerGas", - // "maxPriorityFeePerGas", - // "signature", - // ] - // this.validateUserOp(userOp, requiredFields) - - if (!this.bundler) throw new Error("Bundler is not provided") - - console.log("sendSignedUserOp", { userOp }) - - return { wait: () => {}, hash: undefined } as unknown as UserOpResponse - // return await this.bundler.sendUserOp(userOp) - } - - /** - * Packs gas values into the format required by PackedUserOperation. - * @param callGasLimit Call gas limit. - * @param verificationGasLimit Verification gas limit. - * @param maxFeePerGas Maximum fee per gas. - * @param maxPriorityFeePerGas Maximum priority fee per gas. - * @returns An object containing packed gasFees and accountGasLimits. - */ - public packGasValues( - callGasLimit: BigNumberish, - verificationGasLimit: BigNumberish, - maxFeePerGas: BigNumberish, - maxPriorityFeePerGas: BigNumberish - ) { - const gasFees = concat([ - pad(toHex(maxPriorityFeePerGas ?? 0n), { - size: 16 - }), - pad(toHex(maxFeePerGas ?? 0n), { size: 16 }) - ]) - const accountGasLimits = concat([ - pad(toHex(verificationGasLimit ?? 0n), { - size: 16 - }), - pad(toHex(callGasLimit ?? 0n), { size: 16 }) - ]) - - return { gasFees, accountGasLimits } - } - - async getUserOpHash(userOp: Partial): Promise { - const packedUserOp = packUserOp(userOp) - const userOpHash = keccak256(packedUserOp as Hex) - const enc = encodeAbiParameters( - parseAbiParameters("bytes32, address, uint256"), - [userOpHash, this.entryPoint.address, BigInt(this.chainId)] - ) - return keccak256(enc) - } - - async estimateUserOpGas( - userOp: Partial, - stateOverrideSet?: StateOverrideSet - ): Promise> { - if (!this.bundler) throw new Error("Bundler is not provided") - const finalUserOp = userOp - - if (!userOp.maxFeePerGas && !userOp.maxPriorityFeePerGas) { - const feeData = await this.publicClient.estimateFeesPerGas() - if (feeData.maxFeePerGas?.toString()) { - finalUserOp.maxFeePerGas = feeData.maxFeePerGas - } else if (feeData.gasPrice) { - finalUserOp.maxFeePerGas = feeData.gasPrice - } else { - finalUserOp.maxFeePerGas = await this.publicClient.getGasPrice() - } - - if (feeData.maxPriorityFeePerGas?.toString()) { - finalUserOp.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas - } else if (feeData.gasPrice) { - finalUserOp.maxPriorityFeePerGas = feeData.gasPrice ?? 0n - } else { - finalUserOp.maxPriorityFeePerGas = await this.publicClient.getGasPrice() - } - } - - const { callGasLimit, verificationGasLimit, preVerificationGas } = - await this.bundler.estimateUserOpGas(userOp, stateOverrideSet) - finalUserOp.verificationGasLimit = - verificationGasLimit ?? userOp.verificationGasLimit - finalUserOp.callGasLimit = callGasLimit ?? userOp.callGasLimit - finalUserOp.preVerificationGas = - preVerificationGas ?? userOp.preVerificationGas - - return finalUserOp - } - - override async getNonce(validationMode?: "0x00" | "0x01"): Promise { - try { - const vm = - this.activeValidationModule.moduleAddress ?? - this.defaultValidationModule.moduleAddress - const key = concat(["0x000000", validationMode ?? MODE_VALIDATION, vm]) - const accountAddress = await this.getAddress() - return (await this.entryPoint.read.getNonce([ - accountAddress, - BigInt(key) - ])) as bigint - } catch (e) { - return BigInt(0) - } - } - - private async getBuildUserOpNonce( - nonceOptions: NonceOptions | undefined - ): Promise { - let nonce = BigInt(0) - try { - if (nonceOptions?.nonceOverride) { - nonce = BigInt(nonceOptions?.nonceOverride) - } else { - nonce = await this.getNonce(nonceOptions?.validationMode) - } - } catch (error) { - // Not throwing this error as nonce would be 0 if this.getNonce() throw exception, which is expected flow for undeployed account - Logger.warn( - "Error while getting nonce for the account. This is expected for undeployed accounts set nonce to 0" - ) - } - return nonce - } - - /** - * Transfers ownership of the smart account to a new owner. - * @param newOwner The address of the new owner. - * @param moduleAddress {@link TransferOwnershipCompatibleModule} The address of the validation module (ECDSA Ownership Module or Multichain Validation Module). - * @param buildUseropDto {@link BuildUserOpOptions}. Optional parameter - * @returns A Promise that resolves to a Hash or rejects with an Error. - * @description This function will transfer ownership of the smart account to a new owner. If you use session key manager module, after transferring the ownership - * you will need to re-create a session for the smart account with the new owner (signer) and specify "accountAddress" in "createSmartAccountClient" function. - * @example - * ```typescript - * - * let walletClient = createWalletClient({ - account, - chain: baseSepolia, - transport: http() - }); - - let smartAccount = await createSmartAccountClient({ - signer: walletClient, - paymasterUrl: "https://paymaster.biconomy.io/api/v1/...", - bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`, - chainId: 84532 - }); - const response = await smartAccount.transferOwnership(newOwner, DEFAULT_ECDSA_OWNERSHIP_MODULE, {paymasterServiceData: {mode: "SPONSORED"}}); - - walletClient = createWalletClient({ - newOwnerAccount, - chain: baseSepolia, - transport: http() - }) - - smartAccount = await createSmartAccountClient({ - signer: walletClient, - paymasterUrl: "https://paymaster.biconomy.io/api/v1/...", - bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`, - chainId: 84532, - accountAddress: await smartAccount.getAddress() - }) - * ``` - */ - async transferOwnership( - newOwner: Address, - moduleAddress: TransferOwnershipCompatibleModule, - buildUseropDto?: BuildUserOpOptions - ): Promise { - const encodedCall = encodeFunctionData({ - abi: parseAbi(["function transferOwnership(address newOwner) public"]), - functionName: "transferOwnership", - args: [newOwner] - }) - const transaction = { - to: moduleAddress, - data: encodedCall - } - const userOpResponse: UserOpResponse = await this.sendTransaction( - transaction, - buildUseropDto - ) - return userOpResponse - } - - /** - * Sends a transaction (builds and sends a user op in sequence) - * - * - Docs: https://docs.biconomy.io/account/methods#sendtransaction- - * - * @param manyOrOneTransactions Array of {@link Transaction} to be batched and sent. Can also be a single {@link Transaction}. - * @param buildUseropDto {@link BuildUserOpOptions}. - * @returns Promise<{@link Hash}> that you can use to track the user operation. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const transaction = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const { waitForTxHash } = await smartAccount.sendTransaction(transaction); - * const { transactionHash, userOperationReceipt } = await wait(); - * - * @remarks - * This example shows how to increase the estimated gas values for a transaction using `gasOffset` parameter. - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const transaction = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const { waitForTxHash } = await smartAccount.sendTransaction(transaction, { - * gasOffset: { - * verificationGasLimitOffsetPct: 25, // 25% increase for the already estimated gas limit - * preVerificationGasOffsetPct: 10 // 10% increase for the already estimated gas limit - * } - * }); - * const { transactionHash, userOperationReceipt } = await wait(); - * - */ - async sendTransaction( - manyOrOneTransactions: Transaction | Transaction[], - buildUseropDto?: BuildUserOpOptions - ): Promise { - const userOp = await this.buildUserOp( - Array.isArray(manyOrOneTransactions) - ? manyOrOneTransactions - : [manyOrOneTransactions], - buildUseropDto - ) - const response = await this.sendUserOp(userOp) - this.setDeploymentState(response) // don't wait for this to finish... - return response - } - - public async setDeploymentState({ wait }: UserOpResponse) { - if (this.deploymentState === DeploymentState.DEPLOYED) return - const { success } = await wait() - if (success) { - this.deploymentState = DeploymentState.DEPLOYED - } - } - - async sendTransactionWithExecutor( - manyOrOneTransactions: Transaction | Transaction[], - ownedAccountAddress?: Address - // buildUseropDto?: BuildUserOpOptions - ): Promise { - return await this.executeFromExecutor( - Array.isArray(manyOrOneTransactions) - ? manyOrOneTransactions - : [manyOrOneTransactions], - ownedAccountAddress - // buildUseropDto - ) - } - - /** - * Builds a user operation - * - * This method will also simulate the validation and execution of the user operation, telling the user if the user operation will be successful or not. - * - * - Docs: https://docs.biconomy.io/account/methods#builduserop- - * - * @param transactions Array of {@link Transaction} to be sent. - * @param buildUseropDto {@link BuildUserOpOptions}. - * @returns Promise> the built user operation to be sent. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard - * const encodedCall = encodeFunctionData({ - * abi: CounterAbi, - * functionName: "incrementNumber", - * args: ["0x..."], - * }); - * - * const transaction = { - * to: mockAddresses.Counter, - * data: encodedCall - * } - * - * const userOp = await smartAccount.buildUserOp([{ to: "0x...", data: encodedCall }]); - * - */ - async buildUserOp( - transactions: Transaction[], - buildUseropDto?: BuildUserOpOptions - ): Promise> { - const dummySignatureFetchPromise = this.getDummySignatures() - const [nonceFromFetch, dummySignature] = await Promise.all([ - this.getBuildUserOpNonce(buildUseropDto?.nonceOptions), - dummySignatureFetchPromise, - this.getInitCode() // Not used, but necessary to determine if the account is deployed. Will return immediately if the account is already deployed - ]) - - if (transactions.length === 0) { - throw new Error("Transactions array cannot be empty") - } - let callData: Hex = "0x" - if (!buildUseropDto?.useEmptyDeployCallData) { - if (transactions.length > 1 || buildUseropDto?.forceEncodeForBatch) { - callData = await this.encodeExecuteBatch(transactions) - } else { - callData = await this.encodeExecute(transactions[0]) - } - } - - const factoryData = await this.getFactoryData() - let userOp: Partial = { - sender: (await this.getAddress()) as Hex, - nonce: nonceFromFetch, - factoryData, - callData - } - - console.log({ nonceFromFetch }) - - if (!(await this.isAccountDeployed())) { - userOp.factory = this.factoryAddress - } - - userOp.signature = dummySignature - - const gasFeeValues: GetUserOperationGasPriceReturnType | undefined = - await this.bundler?.getGasFeeValues() - - userOp.maxFeePerGas = gasFeeValues?.fast.maxFeePerGas ?? 0n - userOp.maxPriorityFeePerGas = gasFeeValues?.fast.maxPriorityFeePerGas ?? 0n - - userOp = await this.estimateUserOpGas(userOp) - - if (buildUseropDto?.paymasterServiceData?.mode === "SPONSORED") { - userOp = await this.getPaymasterAndData( - userOp, - buildUseropDto?.paymasterServiceData - ) - } - - return userOp - } - - private async getPaymasterAndData( - userOp: Partial, - paymasterServiceData: PaymasterUserOperationDto - ): Promise { - const paymaster = this - .paymaster as IHybridPaymaster - const paymasterData = await paymaster.getPaymasterAndData( - userOp, - paymasterServiceData - ) - const userOpStruct = { - ...userOp, - ...paymasterData, - callGasLimit: BigInt(userOp.callGasLimit ?? 0n), - verificationGasLimit: BigInt(userOp.verificationGasLimit ?? 0n), - preVerificationGas: BigInt(userOp.preVerificationGas ?? 0n), - sender: (await this.getAddress()) as Hex, - paymasterAndData: undefined - // paymasterAndData: paymasterData?.paymasterAndData as Hex - } as UserOperationStruct - - return userOpStruct - } - - private validateUserOpAndPaymasterRequest( - userOp: Partial, - tokenPaymasterRequest: BiconomyTokenPaymasterRequest - ): void { - if (isNullOrUndefined(userOp.callData)) { - throw new Error("UserOp callData cannot be undefined") - } - - const feeTokenAddress = tokenPaymasterRequest?.feeQuote?.tokenAddress - Logger.warn("Requested fee token is ", feeTokenAddress) - - if (!feeTokenAddress || feeTokenAddress === ADDRESS_ZERO) { - throw new Error( - "Invalid or missing token address. Token address must be part of the feeQuote in tokenPaymasterRequest" - ) - } - - const spender = tokenPaymasterRequest?.spender - Logger.warn("Spender address is ", spender) - - if (!spender || spender === ADDRESS_ZERO) { - throw new Error( - "Invalid or missing spender address. Sepnder address must be part of tokenPaymasterRequest" - ) - } - } - - /** - * - * @param userOp partial user operation without signature and paymasterAndData - * @param tokenPaymasterRequest This dto provides information about fee quote. Fee quote is received from earlier request getFeeQuotesOrData() to the Biconomy paymaster. - * maxFee and token decimals from the quote, along with the spender is required to append approval transaction. - * @notice This method should be called when gas is paid in ERC20 token using TokenPaymaster - * @description Optional method to update the userOp.calldata with batched transaction which approves the paymaster spender with necessary amount(if required) - * @returns updated userOp with new callData, callGasLimit - */ - async buildTokenPaymasterUserOp( - userOp: Partial, - tokenPaymasterRequest: BiconomyTokenPaymasterRequest - ): Promise> { - this.validateUserOpAndPaymasterRequest(userOp, tokenPaymasterRequest) - try { - Logger.warn( - "Received information about fee token address and quote ", - tokenPaymasterRequest.toString() - ) - - if (this.paymaster && this.paymaster instanceof Paymaster) { - // Make a call to paymaster.buildTokenApprovalTransaction() with necessary details - - // Review: might request this form of an array of Transaction - const approvalRequest: Transaction = await ( - this.paymaster as IHybridPaymaster - ).buildTokenApprovalTransaction(tokenPaymasterRequest) - Logger.warn("ApprovalRequest is for erc20 token ", approvalRequest.to) - - if ( - approvalRequest.data === "0x" || - approvalRequest.to === ADDRESS_ZERO - ) { - return userOp - } - - if (isNullOrUndefined(userOp.callData)) { - throw new Error("UserOp callData cannot be undefined") - } - - const decodedSmartAccountData = decodeFunctionData({ - abi: NexusAbi, - data: (userOp.callData as Hex) ?? "0x" - }) - - if (!decodedSmartAccountData) { - throw new Error( - "Could not parse userOp call data for this smart account" - ) - } - - const smartAccountExecFunctionName = - decodedSmartAccountData.functionName - - Logger.warn( - `Originally an ${smartAccountExecFunctionName} method call for Biconomy Account V2` - ) - - const initialTransaction: Transaction = { - to: "0x", - data: "0x", - value: 0n - } - if ( - smartAccountExecFunctionName === "execute" || - smartAccountExecFunctionName === "executeFromExecutor" - ) { - const methodArgsSmartWalletExecuteCall = - decodedSmartAccountData.args ?? [] - const toOriginal = - (methodArgsSmartWalletExecuteCall[0] as Hex) ?? "0x" - const valueOriginal = methodArgsSmartWalletExecuteCall[1] ?? 0n - // @ts-ignore - const dataOriginal = methodArgsSmartWalletExecuteCall?.[2] ?? "0x" - - initialTransaction.to = toOriginal - initialTransaction.value = valueOriginal - initialTransaction.data = dataOriginal - } else { - throw new Error( - `Unsupported method call: ${smartAccountExecFunctionName}` - ) - } - - const finalUserOp: Partial = { - ...userOp, - callData: await this.encodeExecuteBatch([ - approvalRequest, - initialTransaction - ]) - } - - return finalUserOp - } - } catch (error) { - Logger.log("Failed to update userOp. Sending back original op") - Logger.error( - "Failed to update callData with error", - JSON.stringify(error) - ) - return userOp - } - return userOp - } - - async signUserOpHash(userOpHash: string, params?: ModuleInfo): Promise { - this.isActiveValidationModuleDefined() - const moduleSig = (await this.signUserOpHash(userOpHash, params)) as Hex - - return moduleSig - } - - /** - * Deploys the smart contract - * - * This method will deploy a Smart Account contract. It is useful for deploying in a moment when you know that gas prices are low, - * and you want to deploy the account before sending the first user operation. This step can otherwise be skipped, - * as the deployment will alternatively be bundled with the first user operation. - * - * @param buildUseropDto {@link BuildUserOpOptions}. - * @returns Promise<{@link Hash}> that you can use to track the user operation. - * @error Throws an error if the account has already been deployed. - * @error Throws an error if the account has not enough native token balance to deploy, if not using a paymaster. - * - * @example - * import { createClient } from "viem" - * import { createSmartAccountClient } from "@biconomy/account" - * import { createWalletClient, http } from "viem"; - * import { polygonAmoy } from "viem/chains"; - * - * const signer = createWalletClient({ - * account, - * chain: polygonAmoy, - * transport: http(), - * }); - * - * const smartAccount = await createSmartAccountClient({ - * signer, - * biconomyPaymasterApiKey, - * bundlerUrl - * }); - * - * // If you want to use a paymaster... - * const { wait } = await smartAccount.deploy({ - * paymasterServiceData: { mode: "SPONSORED" }, - * }); - * - * // Or if you can't use a paymaster send native token to this address: - * const counterfactualAddress = await smartAccount.getAddress(); - * - * // Then deploy the account - * const { wait } = await smartAccount.deploy(); - * - * const { success, receipt } = await wait(); - * - */ - public async deploy( - buildUseropDto?: BuildUserOpOptions - ): Promise { - const accountAddress = this.accountAddress ?? (await this.getAddress()) - - // Check that the account has not already been deployed - const byteCode = await this.publicClient?.getBytecode({ - address: accountAddress as Hex - }) - - if (byteCode !== undefined) { - throw new Error(ERROR_MESSAGES.ACCOUNT_ALREADY_DEPLOYED) - } - - // Check that the account has enough native token balance to deploy, if not using a paymaster - if (!buildUseropDto?.paymasterServiceData?.mode) { - const nativeTokenBalance = await this.publicClient?.getBalance({ - address: accountAddress - }) - - if (nativeTokenBalance === BigInt(0)) { - throw new Error(ERROR_MESSAGES.NO_NATIVE_TOKEN_BALANCE_DURING_DEPLOY) - } - } - - const useEmptyDeployCallData = true - - return this.sendTransaction( - { - to: accountAddress, - data: "0x" - }, - { ...buildUseropDto, useEmptyDeployCallData } - ) - } - - async getFactoryData() { - if (await this.isAccountDeployed()) return undefined - this.isDefaultValidationModuleDefined() - - const signerAddress = await this.signer.getAddress() - - return encodeFunctionData({ - abi: contracts.k1ValidatorFactory.abi, - functionName: "createAccount", - args: [signerAddress, this.index, [], 0] - }) - } - - async signMessage(message: string | Uint8Array): Promise { - let signature: Hex - this.isActiveValidationModuleDefined() - const dataHash = typeof message === "string" ? toBytes(message) : message - - signature = - (await this.activeValidationModule.signMessage(dataHash)) ?? - this.defaultValidationModule.signMessage(dataHash) - signature = encodePacked( - ["address", "bytes"], - [ - this.activeValidationModule.getAddress() ?? - this.defaultValidationModule.getAddress(), - signature - ] - ) - if (await this.isAccountDeployed()) { - return signature - } - // If the account is not deployed, follow ERC 6492 - const abiEncodedMessage = encodeAbiParameters( - [ - { - type: "address", - name: "create2Factory" - }, - { - type: "bytes", - name: "factoryCalldata" - }, - { - type: "bytes", - name: "originalERC1271Signature" - } - ], - [ - this.getFactoryAddress() ?? "0x", - (await this.getFactoryData()) ?? "0x", - signature - ] - ) - return concat([abiEncodedMessage, MAGIC_BYTES]) - } - - async getIsValidSignatureData( - messageHash: Hex, - signature: Hex - ): Promise { - return encodeFunctionData({ - abi: NexusAbi, - functionName: "isValidSignature", - args: [messageHash, signature] - }) - } - - async isModuleInstalled(module: Module) { - if (await this.isAccountDeployed()) { - const accountContract = await this._getAccountContract() - const result = await accountContract.read.isModuleInstalled([ - BigInt(moduleTypeIds[module.type]), - module.moduleAddress, - module.data ?? "0x" - ]) - return result - } - return false - } - - getSmartAccountOwner(): SmartAccountSigner { - return this.signer - } - - async installModule( - module: Module, - buildUserOpOptions?: BuildUserOpOptions - ): Promise { - let execution: Execution - switch (module.type) { - case "validator": - case "executor": - case "hook": - execution = await this._installModule({ - moduleAddress: module.moduleAddress, - type: module.type, - data: module.data - }) - return this.sendTransaction( - { - to: execution.target, - data: execution.callData, - value: execution.value - }, - buildUserOpOptions - ) - case "fallback": - if (!module.selector || !module.callType) { - throw new Error( - "Selector param is required for a Fallback Handler Module" - ) - } - execution = await this._uninstallFallback(module) - return this.sendTransaction( - { - to: execution.target, - data: execution.callData, - value: execution.value - }, - buildUserOpOptions - ) - default: - throw new Error(`Unknown module type ${module.type}`) - } - } - - async _installModule(module: Module): Promise { - const isInstalled = await this.isModuleInstalled(module) - - if (!isInstalled) { - const execution = { - target: await this.getAddress(), - value: BigInt(0), - callData: encodeFunctionData({ - functionName: "installModule", - abi: parseAbi([ - "function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external payable" - ]), - args: [ - BigInt(moduleTypeIds[module.type]), - module.moduleAddress, - module.data || "0x" - ] - }) - } - return execution - } - throw new Error("Module already installed") - } - - async uninstallModule( - module: Module, - buildUserOpOptions?: BuildUserOpOptions - ): Promise { - let execution: Execution - switch (module.type) { - case "validator": - case "executor": - case "hook": - execution = await this._uninstallModule(module) - return await this.sendTransaction( - { - to: execution.target, - data: execution.callData, - value: execution.value - }, - buildUserOpOptions - ) - case "fallback": - if (!module.selector) { - throw new Error( - `Selector param is required for module type ${module.type}` - ) - } - execution = await this._uninstallFallback(module) - return await this.sendTransaction( - { - to: execution.target, - data: execution.callData, - value: execution.value - }, - buildUserOpOptions - ) - default: - throw new Error(`Unknown module type ${module.type}`) - } - } - - private _getModuleIndex( - installedModules: - | Awaited> - | Awaited>, - module: Module - ): Hex { - const index = installedModules.indexOf(getAddress(module.moduleAddress)) - if (index === 0) { - return SENTINEL_ADDRESS - } - if (index > 0) { - // @ts-ignore: TODO: Gabi This looks wrong - return installedModules[index - 1] - } - throw new Error( - `Module ${module.moduleAddress} not found in installed modules` - ) - } - - async getPreviousModule(module: Module) { - if (module.type === "validator") { - const installedModules = await this.getInstalledValidators() - return this._getModuleIndex(installedModules, module) - } - if (module.type === "executor") { - const installedModules = await this.getInstalledExecutors() - return this._getModuleIndex(installedModules, module) - } - throw new Error(`Unknown module type ${module.type}`) - } - - private async _uninstallFallback(module: Module): Promise { - let execution: Execution - - const isInstalled = await this.isModuleInstalled(module) - - if (isInstalled) { - execution = { - target: await this.getAddress(), - value: BigInt(0), - callData: encodeFunctionData({ - functionName: "uninstallModule", - abi: parseAbi([ - "function uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)" - ]), - args: [ - BigInt(moduleTypeIds[module.type]), - module.moduleAddress, - encodePacked( - ["bytes4", "bytes"], - [module.selector ?? "0x", module.data ?? "0x"] - ) - ] - }) - } - return execution - } - throw new Error("Module is not installed") - } - - private async _uninstallModule(module: Module): Promise { - let execution: Execution - const isInstalled = await this.isModuleInstalled(module) - - if (isInstalled) { - let moduleData = module.data || "0x" - if (module.type === "validator" || module.type === "executor") { - const prev = await this.getPreviousModule(module) - moduleData = encodeAbiParameters( - [ - { name: "prev", type: "address" }, - { name: "disableModuleData", type: "bytes" } - ], - [prev, moduleData] - ) - } - execution = { - target: await this.getAddress(), - value: BigInt(0), - callData: encodeFunctionData({ - functionName: "uninstallModule", - abi: parseAbi([ - "function uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)" - ]), - args: [ - BigInt(moduleTypeIds[module.type]), - module.moduleAddress, - moduleData - ] - }) - } - return execution - } - throw new Error("Module is not installed") - } - - private async executeFromExecutor( - transactions: Transaction[], - ownedAccountAddress?: Address - // buildUseropDto?: BuildUserOpOptions - ): Promise { - if (this.activeExecutionModule) { - if (transactions.length > 1) { - const executions: { target: Hex; value: bigint; callData: Hex }[] = - transactions.map((tx) => { - return { - target: tx.to as Hex, - callData: (tx.data ?? "0x") as Hex, - value: BigInt(tx.value ?? 0n) - } - }) - return await this.activeExecutionModule?.execute( - executions, - ownedAccountAddress - ) - } - const execution = { - target: transactions[0].to as Hex, - callData: (transactions[0].data ?? "0x") as Hex, - value: BigInt(transactions[0].value ?? 0n) - } - return await this.activeExecutionModule.execute( - execution, - ownedAccountAddress - ) - } - throw new Error( - "Please set an active executor module before running this method." - ) - } - - /** - * Checks if the account contract supports a specific execution mode. - * @param mode - The execution mode to check, represented as a viem Address. - * @returns A promise that resolves to a boolean indicating whether the execution mode is supported. - */ - async supportsExecutionMode(mode: Address): Promise { - const accountContract = await this._getAccountContract() - return (await accountContract.read.supportsExecutionMode([mode])) as boolean - } - - async getInstalledValidators() { - const accountContract = await this._getAccountContract() - return await accountContract.read.getValidatorsPaginated([ - SENTINEL_ADDRESS, - 100n - ]) - } - - async getInstalledExecutors() { - const accountContract = await this._getAccountContract() - return await accountContract.read.getExecutorsPaginated([ - SENTINEL_ADDRESS, - 100n - ]) - } - - /** - * Retrieves all installed modules for the account, including validators, executors, active hook, and fallback handler. - * @returns A promise that resolves to an array of addresses representing all installed modules. - */ - async getInstalledModules() { - const validators = await this.getInstalledValidators() - const executors = await this.getInstalledExecutors() - const hook = await this.getActiveHook() - const fallbackHandler = await this.getFallbackBySelector() - - return [...validators, ...executors, hook, fallbackHandler] - .flat() - .filter(Boolean) - } - - /** - * Retrieves the active hook for the account. - * @returns A promise that resolves to the address of the active hook. - */ - async getActiveHook() { - const accountContract = await this._getAccountContract() - return await accountContract.read.getActiveHook() - } - - /** - * Retrieves the fallback handler for a given selector. - * @param selector - Optional hexadecimal selector. If not provided, uses a generic fallback selector. - * @returns A promise that resolves to the address of the fallback handler. - */ - async getFallbackBySelector(selector?: Hex) { - const accountContract = await this._getAccountContract() - return await accountContract.read.getFallbackHandlerBySelector([ - selector ?? GENERIC_FALLBACK_SELECTOR - ]) - } - - /** - * Checks if the account supports a specific module type. - * @param moduleType - The type of module to check for support. - * @returns A promise that resolves to a boolean indicating whether the module type is supported. - */ - async supportsModule(module: Module): Promise { - const accountContract = await this._getAccountContract() - const moduleIndex = - module.type === "validator" - ? 1n - : module.type === "executor" - ? 2n - : module.type === "fallback" - ? 3n - : 4n - return await accountContract.read.supportsModule([moduleIndex]) - } -} diff --git a/src/account/index.ts b/src/account/index.ts index 2a132540..77a915ad 100644 --- a/src/account/index.ts +++ b/src/account/index.ts @@ -1,11 +1,7 @@ -import { NexusSmartAccount } from "./NexusSmartAccount.js" import type { NexusSmartAccountConfig } from "./utils/Types.js" -export * from "./NexusSmartAccount.js" export * from "./utils/index.js" export * from "./signers/local-account.js" export * from "./signers/wallet-client.js" -export const createSmartAccountClient = NexusSmartAccount.create - export type SmartWalletConfig = NexusSmartAccountConfig diff --git a/src/account/utils/AccountNotFound.ts b/src/account/utils/AccountNotFound.ts new file mode 100644 index 00000000..3547d09f --- /dev/null +++ b/src/account/utils/AccountNotFound.ts @@ -0,0 +1,17 @@ +import { BaseError } from "viem" + +export class AccountNotFoundError extends BaseError { + constructor({ docsPath }: { docsPath?: string | undefined } = {}) { + super( + [ + "Could not find an Account to execute with this Action.", + "Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client." + ].join("\n"), + { + docsPath, + docsSlug: "account", + name: "AccountNotFoundError" + } + ) + } +} diff --git a/src/clients/biconomy.test.ts b/src/clients/biconomy.test.ts new file mode 100644 index 00000000..9ab6c452 --- /dev/null +++ b/src/clients/biconomy.test.ts @@ -0,0 +1,147 @@ +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/clients/biconomy.ts b/src/clients/biconomy.ts new file mode 100644 index 00000000..7054f3ce --- /dev/null +++ b/src/clients/biconomy.ts @@ -0,0 +1,183 @@ +import { + http, + type Account, + type Address, + type Chain, + type Client, + type ClientConfig, + type EstimateFeesPerGasReturnType, + type Prettify, + type PublicClient, + type RpcSchema, + type Transport +} from "viem" +import { + type BundlerActions, + type BundlerClientConfig, + type PaymasterActions, + type SmartAccount, + type UserOperationRequest, + createBundlerClient +} from "viem/account-abstraction" +import contracts from "../__contracts" +import { type Call, type Nexus, toNexusAccount } from "../account/Nexus" + +import type { BaseValidationModule } from "../modules/base/BaseValidationModule" +import { type Erc7579Actions, erc7579Actions } from "./decorators/erc7579" +import { + type SmartAccountActions, + smartAccountActions +} from "./decorators/smartAccount" + +export type SendTransactionParameters = { + calls: Call | Call[] +} + +export type BiconomyClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends Nexus | undefined = Nexus | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Pick< + ClientConfig, + "cacheTime" | "chain" | "key" | "name" | "pollingInterval" | "rpcSchema" + > & + BundlerActions & + Erc7579Actions & + SmartAccountActions & { + account: Nexus + client?: client | Client | undefined + bundlerTransport?: BundlerClientConfig["transport"] + paymaster?: BundlerClientConfig["paymaster"] | undefined + paymasterContext?: BundlerClientConfig["paymasterContext"] | undefined + userOperation?: BundlerClientConfig["userOperation"] | undefined + } +> + +export type BiconomyClientConfig< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Pick< + ClientConfig, + | "account" + | "cacheTime" + | "chain" + | "key" + | "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 +} + +export async function createBiconomyClient( + parameters: BiconomyClientConfig +): Promise { + const { + client: client_, + chain = parameters.chain ?? client_?.chain, + owner, + index = 0n, + key = "biconomy", + name = "Bicononomy Client", + activeModule, + factoryAddress = contracts.k1ValidatorFactory.address, + k1ValidatorAddress = contracts.k1Validator.address, + bundlerTransport, + paymaster, + transport, + paymasterContext, + userOperation = { + estimateFeesPerGas: async (parameters) => { + const feeData = await ( + parameters?.account?.client as PublicClient + )?.estimateFeesPerGas?.() + return { + maxFeePerGas: feeData.maxFeePerGas * 2n, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n + } + } + } + } = parameters + + if (!chain) throw new Error("Missing chain") + + const account = await toNexusAccount({ + ...parameters, + transport, + chain, + owner, + index, + activeModule, + factoryAddress, + k1ValidatorAddress + }) + + const bundler = createBundlerClient({ + ...parameters, + key, + name, + account: account as Account, + paymaster, + paymasterContext, + transport: + bundlerTransport ?? + http( + `https://bundler.biconomy.io/api/v2/${chain.id}/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14` + ), + userOperation + }) + .extend(erc7579Actions()) + .extend(smartAccountActions()) + + return bundler as unknown as BiconomyClient +} diff --git a/src/clients/decorators/erc7579/accountId.ts b/src/clients/decorators/erc7579/accountId.ts new file mode 100644 index 00000000..ce527efd --- /dev/null +++ b/src/clients/decorators/erc7579/accountId.ts @@ -0,0 +1,93 @@ +import { + type Chain, + type Client, + ContractFunctionExecutionError, + type Transport, + decodeFunctionResult, + encodeFunctionData +} from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { call, readContract } from "viem/actions" +import { getAction } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +export async function accountId( + client: Client, + args?: GetSmartAccountParameter +): Promise { + let account_ = client.account + + if (args) { + account_ = args.account as TSmartAccount + } + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = account_ as SmartAccount + + const publicClient = account.client + + const abi = [ + { + name: "accountId", + type: "function", + stateMutability: "view", + inputs: [], + outputs: [ + { + type: "string", + name: "accountImplementationId" + } + ] + } + ] as const + + try { + return await getAction( + publicClient, + readContract, + "readContract" + )({ + abi, + functionName: "accountId", + address: await account.getAddress() + }) + } catch (error) { + if (error instanceof ContractFunctionExecutionError) { + const { factory, factoryData } = await account.getFactoryArgs() + + const result = await getAction( + publicClient, + call, + "call" + )({ + factory: factory, + factoryData: factoryData, + to: account.address, + data: encodeFunctionData({ + abi, + functionName: "accountId" + }) + }) + + if (!result || !result.data) { + throw new Error("accountId result is empty") + } + + return decodeFunctionResult({ + abi, + functionName: "accountId", + data: result.data + }) + } + + throw error + } +} diff --git a/src/clients/decorators/erc7579/index.ts b/src/clients/decorators/erc7579/index.ts new file mode 100644 index 00000000..458b4774 --- /dev/null +++ b/src/clients/decorators/erc7579/index.ts @@ -0,0 +1,93 @@ +import type { Chain, Client, Hash, Transport } from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { accountId } from "./accountId.js" +import { type InstallModuleParameters, installModule } from "./installModule.js" +import { + type InstallModulesParameters, + installModules +} from "./installModules.js" +import { + type IsModuleInstalledParameters, + isModuleInstalled +} from "./isModuleInstalled.js" +import { + type SupportsExecutionModeParameters, + supportsExecutionMode +} from "./supportsExecutionMode.js" +import type { CallType, ExecutionMode } from "./supportsExecutionMode.js" +import { + type SupportsModuleParameters, + supportsModule +} from "./supportsModule.js" +import type { ModuleType } from "./supportsModule.js" +import { + type UninstallModuleParameters, + uninstallModule +} from "./uninstallModule.js" +import { + type UninstallModulesParameters, + uninstallModules +} from "./uninstallModules.js" + +export type Erc7579Actions = { + accountId: (args?: GetSmartAccountParameter) => Promise + installModule: (args: InstallModuleParameters) => Promise + installModules: ( + args: InstallModulesParameters + ) => Promise + isModuleInstalled: ( + args: IsModuleInstalledParameters + ) => Promise + supportsExecutionMode: ( + args: SupportsExecutionModeParameters + ) => Promise + supportsModule: ( + args: SupportsModuleParameters + ) => Promise + uninstallModule: ( + args: UninstallModuleParameters + ) => Promise + uninstallModules: ( + args: UninstallModulesParameters + ) => Promise +} + +export type { + InstallModuleParameters, + IsModuleInstalledParameters, + CallType, + ExecutionMode, + SupportsExecutionModeParameters, + ModuleType, + SupportsModuleParameters, + UninstallModuleParameters +} + +export { + accountId, + installModule, + installModules, + isModuleInstalled, + supportsExecutionMode, + supportsModule, + uninstallModule, + uninstallModules +} + +export function erc7579Actions() { + return ( + client: Client + ): Erc7579Actions => ({ + accountId: (args) => accountId(client, args), + installModule: (args) => installModule(client, args), + installModules: (args) => installModules(client, args), + isModuleInstalled: (args) => isModuleInstalled(client, args), + supportsExecutionMode: (args) => supportsExecutionMode(client, args), + supportsModule: (args) => supportsModule(client, args), + uninstallModule: (args) => uninstallModule(client, args), + uninstallModules: (args) => uninstallModules(client, args) + }) +} diff --git a/src/clients/decorators/erc7579/installModule.ts b/src/clients/decorators/erc7579/installModule.ts new file mode 100644 index 00000000..974aa721 --- /dev/null +++ b/src/clients/decorators/erc7579/installModule.ts @@ -0,0 +1,97 @@ +import { + type Address, + 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 { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { type ModuleType, parseModuleTypeId } from "./supportsModule" + +export type InstallModuleParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + type: ModuleType + address: Address + context: Hex + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + nonce?: bigint +} + +export async function installModule< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: InstallModuleParameters +): Promise { + const { + account: account_ = client.account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + address, + context + } = 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: "installModule", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "initData" + } + ], + outputs: [] + } + ], + functionName: "installModule", + args: [ + parseModuleTypeId(parameters.type), + getAddress(address), + context + ] + }) + } + ], + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + account + }) +} diff --git a/src/clients/decorators/erc7579/installModules.ts b/src/clients/decorators/erc7579/installModules.ts new file mode 100644 index 00000000..f368b193 --- /dev/null +++ b/src/clients/decorators/erc7579/installModules.ts @@ -0,0 +1,93 @@ +import { + type Address, + type Chain, + type Client, + type Hex, + type Transport, + encodeFunctionData, + getAddress +} from "viem" +import { + type GetSmartAccountParameter, + type SmartAccount, + sendUserOperation +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { type ModuleType, parseModuleTypeId } from "./supportsModule" + +export type InstallModulesParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + modules: { + type: ModuleType + address: Address + context: Hex + }[] + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + nonce?: bigint +} + +export async function installModules< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: InstallModulesParameters +): Promise { + const { + account: account_ = client.account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + modules + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + return getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + calls: modules.map(({ type, address, context }) => ({ + to: account.address, + value: BigInt(0), + data: encodeFunctionData({ + abi: [ + { + name: "installModule", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "initData" + } + ], + outputs: [] + } + ], + functionName: "installModule", + args: [parseModuleTypeId(type), getAddress(address), context] + }) + })), + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + account: account + }) +} diff --git a/src/clients/decorators/erc7579/isModuleInstalled.ts b/src/clients/decorators/erc7579/isModuleInstalled.ts new file mode 100644 index 00000000..7262d286 --- /dev/null +++ b/src/clients/decorators/erc7579/isModuleInstalled.ts @@ -0,0 +1,121 @@ +import { + type Address, + type Chain, + type Client, + ContractFunctionExecutionError, + type Hex, + type Transport, + decodeFunctionResult, + encodeFunctionData, + getAddress +} from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { call, readContract } from "viem/actions" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { type ModuleType, parseModuleTypeId } from "./supportsModule" + +export type IsModuleInstalledParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + type: ModuleType + address: Address + context: Hex +} + +export async function isModuleInstalled< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: IsModuleInstalledParameters +): Promise { + const { account: account_ = client.account, address, context } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + const abi = [ + { + name: "isModuleInstalled", + type: "function", + stateMutability: "view", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "additionalContext" + } + ], + outputs: [ + { + type: "bool" + } + ] + } + ] as const + + try { + return await getAction( + publicClient, + readContract, + "readContract" + )({ + abi, + functionName: "isModuleInstalled", + args: [parseModuleTypeId(parameters.type), getAddress(address), context], + address: account.address + }) + } catch (error) { + if (error instanceof ContractFunctionExecutionError) { + const { factory, factoryData } = await account.getFactoryArgs() + + const result = await getAction( + publicClient, + call, + "call" + )({ + factory: factory, + factoryData: factoryData, + to: account.address, + data: encodeFunctionData({ + abi, + functionName: "isModuleInstalled", + args: [ + parseModuleTypeId(parameters.type), + getAddress(address), + context + ] + }) + }) + + if (!result || !result.data) { + throw new Error("accountId result is empty") + } + + return decodeFunctionResult({ + abi, + functionName: "isModuleInstalled", + data: result.data + }) + } + + throw error + } +} diff --git a/src/clients/decorators/erc7579/supportsExecutionMode.ts b/src/clients/decorators/erc7579/supportsExecutionMode.ts new file mode 100644 index 00000000..568fd118 --- /dev/null +++ b/src/clients/decorators/erc7579/supportsExecutionMode.ts @@ -0,0 +1,159 @@ +import { + type Chain, + type Client, + ContractFunctionExecutionError, + type Hex, + type Transport, + decodeFunctionResult, + encodeFunctionData, + encodePacked, + toBytes, + toHex +} from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { call, readContract } from "viem/actions" +import { getAction } from "viem/utils" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +export type CallType = "call" | "delegatecall" | "batchcall" + +export type ExecutionMode = { + type: callType + revertOnError?: boolean + selector?: Hex + context?: Hex +} + +export type SupportsExecutionModeParameters< + TSmartAccount extends SmartAccount | undefined, + callType extends CallType = CallType +> = GetSmartAccountParameter & ExecutionMode + +function parseCallType(callType: CallType) { + switch (callType) { + case "call": + return "0x00" + case "batchcall": + return "0x01" + case "delegatecall": + return "0xff" + } +} + +export function encodeExecutionMode({ + type, + revertOnError, + selector, + context +}: ExecutionMode): Hex { + return encodePacked( + ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"], + [ + toHex(toBytes(parseCallType(type), { size: 1 })), + toHex(toBytes(revertOnError ? "0x01" : "0x00", { size: 1 })), + toHex(toBytes("0x0", { size: 4 })), + toHex(toBytes(selector ?? "0x", { size: 4 })), + toHex(toBytes(context ?? "0x", { size: 22 })) + ] + ) +} + +export async function supportsExecutionMode< + TSmartAccount extends SmartAccount | undefined, + callType extends CallType = CallType +>( + client: Client, + args: SupportsExecutionModeParameters +): Promise { + const { + account: account_ = client.account, + type, + revertOnError, + selector, + context + } = args + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + const encodedMode = encodeExecutionMode({ + type, + revertOnError, + selector, + context + }) + + const abi = [ + { + name: "supportsExecutionMode", + type: "function", + stateMutability: "view", + inputs: [ + { + type: "bytes32", + name: "encodedMode" + } + ], + outputs: [ + { + type: "bool" + } + ] + } + ] as const + + try { + return await getAction( + publicClient, + readContract, + "readContract" + )({ + abi, + functionName: "supportsExecutionMode", + args: [encodedMode], + address: account.address + }) + } catch (error) { + if (error instanceof ContractFunctionExecutionError) { + const { factory, factoryData } = await account.getFactoryArgs() + + const result = await getAction( + publicClient, + call, + "call" + )({ + factory: factory, + factoryData: factoryData, + to: account.address, + data: encodeFunctionData({ + abi, + functionName: "supportsExecutionMode", + args: [encodedMode] + }) + }) + + if (!result || !result.data) { + throw new Error("accountId result is empty") + } + + return decodeFunctionResult({ + abi, + functionName: "supportsExecutionMode", + data: result.data + }) + } + + throw error + } +} diff --git a/src/clients/decorators/erc7579/supportsModule.ts b/src/clients/decorators/erc7579/supportsModule.ts new file mode 100644 index 00000000..60c937b4 --- /dev/null +++ b/src/clients/decorators/erc7579/supportsModule.ts @@ -0,0 +1,121 @@ +import { + type Chain, + type Client, + ContractFunctionExecutionError, + type Transport, + decodeFunctionResult, + encodeFunctionData +} from "viem" +import type { + GetSmartAccountParameter, + SmartAccount +} from "viem/account-abstraction" +import { call, readContract } from "viem/actions" +import { getAction } from "viem/utils" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +export type ModuleType = "validator" | "executor" | "fallback" | "hook" + +export type SupportsModuleParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + type: ModuleType +} + +export function parseModuleTypeId(type: ModuleType): bigint { + switch (type) { + case "validator": + return BigInt(1) + case "executor": + return BigInt(2) + case "fallback": + return BigInt(3) + case "hook": + return BigInt(4) + default: + throw new Error("Invalid module type") + } +} + +export async function supportsModule< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + args: SupportsModuleParameters +): Promise { + const { account: account_ = client.account } = args + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const publicClient = account.client + + const abi = [ + { + name: "supportsModule", + type: "function", + stateMutability: "view", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + } + ], + outputs: [ + { + type: "bool" + } + ] + } + ] as const + + try { + return await getAction( + publicClient, + readContract, + "readContract" + )({ + abi, + functionName: "supportsModule", + args: [parseModuleTypeId(args.type)], + address: account.address + }) + } catch (error) { + if (error instanceof ContractFunctionExecutionError) { + const { factory, factoryData } = await account.getFactoryArgs() + + const result = await getAction( + publicClient, + call, + "call" + )({ + factory: factory, + factoryData: factoryData, + to: account.address, + data: encodeFunctionData({ + abi, + functionName: "supportsModule", + args: [parseModuleTypeId(args.type)] + }) + }) + + if (!result || !result.data) { + throw new Error("accountId result is empty") + } + + return decodeFunctionResult({ + abi, + functionName: "supportsModule", + data: result.data + }) + } + + throw error + } +} diff --git a/src/clients/decorators/erc7579/uninstallModule.ts b/src/clients/decorators/erc7579/uninstallModule.ts new file mode 100644 index 00000000..8737d257 --- /dev/null +++ b/src/clients/decorators/erc7579/uninstallModule.ts @@ -0,0 +1,100 @@ +import { + type Address, + 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 { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { type ModuleType, parseModuleTypeId } from "./supportsModule" + +export type UninstallModuleParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + type: ModuleType + address: Address + context: Hex + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + nonce?: bigint +} + +export async function uninstallModule< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: UninstallModuleParameters +): Promise { + const { + account: account_ = client.account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + address, + context + } = 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: "uninstallModule", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "deInitData" + } + ], + outputs: [] + } + ], + functionName: "uninstallModule", + args: [ + parseModuleTypeId(parameters.type), + getAddress(address), + context + ] + }) + } + ], + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + account: account + }) +} diff --git a/src/clients/decorators/erc7579/uninstallModules.ts b/src/clients/decorators/erc7579/uninstallModules.ts new file mode 100644 index 00000000..34aa7384 --- /dev/null +++ b/src/clients/decorators/erc7579/uninstallModules.ts @@ -0,0 +1,97 @@ +import { + type Address, + 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 { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import { type ModuleType, parseModuleTypeId } from "./supportsModule" + +export type UninstallModulesParameters< + TSmartAccount extends SmartAccount | undefined +> = GetSmartAccountParameter & { + modules: [ + { + type: ModuleType + address: Address + context: Hex + } + ] + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + nonce?: bigint +} + +export async function uninstallModules< + TSmartAccount extends SmartAccount | undefined +>( + client: Client, + parameters: UninstallModulesParameters +): Promise { + const { + account: account_ = client.account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + modules + } = parameters + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + return getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + calls: modules.map(({ type, address, context }) => ({ + to: account.address, + value: BigInt(0), + data: encodeFunctionData({ + abi: [ + { + name: "uninstallModule", + type: "function", + stateMutability: "nonpayable", + inputs: [ + { + type: "uint256", + name: "moduleTypeId" + }, + { + type: "address", + name: "module" + }, + { + type: "bytes", + name: "deInitData" + } + ], + outputs: [] + } + ], + functionName: "uninstallModule", + args: [parseModuleTypeId(type), getAddress(address), context] + }) + })), + maxFeePerGas, + maxPriorityFeePerGas, + nonce, + account + }) +} diff --git a/src/clients/decorators/smartAccount/index.ts b/src/clients/decorators/smartAccount/index.ts new file mode 100644 index 00000000..adafbf72 --- /dev/null +++ b/src/clients/decorators/smartAccount/index.ts @@ -0,0 +1,324 @@ +import type { + Abi, + Chain, + Client, + ContractFunctionArgs, + ContractFunctionName, + Hash, + SendTransactionParameters, + Transport, + TypedData, + 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" + +export type SmartAccountActions< + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { + /** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param args - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.sendTransaction({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await client.sendTransaction({ + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ + sendTransaction: < + TChainOverride extends Chain | undefined = undefined, + accountOverride extends SmartAccount | undefined = undefined, + calls extends readonly unknown[] = readonly unknown[] + >( + args: Parameters< + typeof sendTransaction< + TSmartAccount, + TChain, + accountOverride, + TChainOverride, + calls + > + >[1] + ) => Promise + /** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param args - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signMessage({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.signMessage({ + * message: 'hello world', + * }) + */ + signMessage: ( + args: Parameters>[1] + ) => ReturnType> + /** + * Signs typed data and calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param args - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signTypedData({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * 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!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.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!', + * }, + * }) + */ + signTypedData: < + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string + >( + args: Parameters< + typeof signTypedData + >[1] + ) => ReturnType> + /** + * Executes a write function on a contract. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/writeContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/writing-to-contracts + * + * A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, and hence a [Transaction](https://viem.sh/docs/glossary/terms.html) is needed to be broadcast in order to change the state. + * + * Internally, uses a [Wallet Client](https://viem.sh/docs/clients/wallet.html) to call the [`sendTransaction` action](https://viem.sh/docs/actions/wallet/sendTransaction.html) with [ABI-encoded `data`](https://viem.sh/docs/contract/encodeFunctionData.html). + * + * __Warning: The `write` internally sends a transaction – it does not validate if the contract write will succeed (the contract may throw an error). It is highly recommended to [simulate the contract write with `contract.simulate`](https://viem.sh/docs/contract/writeContract.html#usage) before you execute it.__ + * + * @param args - {@link WriteContractParameters} + * @returns A [Transaction Hash](https://viem.sh/docs/glossary/terms.html#hash). {@link WriteContractReturnType} + * + * @example + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.writeContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * }) + * + * @example + * // With Validation + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const { request } = await client.simulateContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * } + * const hash = await client.writeContract(request) + */ + writeContract: < + const TAbi extends Abi | readonly unknown[], + TFunctionName extends ContractFunctionName< + TAbi, + "nonpayable" | "payable" + > = ContractFunctionName, + TArgs extends ContractFunctionArgs< + TAbi, + "nonpayable" | "payable", + TFunctionName + > = ContractFunctionArgs, + TChainOverride extends Chain | undefined = undefined + >( + args: WriteContractParameters< + TAbi, + TFunctionName, + TArgs, + TChain, + TSmartAccount, + TChainOverride + > + ) => ReturnType< + typeof writeContract< + TChain, + TSmartAccount, + TAbi, + TFunctionName, + TArgs, + TChainOverride + > + > +} + +export function smartAccountActions() { + return < + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined + >( + client: Client + ): SmartAccountActions => ({ + sendTransaction: (args) => sendTransaction(client, args as any), + signMessage: (args) => signMessage(client, args), + signTypedData: (args) => signTypedData(client, args), + writeContract: (args) => writeContract(client, args) + }) +} diff --git a/src/clients/decorators/smartAccount/sendTransaction.ts b/src/clients/decorators/smartAccount/sendTransaction.ts new file mode 100644 index 00000000..931e0944 --- /dev/null +++ b/src/clients/decorators/smartAccount/sendTransaction.ts @@ -0,0 +1,132 @@ +import type { + Chain, + Client, + Hash, + SendTransactionParameters, + Transport +} from "viem" +import { + type SendUserOperationParameters, + type SmartAccount, + sendUserOperation, + waitForUserOperationReceipt +} from "viem/account-abstraction" +import { getAction, parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +/** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param client - Client to use + * @param parameters - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await sendTransaction(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await sendTransaction(client, { + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ +export async function sendTransaction< + account extends SmartAccount | undefined, + chain extends Chain | undefined, + accountOverride extends SmartAccount | undefined = undefined, + chainOverride extends Chain | undefined = Chain | undefined, + calls extends readonly unknown[] = readonly unknown[] +>( + client: Client, + args: + | SendTransactionParameters + | SendUserOperationParameters +): Promise { + let userOpHash: Hash + + if ("to" in args) { + const { + account: account_ = client.account, + data, + maxFeePerGas, + maxPriorityFeePerGas, + to, + value, + nonce + } = args + + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + if (!to) throw new Error("Missing to address") + + userOpHash = await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ + calls: [ + { + to, + value: value || BigInt(0), + data: data || "0x" + } + ], + account, + maxFeePerGas, + maxPriorityFeePerGas, + nonce: nonce ? BigInt(nonce) : undefined + }) + } else { + userOpHash = await getAction( + client, + sendUserOperation, + "sendUserOperation" + )({ ...args } as SendUserOperationParameters) + } + + const userOperationReceipt = await getAction( + client, + waitForUserOperationReceipt, + "waitForUserOperationReceipt" + )({ + hash: userOpHash + }) + + return userOperationReceipt?.receipt.transactionHash +} diff --git a/src/clients/decorators/smartAccount/signMessage.ts b/src/clients/decorators/smartAccount/signMessage.ts new file mode 100644 index 00000000..fee93f7e --- /dev/null +++ b/src/clients/decorators/smartAccount/signMessage.ts @@ -0,0 +1,73 @@ +import type { + Chain, + Client, + SignMessageParameters, + SignMessageReturnType, + Transport +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +/** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param client - Client to use + * @param parameters - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, custom } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * message: 'hello world', + * }) + */ +export async function signMessage( + client: Client, + { + account: account_ = client.account, + message + }: SignMessageParameters +): Promise { + if (!account_) + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + + const account = parseAccount(account_) as SmartAccount + + return account.signMessage({ message }) +} diff --git a/src/clients/decorators/smartAccount/signTypedData.ts b/src/clients/decorators/smartAccount/signTypedData.ts new file mode 100644 index 00000000..603f8c81 --- /dev/null +++ b/src/clients/decorators/smartAccount/signTypedData.ts @@ -0,0 +1,157 @@ +import { + type Chain, + type Client, + type SignTypedDataParameters, + type SignTypedDataReturnType, + type Transport, + type TypedData, + type TypedDataDefinition, + type TypedDataDomain, + getTypesForEIP712Domain, + validateTypedData +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { parseAccount } from "viem/utils" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" + +/** + * Signs typed data and calculates an Ethereum-specific signature in [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712): `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))` + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param parameters - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signTypedData(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * 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!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await signTypedData(client, { + * 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!', + * }, + * }) + */ +export async function signTypedData< + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string, + TAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client, + { + account: account_ = client.account, + domain, + message, + primaryType, + types: types_ + }: SignTypedDataParameters +): Promise { + if (!account_) { + throw new AccountNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const types = { + EIP712Domain: getTypesForEIP712Domain({ domain } as { + domain: TypedDataDomain + }), + ...(types_ as TTypedData) + } + + validateTypedData({ + domain, + message, + primaryType, + types + } as TypedDataDefinition) + + return account.signTypedData({ + domain, + primaryType, + types, + message + } as TypedDataDefinition) +} diff --git a/src/clients/decorators/smartAccount/writeContract.ts b/src/clients/decorators/smartAccount/writeContract.ts new file mode 100644 index 00000000..1641727e --- /dev/null +++ b/src/clients/decorators/smartAccount/writeContract.ts @@ -0,0 +1,70 @@ +import { + type Abi, + type Chain, + type Client, + type ContractFunctionArgs, + type ContractFunctionName, + type EncodeFunctionDataParameters, + type Hash, + type SendTransactionParameters, + type Transport, + type WriteContractParameters, + encodeFunctionData +} from "viem" +import type { SmartAccount } from "viem/account-abstraction" +import { getAction } from "viem/utils" +import { sendTransaction } from "./sendTransaction" + +export async function writeContract< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined, + const TAbi extends Abi | readonly unknown[], + TFunctionName extends ContractFunctionName< + TAbi, + "nonpayable" | "payable" + > = ContractFunctionName, + TArgs extends ContractFunctionArgs< + TAbi, + "nonpayable" | "payable", + TFunctionName + > = ContractFunctionArgs, + TChainOverride extends Chain | undefined = undefined +>( + client: Client, + { + abi, + address, + args, + dataSuffix, + functionName, + ...request + }: WriteContractParameters< + TAbi, + TFunctionName, + TArgs, + TChain, + TAccount, + TChainOverride + > +): Promise { + const data = encodeFunctionData({ + abi, + args, + functionName + } as EncodeFunctionDataParameters) + + const hash = await getAction( + client, + sendTransaction, + "sendTransaction" + )({ + data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, + to: address, + ...request + } as unknown as SendTransactionParameters< + Chain | undefined, + TAccount, + undefined + >) + return hash +} diff --git a/tests/biconomy.test.ts b/tests/biconomy.test.ts new file mode 100644 index 00000000..a6250a0c --- /dev/null +++ b/tests/biconomy.test.ts @@ -0,0 +1,148 @@ +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/nexus.test.ts b/tests/nexus.test.ts deleted file mode 100644 index eb03332b..00000000 --- a/tests/nexus.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { - http, - type Account, - type Chain, - type Hex, - type Prettify, - type PrivateKeyAccount, - type PublicClient, - type WalletClient, - createPublicClient, - createWalletClient, - isHex -} from "viem" -import { - type BundlerClient, - createBundlerClient -} from "viem/account-abstraction" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import contracts from "../src/__contracts" -import { - type NexusSmartAccount, - createSmartAccountClient -} from "../src/account" -import { toNexus } from "../src/account/Nexus" -import { type TestFileNetworkType, toNetwork } from "./src/testSetup" -import { killNetwork } from "./src/testUtils" -import type { NetworkConfig } from "./src/testUtils" - -const NETWORK_TYPE: TestFileNetworkType = "PUBLIC_TESTNET" - -// 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" - -describe("nexus", () => { - let network: NetworkConfig - // Nexus Config - let chain: Chain - let bundlerUrl: string - let walletClient: WalletClient - let bundlerClient: BundlerClient - - // Test utils - let publicClient: PublicClient - let account: Account - let smartAccount: NexusSmartAccount - let nexus: Prettify>> - let smartAccountAddress: Hex - let nexusAccountAddress: Hex - - beforeAll(async () => { - network = (await toNetwork(NETWORK_TYPE)) as NetworkConfig - - chain = network.chain - bundlerUrl = network.bundlerUrl - - account = network.account as PrivateKeyAccount - - walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - publicClient = createPublicClient({ - chain, - transport: http() - }) - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should create a smart account", async () => { - const regularWalletClient = createWalletClient({ - account, - chain, - transport: http() - }) - - smartAccount = await createSmartAccountClient({ - signer: regularWalletClient, - bundlerUrl, - chain, - k1ValidatorAddress, - factoryAddress - }) - - nexus = await toNexus({ - owner: account, - chain, - transport: http(), - factoryAddress, - k1ValidatorAddress - }) - - smartAccountAddress = await smartAccount.getAddress() - nexusAccountAddress = await nexus.getAddress() - }) - - test("should produce the same results", async () => { - const [smartAccountAddress, nexusAddress] = await Promise.all([ - smartAccount.getAddress(), - nexus.getAddress() - ]) - expect(smartAccountAddress).toEqual(nexusAddress) - nexusAccountAddress = nexusAddress - - console.log({ smartAccountAddress }, account.address) - - const [initCode, nexusInitCode] = await Promise.all([ - smartAccount.getAccountInitCode(), - nexus.getInitCode() - ]) - - expect([initCode, nexusInitCode].every((code) => isHex(code))).toBeTruthy() - - const [smartAccountFactoryData, nexusFactoryData] = await Promise.all([ - smartAccount.getFactoryData(), - nexus.factoryData - ]) - expect(isHex(nexusFactoryData)).toBeTruthy() - - const [smartAccountIsDeployed, nexusIsDeployed] = await Promise.all([ - smartAccount.isAccountDeployed(), - nexus.isDeployed() - ]) - expect(smartAccountIsDeployed).toEqual(nexusIsDeployed) - }) - - test("should check balances and top up relevant addresses", async () => { - const [ownerBalance, smartAccountBalance] = await Promise.all([ - publicClient.getBalance({ - address: account.address - }), - publicClient.getBalance({ - address: smartAccountAddress - }) - ]) - const balancesAreOfCorrectType = [ownerBalance, smartAccountBalance].every( - (balance) => typeof balance === "bigint" - ) - - console.log({ ownerBalance, smartAccountBalance }) - if (smartAccountBalance < 1000000000000n) { - const hash = await walletClient.sendTransaction({ - chain, - account, - to: smartAccountAddress, - value: 1000000000000n - }) - const receipt = await publicClient.waitForTransactionReceipt({ hash }) - console.log({ receipt }) - } - expect(balancesAreOfCorrectType).toBeTruthy() - }) - - test("should have a working bundler client", async () => { - const bundlerClient = createBundlerClient({ - account: nexus, - chain, - transport: http(bundlerUrl) - }) - - const chainId = await bundlerClient.getChainId() - expect(chainId).toEqual(chain.id) - - const supportedEntrypoints = await bundlerClient.getSupportedEntryPoints() - expect(supportedEntrypoints).to.include(contracts.entryPoint.address) - }) - - test("should prepare a user operation", async () => { - const bundlerClient = createBundlerClient({ - account: nexus, - chain, - transport: http(bundlerUrl) - }) - - const isDeployed = await nexus.isDeployed() - - const calls = [{ to: account.address, value: 1n }] - const feeData = await publicClient.estimateFeesPerGas() - const gas = { - maxFeePerGas: feeData.maxFeePerGas * 2n, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n - } - - const UNDER_THE_HOOD = true - - if (UNDER_THE_HOOD) { - const preppedUserOp = await bundlerClient.prepareUserOperation({ - calls, - ...gas - }) - const signature = await nexus.signUserOperation(preppedUserOp) - const uO = { ...preppedUserOp, signature } - const gasEstimates = await bundlerClient.estimateUserOperationGas(uO) - const userOpWithGasEstimates = { ...uO, ...gasEstimates } - - const nexusNonce = await nexus.getNonce() - console.log({ nexusNonce }) - - const { wait } = await smartAccount.sendTransaction(calls) - // const { success } = await wait() - // expect(success).toBe(true) - - console.log({ userOpWithGasEstimates }) - - const hash = await bundlerClient.sendUserOperation(userOpWithGasEstimates) - - const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) - console.log({ receipt }) - } else { - const hash = await bundlerClient.sendUserOperation({ calls, ...gas }) - const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) - console.log({ receipt }) - } - - // const encodedCalls = await nexus.encodeCalls(calls) - // expect(isHex(encodedCalls)).toBeTruthy() - - // const preppedUserOperation = await bundlerClient.prepareUserOperation({ - // calls, - // ...feeData - // }) - - // // const gasPrice = await bundlerClient - - // const { - // factory, - // factoryData, - // paymasterPostOpGasLimit, - // paymasterVerificationGasLimit, - // ...rawUserOperation - // } = preppedUserOperation - - // const signature = await nexus.signUserOperation(preppedUserOperation) - - // const uO = { ...rawUserOperation, signature } - - // console.log({ uO }) - - // // const gasEstimates = - // // await bundlerClient.estimateUserOperationGas(preppedUserOperation) - - // // const userOpWithOld = await smartAccount.buildUserOp([]) - // // const signedUserOpWithOld = await smartAccount.signUserOp(userOpWithOld) - // // console.log({ signedUserOpWithOld }) - // const { wait } = await smartAccount.sendTransaction(calls) - // const { receipt: receiptOld } = await wait() - // console.log({ receiptOld }) - - // const receipt = await bundlerClient.sendUserOperation(uO) - // console.log({ receipt }) - - // const gasEstimates = await bundlerClient.prepareUserOperation({ - // // account: parseAccount(account), - // callData: "0x", - // maxFeePerGas: 1n, - // maxPriorityFeePerGas: 1n - // }) - - // console.log(gasEstimates) - - // const userOperation = await nexus.signUserOperation({ - // callData: encodedCalls, - // callGasLimit, - // maxFeePerGas, - // maxPriorityFeePerGas, - // nonce - // }) - - // estimateUserOperationGas: [Function: estimateUserOperationGas], - // getChainId: [Function: getChainId], - // getSupportedEntryPoints: [Function: getSupportedEntryPoints], - // getUserOperation: [Function: getUserOperation], - // getUserOperationReceipt: [Function: getUserOperationReceipt], - // prepareUserOperation: [Function: prepareUserOperation], - // sendUserOperation: [Function: sendUserOperation], - // waitForUserOperationReceipt: [Function: waitForUserOperationReceipt] - - // console.log(bundlerClient) - }) -}) diff --git a/tests/src/testSetup.ts b/tests/src/testSetup.ts index 53505827..e6ad9a1a 100644 --- a/tests/src/testSetup.ts +++ b/tests/src/testSetup.ts @@ -46,7 +46,7 @@ export type TestFileNetworkType = | "PUBLIC_TESTNET" export const toNetwork = async ( - networkType: TestFileNetworkType + networkType: TestFileNetworkType = "FILE_LOCALHOST" ): Promise => await (networkType === "COMMON_LOCALHOST" ? // @ts-ignore diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts index ef719a81..e3294080 100644 --- a/tests/vitest.config.ts +++ b/tests/vitest.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ statements: 80 } }, - include: ["tests/**/*.test.ts"], + include: ["tests/**/*.test.ts", "src/**/*.test.ts"], globalSetup: join(__dirname, "src/globalSetup.ts"), environment: "node", testTimeout: 60_000, diff --git a/tsconfig/tsconfig.json b/tsconfig/tsconfig.json index d820773c..2aa922c4 100644 --- a/tsconfig/tsconfig.json +++ b/tsconfig/tsconfig.json @@ -4,6 +4,7 @@ "../src/" ], "exclude": [ + "../src/**/*.spec.ts", "../src/**/*.test.ts", "../src/**/*.test-d.ts", "../src/**/*.bench.ts", From 96a840babbbb1cb90dec0208bbf193260d45002d Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 12 Sep 2024 15:24:37 +0100 Subject: [PATCH 03/29] 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 e751d84a..91f29092 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 88e5fabf..0dd615ed 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 2eee0c79..0c08fc6c 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 eae2c5a6..38595002 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 00000000..a7d2a4d2 --- /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 00000000..1e9494dd --- /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 fccaab29..b5ee25b0 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 cdca61c4..fb51d895 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 8af82cb1..bed5708b 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 0fcc5102..474b12e7 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 44b5f554..e6ebda18 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 843d66c7..b00c4b1a 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 3dd8bfd5..ec169c5a 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 2f0425b3..8be4806c 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 ede2ea4d..991a3f35 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 00000000..b65e73c6 --- /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 00000000..aa9e014e --- /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 00000000..0e4dbb8b --- /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 00000000..1257e2d5 --- /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 00000000..af1e634e --- /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 00000000..07cebce6 --- /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 458b4774..4d883440 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 974aa721..55ab308b 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 7262d286..06f8b4f2 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 00000000..5ebf06ce --- /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 8737d257..e8975be8 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 34aa7384..93731d32 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 00000000..1deb2a3d --- /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 adafbf72..d0bbb611 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 00000000..51b7160b --- /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 00000000..7d0ec12d --- /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 00000000..5e226370 --- /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 00000000..a89a000d --- /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 7054f3ce..63e07173 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 903f1f5b..9c227a1c 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 f6314075..678a7aa0 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 20eb272f..21f9a891 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 67f5af00..2d7625a4 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 76be22bb..36678981 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 ce60eaf0..2496a007 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 a6250108..8975e154 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 ada55804..7a56b5f4 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 03a655da..3c51e6b9 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 8390243a..e75c627b 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 00000000..2873dfa9 --- /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 b3e12288..ac462081 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 51a8ca59..edbaa155 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 e6ad9a1a..dd9813f8 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 4463d4c7..c5f58fa9 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 e3294080..91ec68f4 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 1ea138b5..bc3aba73 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 badbbfdd..e8442945 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 00000000..c8b488c3 --- /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 77a915ad..00000000 --- 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 ceb2cc0e..00000000 --- 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 5986083f..00000000 --- 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 bb465ee1..00000000 --- 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 c615713a..00000000 --- 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 cf96b536..00000000 --- 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 53688a28..00000000 --- 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 a5971ff9..00000000 --- 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 9ab6c452..00000000 --- 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 f833ceee..00000000 --- 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 70cbb99d..00000000 --- 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 104029e0..00000000 --- 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 3aee820b..00000000 --- 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 a148d17a..00000000 --- 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 d9aeceea..00000000 --- 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 25326220..00000000 --- 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 1533c32e..00000000 --- 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 065ff4de..00000000 --- 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 a6250a0c..00000000 --- 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 7995e845..00000000 --- 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 0b0262d4..00000000 --- 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 41522182..00000000 --- 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 680b4ca8..00000000 --- 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 09fbd4a7..00000000 --- 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 d5607ac1..95ca3b2d 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 136a81fc..85dcc3a0 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 2aa922c4..0612f8fe 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 bb70b996..a6b1ae51 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.*", + ] } From 3aa9a9d3c3fa2990329178cedbd57ff8464c583d Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Tue, 17 Sep 2024 13:09:25 +0100 Subject: [PATCH 04/29] chore: refactor signer -> holder --- bun.lockb | Bin 355888 -> 355840 bytes package.json | 7 +- packages/sdk/account/index.ts | 2 - packages/sdk/account/signers/local-account.ts | 55 ---- packages/sdk/account/signers/wallet-client.ts | 48 ---- packages/sdk/account/toNexusAccount.test.ts | 5 +- packages/sdk/account/toNexusAccount.ts | 67 ++--- packages/sdk/account/utils/EthersSigner.ts | 42 --- packages/sdk/account/utils/HttpRequests.ts | 80 ------ packages/sdk/account/utils/Types.ts | 265 +----------------- packages/sdk/account/utils/Utils.ts | 15 +- packages/sdk/account/utils/convertSigner.ts | 76 ----- packages/sdk/account/utils/index.ts | 4 +- packages/sdk/account/utils/toHolder.ts | 85 ++++++ .../clients/createBicoBundlerClient.test.ts | 2 +- .../sdk/clients/createBicoBundlerClient.ts | 22 +- .../sdk/clients/createBicoPaymasterClient.ts | 2 +- .../sdk/clients/createNexusClient.test.ts | 103 ++----- packages/sdk/clients/createNexusClient.ts | 52 ++-- .../erc7579/erc7579.actions.test.ts | 2 +- .../smartAccount/erc7579.actions.test.ts | 2 +- packages/sdk/modules/base/BaseModule.ts | 8 +- .../sdk/modules/base/BaseValidationModule.ts | 22 +- .../sdk/modules/executors/OwnableExecutor.ts | 20 +- .../modules/interfaces/IValidationModule.ts | 4 +- packages/sdk/modules/smart.sessions.test.ts | 2 +- packages/sdk/modules/utils/Types.ts | 67 +---- .../modules/validators/K1ValidatorModule.ts | 12 +- .../modules/validators/ValidationModule.ts | 10 +- .../modules/validators/k1Validator.test.ts | 3 +- packages/test/playground.test.ts | 4 +- packages/test/testUtils.ts | 4 +- scripts/send:userOp.ts | 2 +- scripts/viem:bundler.ts | 21 +- 34 files changed, 283 insertions(+), 832 deletions(-) delete mode 100644 packages/sdk/account/signers/local-account.ts delete mode 100644 packages/sdk/account/signers/wallet-client.ts delete mode 100644 packages/sdk/account/utils/EthersSigner.ts delete mode 100644 packages/sdk/account/utils/HttpRequests.ts delete mode 100644 packages/sdk/account/utils/convertSigner.ts create mode 100644 packages/sdk/account/utils/toHolder.ts diff --git a/bun.lockb b/bun.lockb index 4235b449d012e3ef19940eb63d1a57aaa1dc1e4f..db0ee96a0b9a070f57b7076eddfe24358dd1c3cf 100755 GIT binary patch delta 20074 zcmeI4d%RU+{>S$|`<$i}l~gK`O6h)gi7qEfq$s0O3L#xpsFbo#(N!){77Z!JV9XdZ zW2R8KGZ9W_K=1SU{DL83lzYCV93`D>8@bCL%?)* zFyyQCdxGVPQ>kQl{dAQqaT7VSmkEMu;loW3zK{Nh^8IqPzO8m@0J!mEA|K6dKi|k@ ztTrm8vCc_$}8)t3nyj;$=)o-Q*s=`HJ zxg}u8*WF34%(?%Sj>}d3KQ!=<`tIcF=ty~5H)+@~T{mgnFkSio>toN{S9~X(a`f(s z&Z%~GJ?L)$gQ|ZY=-&?pRsSLI5c?w-^40QBfchjD&}{Bl-dLJW?M$usbd<|i&xL2e z4m<}QQhxzMd0GYk^<1u+blp2$KX-_|8Qy=QZfgtJr+1>BYI zw+*{I^)i1zR6j8EpeV1}d{?w4+AsX?p`};UomH^Sqhmt@Vgst@)$yp-;PCU+fj%+( z@7B`of2*K%M)2V`F$#4L{Xz`+sy{k>)eTNSJp?DAW=-}ZRR8Qq|3h=*9~m|{l>+YB zEYt?)qSm>95A)}l_zrag3nPDLT8(ncqFgexak^Tr6U)Qdnc9P-tR`yosz~ol?b%PF zTshh?x69X4z%joGwd~DNp**dW?Ajzg4GzH0Q*NbgMj=AMmmsz=JE zbSgQlX}V@T{mqe?uT_$%P1DVjWlhsHas##{%BV))3EdjL>JGja{`-;sF!J+tH_CmD z?~dgvg__RLh+4_UO11QC%QMO{a&;?5g{r9AsumT?QyZ>MzC~(AIn`(_KFqHZzG{1Q z3*v0A8^)ffW$H(|0cyp)BfU?g8>0rbE_!gJzqe-p+loI>y{tWU4-ZBCQ`i%AXo^F7 zp@w|bIy`*U^1UOi+Fsw#evyB4S=Z*YWO~Cm8+s&x$5y}hGkZSUK15mJ8}bR z&u>B<+Mh@IHq@Y+{~Odj{VnQcb8Y0S`gfw1TaTJ`4jS*sulM^-Cz^chMR`&hZkvEKFX`+PY9iey74KIJ_|MML`yHd`30;qH5!Njj`FJM^CO+Fj$TP7F8w7@Ks947>Tq3*+Q22Dm!gJzwfs_iZDN%(c^CC;&Tb=OgWFMqYK1Q%ts4D0()sF!ci>y!FMd~KR}X-HQuC|h+r;ju z7t*~^{A{Wbkv%Xvml~-9P=jhudxT$%y1`zNK0MOBQA57E!6U*~t=AWI$BscAsUM?W zdQwA(81mH$LvsaEso{~Y>Yo@oB6K9`4eJ=xkgr-3_;7<$P}5WWDAT?XvEgZvp}Gg> zqFS@UR~^DEYWb4z)pT-k+n8})5VmSF^CGQUZGNQl)k9%P_~ohfE{=TFb}x;zYV?Xi zM$;X=Dl+ob4Xg}*XX^f~js|Z;-SE$%p6Z5gj�S?v_HnO$*!_1%HX|OZrLFul`xo zg3pCMj~Y}vxEb}V--6oC*2w>l)o;c-GjJo-AB0aq*<^(e$V>W6~EjTB1 zUihm1qDZT5aB=8G;j8*fP3vAwlQJ@e~Jv%4LuWS)%<7Gq+zExWd0J_`RY0KX88GP^|$eDc1z^vtNZtE_^SQ< zFw&~&?Wpbit?ZEbdlbl53w$1adFqC~iu|3a?d*ths`b*I2s!Ok7Wr4TU||$g-QyxO zlT7R!1Gh_L=d0VR7JhkZ*6w^b5Oq*DQ!mmD%_QWjU2hzIo_5Vu+>Zoh^?=4f$$!ri8x}EuF-D zR{ni4m{I7JFBfh$k34O*}ltF4P|TYd>&Rm=eUL= z3c68NXXZksm`}RUrBSJsTbO5qKHmKxyed)7-|V)9S2fym7Pc<_1eYz|^U2>m;Z@I- zYgf8b>Y*^pR`a>HkB3(?HsZ?#Pb5pvW6=6WmW%uBlaJ(%3a@r}t}^NuUY+n9oTJ05 z8=l`~gS6VGf1>}KMIy`tQn#0N0YB(tf8d^+0oV31!Cq3r(5kv%TFvPFaq@P>z1 zwvf=lJ}EpG74k3D5Ka!S1%{p48%_zYC0+sBPwj&_I?UENv-F#CZLgtiRNj}kE1eDP z!fQ-?yDbyihu4Jom*I85vr1EN4Y?!IDZKrNzmi)+N)@cSCqw)daHOIRyJeNWm z^;zsHB97ca(O?VWtD{`kDAy8iI-bYwA>p+mJ|oIy4-K<5=CPO_(A~moL)>>!4Bf+P zOS}eNZiN}1d&iNN8U`NYuBUV_eMo#N7`!U$k^_lqb^h;T=l+X?LA4B--mnd}(+?!}DqPso|aAGDb(Z z2e`n_Ff6>D#0TPe`VGf3yBG#Vxs#$?FTCF2ogCg_cpnlsoD$yQ#Fr8G^ebDTn(a+u zd6=h0!9I8{Y4wym4YlnfzzrJ4M7h4iy}lX7hIb_KryMpy*{=&n5#Jb|OJ9xpF#hS> z`A5X{ua3ge@JtjuGYa;{`*V1c@%Wc}V*h-2XGb|7&c6`e)bNhQ`)zpB@!ZBhaNV}2 z{kbk<)$|GeoG@K8t9KmCjmk5l!5`yY5Zb%-66!gN4b(H zH`L?r8@AR2ofGB>#C>U`De8J!JLBnfHxaJ|7lb#Q_&PHQ7lwBt@f+~G7R(E81o2a% z-2CuH;?==(`>6$CoEMOM@KXwUz>S6mobO&A`~zaSGr)CDo~E~iH-`8-c!r;cH8W65m9xJjZ{9X9v%ORYnNEjdGKS zCwQLscSO0##3x3%ven8{@Xo~R?)bZO*}XdpTua*ny$jD{#bcYk;@@m|9l zfIbl3dBooc@4@h9;k^}g9|~_ao^L{Unm!!f`5u3p!*qeRy>>)=cf-@~58;&%Umf0~ z;myIj3eQvWvGC>+_xF8I$;ZRHfcQc@!ym)Dkho<|aBEM5IS*q!J@B-8GQ9c3Z^iSp zdMdmH#LM1W+=$1&)I#tYG76rFa*K%ja+?S8pTk=mo(J-?dhYE-@O&JtE<2}A?)`Fm z=)w4Wco!3QrL70!3*lWt{N?ao4DV9Bp?Droe+ln0;swM#CSD3}Dea{ShBIO=C zl&^<(HSrf18As@i@Kz9ikG*p=-VE<2#O&Rg`FF!x9iHXi3$KQkQ^JE+CiRm{v!w5W%&Y;8F!qJU zuod2g_uzf_06v6|U>kf4F6J8t<6#0!gfn3hOsHCr3l-()1Q}Uk4 z#S1e{dUq$<2=;|0&=guiYiI*)p*?hfj&Kv*_Co7*&}*FUn)Oe8?}R zAH!g9y6W`w1aPK#B8-5M;H2+lI0cHKIqVNDpcS-+HqZ__LMPY{_JG>p4P#x{6TJW2 z-9P%y8lU61LspcU>|4%zOuSI ztYr|Ypp{@fasQU4p#U~|(f1|Wx8P0k-h#KmH?fz4?{Yr~55dFW8_UkKu7{t(jqo#= z4+~%x%!c#9zo#WI2S&h1aPHL>+Ch8hP|Qb1=nP$82n>Z2U>FPsCuhEbya)InD^!Lm zunSa8Cp#|A)XzRnr=NyD!5o+iPr_5M4(;`>O0;dOfGJ5GneHjb~4 z;S=~2K8F%;E}n%M;GF(EmWn%7`nnC&X3m#D1w{$hHrsi!aMBK7Py1B@A01o zr$b{n5Sl>)*bDsMHD09bGq5C?cUh)K*1vc6!BlYOR}4Mi5a8F zsW1(EKj0Ja{Q=)KaE9W$5$j+ZWj}`P@Cm#Iufv<*J3_1B`a)(tR})zQKY?q(o4QHh zjocE_7r+!a3(kgf!1vQyurJ88`xDlqXH}g=ZDN~^->B)4n zDYSxO%HIIj!78{GE`t)71t-B_&>LD)z7Ki?^o9Pk*9rWcZ6IvszcqMZQKn}0B?_(r ze--ivpX%TbI>XqN9q!H02DdyN4;J4{_I_N z8{S}JccZ_DHQ?><5|{^rp&!(S`tXt$t^8h=PtbiNg8dwe99(g;|7mk8rsQ)W^>ooQObUoY(qo7SX znYlbOEW3t0r?}oJI4$l4-Jt_?gig>IHc&@CU=*H&hu{IQZSPWEhPU8txB>ooL@vae z3*M=m0Cpa-r7!X`3hqe?ol=^SOM7QhwwL*8d>IUmj=7Vg!MhRfKJ3)JsCYwRx%b$p zJKzn0^-JknT4)LxlVAck3RZZLiry61$V~J^uwgM@KH?BJ+;O{;|Hef>Cw}dk;v{oV zy((L7FU=d3RQWZN(^RspNn!1MM|R0oNKG4ix;J*I9V=F6Hf*nRUybz0j%_=(Z`*#& uk-yFyaZ{Ds9Vo)&S$|`=m)}kTl6@kTfbGN+*>RQE4D@iA<4P4Vvttq!Jb9U{VWVA1Hu*!IZBV@?F7p2Y~tR zV9Hn9_Wr{lfAV|FVaC6}gd()Ae(Wo1|-H z-Rc$K*K=RARrMbP{fEG$>OTq|pML{WzFPljP*;LU^*mT*hLop%`#&(~+#OBQ_4`@> zIdFZ?gNNVCU@A|m;=i4g{!r7omb;~!Cf~}tQCyz7n)P6}-;a8#`3;dTPpeV(8Q9&= zL%%>xs_nn@L@?L_R{T2j+t973Nwp&dRu1(vsvLRM{ZKvf+fu9S$cI`Z9Y z-zNOF;U}7FxqG^4uG{YEDp{}oU82suq5DN$)n#-=Yoq(ljHbsr=RGz>=vFB3(8mh(c!DEa183R*^Eqh7vhEegL(vt>cP+L6UkQ8mm(ep_k> zua0`FmN0Hwc<@s8Z zdf(u?y<4MPHDAC&)JiU_d@a4?TERM%qd_&)WmVVw33b6WDYr_ksHYm%;luKJ;j1oh zm&mK;>lehupg|Z7Q5!ai{BDsiLQQIYbpOczpEWtK|D6l`869&252T)dB(psT+!e*4 zJ!1j+s?{re)%v|7ue!j#q5Y%$uqe;h#?(7C>YY(qo=ruNs3-rpsBmuR#L|kXe6<6U z@YVC8JYV%EdS9Tn`wq1uTT%X{(yTEHW zTZZltzUm&^2ekwHhOfHf10$cWE~i`g-Ev!6r>p0_X`Qa}{m1^f#(Qzg;o^Ew&FaOW zJyE;aCz|JLb^L+h=d0}pg`cmr@K40Id=%;zc6#K`DEndvk_ux&&qVFOxX7P_nzl!6 zGCtZ*K+T`$#}CvK;|E4*#i`M7S~O7Ig6WaZSNC9Y=k_DjpGkf9*d@_^LFlEioJ-3Z zYS>^Q>Yvmlk-sMNI+TB@54^U6yO{fdpjG9#IZ;8BW_)X+Pvx(t~zoozhY(;CJ zJ9+^8NG-34?=otmUO$_l`c2WD(aw?Yf|^u2dQkYqsO#&Qv1)EzyYx<3Z(L7};(XN_!-uOGhngSn$B)#- zO$cAL+mleOso|@3^L*6$$tAf|>mA`^{aqCm+^pZ6?(GUQym5{Cu^%WB96J7e3se?12PU=oZ=?wP(eV?}?gJJJtu) z>d%Mm4huazbO7p}IWqDCQIqNlhlM}9G@nh4B;a3a6d$g5bm(cQD>yy!XQC$6dS|0{ z=-kLpL|yTD;ZH+Ns`X}s&I(^mj=zh;P+j5N(0Spj`twm&umH6K3sD!mDE!6YFA4v; z$d{nDzcKPRhb~n;{%;H84%A;%{(`!qWvEGYMaxkav?BbUqb}$EsIS`bhhjMoqsd*{ zBT+%M!DErnS3B@jl>ZGizbfkGtL>i+U$w)}MP9XhP3Q}i;&%AOsQ6Oo%cv<|J?-8N zKVMzod(m!vl;^8k{6YAt9p4!Fjg{E{8k;d(&_6=|85Q!?@-M?LPhHX1QNAs8Ip0RT zZ-)@rFgZ8=g<3D;ITY%yEhMjB88u(U^4!RMxUv(Zs*-hm)wO>@Evn6j`=K7{;&zRE zV+%?7YS)Xx&sW>E!1oYty8};k>`%L>sM?VZsD4M(3+{fA-#@e~n##57LjPS)kEnJ? z=m1oIaO96cUEB!N(er7jDPQf+xbU|_ll}L7Ll@dCumi2Y1^@p&`(pj>upZzR7K5oc z`7Blao{?9r-z)Olqka+j&%9hjf4ASS^nV_HY5NeYH2oa4{V#p?)#QIY{E8J%LMxtq z@n<0hPZiI;JP#_KeI=KPif3OH&%QVthHy%FR^&hPBAev>Q@B0PyliKEmv_?;@MZl zv#*M0Uz`jT&%Tmt!uOB5ct%$Gv~1j*ErZffux!hqOlHr5#&dGtP0kEXXL5t4Wa=i9 z|84mdw_*N)@nMhimP}uV=QBko|C_$SvyRswXYrVv&u3d#xQm8~N_sw(YG9s}4$nt! zjS`2UO#vqDlc_(um6}cjOjh7@o2KDajmADfUT%*_)$sV2OfR}Wyc$u@sY#E9S2jP; zhl_s=&$)sHKKXNNpANHjG=humS7<_dz2(cdS#22WgOYk9xZj4=(kxsiH95vm>Hn*<3|mK|4M? z=PmLt+1tMg&pC>A%GtLkgx3nsunCL~uXT8SDP{AT+azrnkNT~Y&7N)>3)~%Vvr8tm z3$KXytMJ;#0-NDEGo5UzV|dMp--vph@C;kPs_;68*Anmg z03Yp^MC1LU-kx|9@jOoV53e=xc~P%xczfX;iRZCWEn+y!byy(6RTzIfAFoAdMrhPNN_tL{>g6Gh$l z{oysITbKri*_HV9sCZO(z76_Dcth}9@qyrM7tg|B;dLW++LkBb(cyI`UhSrnM#SBYvChS`H$7lfn8$@zjOThj%U>|58W73*k+SdIRxZ4A0*p z41N6n$MB{&-N_yf1}DpV(U?j_Jzh*n%?NK=cth}JM&s$Rz@c~-gje=;4#T@JycyAU zIG(Q*coA}ruEj@t{B@6t7e+--oA2mB5jrcp5yZZ`(hQv)-m%2@;DdrXXi8DRQt z)H{iIdEziLGQ5+?`?Z*EaW1y&8x3B)Ot(hGQ;5A%nfyazy;H%9lIeGN9)zcX7bVl} zQSWr(hFllCj@%L68N^P*@^t<~cw^j*_c2XQZ+0bTf)mS3cZPQsaoMc3yTTib_W@(o?Smf5x-Z=fDjH?#8>L;;)GpV|szOCn}yxe5Msi{+471 zoriK3UJvyC@FoyD3Ah-20MA2eBKSgur{6%-`w!P`CE81iPwhrba)rw`KpPh>B{gfBrXZ>neb-e`Fs6P_y4LeXJh)JjOXs^ z@Gc^rgJ*g+yg9_Kz*F+M@Gd4^!FS{7_xJGT65odB>9;1ldBkOJWjeb(nQl2B)2q_S z@KRK~gxHt%Jd$4yZ$Wq-$*+WWDKWDalk{qMmyxrB9*k?lTWHMj;lcP?c$X7rUk~&3 zFt5NIj^_dOMtD~eJK*)0cr(03#15AI{@)7kD&h@zeuZy`x0u)saSy!{UXIvzP}v*V z)VpE+2J_TXGqo zNIbfFJsErG?)We&-auS7x&5Q?O7NUV?;iR%yc>!A>g<$%TJ6_O;B;zN_DOiZCANds z-xS`>dJXun!KYy!>P4GWE0=p_ai&?W)|||=noTiwhaxy_hupn$GP@PrUYL7jPUeCJ z4($(xe&AT%v3n2blACaGrul%Tgu6pCXb!ER4YY-J&;dHa-p~oW`g*nXO6pb8ciaw# zfiNid+QpgS4IMytg@(`w96S3K;>L9Dh`E_gReh!MV|Xn$cW$Op?H34Nf|ubHcry3< zxtR|Ae53XnxE8L1>!AdEBXq01nyHPyjDi%C(=L*`vE}-#WMX zad-lrglA~G3LHK@2fnj#88~zt3CF{)U{vn%`I)+194I;n91ITpUSmyfKr!*Xe34#< zYZ31P)xo2+PwwUUnKlg^{2m2E!NKfsZ~!|Z*W{8+qiiwZ9?%N*guS2*w1xJtH*|s) zP#1Ov?;O2TtPi!IHdKO@94C$h8TlmxJqLCU;_H2S7U3#GB&pgS_xJVZ)LCghcFGR2)`lk>%#96uY>o%7s3~TZ z+yjoIZcNu_JVS6Z+yb-VBAA*Rupm>v#&Lur;RI-xJ7)oR)SU_UfnhKlj)r4k1US0d z73xBDs0>x0DpZ3sJW977gU8_scoLq1zrt#G7G}UqSP9R-a=06&ct!OZe+~?U{@_r% z6|{!Ez)@9OXb0`VDbMYJInt?}AGY1E95MN>z};{U+zY+yK6cb0sW-1uzFLhPf~X90ZL8-+USh zZ_`0vr#cwCqIv=S3_f=V{w1gm(a(qTU@}aB2`~vxhS6{;oCe+DAlMi72k)Q{fR<1M zb)XtJD00B#uSa9xOgIZ1@Vp82*74z(;ZW!a2SZWr^2;*Ej;upi6AB;$g|L*b{C99W zyw66h2VeKU6&xy@3`MXnw1kGx2zG>*sQVoFcW*(i-@;6f?1O~k!NFfKIG8*Dx`A`r zeKle=%wWIy9`bmY0KRbW8TdwmZ!b8g@b!!3u!*{#!e;mk-h#K`UGT-E61Wk5Q zwS?emxE8$G8w+Q{eDW8-I5-D>4d;T71NUTGyv6d?%l{d{A0?VW4VXgbrouGvE!~e{ zZtkqhGp(|V2z$YuG&u!M1BV99U=NtYHunHu`C&EDJ z1Fz8LRagseK{dREY}A$D?*~^oyv-2|f(G=i5Pceb7M_R4;URbgy#1a5-XiY`RpDDY z>z%N-R3E^5@D3}w7yS$T7kFnoA7;T&=np$X1K7&?`KnkD@*Q|5`%n0B&#iYKG+@iU zacl%-3#m(dK3nz;dtnRsuFiVc0N(#SXv{k7p!a!;!P~Q8kp1zBhG67W$ zhKpbxc#C~5RE0a(1HLG~6uf8i7R}qQ&F~NS9Nq%&pu9WsZfF@Sft%r)0`5&q2)xyq z4Sk^>6vH3rts~Nh(G_qToCs~<3;dE)uKiV+5m`sE-U~R&?FrqXBkT>Gz+3DN;0Rhi zW^b&7N5S{GT!%LvufsZc4{m}V@0bhmW`g$~M}r#y*(C7h!!7cL!~aB~Yzw{FD2wye z_(~WW9dlzxgZCHSPuQskQ1KqYdLObk!UiwV&~d)ylhI?r z1&ifU?9p$!D;dtyf<3+L^TnA58dU9gS83V8+?b@oVx!FAugnZ diff --git a/package.json b/package.json index 5a8d835c..2007b9db 100644 --- a/package.json +++ b/package.json @@ -107,12 +107,13 @@ "tsc-alias": "^1.8.8", "tslib": "^2.6.3", "typedoc": "^0.25.9", + "viem": "2.21.6", "vitest": "^1.3.1", - "yargs": "^17.7.2", - "viem": "2.21.6" + "yargs": "^17.7.2" }, "peerDependencies": { - "typescript": "^5" + "typescript": "^5", + "viem": "^2.20.0" }, "commitlint": { "extends": [ diff --git a/packages/sdk/account/index.ts b/packages/sdk/account/index.ts index a7d2a4d2..57756f3e 100644 --- a/packages/sdk/account/index.ts +++ b/packages/sdk/account/index.ts @@ -1,4 +1,2 @@ export * from "./utils/index.js" -export * from "./signers/local-account.js" -export * from "./signers/wallet-client.js" export * from "./toNexusAccount.js" diff --git a/packages/sdk/account/signers/local-account.ts b/packages/sdk/account/signers/local-account.ts deleted file mode 100644 index 65bf3c52..00000000 --- a/packages/sdk/account/signers/local-account.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { - HDAccount, - Hex, - LocalAccount, - PrivateKeyAccount, - SignableMessage, - TypedData, - TypedDataDefinition -} from "viem" -import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts" -import type { SmartAccountSigner } from "../utils/Types.js" - -export class LocalAccountSigner< - T extends HDAccount | PrivateKeyAccount | LocalAccount -> implements SmartAccountSigner -{ - inner: T - signerType: string - - constructor(inner: T) { - this.inner = inner - this.signerType = inner.type // type: "local" - } - - readonly signMessage: (message: SignableMessage) => Promise<`0x${string}`> = ( - message - ) => { - return this.inner.signMessage({ message }) - } - - readonly signTypedData = async < - const TTypedData extends TypedData | { [key: string]: unknown }, - TPrimaryType extends string = string - >( - params: TypedDataDefinition - ): Promise => { - return this.inner.signTypedData(params) - } - - readonly getAddress: () => Promise<`0x${string}`> = async () => { - return this.inner.address - } - - static mnemonicToAccountSigner(key: string): LocalAccountSigner { - const signer = mnemonicToAccount(key) - return new LocalAccountSigner(signer) - } - - static privateKeyToAccountSigner( - key: Hex - ): LocalAccountSigner { - const signer = privateKeyToAccount(key) - return new LocalAccountSigner(signer) - } -} diff --git a/packages/sdk/account/signers/wallet-client.ts b/packages/sdk/account/signers/wallet-client.ts deleted file mode 100644 index 69c10fe5..00000000 --- a/packages/sdk/account/signers/wallet-client.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - type Hex, - type SignableMessage, - type TypedData, - type TypedDataDefinition, - type WalletClient, - getAddress -} from "viem" -import type { SmartAccountSigner } from "../utils/Types.js" - -export class WalletClientSigner implements SmartAccountSigner { - signerType: string - inner: WalletClient - - constructor(client: WalletClient, signerType: string) { - this.inner = client - if (!signerType) { - throw new Error(`InvalidSignerTypeError: ${signerType}`) - } - this.signerType = signerType - } - - getAddress: () => Promise<`0x${string}`> = async () => { - const addresses = await this.inner.getAddresses() - return getAddress(addresses[0]) - } - - readonly signMessage: (message: SignableMessage) => Promise<`0x${string}`> = - async (message) => { - const account = this.inner.account ?? (await this.getAddress()) - - return this.inner.signMessage({ message, account }) - } - - signTypedData = async < - const TTypedData extends TypedData | { [key: string]: unknown }, - TPrimaryType extends string = string - >( - typedData: TypedDataDefinition - ): Promise => { - const account = this.inner.account ?? (await this.getAddress()) - - return this.inner.signTypedData({ - account, - ...typedData - }) - } -} diff --git a/packages/sdk/account/toNexusAccount.test.ts b/packages/sdk/account/toNexusAccount.test.ts index adad3c9d..42d58a34 100644 --- a/packages/sdk/account/toNexusAccount.test.ts +++ b/packages/sdk/account/toNexusAccount.test.ts @@ -78,7 +78,7 @@ describe("nexus.account", async () => { }) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) @@ -275,7 +275,7 @@ describe("nexus.account", async () => { expect(contractResponse).toBe(eip1271MagicValue) }) - test.skip("should sign using signTypedData SDK method", async () => { + test("should sign using signTypedData SDK method", async () => { const appDomain = { chainId: chain.id, name: "TokenWithPermit", @@ -348,7 +348,6 @@ describe("nexus.account", async () => { }) const permitTokenResponse = await nexusClient.writeContract({ - account: account, address: mockAddresses.TokenWithPermit, abi: TokenWithPermitAbi, functionName: "permitWith1271", diff --git a/packages/sdk/account/toNexusAccount.ts b/packages/sdk/account/toNexusAccount.ts index c2ed0cfc..a7a60b5a 100644 --- a/packages/sdk/account/toNexusAccount.ts +++ b/packages/sdk/account/toNexusAccount.ts @@ -10,6 +10,7 @@ import { type RpcSchema, type SignableMessage, type Transport, + type TypedData, type TypedDataDefinition, type UnionPartialBy, concat, @@ -37,7 +38,6 @@ import { getUserOperationHash, toSmartAccount } from "viem/account-abstraction" -import { parseAccount } from "viem/accounts" import contracts from "../__contracts" import { EntrypointAbi, K1ValidatorFactoryAbi } from "../__contracts/abi" import type { Call, GetNonceArgs, UserOperationStruct } from "./utils/Types" @@ -53,19 +53,20 @@ import { 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 { + type TypedDataWith712, eip712WrapHash, getAccountDomainStructFields, getTypesForEIP712Domain, packUserOp, typeToString } from "./utils/Utils" +import { type UnknownHolder, toHolder } from "./utils/toHolder" export type ToNexusSmartAccountParameters = { chain: Chain transport: ClientConfig["transport"] - owner: Account | Address + holder: UnknownHolder index?: bigint | undefined activeModule?: BaseValidationModule executorModule?: BaseExecutionModule @@ -123,7 +124,7 @@ export const toNexusAccount = async ( const { chain, transport, - owner, + holder: holder_, index = 0n, activeModule, executorModule: _, @@ -133,8 +134,10 @@ export const toNexusAccount = async ( name = "Nexus Account" } = parameters + const holder = await toHolder({ holder: holder_ }) + const masterClient = createWalletClient({ - account: parseAccount(owner), + account: holder, chain, transport, key, @@ -143,7 +146,6 @@ export const toNexusAccount = async ( .extend(walletActions) .extend(publicActions) - const moduleSigner = new WalletClientSigner(masterClient, "viem") const signerAddress = masterClient.account.address const entryPointContract = getContract({ address: contracts.entryPoint.address, @@ -169,7 +171,7 @@ export const toNexusAccount = async ( context: signerAddress, additionalContext: "0x" }, - moduleSigner + holder ) let _accountAddress: Address @@ -337,8 +339,8 @@ export const toNexusAccount = async ( message }: { message: SignableMessage }): Promise => { const tempSignature = await defaultedActiveModule - .getSigner() - .signMessage(message) + .getHolder() + .signMessage({ message }) const signature = encodePacked( ["address", "bytes"], @@ -370,33 +372,32 @@ export const toNexusAccount = async ( return accountIsDeployed ? signature : erc6492Signature } - const signTypedData = async ( - // biome-ignore lint/suspicious/noExplicitAny: - typedData: any - ): Promise => { + async function signTypedData< + const typedData extends TypedData | Record, + primaryType extends keyof typedData | "EIP712Domain" = keyof typedData + >(parameters: TypedDataDefinition): Promise { + const { message, primaryType, types: _types, domain } = parameters + + if (!domain) throw new Error("Missing domain") + if (!message) throw new Error("Missing message") + const types = { - EIP712Domain: getTypesForEIP712Domain({ - domain: typedData.domain - }), - ...typedData.types + EIP712Domain: getTypesForEIP712Domain({ domain }), + ..._types } + // @ts-ignore: Comes from nexus parent typehash + const messageStuff: Hex = message.stuff + + // @ts-ignore validateTypedData({ - domain: typedData.domain, - message: typedData.message, - primaryType: typedData.primaryType, + domain, + message, + primaryType, types - } as TypedDataDefinition) - - const appDomainSeparator = domainSeparator({ - domain: { - name: typedData.domain.name, - version: typedData.domain.version, - chainId: typedData.domain.chainId, - verifyingContract: typedData.domain.verifyingContract - } }) + const appDomainSeparator = domainSeparator({ domain }) const accountDomainStructFields = await getAccountDomainStructFields( masterClient as unknown as PublicClient, await getAddress() @@ -408,14 +409,14 @@ export const toNexusAccount = async ( [ encodeAbiParameters(parseAbiParameters(["bytes32, bytes32"]), [ keccak256(toBytes(PARENT_TYPEHASH)), - typedData.message.stuff + messageStuff ]), accountDomainStructFields ] ) ) - const wrappedTypedHash = await eip712WrapHash( + const wrappedTypedHash = eip712WrapHash( parentStructHash, appDomainSeparator ) @@ -424,12 +425,12 @@ export const toNexusAccount = async ( toBytes(wrappedTypedHash) ) - const contentsType = toBytes(typeToString(types)[1]) + const contentsType = toBytes(typeToString(types as TypedDataWith712)[1]) const signatureData = concatHex([ signature, appDomainSeparator, - typedData.message.stuff, + messageStuff, toHex(contentsType), toHex(contentsType.length, { size: 2 }) ]) diff --git a/packages/sdk/account/utils/EthersSigner.ts b/packages/sdk/account/utils/EthersSigner.ts deleted file mode 100644 index bed5708b..00000000 --- a/packages/sdk/account/utils/EthersSigner.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Hex, SignableMessage } from "viem" -import type { LightSigner, SmartAccountSigner } from "./Types.js" - -export class EthersSigner - implements SmartAccountSigner -{ - signerType = "ethers" - - inner: T - - constructor(inner: T, signerType: string) { - this.inner = inner - this.signerType = signerType - } - - async getAddress() { - return (await this.inner.getAddress()) as Hex - } - - async signMessage(_message: SignableMessage): Promise { - const message = typeof _message === "string" ? _message : _message.raw - const signature = await this.inner?.signMessage(message) - return this.#correctSignature(signature as Hex) - } - - // biome-ignore lint/suspicious/noExplicitAny: - async signTypedData(_: any): Promise { - throw new Error("signTypedData is not supported for Ethers Signer") - } - - #correctSignature = (_signature: Hex): Hex => { - let signature = _signature - const potentiallyIncorrectV = Number.parseInt(signature.slice(-2), 16) - if (![27, 28].includes(potentiallyIncorrectV)) { - const correctV = potentiallyIncorrectV + 27 - signature = signature.slice(0, -2) + correctV.toString(16) - } - return signature as Hex - } -} - -export default EthersSigner diff --git a/packages/sdk/account/utils/HttpRequests.ts b/packages/sdk/account/utils/HttpRequests.ts deleted file mode 100644 index 474b12e7..00000000 --- a/packages/sdk/account/utils/HttpRequests.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Logger } from "./Logger.js" -import type { Service } from "./Types.js" -import { getAAError } from "./getAAError.js" - -export enum HttpMethod { - Get = "get", - Post = "post", - Delete = "delete" -} - -export interface HttpRequest { - url: string - method: HttpMethod - // biome-ignore lint/suspicious/noExplicitAny: - body?: Record -} - -export async function sendRequest( - { url, method, body }: HttpRequest, - service: Service -): Promise { - const stringifiedBody = JSON.stringify(body) - - Logger.log(`${service} RPC Request`, { url, body: stringifiedBody }) - - const response = await fetch(url, { - method, - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: stringifiedBody - }) - - // biome-ignore lint/suspicious/noExplicitAny: - let jsonResponse: any - try { - jsonResponse = await response.json() - Logger.log(`${service} RPC Response`, jsonResponse) - } catch (error) { - if (!response.ok) { - throw await getAAError(response.statusText, response.status, service) - } - } - - if (response.ok) { - return jsonResponse as T - } - if (jsonResponse.error) { - throw await getAAError( - `Error coming from ${service}: ${jsonResponse.error.message}` - ) - } - if (jsonResponse.message) { - throw await getAAError(jsonResponse.message, response.status, service) - } - if (jsonResponse.msg) { - throw await getAAError(jsonResponse.msg, response.status, service) - } - if (jsonResponse.data) { - throw await getAAError(jsonResponse.data, response.status, service) - } - if (jsonResponse.detail) { - throw await getAAError(jsonResponse.detail, response.status, service) - } - if (jsonResponse.message) { - throw await getAAError(jsonResponse.message, response.status, service) - } - if (jsonResponse.nonFieldErrors) { - throw await getAAError( - jsonResponse.nonFieldErrors, - response.status, - service - ) - } - if (jsonResponse.delegate) { - throw await getAAError(jsonResponse.delegate, response.status, service) - } - throw await getAAError(response.statusText, response.status, service) -} diff --git a/packages/sdk/account/utils/Types.ts b/packages/sdk/account/utils/Types.ts index 3bf52876..b36dbe2b 100644 --- a/packages/sdk/account/utils/Types.ts +++ b/packages/sdk/account/utils/Types.ts @@ -1,20 +1,8 @@ -import type { - Address, - Chain, - Hash, - Hex, - Log, - PrivateKeyAccount, - PublicClient, - SignTypedDataParameters, - SignableMessage, - TypedData, - TypedDataDefinition, - WalletClient -} from "viem" +import type { Address, Hash, Hex, Log, SignTypedDataParameters } from "viem" import type { ModuleInfo, ModuleType } from "../../modules" import type { BaseValidationModule } from "../../modules/base/BaseValidationModule" import type { MODE_MODULE_ENABLE, MODE_VALIDATION } from "./Constants" +import type { Holder } from "./toHolder" export type EntryPointAddresses = Record export type BiconomyFactories = Record @@ -235,8 +223,6 @@ export interface GasOverheads { export type BaseSmartAccountConfig = { /** index: helps to not conflict with other smart account instances */ index?: bigint - /** provider: WalletClientSigner from viem */ - provider?: WalletClient /** entryPointAddress: address of the smart account entry point */ entryPointAddress?: string /** accountAddress: address of the smart account, potentially counterfactual */ @@ -276,21 +262,14 @@ export type ConditionalBundlerProps = RequireAtLeastOne< export type ResolvedBundlerProps = { bundler: IBundler } -export type ConditionalValidationProps = RequireAtLeastOne< - { - defaultValidationModule: BaseValidationModule - signer: SupportedSigner - }, - "defaultValidationModule" | "signer" -> export type ResolvedValidationProps = { /** defaultValidationModule: {@link BaseValidationModule} */ defaultValidationModule: BaseValidationModule /** activeValidationModule: {@link BaseValidationModule}. The active validation module. Will default to the defaultValidationModule */ activeValidationModule: BaseValidationModule - /** signer: ethers Wallet, viemWallet or alchemys SmartAccountSigner */ - signer: SmartAccountSigner + /** holder */ + holder: Holder /** rpcUrl */ rpcUrl: string } @@ -301,48 +280,6 @@ export type ConfigurationAddresses = { entryPointAddress: Hex } -export type NexusSmartAccountConfigBaseProps = { - /** chain: The chain from viem */ - chain: Chain - /** Factory address of biconomy factory contract or some other contract you have deployed on chain */ - factoryAddress?: Hex - /** K1Validator Address */ - k1ValidatorAddress?: Hex - /** Sender address: If you want to override the Signer address with some other address and get counterfactual address can use this to pass the EOA and get SA address */ - senderAddress?: Hex - /** implementation of smart contract address or some other contract you have deployed and want to override */ - implementationAddress?: Hex - /** defaultFallbackHandler: override the default fallback contract address */ - defaultFallbackHandler?: Hex - /** rpcUrl */ - rpcUrl?: string - /** paymasterUrl: The Paymaster URL retrieved from the Biconomy dashboard */ - paymasterUrl?: string - /** biconomyPaymasterApiKey: The API key retrieved from the Biconomy dashboard */ - biconomyPaymasterApiKey?: string - /** activeValidationModule: The active validation module. Will default to the defaultValidationModule */ - activeValidationModule?: BaseValidationModule - /** scanForUpgradedAccountsFromV1: set to true if you you want the userwho was using biconomy SA v1 to upgrade to biconomy SA v2 */ - scanForUpgradedAccountsFromV1?: boolean - /** the index of SA the EOA have generated and till which indexes the upgraded SA should scan */ - maxIndexForScan?: bigint - /** The initial code to be used for the smart account */ - initCode?: Hex - /** Used for session key manager module */ - sessionData?: ModuleInfo -} -export type NexusSmartAccountConfig = NexusSmartAccountConfigBaseProps & - BaseSmartAccountConfig & - ConditionalBundlerProps & - ConditionalValidationProps - -export type NexusSmartAccountConfigConstructorProps = - NexusSmartAccountConfigBaseProps & - BaseSmartAccountConfig & - ResolvedBundlerProps & - ResolvedValidationProps & - ConfigurationAddresses - /** * Represents options for building a user operation. * @typedef BuildUserOpOptions @@ -530,12 +467,6 @@ export type Signer = LightSigner & { provider: any } export type SupportedSignerName = "alchemy" | "ethers" | "viem" -export type SupportedSigner = - | SmartAccountSigner - | WalletClient - | Signer - | LightSigner - | PrivateKeyAccount export type Service = "Bundler" | "Paymaster" export interface LightSigner { @@ -580,38 +511,6 @@ export type UserOperationStruct = { } //#endregion UserOperationStruct -//#region SmartAccountSigner -/** - * A signer that can sign messages and typed data. - * - * @template Inner - the generic type of the inner client that the signer wraps to provide functionality such as signing, etc. - * - * @var signerType - the type of the signer (e.g. local, hardware, etc.) - * @var inner - the inner client of @type {Inner} - * - * @method getAddress - get the address of the signer - * @method signMessage - sign a message - * @method signTypedData - sign typed data - */ - -// biome-ignore lint/suspicious/noExplicitAny: -export interface SmartAccountSigner { - signerType: string - inner: Inner - - getAddress: () => Promise
- - signMessage: (message: SignableMessage) => Promise - - signTypedData: < - const TTypedData extends TypedData | { [key: string]: unknown }, - TPrimaryType extends string = string - >( - params: TypedDataDefinition - ) => Promise -} -//#endregion SmartAccountSigner - //#region UserOperationCallData export type UserOperationCallData = | { @@ -631,162 +530,6 @@ export type BatchUserOperationCallData = Exclude[] export type SignTypedDataParams = Omit -export type BaseSmartContractAccountProps = - NexusSmartAccountConfigConstructorProps & { - /** chain: The chain from viem */ - chain: Chain - /** factoryAddress: The address of the factory */ - factoryAddress: Hex - /** entryPointAddress: The address of the entry point */ - entryPointAddress: Hex - /** accountAddress: The address of the account */ - 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: Call, 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 typedData - * @returns the signed hash for the message passed - */ - - signTypedData(typedData: 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" diff --git a/packages/sdk/account/utils/Utils.ts b/packages/sdk/account/utils/Utils.ts index fdd27d33..a0c024c7 100644 --- a/packages/sdk/account/utils/Utils.ts +++ b/packages/sdk/account/utils/Utils.ts @@ -4,6 +4,7 @@ import { type Hash, type Hex, type PublicClient, + type TypedData, type TypedDataDomain, type TypedDataParameter, concat, @@ -33,7 +34,6 @@ import { type ModuleType, moduleTypeIds } from "../../modules/utils/Types" import type { AccountMetadata, EIP712DomainReturn, - TypeDefinition, UserOperationStruct } from "./Types" @@ -289,15 +289,24 @@ export const accountMetadata = async ( export const eip712WrapHash = (typedHash: Hex, appDomainSeparator: Hex): Hex => keccak256(concat(["0x1901", appDomainSeparator, typedHash])) -export function typeToString(typeDef: TypeDefinition): string[] { +export type TypedDataWith712 = { + EIP712Domain: TypedDataParameter[] +} & TypedData + +export function typeToString(typeDef: TypedDataWith712): string[] { return Object.entries(typeDef).map(([key, fields]) => { - const fieldStrings = fields + const fieldStrings = (fields ?? []) .map((field) => `${field.type} ${field.name}`) .join(",") return `${key}(${fieldStrings})` }) } +/** @ignore */ +export function bigIntReplacer(_key: string, value: any): any { + return typeof value === "bigint" ? value.toString() : value +} + export const getAccountDomainStructFields = async ( publicClient: PublicClient, accountAddress: Address diff --git a/packages/sdk/account/utils/convertSigner.ts b/packages/sdk/account/utils/convertSigner.ts deleted file mode 100644 index ec169c5a..00000000 --- a/packages/sdk/account/utils/convertSigner.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - http, - type PrivateKeyAccount, - type WalletClient, - createWalletClient -} from "viem" -import { ERROR_MESSAGES, WalletClientSigner } from "../index.js" -import type { Signer, SmartAccountSigner, SupportedSigner } from "../index.js" -import { EthersSigner } from "./EthersSigner.js" - -interface SmartAccountResult { - signer: SmartAccountSigner -} - -function isPrivateKeyAccount( - signer: SupportedSigner -): signer is PrivateKeyAccount { - return (signer as PrivateKeyAccount).type === "local" -} - -export function isWalletClient( - signer: SupportedSigner -): signer is WalletClient { - return (signer as WalletClient).name === "Wallet Client" -} - -function isEthersSigner(signer: SupportedSigner): signer is Signer { - return (signer as Signer).provider !== undefined -} - -function isAlchemySigner( - signer: SupportedSigner -): signer is SmartAccountSigner { - return (signer as SmartAccountSigner).signerType !== undefined -} - -export const convertSigner = async ( - signer: SupportedSigner, - rpcUrl?: string -): Promise => { - let resolvedSmartAccountSigner: SmartAccountSigner - - if (!isAlchemySigner(signer)) { - if (isEthersSigner(signer)) { - const ethersSigner = signer as Signer - if (!rpcUrl) throw new Error(ERROR_MESSAGES.MISSING_RPC_URL) - if (!ethersSigner.provider) { - throw new Error("Cannot consume an ethers Wallet without a provider") - } - // convert ethers Wallet to alchemy's SmartAccountSigner under the hood - resolvedSmartAccountSigner = new EthersSigner(ethersSigner, "ethers") - } else if (isWalletClient(signer)) { - const walletClient = signer as WalletClient - if (!walletClient.account) { - throw new Error("Cannot consume a viem wallet without an account") - } - // convert viems walletClient to alchemy's SmartAccountSigner under the hood - resolvedSmartAccountSigner = new WalletClientSigner(walletClient, "viem") - } else if (isPrivateKeyAccount(signer)) { - if (!rpcUrl) throw new Error(ERROR_MESSAGES.MISSING_RPC_URL) - const walletClient = createWalletClient({ - account: signer as PrivateKeyAccount, - transport: http(rpcUrl) - }) - resolvedSmartAccountSigner = new WalletClientSigner(walletClient, "viem") - } else { - throw new Error("Unsupported signer") - } - } else { - if (!rpcUrl) throw new Error(ERROR_MESSAGES.MISSING_RPC_URL) - resolvedSmartAccountSigner = signer as SmartAccountSigner - } - return { - signer: resolvedSmartAccountSigner - } -} diff --git a/packages/sdk/account/utils/index.ts b/packages/sdk/account/utils/index.ts index b1f3d837..36c7543f 100644 --- a/packages/sdk/account/utils/index.ts +++ b/packages/sdk/account/utils/index.ts @@ -1,8 +1,6 @@ export * from "./Types.js" export * from "./Utils.js" export * from "./Constants.js" -export * from "./convertSigner.js" export * from "./getChain.js" export * from "./Logger.js" -export * from "./HttpRequests.js" -export * from "./EthersSigner.js" +export * from "./toHolder.js" diff --git a/packages/sdk/account/utils/toHolder.ts b/packages/sdk/account/utils/toHolder.ts new file mode 100644 index 00000000..94d2948f --- /dev/null +++ b/packages/sdk/account/utils/toHolder.ts @@ -0,0 +1,85 @@ +import { + type Account, + type Address, + type Chain, + type EIP1193Provider, + type EIP1193RequestFn, + type EIP1474Methods, + type LocalAccount, + type OneOf, + type Transport, + type WalletClient, + createWalletClient, + custom +} from "viem" +import { toAccount } from "viem/accounts" + +import { signTypedData } from "viem/actions" +import { getAction } from "viem/utils" + +export type Holder = LocalAccount +export type UnknownHolder = OneOf< + | EIP1193Provider + | WalletClient + | LocalAccount +> +export async function toHolder({ + holder, + address +}: { + holder: UnknownHolder + address?: Address +}): Promise { + if ("type" in holder && holder.type === "local") { + return holder as LocalAccount + } + + let walletClient: + | WalletClient + | undefined = undefined + + if ("request" in holder) { + if (!address) { + try { + ;[address] = await (holder.request as EIP1193RequestFn)( + { + method: "eth_requestAccounts" + } + ) + } catch { + ;[address] = await (holder.request as EIP1193RequestFn)( + { + method: "eth_accounts" + } + ) + } + } + if (!address) throw new Error("address required") + + walletClient = createWalletClient({ + account: address, + transport: custom(holder as EIP1193Provider) + }) + } + + if (!walletClient) { + walletClient = holder as WalletClient + } + + return toAccount({ + address: walletClient.account.address, + async signMessage({ message }) { + return walletClient.signMessage({ message }) + }, + async signTypedData(typedData) { + return getAction( + walletClient, + signTypedData, + "signTypedData" + )(typedData as any) + }, + async signTransaction(_) { + throw new Error("Not supported") + } + }) +} diff --git a/packages/sdk/clients/createBicoBundlerClient.test.ts b/packages/sdk/clients/createBicoBundlerClient.test.ts index d0986929..2592ff0d 100644 --- a/packages/sdk/clients/createBicoBundlerClient.test.ts +++ b/packages/sdk/clients/createBicoBundlerClient.test.ts @@ -34,7 +34,7 @@ describe("bico.bundler", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusAccount = await toNexusAccount({ - owner: account, + holder: account, chain, transport: http() }) diff --git a/packages/sdk/clients/createBicoBundlerClient.ts b/packages/sdk/clients/createBicoBundlerClient.ts index fa4b6a42..5e10bc67 100644 --- a/packages/sdk/clients/createBicoBundlerClient.ts +++ b/packages/sdk/clients/createBicoBundlerClient.ts @@ -14,23 +14,35 @@ type BicoBundlerClientConfig = Omit & bundlerUrl: string } | { - chainId: number apiKey?: string } > export const createBicoBundlerClient = ( parameters: BicoBundlerClientConfig -): BundlerClient => - createBundlerClient({ +): BundlerClient => { + if ( + !parameters.apiKey && + !parameters.bundlerUrl && + !parameters.transport && + !parameters?.chain + ) { + throw new Error( + "Cannot set determine a bundler url, please provide a chain." + ) + } + + return createBundlerClient({ ...parameters, transport: - parameters.transport ?? parameters.bundlerUrl + parameters.transport ? parameters.transport : parameters.bundlerUrl ? http(parameters.bundlerUrl) : http( - `https://bundler.biconomy.io/api/v2/${parameters.chainId}/${ + // @ts-ignore: Type saftey provided by the if statement above + `https://bundler.biconomy.io/api/v3/${parameters.chain.id}/${ parameters.apiKey ?? "nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14" }` ) }) +} diff --git a/packages/sdk/clients/createBicoPaymasterClient.ts b/packages/sdk/clients/createBicoPaymasterClient.ts index dc221c66..8653a803 100644 --- a/packages/sdk/clients/createBicoPaymasterClient.ts +++ b/packages/sdk/clients/createBicoPaymasterClient.ts @@ -28,6 +28,6 @@ export const createBicoPaymasterClient = ( parameters.transport ?? parameters.paymasterUrl ? http(parameters.paymasterUrl) : http( - `https://paymaster.biconomy.io/api/v2/${parameters.chainId}/${parameters.apiKey}` + `https://paymaster.biconomy.io/api/v3/${parameters.chainId}/${parameters.apiKey}` ) }) diff --git a/packages/sdk/clients/createNexusClient.test.ts b/packages/sdk/clients/createNexusClient.test.ts index f1c7d208..15681915 100644 --- a/packages/sdk/clients/createNexusClient.test.ts +++ b/packages/sdk/clients/createNexusClient.test.ts @@ -23,11 +23,7 @@ import { toTestClient, topUp } from "../../test/testUtils" -import { - type MasterClient, - type NetworkConfig, - fundAndDeployClients -} from "../../test/testUtils" +import type { MasterClient, NetworkConfig } from "../../test/testUtils" import { pKey } from "../../test/testUtils" import { addresses } from "../__contracts/addresses" import { ERROR_MESSAGES } from "../account/utils/Constants" @@ -60,14 +56,13 @@ describe("nexus.client", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) }) nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() - await fundAndDeployClients(testClient, [nexusClient]) }) afterAll(async () => { await killNetwork([network?.rpcPort, network?.bundlerPort]) @@ -129,31 +124,27 @@ describe("nexus.client", async () => { ]) }) - test.concurrent( - "should estimate gas for writing to a contract", - async () => { - const encodedCall = encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - const call = { - to: mockAddresses.Counter, - data: encodedCall - } - const results = await Promise.all([ - nexusClient.estimateUserOperationGas({ calls: [call] }), - nexusClient.estimateUserOperationGas({ calls: [call, call] }) - ]) + test("should estimate gas for writing to a contract", async () => { + const encodedCall = encodeFunctionData({ + abi: CounterAbi, + functionName: "incrementNumber" + }) + const call = { + to: mockAddresses.Counter, + data: encodedCall + } + const results = await Promise.all([ + nexusClient.estimateUserOperationGas({ calls: [call] }), + nexusClient.estimateUserOperationGas({ calls: [call, call] }) + ]) - const increasingGasExpenditure = results.every( - ({ preVerificationGas }, i) => - preVerificationGas > (results[i - 1]?.preVerificationGas ?? 0) - ) + const increasingGasExpenditure = results.every( + ({ preVerificationGas }, i) => + preVerificationGas > (results[i - 1]?.preVerificationGas ?? 0) + ) - expect(increasingGasExpenditure).toBeTruthy() - }, - 60000 - ) + expect(increasingGasExpenditure).toBeTruthy() + }, 60000) test("should check enable mode", async () => { const result = makeInstallDataAndHash(account.address, [ @@ -231,58 +222,6 @@ describe("nexus.client", async () => { expect(() => getChain(chainId)).toThrow(ERROR_MESSAGES.CHAIN_NOT_FOUND) }) - 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 = AbiCoder.defaultAbiCoder().encode( - [Executions], - [txs] - ) - - const executionCalldataPrepWithViem = encodeAbiParameters( - [viemExecutions], - [txs] - ) - - expect(executionCalldataPrepWithEthers).toBe(expectedResult) - expect(executionCalldataPrepWithViem).toBe(expectedResult) - }) - test("should have attached erc757 actions", async () => { const [ accountId, diff --git a/packages/sdk/clients/createNexusClient.ts b/packages/sdk/clients/createNexusClient.ts index 6f989c60..99e47d1c 100644 --- a/packages/sdk/clients/createNexusClient.ts +++ b/packages/sdk/clients/createNexusClient.ts @@ -1,30 +1,29 @@ -import { - http, - type Account, - type Address, - type Chain, - type Client, - type ClientConfig, - type EstimateFeesPerGasReturnType, - type Prettify, - type PublicClient, - type RpcSchema, - type Transport +import type { + Address, + Chain, + Client, + ClientConfig, + EstimateFeesPerGasReturnType, + Prettify, + PublicClient, + RpcSchema, + Transport } from "viem" -import { - type BundlerActions, - type BundlerClientConfig, - type PaymasterActions, - type SmartAccount, - type UserOperationRequest, - createBundlerClient +import type { + BundlerActions, + BundlerClientConfig, + PaymasterActions, + SmartAccount, + UserOperationRequest } from "viem/account-abstraction" import contracts from "../__contracts" import type { Call } from "../account/utils/Types" import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" +import type { UnknownHolder } from "../account/utils/toHolder" import type { BaseExecutionModule } from "../modules/base/BaseExecutionModule" import type { BaseValidationModule } from "../modules/base/BaseValidationModule" +import { createBicoBundlerClient } from "./createBicoBundlerClient" import { type Erc7579Actions, erc7579Actions } from "./decorators/erc7579" import { type SmartAccountActions, @@ -109,7 +108,7 @@ export type NexusClientConfig< } | undefined /** Owner of the account. */ - owner: Address | Account + holder: UnknownHolder /** Index of the account. */ index?: bigint /** Active module of the account. */ @@ -128,7 +127,7 @@ export async function createNexusClient( const { client: client_, chain = parameters.chain ?? client_?.chain, - owner, + holder, index = 0n, key = "nexus client", name = "Nexus Client", @@ -158,7 +157,7 @@ export async function createNexusClient( const nexusAccount = await toNexusAccount({ transport, chain, - owner, + holder, index, activeModule, executorModule, @@ -166,17 +165,14 @@ export async function createNexusClient( k1ValidatorAddress }) - const bundler = createBundlerClient({ + const bundler = createBicoBundlerClient({ + ...parameters, key, name, account: nexusAccount, paymaster, paymasterContext, - transport: - bundlerTransport ?? - http( - `https://bundler.biconomy.io/api/v2/${chain.id}/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14` - ), + transport: bundlerTransport, userOperation }) .extend(erc7579Actions()) diff --git a/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts b/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts index c4432a44..e8d5c7cc 100644 --- a/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts +++ b/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts @@ -37,7 +37,7 @@ describe("erc7579.decorators", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) diff --git a/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts b/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts index ee5a3bd0..7dfa9c8d 100644 --- a/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts +++ b/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts @@ -38,7 +38,7 @@ describe("account.decorators", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) diff --git a/packages/sdk/modules/base/BaseModule.ts b/packages/sdk/modules/base/BaseModule.ts index 21f9a891..7b6e9d21 100644 --- a/packages/sdk/modules/base/BaseModule.ts +++ b/packages/sdk/modules/base/BaseModule.ts @@ -1,6 +1,6 @@ import { type Address, type Hex, encodeFunctionData, parseAbi } from "viem" import contracts from "../../__contracts/index.js" -import type { SmartAccountSigner } from "../../account/index.js" +import type { Holder } from "../../account/utils/toHolder.js" import type { Module } from "../../clients/decorators/erc7579/index.js" import { type ModuleType, @@ -16,15 +16,15 @@ export abstract class BaseModule { hook?: Address version: ModuleVersion = "1.0.0-beta" entryPoint: Address = contracts.entryPoint.address - signer: SmartAccountSigner + holder: Holder - constructor(module: Module, signer: SmartAccountSigner) { + constructor(module: Module, holder: Holder) { this.address = module.address this.context = module.context ?? "0x" this.additionalContext = module.additionalContext ?? "0x" this.hook = module.hook this.type = module.type - this.signer = signer + this.holder = holder } public installModule(): Hex { diff --git a/packages/sdk/modules/base/BaseValidationModule.ts b/packages/sdk/modules/base/BaseValidationModule.ts index 2d7625a4..6bc74508 100644 --- a/packages/sdk/modules/base/BaseValidationModule.ts +++ b/packages/sdk/modules/base/BaseValidationModule.ts @@ -1,10 +1,10 @@ import { type Hex, getAddress } from "viem" -import type { SmartAccountSigner } from "../../account/index.js" +import type { Holder } from "../../account/utils/toHolder.js" import { BaseModule } from "./BaseModule.js" export abstract class BaseValidationModule extends BaseModule { - public getSigner(): SmartAccountSigner { - return this.signer + public getHolder(): Holder { + return this.holder } getDummySignature(): Hex { @@ -19,16 +19,18 @@ export abstract class BaseValidationModule extends BaseModule { } async signUserOpHash(userOpHash: string): Promise { - const signature = await this.signer.signMessage({ raw: userOpHash as Hex }) + const signature = await this.holder.signMessage({ + message: { raw: userOpHash as Hex } + }) return signature as Hex } - async signMessageSmartAccountSigner( + async signMessageHolder( _message: string | Uint8Array, - signer: SmartAccountSigner + holder: Holder ): Promise { const message = typeof _message === "string" ? _message : { raw: _message } - let signature: `0x${string}` = await signer.signMessage(message) + let signature: `0x${string}` = await holder.signMessage({ message }) const potentiallyIncorrectV = Number.parseInt(signature.slice(-2), 16) if (![27, 28].includes(potentiallyIncorrectV)) { @@ -40,15 +42,15 @@ export abstract class BaseValidationModule extends BaseModule { } /** - * Signs a message using the appropriate method based on the type of signer. + * Signs a message using the appropriate method based on the type of holder. * * @param {Uint8Array | string} message - The message to be signed. * @returns {Promise} A promise resolving to the signature or error message. - * @throws {Error} If the signer type is invalid or unsupported. + * @throws {Error} If the holder type is invalid or unsupported. */ async signMessage(_message: Uint8Array | string): Promise { const message = typeof _message === "string" ? _message : { raw: _message } - let signature = await this.signer.signMessage(message) + let signature = await this.holder.signMessage({ message }) const potentiallyIncorrectV = Number.parseInt(signature.slice(-2), 16) if (![27, 28].includes(potentiallyIncorrectV)) { diff --git a/packages/sdk/modules/executors/OwnableExecutor.ts b/packages/sdk/modules/executors/OwnableExecutor.ts index 3e923082..fd9b94d9 100644 --- a/packages/sdk/modules/executors/OwnableExecutor.ts +++ b/packages/sdk/modules/executors/OwnableExecutor.ts @@ -1,8 +1,11 @@ import { + type Account, type Address, + type Chain, type Hash, type Hex, type PublicClient, + type Transport, type WalletClient, encodeAbiParameters, encodeFunctionData, @@ -10,13 +13,12 @@ import { getAddress, parseAbi } from "viem" -import { WalletClientSigner } from "../../account/signers/wallet-client" import { SENTINEL_ADDRESS } from "../../account/utils/Constants" +import { type Holder, toHolder } from "../../account/utils/toHolder" import type { NexusClient } from "../../clients/createNexusClient" import type { Module } from "../../clients/decorators/erc7579" import { BaseExecutionModule } from "../base/BaseExecutionModule" import type { Execution } from "../utils/Types" - export class OwnableExecutorModule extends BaseExecutionModule { public nexusClient: NexusClient public owners: Address[] @@ -26,12 +28,10 @@ export class OwnableExecutorModule extends BaseExecutionModule { module: Module, nexusClient: NexusClient, owners: Address[], - address: Address + address: Address, + holder: Holder ) { - super( - module, - new WalletClientSigner(nexusClient.account.client as WalletClient, "viem") - ) + super(module, holder) this.nexusClient = nexusClient this.owners = owners this.context = module.context ?? "0x" @@ -59,11 +59,15 @@ export class OwnableExecutorModule extends BaseExecutionModule { functionName: "getOwners", args: [await nexusClient.account.getAddress()] }) + const holder = await toHolder({ holder: nexusClient.account.client } as { + holder: WalletClient + }) return new OwnableExecutorModule( module, nexusClient, owners as Address[], - address + address, + holder ) } diff --git a/packages/sdk/modules/interfaces/IValidationModule.ts b/packages/sdk/modules/interfaces/IValidationModule.ts index fd451989..1ab6b5c6 100644 --- a/packages/sdk/modules/interfaces/IValidationModule.ts +++ b/packages/sdk/modules/interfaces/IValidationModule.ts @@ -1,10 +1,10 @@ import type { Hex } from "viem" -import type { SmartAccountSigner } from "../../account/utils/Types" +import type { Holder } from "../../account/utils/toHolder" export interface IValidationModule { getAddress(): Hex getInitData(): Promise - getSigner(): Promise + getHolder(): Promise signUserOpHash(_userOpHash: string): Promise signMessage(_message: string | Uint8Array): Promise getDummySignature(): Promise diff --git a/packages/sdk/modules/smart.sessions.test.ts b/packages/sdk/modules/smart.sessions.test.ts index 5b6ea8e9..de07b310 100644 --- a/packages/sdk/modules/smart.sessions.test.ts +++ b/packages/sdk/modules/smart.sessions.test.ts @@ -40,7 +40,7 @@ describe("smart.sessions", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) diff --git a/packages/sdk/modules/utils/Types.ts b/packages/sdk/modules/utils/Types.ts index 3519ea96..ba755135 100644 --- a/packages/sdk/modules/utils/Types.ts +++ b/packages/sdk/modules/utils/Types.ts @@ -1,10 +1,7 @@ import type { Address, Chain, Hex } from "viem" -import type { - SimulationType, - SmartAccountSigner, - SupportedSigner, - UserOperationStruct -} from "../../account/utils/Types" +import type { SimulationType } from "../../account/utils/Types" +import type { Holder, UnknownHolder } from "../../account/utils/toHolder" + export type ModuleVersion = "1.0.0-beta" // | 'V1_0_1' export interface BaseValidationModuleConfig { @@ -18,7 +15,7 @@ export interface K1ValidationModuleConfig extends BaseValidationModuleConfig { /** Version of the module */ version?: ModuleVersion /** Signer: viemWallet or ethers signer. Ingested when passed into smartAccount */ - signer: SupportedSigner + signer: UnknownHolder } export interface K1ValidatorModuleConfigConstructorProps @@ -27,41 +24,8 @@ export interface K1ValidatorModuleConfigConstructorProps moduleAddress?: Hex /** Version of the module */ version?: ModuleVersion - /** Signer: Converted from viemWallet or ethers signer to SmartAccountSigner */ - signer: SmartAccountSigner -} - -// export interface SessionKeyManagerModuleConfig -// extends BaseValidationModuleConfig { -// /** Address of the module */ -// moduleAddress?: Hex -// /** Version of the module */ -// version?: ModuleVersion -// /** SmartAccount address */ -// smartAccountAddress: Hex -// storageType?: StorageType -// sessionStorageClient?: ISessionStorage -// } - -// export interface BatchedSessionRouterModuleConfig -// extends BaseValidationModuleConfig { -// /** Address of the module */ -// moduleAddress?: Hex -// /** Version of the module */ -// version?: ModuleVersion -// /** Session Key Manager module: Could be BaseValidationModule */ -// sessionKeyManagerModule?: SessionKeyManagerModule -// /** Session Key Manager module address */ -// sessionManagerModuleAddress?: Hex -// /** Address of the associated smart account */ -// smartAccountAddress: Hex -// /** Storage type, e.g. local storage */ -// storageType?: StorageType -// } - -export enum StorageType { - LOCAL_STORAGE = 0, - MEMORY_STORAGE = 1 + /** Signer: Converted from viemWallet or ethers signer to Holder */ + holder: Holder } export type SessionDataTuple = [ @@ -77,7 +41,7 @@ export type SessionParams = { /** ID of the session */ sessionID?: string /** Session Signer: viemWallet or ethers signer. Ingested when passed into smartAccount */ - sessionSigner: SupportedSigner + sessionSigner: UnknownHolder /** The session validation module is a sub-module smart-contract which works with session key manager validation module. It validates the userop calldata against the defined session permissions (session key data) within the contract. */ sessionValidationModule?: Hex /** Additional info if needed to be appended in signature */ @@ -86,7 +50,7 @@ export type SessionParams = { export type StrictSessionParams = { sessionID: string - sessionSigner: SupportedSigner + sessionSigner: UnknownHolder } export type ModuleInfo = { @@ -94,7 +58,7 @@ export type ModuleInfo = { // sessionParams?: SessionParams[] // where SessionParams is below four sessionID?: string /** Session Signer: viemWallet or ethers signer. Ingested when passed into smartAccount */ - sessionSigner?: SupportedSigner + sessionHolder?: UnknownHolder /** The session validation module is a sub-module smart-contract which works with session key manager validation module. It validates the userop calldata against the defined session permissions (session key data) within the contract. */ sessionValidationModule?: Hex /** Additional info if needed to be appended in signature */ @@ -144,7 +108,7 @@ export interface MultiChainValidationModuleConfig /** Version of the module */ version?: ModuleVersion /** Signer: viemWallet or ethers signer. Ingested when passed into smartAccount */ - signer: SupportedSigner + signer: UnknownHolder } export interface MultiChainValidationModuleConfigConstructorProps extends BaseValidationModuleConfig { @@ -153,16 +117,7 @@ export interface MultiChainValidationModuleConfigConstructorProps /** Version of the module */ version?: ModuleVersion /** Signer: viemWallet or ethers signer. Ingested when passed into smartAccount */ - signer: SmartAccountSigner -} - -export type MultiChainUserOpDto = { - /** window end timestamp */ - validUntil?: number - /** window start timestamp */ - validAfter?: number - chainId: number - userOp: Partial + holder: Holder } export interface BaseSessionKeyData { diff --git a/packages/sdk/modules/validators/K1ValidatorModule.ts b/packages/sdk/modules/validators/K1ValidatorModule.ts index 3c51e6b9..eb9f7415 100644 --- a/packages/sdk/modules/validators/K1ValidatorModule.ts +++ b/packages/sdk/modules/validators/K1ValidatorModule.ts @@ -1,25 +1,25 @@ import addresses from "../../__contracts/addresses.js" -import type { SmartAccountSigner } from "../../account/index.js" +import type { Holder } from "../../account/utils/toHolder.js" import type { Module } from "../../clients/decorators/erc7579/index.js" import { BaseValidationModule } from "../base/BaseValidationModule.js" export class K1ValidatorModule extends BaseValidationModule { // biome-ignore lint/complexity/noUselessConstructor: - public constructor(moduleConfig: Module, signer: SmartAccountSigner) { - super(moduleConfig, signer) + public constructor(moduleConfig: Module, holder: Holder) { + super(moduleConfig, holder) } public static async create( - signer: SmartAccountSigner, + holder: Holder, k1ValidatorAddress = addresses.K1Validator ): Promise { const module: Module = { address: k1ValidatorAddress, type: "validator", - context: await signer.getAddress(), + context: holder.address, additionalContext: "0x" } - const instance = new K1ValidatorModule(module, signer) + const instance = new K1ValidatorModule(module, holder) return instance } } diff --git a/packages/sdk/modules/validators/ValidationModule.ts b/packages/sdk/modules/validators/ValidationModule.ts index e75c627b..4a57b9ce 100644 --- a/packages/sdk/modules/validators/ValidationModule.ts +++ b/packages/sdk/modules/validators/ValidationModule.ts @@ -1,15 +1,15 @@ import type { Address, Hex } from "viem" -import type { SmartAccountSigner } from "../../account/index.js" +import type { Holder } from "../../account/utils/toHolder.js" import type { Module } from "../../clients/decorators/erc7579/index.js" import { BaseValidationModule } from "../base/BaseValidationModule.js" export class ValidationModule extends BaseValidationModule { - private constructor(moduleConfig: Module, signer: SmartAccountSigner) { - super(moduleConfig, signer) + private constructor(moduleConfig: Module, holder: Holder) { + super(moduleConfig, holder) } public static async create( - signer: SmartAccountSigner, + holder: Holder, address: Address, context: Hex ): Promise { @@ -19,7 +19,7 @@ export class ValidationModule extends BaseValidationModule { context, additionalContext: "0x" } - const instance = new ValidationModule(module, signer) + const instance = new ValidationModule(module, holder) return instance } } diff --git a/packages/sdk/modules/validators/k1Validator.test.ts b/packages/sdk/modules/validators/k1Validator.test.ts index 95090acb..e50d0692 100644 --- a/packages/sdk/modules/validators/k1Validator.test.ts +++ b/packages/sdk/modules/validators/k1Validator.test.ts @@ -40,7 +40,7 @@ describe("modules.k1Validator.write", async () => { testClient = toTestClient(chain, getTestAccount(5)) nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl) @@ -109,7 +109,6 @@ describe("modules.k1Validator.write", async () => { const byteCode = await testClient.getCode({ address: addresses.K1Validator }) - console.log({ byteCode }) const hash = await nexusClient.uninstallModule({ module: { diff --git a/packages/test/playground.test.ts b/packages/test/playground.test.ts index 776006bf..17628020 100644 --- a/packages/test/playground.test.ts +++ b/packages/test/playground.test.ts @@ -74,7 +74,7 @@ describe.skipIf(!playgroundTrue)("playground", () => { test("should init the smart account", async () => { nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl), @@ -141,7 +141,7 @@ describe.skipIf(!playgroundTrue)("playground", () => { } nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl), diff --git a/packages/test/testUtils.ts b/packages/test/testUtils.ts index 1554c419..ac6be0d1 100644 --- a/packages/test/testUtils.ts +++ b/packages/test/testUtils.ts @@ -310,7 +310,7 @@ export const toFundedTestClients = async ({ const testClient = toTestClient(chain, getTestAccount()) const nexus = await createNexusClient({ - owner: account, + holder: account, transport: http(), bundlerTransport: http(bundlerUrl), chain @@ -423,7 +423,7 @@ export const topUp = async ( } export const getBundlerUrl = (chainId: number) => - `https://bundler.biconomy.io/api/v2/${chainId}/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14` + `https://bundler.biconomy.io/api/v3/${chainId}/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f14` const getTestChainFromPort = (port: number): Chain => getCustomChain(`Anvil-${port}`, port, `http://localhost:${port}`, "") diff --git a/scripts/send:userOp.ts b/scripts/send:userOp.ts index 8592219e..6d913345 100644 --- a/scripts/send:userOp.ts +++ b/scripts/send:userOp.ts @@ -49,7 +49,7 @@ const accountTwo = privateKeyToAccount(`0x${privateKeyTwo}`) const recipient = accountTwo.address const nexusClient = await createNexusClient({ - owner: account, + holder: account, chain, transport: http(), bundlerTransport: http(bundlerUrl), diff --git a/scripts/viem:bundler.ts b/scripts/viem:bundler.ts index d2835314..f8936aea 100644 --- a/scripts/viem:bundler.ts +++ b/scripts/viem:bundler.ts @@ -1,9 +1,12 @@ -import { http, type PublicClient, createPublicClient, parseEther } from "viem" +import { config } from "dotenv" +import { http, type PublicClient, createPublicClient } from "viem" import { privateKeyToAccount } from "viem/accounts" import { toNexusAccount } from "../packages/sdk/account/toNexusAccount" import { getChain } from "../packages/sdk/account/utils/getChain" import { createBicoBundlerClient } from "../packages/sdk/clients/createBicoBundlerClient" +config() + const k1ValidatorAddress = "0x663E709f60477f07885230E213b8149a7027239B" const factoryAddress = "0x887Ca6FaFD62737D0E79A2b8Da41f0B15A864778" @@ -31,8 +34,8 @@ export const getConfig = () => { privateKeyTwo } = getEnvVars() - const chains = [Number.parseInt(chainIdFromEnv)] - const chainId = chains[0] + const chainId = Number.parseInt(chainIdFromEnv) + const chain = getChain(chainId) return { @@ -61,7 +64,7 @@ const publicClient = createPublicClient({ const main = async () => { const nexusAccount = await toNexusAccount({ - owner: account, + holder: account, chain, transport: http(), k1ValidatorAddress, @@ -108,8 +111,12 @@ const main = async () => { }) ]) console.timeEnd("read methods") + + const successCount = results.filter((result) => result.status === "fulfilled") console.log( - `${results}: running the ${usesAltoBundler ? "Alto" : "Bico"} bundler` + `running the ${usesAltoBundler ? "Alto" : "Bico"} bundler with ${ + successCount.length + } successful calls` ) console.time("write methods") @@ -123,7 +130,11 @@ const main = async () => { account: nexusAccount }) const userOpReceipt = await bicoBundler.waitForUserOperationReceipt({ hash }) + const txHash = await publicClient.waitForTransactionReceipt({ + hash: userOpReceipt.receipt.transactionHash + }) console.timeEnd("write methods") + console.log({ txHash }) } main() From d5c3c831f11c54ba811a1b31612d787de2f10eb8 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Wed, 18 Sep 2024 18:05:19 +0100 Subject: [PATCH 05/29] chore: add relevant exports --- packages/sdk/account/utils/Types.ts | 7 ------- packages/sdk/clients/createBicoBundlerClient.ts | 5 +++-- packages/sdk/clients/decorators/erc7579/index.ts | 3 +-- packages/sdk/clients/decorators/erc7579/installModules.ts | 3 ++- packages/sdk/clients/decorators/erc7579/supportsModule.ts | 3 +-- packages/sdk/clients/index.ts | 5 +++++ packages/sdk/index.ts | 1 + 7 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 packages/sdk/clients/index.ts diff --git a/packages/sdk/account/utils/Types.ts b/packages/sdk/account/utils/Types.ts index b36dbe2b..849bbcd6 100644 --- a/packages/sdk/account/utils/Types.ts +++ b/packages/sdk/account/utils/Types.ts @@ -551,13 +551,6 @@ export type EIP712DomainReturn = [ bigint[] ] -export enum CallType { - CALLTYPE_SINGLE = "0x00", - CALLTYPE_BATCH = "0x01", - CALLTYPE_STATIC = "0xFE", - CALLTYPE_DELEGATECALL = "0xFF" -} - export type NEXUS_VERSION_TYPE = "1.0.0-beta" export type AccountMetadata = { diff --git a/packages/sdk/clients/createBicoBundlerClient.ts b/packages/sdk/clients/createBicoBundlerClient.ts index 5e10bc67..cf78768c 100644 --- a/packages/sdk/clients/createBicoBundlerClient.ts +++ b/packages/sdk/clients/createBicoBundlerClient.ts @@ -34,8 +34,9 @@ export const createBicoBundlerClient = ( return createBundlerClient({ ...parameters, - transport: - parameters.transport ? parameters.transport : parameters.bundlerUrl + transport: parameters.transport + ? parameters.transport + : parameters.bundlerUrl ? http(parameters.bundlerUrl) : http( // @ts-ignore: Type saftey provided by the if statement above diff --git a/packages/sdk/clients/decorators/erc7579/index.ts b/packages/sdk/clients/decorators/erc7579/index.ts index 4d883440..a0e79cd9 100644 --- a/packages/sdk/clients/decorators/erc7579/index.ts +++ b/packages/sdk/clients/decorators/erc7579/index.ts @@ -3,7 +3,7 @@ import type { GetSmartAccountParameter, SmartAccount } from "viem/account-abstraction" -import type { SafeHookType } from "../../../modules/utils/Types.js" +import type { ModuleType, SafeHookType } from "../../../modules/utils/Types.js" import { accountId } from "./accountId.js" import { type GetActiveHookParameters, getActiveHook } from "./getActiveHook.js" import { @@ -36,7 +36,6 @@ import { type SupportsModuleParameters, supportsModule } from "./supportsModule.js" -import type { ModuleType } from "./supportsModule.js" import { type UninstallModuleParameters, uninstallModule diff --git a/packages/sdk/clients/decorators/erc7579/installModules.ts b/packages/sdk/clients/decorators/erc7579/installModules.ts index f368b193..5f4d905c 100644 --- a/packages/sdk/clients/decorators/erc7579/installModules.ts +++ b/packages/sdk/clients/decorators/erc7579/installModules.ts @@ -14,7 +14,8 @@ import { } from "viem/account-abstraction" import { getAction, parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import { type ModuleType, parseModuleTypeId } from "./supportsModule" +import type { ModuleType } from "../../../modules/utils/Types" +import { parseModuleTypeId } from "./supportsModule" export type InstallModulesParameters< TSmartAccount extends SmartAccount | undefined diff --git a/packages/sdk/clients/decorators/erc7579/supportsModule.ts b/packages/sdk/clients/decorators/erc7579/supportsModule.ts index 60c937b4..862b0891 100644 --- a/packages/sdk/clients/decorators/erc7579/supportsModule.ts +++ b/packages/sdk/clients/decorators/erc7579/supportsModule.ts @@ -14,8 +14,7 @@ import { call, readContract } from "viem/actions" import { getAction } from "viem/utils" import { parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" - -export type ModuleType = "validator" | "executor" | "fallback" | "hook" +import type { ModuleType } from "../../../modules/utils/Types" export type SupportsModuleParameters< TSmartAccount extends SmartAccount | undefined diff --git a/packages/sdk/clients/index.ts b/packages/sdk/clients/index.ts new file mode 100644 index 00000000..46973e1a --- /dev/null +++ b/packages/sdk/clients/index.ts @@ -0,0 +1,5 @@ +export * from "./createBicoBundlerClient" +export * from "./createBicoPaymasterClient" +export * from "./createNexusClient" +export * from "./decorators/erc7579" +export * from "./decorators/smartAccount" diff --git a/packages/sdk/index.ts b/packages/sdk/index.ts index 9c227a1c..e5e14f2c 100644 --- a/packages/sdk/index.ts +++ b/packages/sdk/index.ts @@ -1,2 +1,3 @@ export * from "./account" export * from "./modules" +export * from "./clients" From 9a3ac57ae35eea8320fb5c37ebbc1280f31cd151 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 11:51:23 +0100 Subject: [PATCH 06/29] chore: packages -> src --- package.json | 4 +-- scripts/send:userOp.ts | 4 +-- scripts/viem:bundler.ts | 6 ++-- .../sdk/__contracts/abi/EIP1271Abi.ts | 0 .../sdk/__contracts/abi/EntryPointABI.ts | 0 .../sdk/__contracts/abi/K1ValidatorAbi.ts | 0 .../__contracts/abi/K1ValidatorFactoryAbi.ts | 0 .../sdk/__contracts/abi/NexusAbi.ts | 0 .../sdk/__contracts/abi/UniActionPolicyAbi.ts | 0 .../sdk/__contracts/abi/index.ts | 0 .../sdk/__contracts/addresses.ts | 0 {packages => src}/sdk/__contracts/index.ts | 0 {packages => src}/sdk/account/index.ts | 0 .../sdk/account/toNexusAccount.test.ts | 0 .../sdk/account/toNexusAccount.ts | 0 .../sdk/account/utils/AccountNotFound.ts | 0 .../sdk/account/utils/Constants.ts | 0 .../sdk/account/utils/Helpers.ts | 0 {packages => src}/sdk/account/utils/Logger.ts | 0 {packages => src}/sdk/account/utils/Types.ts | 0 {packages => src}/sdk/account/utils/Utils.ts | 0 .../sdk/account/utils/getAAError.ts | 0 .../sdk/account/utils/getChain.ts | 0 {packages => src}/sdk/account/utils/index.ts | 0 .../sdk/account/utils/toHolder.ts | 0 .../sdk/account/utils/utils.test.ts | 0 .../clients/createBicoBundlerClient.test.ts | 0 .../sdk/clients/createBicoBundlerClient.ts | 0 .../sdk/clients/createBicoPaymasterClient.ts | 0 .../sdk/clients/createNexusClient.test.ts | 0 .../sdk/clients/createNexusClient.ts | 0 .../clients/decorators/erc7579/accountId.ts | 0 .../erc7579/erc7579.actions.test.ts | 0 .../decorators/erc7579/getActiveHook.ts | 0 .../erc7579/getFallbackBySelector.ts | 0 .../erc7579/getInstalledExecutors.ts | 0 .../erc7579/getInstalledValidators.ts | 0 .../sdk/clients/decorators/erc7579/index.ts | 0 .../decorators/erc7579/installModule.ts | 0 .../decorators/erc7579/installModules.ts | 0 .../decorators/erc7579/isModuleInstalled.ts | 0 .../erc7579/supportsExecutionMode.ts | 0 .../decorators/erc7579/supportsModule.ts | 0 .../decorators/erc7579/uninstallFallback.ts | 0 .../decorators/erc7579/uninstallModule.ts | 0 .../decorators/erc7579/uninstallModules.ts | 0 .../smartAccount/erc7579.actions.test.ts | 0 .../clients/decorators/smartAccount/index.ts | 0 .../smartAccount/sendTransaction.ts | 0 .../decorators/smartAccount/signMessage.ts | 0 .../decorators/smartAccount/signTypedData.ts | 0 .../decorators/smartAccount/writeContract.ts | 0 {packages => src}/sdk/clients/index.ts | 0 {packages => src}/sdk/index.ts | 0 .../sdk/modules/base/BaseExecutionModule.ts | 0 .../sdk/modules/base/BaseModule.ts | 0 .../sdk/modules/base/BaseValidationModule.ts | 0 .../sdk/modules/executors/OwnableExecutor.ts | 0 {packages => src}/sdk/modules/index.ts | 0 .../sdk/modules/interfaces/IExecutorModule.ts | 0 .../modules/interfaces/IValidationModule.ts | 0 .../sdk/modules/smart.sessions.test.ts | 0 .../sdk/modules/smartSessions.ts | 0 .../sdk/modules/utils/Constants.ts | 0 {packages => src}/sdk/modules/utils/Helper.ts | 0 {packages => src}/sdk/modules/utils/Types.ts | 0 {packages => src}/sdk/modules/utils/Uid.ts | 0 .../modules/validators/K1ValidatorModule.ts | 0 .../modules/validators/OwnableValidator.ts | 0 .../modules/validators/ValidationModule.ts | 0 .../modules/validators/k1Validator.test.ts | 0 {packages => src}/test/README.md | 0 .../__contracts/abi/BiconomyMetaFactoryAbi.ts | 0 .../test/__contracts/abi/BootstrapAbi.ts | 0 .../test/__contracts/abi/BootstrapLibAbi.ts | 0 .../test/__contracts/abi/CounterAbi.ts | 0 .../test/__contracts/abi/MockExecutorAbi.ts | 0 .../test/__contracts/abi/MockHandlerAbi.ts | 0 .../test/__contracts/abi/MockHookAbi.ts | 0 .../test/__contracts/abi/MockRegistryAbi.ts | 0 .../test/__contracts/abi/MockTokenAbi.ts | 0 .../test/__contracts/abi/MockValidatorAbi.ts | 0 .../__contracts/abi/NexusAccountFactoryAbi.ts | 0 .../test/__contracts/abi/StakeableAbi.ts | 0 .../__contracts/abi/TokenWithPermitAbi.ts | 0 .../test/__contracts/abi/index.ts | 0 .../test/__contracts/mockAddresses.ts | 0 {packages => src}/test/callDatas.ts | 0 {packages => src}/test/executables.ts | 0 {packages => src}/test/globalSetup.ts | 0 {packages => src}/test/playground.test.ts | 0 {packages => src}/test/testSetup.ts | 0 {packages => src}/test/testUtils.ts | 0 {packages => src}/test/vitest.config.ts | 4 +-- tsconfig/tsconfig.cjs.json | 30 +++++++++---------- tsconfig/tsconfig.esm.json | 10 +++---- tsconfig/tsconfig.json | 10 +++---- tsconfig/tsconfig.types.json | 10 +++---- 98 files changed, 39 insertions(+), 39 deletions(-) rename {packages => src}/sdk/__contracts/abi/EIP1271Abi.ts (100%) rename {packages => src}/sdk/__contracts/abi/EntryPointABI.ts (100%) rename {packages => src}/sdk/__contracts/abi/K1ValidatorAbi.ts (100%) rename {packages => src}/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts (100%) rename {packages => src}/sdk/__contracts/abi/NexusAbi.ts (100%) rename {packages => src}/sdk/__contracts/abi/UniActionPolicyAbi.ts (100%) rename {packages => src}/sdk/__contracts/abi/index.ts (100%) rename {packages => src}/sdk/__contracts/addresses.ts (100%) rename {packages => src}/sdk/__contracts/index.ts (100%) rename {packages => src}/sdk/account/index.ts (100%) rename {packages => src}/sdk/account/toNexusAccount.test.ts (100%) rename {packages => src}/sdk/account/toNexusAccount.ts (100%) rename {packages => src}/sdk/account/utils/AccountNotFound.ts (100%) rename {packages => src}/sdk/account/utils/Constants.ts (100%) rename {packages => src}/sdk/account/utils/Helpers.ts (100%) rename {packages => src}/sdk/account/utils/Logger.ts (100%) rename {packages => src}/sdk/account/utils/Types.ts (100%) rename {packages => src}/sdk/account/utils/Utils.ts (100%) rename {packages => src}/sdk/account/utils/getAAError.ts (100%) rename {packages => src}/sdk/account/utils/getChain.ts (100%) rename {packages => src}/sdk/account/utils/index.ts (100%) rename {packages => src}/sdk/account/utils/toHolder.ts (100%) rename {packages => src}/sdk/account/utils/utils.test.ts (100%) rename {packages => src}/sdk/clients/createBicoBundlerClient.test.ts (100%) rename {packages => src}/sdk/clients/createBicoBundlerClient.ts (100%) rename {packages => src}/sdk/clients/createBicoPaymasterClient.ts (100%) rename {packages => src}/sdk/clients/createNexusClient.test.ts (100%) rename {packages => src}/sdk/clients/createNexusClient.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/accountId.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/erc7579.actions.test.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/getActiveHook.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/getFallbackBySelector.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/getInstalledExecutors.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/getInstalledValidators.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/index.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/installModule.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/installModules.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/isModuleInstalled.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/supportsExecutionMode.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/supportsModule.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/uninstallFallback.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/uninstallModule.ts (100%) rename {packages => src}/sdk/clients/decorators/erc7579/uninstallModules.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/index.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/sendTransaction.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/signMessage.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/signTypedData.ts (100%) rename {packages => src}/sdk/clients/decorators/smartAccount/writeContract.ts (100%) rename {packages => src}/sdk/clients/index.ts (100%) rename {packages => src}/sdk/index.ts (100%) rename {packages => src}/sdk/modules/base/BaseExecutionModule.ts (100%) rename {packages => src}/sdk/modules/base/BaseModule.ts (100%) rename {packages => src}/sdk/modules/base/BaseValidationModule.ts (100%) rename {packages => src}/sdk/modules/executors/OwnableExecutor.ts (100%) rename {packages => src}/sdk/modules/index.ts (100%) rename {packages => src}/sdk/modules/interfaces/IExecutorModule.ts (100%) rename {packages => src}/sdk/modules/interfaces/IValidationModule.ts (100%) rename {packages => src}/sdk/modules/smart.sessions.test.ts (100%) rename {packages => src}/sdk/modules/smartSessions.ts (100%) rename {packages => src}/sdk/modules/utils/Constants.ts (100%) rename {packages => src}/sdk/modules/utils/Helper.ts (100%) rename {packages => src}/sdk/modules/utils/Types.ts (100%) rename {packages => src}/sdk/modules/utils/Uid.ts (100%) rename {packages => src}/sdk/modules/validators/K1ValidatorModule.ts (100%) rename {packages => src}/sdk/modules/validators/OwnableValidator.ts (100%) rename {packages => src}/sdk/modules/validators/ValidationModule.ts (100%) rename {packages => src}/sdk/modules/validators/k1Validator.test.ts (100%) rename {packages => src}/test/README.md (100%) rename {packages => src}/test/__contracts/abi/BiconomyMetaFactoryAbi.ts (100%) rename {packages => src}/test/__contracts/abi/BootstrapAbi.ts (100%) rename {packages => src}/test/__contracts/abi/BootstrapLibAbi.ts (100%) rename {packages => src}/test/__contracts/abi/CounterAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockExecutorAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockHandlerAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockHookAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockRegistryAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockTokenAbi.ts (100%) rename {packages => src}/test/__contracts/abi/MockValidatorAbi.ts (100%) rename {packages => src}/test/__contracts/abi/NexusAccountFactoryAbi.ts (100%) rename {packages => src}/test/__contracts/abi/StakeableAbi.ts (100%) rename {packages => src}/test/__contracts/abi/TokenWithPermitAbi.ts (100%) rename {packages => src}/test/__contracts/abi/index.ts (100%) rename {packages => src}/test/__contracts/mockAddresses.ts (100%) rename {packages => src}/test/callDatas.ts (100%) rename {packages => src}/test/executables.ts (100%) rename {packages => src}/test/globalSetup.ts (100%) rename {packages => src}/test/playground.test.ts (100%) rename {packages => src}/test/testSetup.ts (100%) rename {packages => src}/test/testUtils.ts (100%) rename {packages => src}/test/vitest.config.ts (81%) diff --git a/package.json b/package.json index 2007b9db..7a488570 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 ./packages/test/vitest.config.ts", + "test": "vitest -c ./src/test/vitest.config.ts", "test:watch": "bun run test dev", - "playground": "RUN_PLAYGROUND=true vitest -c ./packages/test/vitest.config.ts -t=playground", + "playground": "RUN_PLAYGROUND=true vitest -c ./src/test/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", diff --git a/scripts/send:userOp.ts b/scripts/send:userOp.ts index 6d913345..0b18d941 100644 --- a/scripts/send:userOp.ts +++ b/scripts/send:userOp.ts @@ -1,7 +1,7 @@ import { http, type PublicClient, parseEther } from "viem" import { privateKeyToAccount } from "viem/accounts" -import { getChain } from "../packages/sdk/account/utils/getChain" -import { createNexusClient } from "../packages/sdk/clients/createNexusClient" +import { getChain } from "../src/sdk/account/utils/getChain" +import { createNexusClient } from "../src/sdk/clients/createNexusClient" const k1ValidatorAddress = "0x663E709f60477f07885230E213b8149a7027239B" const factoryAddress = "0x887Ca6FaFD62737D0E79A2b8Da41f0B15A864778" diff --git a/scripts/viem:bundler.ts b/scripts/viem:bundler.ts index f8936aea..c4b7e3b9 100644 --- a/scripts/viem:bundler.ts +++ b/scripts/viem:bundler.ts @@ -1,9 +1,9 @@ import { config } from "dotenv" import { http, type PublicClient, createPublicClient } from "viem" import { privateKeyToAccount } from "viem/accounts" -import { toNexusAccount } from "../packages/sdk/account/toNexusAccount" -import { getChain } from "../packages/sdk/account/utils/getChain" -import { createBicoBundlerClient } from "../packages/sdk/clients/createBicoBundlerClient" +import { toNexusAccount } from "../src/sdk/account/toNexusAccount" +import { getChain } from "../src/sdk/account/utils/getChain" +import { createBicoBundlerClient } from "../src/sdk/clients/createBicoBundlerClient" config() diff --git a/packages/sdk/__contracts/abi/EIP1271Abi.ts b/src/sdk/__contracts/abi/EIP1271Abi.ts similarity index 100% rename from packages/sdk/__contracts/abi/EIP1271Abi.ts rename to src/sdk/__contracts/abi/EIP1271Abi.ts diff --git a/packages/sdk/__contracts/abi/EntryPointABI.ts b/src/sdk/__contracts/abi/EntryPointABI.ts similarity index 100% rename from packages/sdk/__contracts/abi/EntryPointABI.ts rename to src/sdk/__contracts/abi/EntryPointABI.ts diff --git a/packages/sdk/__contracts/abi/K1ValidatorAbi.ts b/src/sdk/__contracts/abi/K1ValidatorAbi.ts similarity index 100% rename from packages/sdk/__contracts/abi/K1ValidatorAbi.ts rename to src/sdk/__contracts/abi/K1ValidatorAbi.ts diff --git a/packages/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts b/src/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts similarity index 100% rename from packages/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts rename to src/sdk/__contracts/abi/K1ValidatorFactoryAbi.ts diff --git a/packages/sdk/__contracts/abi/NexusAbi.ts b/src/sdk/__contracts/abi/NexusAbi.ts similarity index 100% rename from packages/sdk/__contracts/abi/NexusAbi.ts rename to src/sdk/__contracts/abi/NexusAbi.ts diff --git a/packages/sdk/__contracts/abi/UniActionPolicyAbi.ts b/src/sdk/__contracts/abi/UniActionPolicyAbi.ts similarity index 100% rename from packages/sdk/__contracts/abi/UniActionPolicyAbi.ts rename to src/sdk/__contracts/abi/UniActionPolicyAbi.ts diff --git a/packages/sdk/__contracts/abi/index.ts b/src/sdk/__contracts/abi/index.ts similarity index 100% rename from packages/sdk/__contracts/abi/index.ts rename to src/sdk/__contracts/abi/index.ts diff --git a/packages/sdk/__contracts/addresses.ts b/src/sdk/__contracts/addresses.ts similarity index 100% rename from packages/sdk/__contracts/addresses.ts rename to src/sdk/__contracts/addresses.ts diff --git a/packages/sdk/__contracts/index.ts b/src/sdk/__contracts/index.ts similarity index 100% rename from packages/sdk/__contracts/index.ts rename to src/sdk/__contracts/index.ts diff --git a/packages/sdk/account/index.ts b/src/sdk/account/index.ts similarity index 100% rename from packages/sdk/account/index.ts rename to src/sdk/account/index.ts diff --git a/packages/sdk/account/toNexusAccount.test.ts b/src/sdk/account/toNexusAccount.test.ts similarity index 100% rename from packages/sdk/account/toNexusAccount.test.ts rename to src/sdk/account/toNexusAccount.test.ts diff --git a/packages/sdk/account/toNexusAccount.ts b/src/sdk/account/toNexusAccount.ts similarity index 100% rename from packages/sdk/account/toNexusAccount.ts rename to src/sdk/account/toNexusAccount.ts diff --git a/packages/sdk/account/utils/AccountNotFound.ts b/src/sdk/account/utils/AccountNotFound.ts similarity index 100% rename from packages/sdk/account/utils/AccountNotFound.ts rename to src/sdk/account/utils/AccountNotFound.ts diff --git a/packages/sdk/account/utils/Constants.ts b/src/sdk/account/utils/Constants.ts similarity index 100% rename from packages/sdk/account/utils/Constants.ts rename to src/sdk/account/utils/Constants.ts diff --git a/packages/sdk/account/utils/Helpers.ts b/src/sdk/account/utils/Helpers.ts similarity index 100% rename from packages/sdk/account/utils/Helpers.ts rename to src/sdk/account/utils/Helpers.ts diff --git a/packages/sdk/account/utils/Logger.ts b/src/sdk/account/utils/Logger.ts similarity index 100% rename from packages/sdk/account/utils/Logger.ts rename to src/sdk/account/utils/Logger.ts diff --git a/packages/sdk/account/utils/Types.ts b/src/sdk/account/utils/Types.ts similarity index 100% rename from packages/sdk/account/utils/Types.ts rename to src/sdk/account/utils/Types.ts diff --git a/packages/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts similarity index 100% rename from packages/sdk/account/utils/Utils.ts rename to src/sdk/account/utils/Utils.ts diff --git a/packages/sdk/account/utils/getAAError.ts b/src/sdk/account/utils/getAAError.ts similarity index 100% rename from packages/sdk/account/utils/getAAError.ts rename to src/sdk/account/utils/getAAError.ts diff --git a/packages/sdk/account/utils/getChain.ts b/src/sdk/account/utils/getChain.ts similarity index 100% rename from packages/sdk/account/utils/getChain.ts rename to src/sdk/account/utils/getChain.ts diff --git a/packages/sdk/account/utils/index.ts b/src/sdk/account/utils/index.ts similarity index 100% rename from packages/sdk/account/utils/index.ts rename to src/sdk/account/utils/index.ts diff --git a/packages/sdk/account/utils/toHolder.ts b/src/sdk/account/utils/toHolder.ts similarity index 100% rename from packages/sdk/account/utils/toHolder.ts rename to src/sdk/account/utils/toHolder.ts diff --git a/packages/sdk/account/utils/utils.test.ts b/src/sdk/account/utils/utils.test.ts similarity index 100% rename from packages/sdk/account/utils/utils.test.ts rename to src/sdk/account/utils/utils.test.ts diff --git a/packages/sdk/clients/createBicoBundlerClient.test.ts b/src/sdk/clients/createBicoBundlerClient.test.ts similarity index 100% rename from packages/sdk/clients/createBicoBundlerClient.test.ts rename to src/sdk/clients/createBicoBundlerClient.test.ts diff --git a/packages/sdk/clients/createBicoBundlerClient.ts b/src/sdk/clients/createBicoBundlerClient.ts similarity index 100% rename from packages/sdk/clients/createBicoBundlerClient.ts rename to src/sdk/clients/createBicoBundlerClient.ts diff --git a/packages/sdk/clients/createBicoPaymasterClient.ts b/src/sdk/clients/createBicoPaymasterClient.ts similarity index 100% rename from packages/sdk/clients/createBicoPaymasterClient.ts rename to src/sdk/clients/createBicoPaymasterClient.ts diff --git a/packages/sdk/clients/createNexusClient.test.ts b/src/sdk/clients/createNexusClient.test.ts similarity index 100% rename from packages/sdk/clients/createNexusClient.test.ts rename to src/sdk/clients/createNexusClient.test.ts diff --git a/packages/sdk/clients/createNexusClient.ts b/src/sdk/clients/createNexusClient.ts similarity index 100% rename from packages/sdk/clients/createNexusClient.ts rename to src/sdk/clients/createNexusClient.ts diff --git a/packages/sdk/clients/decorators/erc7579/accountId.ts b/src/sdk/clients/decorators/erc7579/accountId.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/accountId.ts rename to src/sdk/clients/decorators/erc7579/accountId.ts diff --git a/packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts b/src/sdk/clients/decorators/erc7579/erc7579.actions.test.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/erc7579.actions.test.ts rename to src/sdk/clients/decorators/erc7579/erc7579.actions.test.ts diff --git a/packages/sdk/clients/decorators/erc7579/getActiveHook.ts b/src/sdk/clients/decorators/erc7579/getActiveHook.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/getActiveHook.ts rename to src/sdk/clients/decorators/erc7579/getActiveHook.ts diff --git a/packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts b/src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/getFallbackBySelector.ts rename to src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts diff --git a/packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts b/src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/getInstalledExecutors.ts rename to src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts diff --git a/packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts b/src/sdk/clients/decorators/erc7579/getInstalledValidators.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/getInstalledValidators.ts rename to src/sdk/clients/decorators/erc7579/getInstalledValidators.ts diff --git a/packages/sdk/clients/decorators/erc7579/index.ts b/src/sdk/clients/decorators/erc7579/index.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/index.ts rename to src/sdk/clients/decorators/erc7579/index.ts diff --git a/packages/sdk/clients/decorators/erc7579/installModule.ts b/src/sdk/clients/decorators/erc7579/installModule.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/installModule.ts rename to src/sdk/clients/decorators/erc7579/installModule.ts diff --git a/packages/sdk/clients/decorators/erc7579/installModules.ts b/src/sdk/clients/decorators/erc7579/installModules.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/installModules.ts rename to src/sdk/clients/decorators/erc7579/installModules.ts diff --git a/packages/sdk/clients/decorators/erc7579/isModuleInstalled.ts b/src/sdk/clients/decorators/erc7579/isModuleInstalled.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/isModuleInstalled.ts rename to src/sdk/clients/decorators/erc7579/isModuleInstalled.ts diff --git a/packages/sdk/clients/decorators/erc7579/supportsExecutionMode.ts b/src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/supportsExecutionMode.ts rename to src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts diff --git a/packages/sdk/clients/decorators/erc7579/supportsModule.ts b/src/sdk/clients/decorators/erc7579/supportsModule.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/supportsModule.ts rename to src/sdk/clients/decorators/erc7579/supportsModule.ts diff --git a/packages/sdk/clients/decorators/erc7579/uninstallFallback.ts b/src/sdk/clients/decorators/erc7579/uninstallFallback.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/uninstallFallback.ts rename to src/sdk/clients/decorators/erc7579/uninstallFallback.ts diff --git a/packages/sdk/clients/decorators/erc7579/uninstallModule.ts b/src/sdk/clients/decorators/erc7579/uninstallModule.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/uninstallModule.ts rename to src/sdk/clients/decorators/erc7579/uninstallModule.ts diff --git a/packages/sdk/clients/decorators/erc7579/uninstallModules.ts b/src/sdk/clients/decorators/erc7579/uninstallModules.ts similarity index 100% rename from packages/sdk/clients/decorators/erc7579/uninstallModules.ts rename to src/sdk/clients/decorators/erc7579/uninstallModules.ts diff --git a/packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts b/src/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts rename to src/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts diff --git a/packages/sdk/clients/decorators/smartAccount/index.ts b/src/sdk/clients/decorators/smartAccount/index.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/index.ts rename to src/sdk/clients/decorators/smartAccount/index.ts diff --git a/packages/sdk/clients/decorators/smartAccount/sendTransaction.ts b/src/sdk/clients/decorators/smartAccount/sendTransaction.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/sendTransaction.ts rename to src/sdk/clients/decorators/smartAccount/sendTransaction.ts diff --git a/packages/sdk/clients/decorators/smartAccount/signMessage.ts b/src/sdk/clients/decorators/smartAccount/signMessage.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/signMessage.ts rename to src/sdk/clients/decorators/smartAccount/signMessage.ts diff --git a/packages/sdk/clients/decorators/smartAccount/signTypedData.ts b/src/sdk/clients/decorators/smartAccount/signTypedData.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/signTypedData.ts rename to src/sdk/clients/decorators/smartAccount/signTypedData.ts diff --git a/packages/sdk/clients/decorators/smartAccount/writeContract.ts b/src/sdk/clients/decorators/smartAccount/writeContract.ts similarity index 100% rename from packages/sdk/clients/decorators/smartAccount/writeContract.ts rename to src/sdk/clients/decorators/smartAccount/writeContract.ts diff --git a/packages/sdk/clients/index.ts b/src/sdk/clients/index.ts similarity index 100% rename from packages/sdk/clients/index.ts rename to src/sdk/clients/index.ts diff --git a/packages/sdk/index.ts b/src/sdk/index.ts similarity index 100% rename from packages/sdk/index.ts rename to src/sdk/index.ts diff --git a/packages/sdk/modules/base/BaseExecutionModule.ts b/src/sdk/modules/base/BaseExecutionModule.ts similarity index 100% rename from packages/sdk/modules/base/BaseExecutionModule.ts rename to src/sdk/modules/base/BaseExecutionModule.ts diff --git a/packages/sdk/modules/base/BaseModule.ts b/src/sdk/modules/base/BaseModule.ts similarity index 100% rename from packages/sdk/modules/base/BaseModule.ts rename to src/sdk/modules/base/BaseModule.ts diff --git a/packages/sdk/modules/base/BaseValidationModule.ts b/src/sdk/modules/base/BaseValidationModule.ts similarity index 100% rename from packages/sdk/modules/base/BaseValidationModule.ts rename to src/sdk/modules/base/BaseValidationModule.ts diff --git a/packages/sdk/modules/executors/OwnableExecutor.ts b/src/sdk/modules/executors/OwnableExecutor.ts similarity index 100% rename from packages/sdk/modules/executors/OwnableExecutor.ts rename to src/sdk/modules/executors/OwnableExecutor.ts diff --git a/packages/sdk/modules/index.ts b/src/sdk/modules/index.ts similarity index 100% rename from packages/sdk/modules/index.ts rename to src/sdk/modules/index.ts diff --git a/packages/sdk/modules/interfaces/IExecutorModule.ts b/src/sdk/modules/interfaces/IExecutorModule.ts similarity index 100% rename from packages/sdk/modules/interfaces/IExecutorModule.ts rename to src/sdk/modules/interfaces/IExecutorModule.ts diff --git a/packages/sdk/modules/interfaces/IValidationModule.ts b/src/sdk/modules/interfaces/IValidationModule.ts similarity index 100% rename from packages/sdk/modules/interfaces/IValidationModule.ts rename to src/sdk/modules/interfaces/IValidationModule.ts diff --git a/packages/sdk/modules/smart.sessions.test.ts b/src/sdk/modules/smart.sessions.test.ts similarity index 100% rename from packages/sdk/modules/smart.sessions.test.ts rename to src/sdk/modules/smart.sessions.test.ts diff --git a/packages/sdk/modules/smartSessions.ts b/src/sdk/modules/smartSessions.ts similarity index 100% rename from packages/sdk/modules/smartSessions.ts rename to src/sdk/modules/smartSessions.ts diff --git a/packages/sdk/modules/utils/Constants.ts b/src/sdk/modules/utils/Constants.ts similarity index 100% rename from packages/sdk/modules/utils/Constants.ts rename to src/sdk/modules/utils/Constants.ts diff --git a/packages/sdk/modules/utils/Helper.ts b/src/sdk/modules/utils/Helper.ts similarity index 100% rename from packages/sdk/modules/utils/Helper.ts rename to src/sdk/modules/utils/Helper.ts diff --git a/packages/sdk/modules/utils/Types.ts b/src/sdk/modules/utils/Types.ts similarity index 100% rename from packages/sdk/modules/utils/Types.ts rename to src/sdk/modules/utils/Types.ts diff --git a/packages/sdk/modules/utils/Uid.ts b/src/sdk/modules/utils/Uid.ts similarity index 100% rename from packages/sdk/modules/utils/Uid.ts rename to src/sdk/modules/utils/Uid.ts diff --git a/packages/sdk/modules/validators/K1ValidatorModule.ts b/src/sdk/modules/validators/K1ValidatorModule.ts similarity index 100% rename from packages/sdk/modules/validators/K1ValidatorModule.ts rename to src/sdk/modules/validators/K1ValidatorModule.ts diff --git a/packages/sdk/modules/validators/OwnableValidator.ts b/src/sdk/modules/validators/OwnableValidator.ts similarity index 100% rename from packages/sdk/modules/validators/OwnableValidator.ts rename to src/sdk/modules/validators/OwnableValidator.ts diff --git a/packages/sdk/modules/validators/ValidationModule.ts b/src/sdk/modules/validators/ValidationModule.ts similarity index 100% rename from packages/sdk/modules/validators/ValidationModule.ts rename to src/sdk/modules/validators/ValidationModule.ts diff --git a/packages/sdk/modules/validators/k1Validator.test.ts b/src/sdk/modules/validators/k1Validator.test.ts similarity index 100% rename from packages/sdk/modules/validators/k1Validator.test.ts rename to src/sdk/modules/validators/k1Validator.test.ts diff --git a/packages/test/README.md b/src/test/README.md similarity index 100% rename from packages/test/README.md rename to src/test/README.md diff --git a/packages/test/__contracts/abi/BiconomyMetaFactoryAbi.ts b/src/test/__contracts/abi/BiconomyMetaFactoryAbi.ts similarity index 100% rename from packages/test/__contracts/abi/BiconomyMetaFactoryAbi.ts rename to src/test/__contracts/abi/BiconomyMetaFactoryAbi.ts diff --git a/packages/test/__contracts/abi/BootstrapAbi.ts b/src/test/__contracts/abi/BootstrapAbi.ts similarity index 100% rename from packages/test/__contracts/abi/BootstrapAbi.ts rename to src/test/__contracts/abi/BootstrapAbi.ts diff --git a/packages/test/__contracts/abi/BootstrapLibAbi.ts b/src/test/__contracts/abi/BootstrapLibAbi.ts similarity index 100% rename from packages/test/__contracts/abi/BootstrapLibAbi.ts rename to src/test/__contracts/abi/BootstrapLibAbi.ts diff --git a/packages/test/__contracts/abi/CounterAbi.ts b/src/test/__contracts/abi/CounterAbi.ts similarity index 100% rename from packages/test/__contracts/abi/CounterAbi.ts rename to src/test/__contracts/abi/CounterAbi.ts diff --git a/packages/test/__contracts/abi/MockExecutorAbi.ts b/src/test/__contracts/abi/MockExecutorAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockExecutorAbi.ts rename to src/test/__contracts/abi/MockExecutorAbi.ts diff --git a/packages/test/__contracts/abi/MockHandlerAbi.ts b/src/test/__contracts/abi/MockHandlerAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockHandlerAbi.ts rename to src/test/__contracts/abi/MockHandlerAbi.ts diff --git a/packages/test/__contracts/abi/MockHookAbi.ts b/src/test/__contracts/abi/MockHookAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockHookAbi.ts rename to src/test/__contracts/abi/MockHookAbi.ts diff --git a/packages/test/__contracts/abi/MockRegistryAbi.ts b/src/test/__contracts/abi/MockRegistryAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockRegistryAbi.ts rename to src/test/__contracts/abi/MockRegistryAbi.ts diff --git a/packages/test/__contracts/abi/MockTokenAbi.ts b/src/test/__contracts/abi/MockTokenAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockTokenAbi.ts rename to src/test/__contracts/abi/MockTokenAbi.ts diff --git a/packages/test/__contracts/abi/MockValidatorAbi.ts b/src/test/__contracts/abi/MockValidatorAbi.ts similarity index 100% rename from packages/test/__contracts/abi/MockValidatorAbi.ts rename to src/test/__contracts/abi/MockValidatorAbi.ts diff --git a/packages/test/__contracts/abi/NexusAccountFactoryAbi.ts b/src/test/__contracts/abi/NexusAccountFactoryAbi.ts similarity index 100% rename from packages/test/__contracts/abi/NexusAccountFactoryAbi.ts rename to src/test/__contracts/abi/NexusAccountFactoryAbi.ts diff --git a/packages/test/__contracts/abi/StakeableAbi.ts b/src/test/__contracts/abi/StakeableAbi.ts similarity index 100% rename from packages/test/__contracts/abi/StakeableAbi.ts rename to src/test/__contracts/abi/StakeableAbi.ts diff --git a/packages/test/__contracts/abi/TokenWithPermitAbi.ts b/src/test/__contracts/abi/TokenWithPermitAbi.ts similarity index 100% rename from packages/test/__contracts/abi/TokenWithPermitAbi.ts rename to src/test/__contracts/abi/TokenWithPermitAbi.ts diff --git a/packages/test/__contracts/abi/index.ts b/src/test/__contracts/abi/index.ts similarity index 100% rename from packages/test/__contracts/abi/index.ts rename to src/test/__contracts/abi/index.ts diff --git a/packages/test/__contracts/mockAddresses.ts b/src/test/__contracts/mockAddresses.ts similarity index 100% rename from packages/test/__contracts/mockAddresses.ts rename to src/test/__contracts/mockAddresses.ts diff --git a/packages/test/callDatas.ts b/src/test/callDatas.ts similarity index 100% rename from packages/test/callDatas.ts rename to src/test/callDatas.ts diff --git a/packages/test/executables.ts b/src/test/executables.ts similarity index 100% rename from packages/test/executables.ts rename to src/test/executables.ts diff --git a/packages/test/globalSetup.ts b/src/test/globalSetup.ts similarity index 100% rename from packages/test/globalSetup.ts rename to src/test/globalSetup.ts diff --git a/packages/test/playground.test.ts b/src/test/playground.test.ts similarity index 100% rename from packages/test/playground.test.ts rename to src/test/playground.test.ts diff --git a/packages/test/testSetup.ts b/src/test/testSetup.ts similarity index 100% rename from packages/test/testSetup.ts rename to src/test/testSetup.ts diff --git a/packages/test/testUtils.ts b/src/test/testUtils.ts similarity index 100% rename from packages/test/testUtils.ts rename to src/test/testUtils.ts diff --git a/packages/test/vitest.config.ts b/src/test/vitest.config.ts similarity index 81% rename from packages/test/vitest.config.ts rename to src/test/vitest.config.ts index af39db8a..48ba25f3 100644 --- a/packages/test/vitest.config.ts +++ b/src/test/vitest.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ "**/*.test.ts", "**/test/**" ], - include: ["./packages/test/**/*.test.ts", "./packages/sdk/**/*.test.ts"], + include: ["./src/test/**/*.test.ts", "./src/sdk/**/*.test.ts"], thresholds: { lines: 80, functions: 50, @@ -25,7 +25,7 @@ export default defineConfig({ statements: 80 } }, - include: ["./packages/test/**/*.test.ts", "./packages/sdk/**/*.test.ts"], + include: ["./src/test/**/*.test.ts", "./src/sdk/**/*.test.ts"], globalSetup: join(__dirname, "globalSetup.ts"), environment: "node", testTimeout: 60_000, diff --git a/tsconfig/tsconfig.cjs.json b/tsconfig/tsconfig.cjs.json index 106791fe..0b905456 100644 --- a/tsconfig/tsconfig.cjs.json +++ b/tsconfig/tsconfig.cjs.json @@ -1,16 +1,16 @@ { - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "commonjs", - "outDir": "../dist/_cjs", - "removeComments": true, - "verbatimModuleSyntax": false, - "noEmit": false, - "rootDir": "../packages/sdk" - }, - "exclude": [ - "../packages/test/**/*.*", - "../packages/sdk/**/*.test.*", - "../packages/sdk/**/*.spec.*", - ] - } + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../dist/_cjs", + "removeComments": true, + "verbatimModuleSyntax": false, + "noEmit": false, + "rootDir": "../src/sdk" + }, + "exclude": [ + "../src/test/**/*.*", + "../src/sdk/**/*.test.*", + "../src/sdk/**/*.spec.*", + ] +} \ No newline at end of file diff --git a/tsconfig/tsconfig.esm.json b/tsconfig/tsconfig.esm.json index 2dd2ce5c..35640e49 100644 --- a/tsconfig/tsconfig.esm.json +++ b/tsconfig/tsconfig.esm.json @@ -4,11 +4,11 @@ "module": "es2015", "outDir": "../dist/_esm", "noEmit": false, - "rootDir": "../packages/sdk" + "rootDir": "../src/sdk" }, "exclude": [ - "../packages/test/**/*.*", - "../packages/sdk/**/*.test.*", - "../packages/sdk/**/*.spec.*", + "../src/test/**/*.*", + "../src/sdk/**/*.test.*", + "../src/sdk/**/*.spec.*", ] -} +} \ No newline at end of file diff --git a/tsconfig/tsconfig.json b/tsconfig/tsconfig.json index 0612f8fe..4d8f6dba 100644 --- a/tsconfig/tsconfig.json +++ b/tsconfig/tsconfig.json @@ -1,16 +1,16 @@ { "extends": "./tsconfig.base.json", "include": [ - "../packages/" + "../src/" ], "exclude": [ - "../packages/**/*.test.ts", - "../packages/**/*.test-d.ts", - "../packages/**/*.bench.ts", + "../src/**/*.test.ts", + "../src/**/*.test-d.ts", + "../src/**/*.bench.ts", ], "compilerOptions": { "moduleResolution": "node", "sourceMap": true, - "rootDir": "../packages" + "rootDir": "../src" } } \ No newline at end of file diff --git a/tsconfig/tsconfig.types.json b/tsconfig/tsconfig.types.json index f8f1ca42..6ee2578c 100644 --- a/tsconfig/tsconfig.types.json +++ b/tsconfig/tsconfig.types.json @@ -8,11 +8,11 @@ "declaration": true, "declarationMap": true, "noEmit": false, - "rootDir": "../packages/sdk" + "rootDir": "../src/sdk" }, "exclude": [ - "../packages/test/**/*.*", - "../packages/sdk/**/*.test.*", - "../packages/sdk/**/*.spec.*", + "../src/test/**/*.*", + "../src/sdk/**/*.test.*", + "../src/sdk/**/*.spec.*", ] -} +} \ No newline at end of file From 759e1d8a8e07508707e7032eb8af7867b0403e5f Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:06:31 +0100 Subject: [PATCH 07/29] chore: tsdoc - createNexusAccount --- src/sdk/account/toNexusAccount.ts | 97 +++++++++++++++++++------------ src/sdk/account/utils/Helpers.ts | 17 +----- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/sdk/account/toNexusAccount.ts b/src/sdk/account/toNexusAccount.ts index a7a60b5a..3633dce9 100644 --- a/src/sdk/account/toNexusAccount.ts +++ b/src/sdk/account/toNexusAccount.ts @@ -63,14 +63,25 @@ import { } from "./utils/Utils" import { type UnknownHolder, toHolder } from "./utils/toHolder" +/** + * Parameters for creating a Nexus Smart Account + */ export type ToNexusSmartAccountParameters = { + /** The blockchain network */ chain: Chain + /** The transport configuration */ transport: ClientConfig["transport"] + /** The holder account or address */ holder: UnknownHolder + /** Optional index for the account */ index?: bigint | undefined + /** Optional active validation module */ activeModule?: BaseValidationModule + /** Optional executor module */ executorModule?: BaseExecutionModule + /** Optional factory address */ factoryAddress?: Address + /** Optional K1 validator address */ k1ValidatorAddress?: Address } & Prettify< Pick< @@ -85,10 +96,16 @@ export type ToNexusSmartAccountParameters = { > > +/** + * Nexus Smart Account type + */ export type NexusAccount = Prettify< SmartAccount > +/** + * Nexus Smart Account Implementation + */ export type NexusSmartAccountImplementation = SmartAccountImplementation< typeof EntrypointAbi, "0.7", @@ -105,18 +122,21 @@ export type NexusSmartAccountImplementation = SmartAccountImplementation< > /** - * 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 {Address} [factoryAddress] - Optional factory address - * @property {Address} [k1ValidatorAddress] - Optional K1 validator address - * @property {string} [executorModule] - Optional Executor module - * @property {string} [key] - Optional key for the wallet client - * @property {string} [name] - Optional name for the wallet client + * @description Create a Nexus Smart Account. + * + * @param parameters - {@link ToNexusSmartAccountParameters} + * @returns Nexus Smart Account. {@link NexusAccount} + * + * @example + * import { toNexusAccount } from '@biconomy/sdk' + * import { createWalletClient, http } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const account = await toNexusAccount({ + * chain: mainnet, + * transport: http(), + * holder: '0x...', + * }) */ export const toNexusAccount = async ( parameters: ToNexusSmartAccountParameters @@ -187,8 +207,8 @@ export const toNexusAccount = async ( } /** - * Gets the counterfactual address of the account - * @returns {Promise
} A promise that resolves to the counterfactual address + * @description Gets the counterfactual address of the account + * @returns The counterfactual address * @throws {Error} If unable to get the counterfactual address */ const getCounterFactualAddress = async (): Promise
=> { @@ -205,14 +225,14 @@ export const toNexusAccount = async ( } /** - * Gets the init code for the account - * @returns {Hex} The init code as a hexadecimal string + * @description Gets the init code for the account + * @returns 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 + * @description Checks if the account is deployed + * @returns True if the account is deployed, false otherwise */ const isDeployed = async (): Promise => { const address = await getCounterFactualAddress() @@ -221,9 +241,9 @@ export const toNexusAccount = async ( } /** - * 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 + * @description Calculates the hash of a user operation + * @param userOp - The user operation + * @returns The hash of the user operation */ const getUserOpHash = async ( userOp: Partial @@ -238,10 +258,10 @@ export const toNexusAccount = async ( } /** - * 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 + * @description Encodes a batch of calls for execution + * @param calls - An array of calls to encode + * @param mode - The execution mode + * @returns The encoded calls */ const encodeExecuteBatch = async ( calls: readonly Call[], @@ -276,10 +296,10 @@ export const toNexusAccount = async ( } /** - * 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 + * @description Encodes a single call for execution + * @param call - The call to encode + * @param mode - The execution mode + * @returns The encoded call */ const encodeExecute = async ( call: Call, @@ -300,9 +320,9 @@ 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 + * @description Gets the nonce for the account + * @param args - Optional arguments for getting the nonce + * @returns The nonce */ const getNonce = async ({ validationMode: _validationMode = MODE_VALIDATION, @@ -330,10 +350,10 @@ 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 + * @description Signs a message + * @param params - The parameters for signing + * @param params.message - The message to sign + * @returns The signature */ const signMessage = async ({ message @@ -372,6 +392,11 @@ export const toNexusAccount = async ( return accountIsDeployed ? signature : erc6492Signature } + /** + * @description Signs typed data + * @param parameters - The typed data parameters + * @returns The signature + */ async function signTypedData< const typedData extends TypedData | Record, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData diff --git a/src/sdk/account/utils/Helpers.ts b/src/sdk/account/utils/Helpers.ts index 38dc3e01..aece4ec8 100644 --- a/src/sdk/account/utils/Helpers.ts +++ b/src/sdk/account/utils/Helpers.ts @@ -1,16 +1 @@ -const VARS_T0_CHECK = [ - "BICONOMY_SDK_DEBUG", - "REACT_APP_BICONOMY_SDK_DEBUG", - "NEXT_PUBLIC_BICONOMY_SDK_DEBUG" -] - -export const isDebugging = (): boolean => { - try { - // @ts-ignore - return VARS_T0_CHECK.some( - (key) => process?.env?.[key]?.toString() === "true" - ) - } catch (e) { - return false - } -} +export const isDebugging = () => process.env.BICONOMY_SDK_DEBUG === "true" || process.env.REACT_APP_BICONOMY_SDK_DEBUG === "true" || process.env.NEXT_PUBLIC_BICONOMY_SDK_DEBUG === "true" \ No newline at end of file From 453a58fcfd65970c4bb4d6a39f9d869b72dcf23b Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:08:23 +0100 Subject: [PATCH 08/29] chore: tsdoc - createNexusClient --- src/sdk/account/utils/Helpers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sdk/account/utils/Helpers.ts b/src/sdk/account/utils/Helpers.ts index aece4ec8..85d5c305 100644 --- a/src/sdk/account/utils/Helpers.ts +++ b/src/sdk/account/utils/Helpers.ts @@ -1 +1,4 @@ -export const isDebugging = () => process.env.BICONOMY_SDK_DEBUG === "true" || process.env.REACT_APP_BICONOMY_SDK_DEBUG === "true" || process.env.NEXT_PUBLIC_BICONOMY_SDK_DEBUG === "true" \ No newline at end of file +export const isDebugging = () => + process.env.BICONOMY_SDK_DEBUG === "true" || + process.env.REACT_APP_BICONOMY_SDK_DEBUG === "true" || + process.env.NEXT_PUBLIC_BICONOMY_SDK_DEBUG === "true" From ba9897c3041150e91b77bc6bed47d50002a649e3 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:15:23 +0100 Subject: [PATCH 09/29] chore: tsdoc - accountId --- src/sdk/clients/decorators/erc7579/accountId.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/accountId.ts b/src/sdk/clients/decorators/erc7579/accountId.ts index ce527efd..10d8c368 100644 --- a/src/sdk/clients/decorators/erc7579/accountId.ts +++ b/src/sdk/clients/decorators/erc7579/accountId.ts @@ -14,6 +14,21 @@ import { call, readContract } from "viem/actions" import { getAction } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +/** + * Retrieves the account ID for a given smart account. + * + * @param client - The client instance. + * @param args - Optional parameters for getting the smart account. + * @returns The account ID as a string. + * @throws {AccountNotFoundError} If the account is not found. + * @throws {Error} If the accountId result is empty. + * + * @example + * import { accountId } from '@biconomy/sdk' + * + * const id = await accountId(nexusClient) + * console.log(id) // 'example_account_id' + */ export async function accountId( client: Client, args?: GetSmartAccountParameter From 140f9dca7a8307d9b819c72d8563dc1d83c3eebe Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:16:27 +0100 Subject: [PATCH 10/29] chore: tsdoc - getActiveHook --- src/sdk/clients/decorators/erc7579/accountId.ts | 2 +- .../clients/decorators/erc7579/getActiveHook.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sdk/clients/decorators/erc7579/accountId.ts b/src/sdk/clients/decorators/erc7579/accountId.ts index 10d8c368..7068e960 100644 --- a/src/sdk/clients/decorators/erc7579/accountId.ts +++ b/src/sdk/clients/decorators/erc7579/accountId.ts @@ -25,7 +25,7 @@ import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" * * @example * import { accountId } from '@biconomy/sdk' - * + * * const id = await accountId(nexusClient) * console.log(id) // 'example_account_id' */ diff --git a/src/sdk/clients/decorators/erc7579/getActiveHook.ts b/src/sdk/clients/decorators/erc7579/getActiveHook.ts index 0e4dbb8b..d13a2787 100644 --- a/src/sdk/clients/decorators/erc7579/getActiveHook.ts +++ b/src/sdk/clients/decorators/erc7579/getActiveHook.ts @@ -11,6 +11,20 @@ export type GetActiveHookParameters< TSmartAccount extends SmartAccount | undefined > = GetSmartAccountParameter +/** + * Retrieves the active hook for a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters for getting the smart account. + * @returns The address of the active hook as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { getActiveHook } from '@biconomy/sdk' + * + * const activeHook = await getActiveHook(nexusClient) + * console.log(activeHook) // '0x...' + */ export async function getActiveHook< TSmartAccount extends SmartAccount | undefined >( From f1e698568dd900e0fb47b680e06daa87354ab564 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:17:39 +0100 Subject: [PATCH 11/29] chore: tsdoc - getFallbackBySelector --- .../decorators/erc7579/getFallbackBySelector.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts b/src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts index 1257e2d5..22b925ae 100644 --- a/src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts +++ b/src/sdk/clients/decorators/erc7579/getFallbackBySelector.ts @@ -15,6 +15,22 @@ export type GetFallbackBySelectorParameters< selector?: Hex }> +/** + * Retrieves the fallback handler for a given selector in a smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account and optional selector. + * @returns A tuple containing the call type and address of the fallback handler. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { getFallbackBySelector } from '@biconomy/sdk' + * + * const [callType, handlerAddress] = await getFallbackBySelector(nexusClient, { + * selector: '0x12345678' + * }) + * console.log(callType, handlerAddress) // '0x1' '0x...' + */ export async function getFallbackBySelector< TSmartAccount extends SmartAccount | undefined >( From d6603ad31c6364389bcb73aee18de8154c8540b4 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:18:54 +0100 Subject: [PATCH 12/29] chore: tsdoc - getInstalledExecutors --- .../decorators/erc7579/getInstalledExecutors.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts b/src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts index af1e634e..9c4adaca 100644 --- a/src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts +++ b/src/sdk/clients/decorators/erc7579/getInstalledExecutors.ts @@ -15,6 +15,22 @@ export type GetInstalledExecutorsParameters< cursor?: Hex } +/** + * Retrieves the installed executors for a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, page size, and cursor. + * @returns A tuple containing an array of executor addresses and the next cursor. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { getInstalledExecutors } from '@biconomy/sdk' + * + * const [executors, nextCursor] = await getInstalledExecutors(nexusClient, { + * pageSize: 10n + * }) + * console.log(executors, nextCursor) // ['0x...', '0x...'], '0x...' + */ export async function getInstalledExecutors< TSmartAccount extends SmartAccount | undefined >( From d0ad76d7503849b0a8c0639f27108e47d1db1825 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:20:19 +0100 Subject: [PATCH 13/29] chore: tsdoc - getInstalledValidators --- .../decorators/erc7579/getInstalledValidators.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/getInstalledValidators.ts b/src/sdk/clients/decorators/erc7579/getInstalledValidators.ts index 07cebce6..96016578 100644 --- a/src/sdk/clients/decorators/erc7579/getInstalledValidators.ts +++ b/src/sdk/clients/decorators/erc7579/getInstalledValidators.ts @@ -15,6 +15,22 @@ export type GetInstalledValidatorsParameters< cursor?: Hex } +/** + * Retrieves the installed validators for a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, page size, and cursor. + * @returns A tuple containing an array of validator addresses and the next cursor. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { getInstalledValidators } from '@biconomy/sdk' + * + * const [validators, nextCursor] = await getInstalledValidators(nexusClient, { + * pageSize: 10n + * }) + * console.log(validators, nextCursor) // ['0x...', '0x...'], '0x...' + */ export async function getInstalledValidators< TSmartAccount extends SmartAccount | undefined >( From a5493d14331b22a4ab74db88888a1a3a9d3ae0d3 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:21:18 +0100 Subject: [PATCH 14/29] chore: tsdoc - installModule --- .../decorators/erc7579/installModule.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/installModule.ts b/src/sdk/clients/decorators/erc7579/installModule.ts index 55ab308b..f5eb2154 100644 --- a/src/sdk/clients/decorators/erc7579/installModule.ts +++ b/src/sdk/clients/decorators/erc7579/installModule.ts @@ -18,6 +18,26 @@ export type InstallModuleParameters< nonce?: bigint } +/** + * Installs a module on a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, module to install, and optional gas settings. + * @returns The hash of the user operation as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { installModule } from '@biconomy/sdk' + * + * const userOpHash = await installModule(nexusClient, { + * module: { + * type: 'executor', + * address: '0x...', + * context: '0x' + * } + * }) + * console.log(userOpHash) // '0x...' + */ export async function installModule< TSmartAccount extends SmartAccount | undefined >( From e5dc236687087efac89b792e61274951adb9d1be Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:22:22 +0100 Subject: [PATCH 15/29] chore: tsdoc - installModules --- .../decorators/erc7579/installModules.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/installModules.ts b/src/sdk/clients/decorators/erc7579/installModules.ts index 5f4d905c..ac7366b8 100644 --- a/src/sdk/clients/decorators/erc7579/installModules.ts +++ b/src/sdk/clients/decorators/erc7579/installModules.ts @@ -30,6 +30,25 @@ export type InstallModulesParameters< nonce?: bigint } +/** + * Installs multiple modules on a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, modules to install, and optional gas settings. + * @returns The hash of the user operation as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { installModules } from '@biconomy/sdk' + * + * const userOpHash = await installModules(nexusClient, { + * modules: [ + * { type: 'executor', address: '0x...', context: '0x' }, + * { type: 'validator', address: '0x...', context: '0x' } + * ] + * }) + * console.log(userOpHash) // '0x...' + */ export async function installModules< TSmartAccount extends SmartAccount | undefined >( From 21e3abdcf58ec30f8d407c772ede8dc459e8bd9f Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:23:41 +0100 Subject: [PATCH 16/29] chore: tsdoc - isModuleInstalled --- .../decorators/erc7579/isModuleInstalled.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/isModuleInstalled.ts b/src/sdk/clients/decorators/erc7579/isModuleInstalled.ts index 06f8b4f2..a135bb9b 100644 --- a/src/sdk/clients/decorators/erc7579/isModuleInstalled.ts +++ b/src/sdk/clients/decorators/erc7579/isModuleInstalled.ts @@ -23,6 +23,27 @@ export type IsModuleInstalledParameters< module: Module } +/** + * Checks if a specific module is installed on a given smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account and the module to check. + * @returns A boolean indicating whether the module is installed. + * @throws {AccountNotFoundError} If the account is not found. + * @throws {Error} If the accountId result is empty. + * + * @example + * import { isModuleInstalled } from '@biconomy/sdk' + * + * const isInstalled = await isModuleInstalled(nexusClient, { + * module: { + * type: 'executor', + * address: '0x...', + * context: '0x' + * } + * }) + * console.log(isInstalled) // true or false + */ export async function isModuleInstalled< TSmartAccount extends SmartAccount | undefined >( From 69cad12dd7f91b50739cdb1a72f37a15a1f3ab54 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:24:51 +0100 Subject: [PATCH 17/29] chore: tsdoc - supportsExecutionMode --- .../erc7579/supportsExecutionMode.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts b/src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts index 568fd118..23c788b8 100644 --- a/src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts +++ b/src/sdk/clients/decorators/erc7579/supportsExecutionMode.ts @@ -44,6 +44,12 @@ function parseCallType(callType: CallType) { } } +/** + * Encodes the execution mode for a smart account operation. + * + * @param mode - The execution mode parameters. + * @returns The encoded execution mode as a hexadecimal string. + */ export function encodeExecutionMode({ type, revertOnError, @@ -62,6 +68,24 @@ export function encodeExecutionMode({ ) } +/** + * Checks if a smart account supports a specific execution mode. + * + * @param client - The client instance. + * @param args - Parameters including the smart account and execution mode details. + * @returns A boolean indicating whether the execution mode is supported. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { supportsExecutionMode } from '@biconomy/sdk' + * + * const isSupported = await supportsExecutionMode(nexusClient, { + * type: 'call', + * revertOnError: true, + * selector: '0x12345678' + * }) + * console.log(isSupported) // true or false + */ export async function supportsExecutionMode< TSmartAccount extends SmartAccount | undefined, callType extends CallType = CallType From 48e8db638561c77c2340e70a3d7c3a0e05fd46d6 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:25:49 +0100 Subject: [PATCH 18/29] chore: tsdoc - supportsMode --- .../decorators/erc7579/supportsModule.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/supportsModule.ts b/src/sdk/clients/decorators/erc7579/supportsModule.ts index 862b0891..cf7da1cf 100644 --- a/src/sdk/clients/decorators/erc7579/supportsModule.ts +++ b/src/sdk/clients/decorators/erc7579/supportsModule.ts @@ -22,6 +22,13 @@ export type SupportsModuleParameters< type: ModuleType } +/** + * Parses a module type to its corresponding ID. + * + * @param type - The module type to parse. + * @returns The corresponding bigint ID for the module type. + * @throws {Error} If an invalid module type is provided. + */ export function parseModuleTypeId(type: ModuleType): bigint { switch (type) { case "validator": @@ -37,6 +44,22 @@ export function parseModuleTypeId(type: ModuleType): bigint { } } +/** + * Checks if a smart account supports a specific module type. + * + * @param client - The client instance. + * @param args - Parameters including the smart account and module type to check. + * @returns A boolean indicating whether the module type is supported. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { supportsModule } from '@biconomy/sdk' + * + * const isSupported = await supportsModule(nexusClient, { + * type: 'executor' + * }) + * console.log(isSupported) // true or false + */ export async function supportsModule< TSmartAccount extends SmartAccount | undefined >( From 6bb3bb2e52a6076bc1a5fe2464c09d52c11a26ff Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:26:37 +0100 Subject: [PATCH 19/29] chore: tsdoc - uninstallFallback --- .../decorators/erc7579/uninstallFallback.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/uninstallFallback.ts b/src/sdk/clients/decorators/erc7579/uninstallFallback.ts index 5ebf06ce..7a4b6dc3 100644 --- a/src/sdk/clients/decorators/erc7579/uninstallFallback.ts +++ b/src/sdk/clients/decorators/erc7579/uninstallFallback.ts @@ -26,6 +26,26 @@ export type UninstallFallbackParameters< nonce?: bigint } +/** + * Uninstalls a fallback module from a smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, module to uninstall, and optional gas settings. + * @returns The hash of the user operation as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { uninstallFallback } from '@biconomy/sdk' + * + * const userOpHash = await uninstallFallback(nexusClient, { + * module: { + * type: 'fallback', + * address: '0x...', + * context: '0x' + * } + * }) + * console.log(userOpHash) // '0x...' + */ export async function uninstallFallback< TSmartAccount extends SmartAccount | undefined >( From 2b9fe68f883b027c09e750e26b4490f3b55746b2 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:27:16 +0100 Subject: [PATCH 20/29] chore: tsdoc - uninstallModule --- .../decorators/erc7579/uninstallModule.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/uninstallModule.ts b/src/sdk/clients/decorators/erc7579/uninstallModule.ts index e8975be8..03f29878 100644 --- a/src/sdk/clients/decorators/erc7579/uninstallModule.ts +++ b/src/sdk/clients/decorators/erc7579/uninstallModule.ts @@ -26,6 +26,26 @@ export type UninstallModuleParameters< nonce?: bigint } +/** + * Uninstalls a module from a smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, module to uninstall, and optional gas settings. + * @returns The hash of the user operation as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { uninstallModule } from '@biconomy/sdk' + * + * const userOpHash = await uninstallModule(nexusClient, { + * module: { + * type: 'executor', + * address: '0x...', + * context: '0x' + * } + * }) + * console.log(userOpHash) // '0x...' + */ export async function uninstallModule< TSmartAccount extends SmartAccount | undefined >( From 1cffbc2e757955e1def8f61e3d6ae5cbf0682015 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:27:51 +0100 Subject: [PATCH 21/29] chore: tsdoc - uninstallModules --- .../decorators/erc7579/uninstallModules.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sdk/clients/decorators/erc7579/uninstallModules.ts b/src/sdk/clients/decorators/erc7579/uninstallModules.ts index 93731d32..0c28dd4d 100644 --- a/src/sdk/clients/decorators/erc7579/uninstallModules.ts +++ b/src/sdk/clients/decorators/erc7579/uninstallModules.ts @@ -26,6 +26,25 @@ export type UninstallModulesParameters< nonce?: bigint } +/** + * Uninstalls multiple modules from a smart account. + * + * @param client - The client instance. + * @param parameters - Parameters including the smart account, modules to uninstall, and optional gas settings. + * @returns The hash of the user operation as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. + * + * @example + * import { uninstallModules } from '@biconomy/sdk' + * + * const userOpHash = await uninstallModules(nexusClient, { + * modules: [ + * { type: 'executor', address: '0x...', context: '0x' }, + * { type: 'validator', address: '0x...', context: '0x' } + * ] + * }) + * console.log(userOpHash) // '0x...' + */ export async function uninstallModules< TSmartAccount extends SmartAccount | undefined >( From b8c47f32ed383a6362f6f60e496235831437369b Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:29:01 +0100 Subject: [PATCH 22/29] chore: tsdoc - sendTransaction --- .../smartAccount/sendTransaction.ts | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/src/sdk/clients/decorators/smartAccount/sendTransaction.ts b/src/sdk/clients/decorators/smartAccount/sendTransaction.ts index 931e0944..c282896d 100644 --- a/src/sdk/clients/decorators/smartAccount/sendTransaction.ts +++ b/src/sdk/clients/decorators/smartAccount/sendTransaction.ts @@ -15,50 +15,23 @@ import { getAction, parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" /** - * Creates, signs, and sends a new transaction to the network. - * This function also allows you to sponsor this transaction if sender is a smartAccount + * Creates, signs, and sends a new transaction to the network using a smart account. + * This function also allows you to sponsor this transaction if the sender is a smart account. * - * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html - * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions - * - JSON-RPC Methods: - * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) - * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) - * - * @param client - Client to use - * @param parameters - {@link SendTransactionParameters} - * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. + * @param client - The client instance. + * @param args - Parameters for sending the transaction or user operation. + * @returns The transaction hash as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. * * @example - * import { createWalletClient, custom } from 'viem' - * import { mainnet } from 'viem/chains' - * import { sendTransaction } from 'viem/wallet' - * - * const client = createWalletClient({ - * chain: mainnet, - * transport: custom(window.ethereum), - * }) - * const hash = await sendTransaction(client, { - * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', - * value: 1000000000000000000n, - * }) + * import { sendTransaction } from '@biconomy/sdk' * - * @example - * // Account Hoisting - * import { createWalletClient, http } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' - * import { mainnet } from 'viem/chains' - * import { sendTransaction } from 'viem/wallet' - * - * const client = createWalletClient({ - * account: privateKeyToAccount('0x…'), - * chain: mainnet, - * transport: http(), - * }) - * const hash = await sendTransaction(client, { - * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', - * value: 1000000000000000000n, + * const hash = await sendTransaction(nexusClient, { + * to: '0x...', + * value: parseEther('0.1'), + * data: '0x...' * }) + * console.log(hash) // '0x...' */ export async function sendTransaction< account extends SmartAccount | undefined, From b78b12cd54e9e4c1ea03ee3afce8c338e90d959f Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:29:43 +0100 Subject: [PATCH 23/29] chore: tsdoc - signMessage --- .../decorators/smartAccount/signMessage.ts | 49 +++++-------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/src/sdk/clients/decorators/smartAccount/signMessage.ts b/src/sdk/clients/decorators/smartAccount/signMessage.ts index fee93f7e..bf5ebb55 100644 --- a/src/sdk/clients/decorators/smartAccount/signMessage.ts +++ b/src/sdk/clients/decorators/smartAccount/signMessage.ts @@ -10,50 +10,23 @@ import { parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" /** - * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * Signs a message using the smart account. * - * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html - * - JSON-RPC Methods: - * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) - * - Local Accounts: Signs locally. No JSON-RPC request. + * This function calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): + * `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. * - * With the calculated signature, you can: - * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, - * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. - * - * @param client - Client to use - * @param parameters - {@link SignMessageParameters} - * @returns The signed message. {@link SignMessageReturnType} + * @param client - The client instance. + * @param parameters - Parameters for signing the message. + * @returns The signature as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. * * @example - * import { createWalletClient, custom } from 'viem' - * import { mainnet } from 'viem/chains' - * import { signMessage } from 'viem/wallet' - * - * const client = createWalletClient({ - * chain: mainnet, - * transport: custom(window.ethereum), - * }) - * const signature = await signMessage(client, { - * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * message: 'hello world', - * }) + * import { signMessage } from '@biconomy/sdk' * - * @example - * // Account Hoisting - * import { createWalletClient, custom } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' - * import { mainnet } from 'viem/chains' - * import { signMessage } from 'viem/wallet' - * - * const client = createWalletClient({ - * account: privateKeyToAccount('0x…'), - * chain: mainnet, - * transport: custom(window.ethereum), - * }) - * const signature = await signMessage(client, { - * message: 'hello world', + * const signature = await signMessage(nexusClient, { + * message: 'Hello, Biconomy!' * }) + * console.log(signature) // '0x...' */ export async function signMessage( client: Client, From 20d27b5c8c5fb6c7830f11d3a48113110770430e Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:34:31 +0100 Subject: [PATCH 24/29] chore: tsdoc - signTypedData --- ...ons.test.ts => erc7579.decorators.test.ts} | 0 ...ons.test.ts => account.decorators.test.ts} | 0 .../decorators/smartAccount/signTypedData.ts | 126 ++++++------------ 3 files changed, 41 insertions(+), 85 deletions(-) rename src/sdk/clients/decorators/erc7579/{erc7579.actions.test.ts => erc7579.decorators.test.ts} (100%) rename src/sdk/clients/decorators/smartAccount/{erc7579.actions.test.ts => account.decorators.test.ts} (100%) diff --git a/src/sdk/clients/decorators/erc7579/erc7579.actions.test.ts b/src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts similarity index 100% rename from src/sdk/clients/decorators/erc7579/erc7579.actions.test.ts rename to src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts diff --git a/src/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts b/src/sdk/clients/decorators/smartAccount/account.decorators.test.ts similarity index 100% rename from src/sdk/clients/decorators/smartAccount/erc7579.actions.test.ts rename to src/sdk/clients/decorators/smartAccount/account.decorators.test.ts diff --git a/src/sdk/clients/decorators/smartAccount/signTypedData.ts b/src/sdk/clients/decorators/smartAccount/signTypedData.ts index 603f8c81..b4db0327 100644 --- a/src/sdk/clients/decorators/smartAccount/signTypedData.ts +++ b/src/sdk/clients/decorators/smartAccount/signTypedData.ts @@ -15,102 +15,58 @@ import { parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" /** - * Signs typed data and calculates an Ethereum-specific signature in [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712): `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))` + * Signs typed data using the smart account. * - * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html - * - JSON-RPC Methods: - * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) - * - Local Accounts: Signs locally. No JSON-RPC request. + * This function calculates an Ethereum-specific signature in [EIP-712 format](https://eips.ethereum.org/EIPS/eip-712): + * `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))` * - * @param client - Client to use - * @param parameters - {@link SignTypedDataParameters} - * @returns The signed data. {@link SignTypedDataReturnType} + * @param client - The client instance. + * @param parameters - Parameters for signing the typed data. + * @returns The signature as a hexadecimal string. + * @throws {AccountNotFoundError} If the account is not found. * * @example - * import { createWalletClient, custom } from 'viem' - * import { mainnet } from 'viem/chains' - * import { signTypedData } from 'viem/wallet' + * import { signTypedData } from '@biconomy/sdk' + * import { keccak256, encodeAbiParameters, parseAbiParameters } from 'viem' * - * const client = createWalletClient({ - * chain: mainnet, - * transport: custom(window.ethereum), - * }) - * const signature = await signTypedData(client, { - * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', - * 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!', - * }, - * }) + * const domain = { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' + * } * - * @example - * // Account Hoisting - * import { createWalletClient, http } from 'viem' - * import { privateKeyToAccount } from 'viem/accounts' - * import { mainnet } from 'viem/chains' - * import { signTypedData } from 'viem/wallet' + * const types = { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' } + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' } + * ] + * } * - * const client = createWalletClient({ - * account: privateKeyToAccount('0x…'), - * chain: mainnet, - * transport: http(), - * }) - * const signature = await signTypedData(client, { - * domain: { - * name: 'Ether Mail', - * version: '1', - * chainId: 1, - * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * const message = { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' * }, - * types: { - * Person: [ - * { name: 'name', type: 'string' }, - * { name: 'wallet', type: 'address' }, - * ], - * Mail: [ - * { name: 'from', type: 'Person' }, - * { name: 'to', type: 'Person' }, - * { name: 'contents', type: 'string' }, - * ], + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' * }, + * contents: 'Hello, Bob!' + * } + * + * const signature = await signTypedData(nexusClient, { + * domain, + * types, * primaryType: 'Mail', - * message: { - * from: { - * name: 'Cow', - * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - * }, - * to: { - * name: 'Bob', - * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - * }, - * contents: 'Hello, Bob!', - * }, + * message * }) + * console.log(signature) // '0x...' */ export async function signTypedData< const TTypedData extends TypedData | { [key: string]: unknown }, From fc3957e3893aacbf75b2798055c3d31d61ade3b8 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:38:13 +0100 Subject: [PATCH 25/29] chore: tsdoc - writeContract --- .../decorators/smartAccount/writeContract.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sdk/clients/decorators/smartAccount/writeContract.ts b/src/sdk/clients/decorators/smartAccount/writeContract.ts index 1641727e..10470875 100644 --- a/src/sdk/clients/decorators/smartAccount/writeContract.ts +++ b/src/sdk/clients/decorators/smartAccount/writeContract.ts @@ -15,6 +15,29 @@ import type { SmartAccount } from "viem/account-abstraction" import { getAction } from "viem/utils" import { sendTransaction } from "./sendTransaction" +/** + * Executes a write operation on a smart contract using a smart account. + * + * @param client - The client instance. + * @param parameters - Parameters for the contract write operation. + * @returns The transaction hash as a hexadecimal string. + * @throws {Error} If the 'to' address is missing in the request. + * + * @example + * import { writeContract } from '@biconomy/sdk' + * import { encodeFunctionData } from 'viem' + * + * const encodedCall = encodeFunctionData({ + * abi: CounterAbi, + * functionName: "incrementNumber" + * }) + * const call = { + * to: '0x61f70428b61864B38D9B45b7B032c700B960acCD', + * data: encodedCall + * } + * const hash = await writeContract(nexusClient, call) + * console.log(hash) // '0x...' + */ export async function writeContract< TChain extends Chain | undefined, TAccount extends SmartAccount | undefined, From 427ce0e09832fcffba5b920516fcfba13a7e5f16 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:40:47 +0100 Subject: [PATCH 26/29] chore: tsdoc - bicoBundlerClient --- src/sdk/clients/createBicoBundlerClient.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sdk/clients/createBicoBundlerClient.ts b/src/sdk/clients/createBicoBundlerClient.ts index cf78768c..9a5d3884 100644 --- a/src/sdk/clients/createBicoBundlerClient.ts +++ b/src/sdk/clients/createBicoBundlerClient.ts @@ -18,6 +18,18 @@ type BicoBundlerClientConfig = Omit & } > +/** + * Creates a Bico Bundler Client with a given Transport configured for a Chain. + * + * @param parameters - Configuration for the Bico Bundler Client + * @returns A Bico Bundler Client + * + * @example + * import { createBicoBundlerClient, http } from '@biconomy/sdk' + * import { mainnet } from 'viem/chains' + * + * const bundlerClient = createBicoBundlerClient({ chain: mainnet }); + */ export const createBicoBundlerClient = ( parameters: BicoBundlerClientConfig ): BundlerClient => { From d0ae8c5fb21193577220ccf386e0710e5807b8c5 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 12:54:05 +0100 Subject: [PATCH 27/29] chore: tsdoc - ignore helpers --- scripts/viem:bundler.ts | 4 ++-- src/sdk/account/utils/AccountNotFound.ts | 3 +++ typedoc.json | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/viem:bundler.ts b/scripts/viem:bundler.ts index c4b7e3b9..1daf9380 100644 --- a/scripts/viem:bundler.ts +++ b/scripts/viem:bundler.ts @@ -130,11 +130,11 @@ const main = async () => { account: nexusAccount }) const userOpReceipt = await bicoBundler.waitForUserOperationReceipt({ hash }) - const txHash = await publicClient.waitForTransactionReceipt({ + const { transactionHash } = await publicClient.waitForTransactionReceipt({ hash: userOpReceipt.receipt.transactionHash }) console.timeEnd("write methods") - console.log({ txHash }) + console.log({ transactionHash }) } main() diff --git a/src/sdk/account/utils/AccountNotFound.ts b/src/sdk/account/utils/AccountNotFound.ts index 3547d09f..e373fcac 100644 --- a/src/sdk/account/utils/AccountNotFound.ts +++ b/src/sdk/account/utils/AccountNotFound.ts @@ -1,5 +1,8 @@ import { BaseError } from "viem" +/** + * @ignore + */ export class AccountNotFoundError extends BaseError { constructor({ docsPath }: { docsPath?: string | undefined } = {}) { super( diff --git a/typedoc.json b/typedoc.json index 6a717b22..1b6162e2 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,6 +1,11 @@ { "$schema": "https://typedoc.org/schema.json", "entryPoints": ["src/index.ts"], + "exclude": [ + "src/sdk/account/utils/**/*.ts", + "src/sdk/__contracts/**/*.ts", + "src/test/**/*.ts" + ], "basePath": "src", "includes": "src", "out": "docs", From 947bd35b6535a16bed5719add0ff9fac01b0e202 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 13:08:17 +0100 Subject: [PATCH 28/29] chore: fix size limit --- .size-limit.json | 8 +- src/sdk/account/utils/Types.ts | 481 +------------------ src/sdk/clients/createBicoPaymasterClient.ts | 29 ++ src/sdk/clients/createNexusClient.ts | 48 +- src/sdk/modules/utils/Types.ts | 6 +- 5 files changed, 82 insertions(+), 490 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index 35bf1e81..7b863659 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -14,16 +14,16 @@ }, { "name": "bundler (tree-shaking)", - "path": "./dist/_esm/bundler/index.js", + "path": "./dist/_esm/clients/createBicoBundlerClient.js", "limit": "15 kB", - "import": "{ createBundler }", + "import": "{ createBicoBundlerClient }", "ignore": ["node:fs", "fs"] }, { "name": "paymaster (tree-shaking)", - "path": "./dist/_esm/paymaster/index.js", + "path": "./dist/_esm/clients/createBicoPaymasterClient.js", "limit": "15 kB", - "import": "{ toPaymaster }", + "import": "{ createBicoPaymasterClient }", "ignore": ["node:fs", "fs"] } ] diff --git a/src/sdk/account/utils/Types.ts b/src/sdk/account/utils/Types.ts index 849bbcd6..f1cd3a42 100644 --- a/src/sdk/account/utils/Types.ts +++ b/src/sdk/account/utils/Types.ts @@ -1,21 +1,6 @@ -import type { Address, Hash, Hex, Log, SignTypedDataParameters } from "viem" -import type { ModuleInfo, ModuleType } from "../../modules" -import type { BaseValidationModule } from "../../modules/base/BaseValidationModule" +import type { Address, Hash, Hex, Log } from "viem" import type { MODE_MODULE_ENABLE, MODE_VALIDATION } from "./Constants" -import type { Holder } from "./toHolder" -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 = { @@ -47,264 +32,6 @@ export type UserOpReceipt = { 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 - /** factoryAddress: address of the smart account factory */ - bundler?: IBundler -} - -export interface BalancePayload { - /** address: The address of the account */ - address: string - /** chainId: The chainId of the network */ - chainId: number - /** amount: The amount of the balance */ - amount: bigint - /** decimals: The number of decimals */ - decimals: number - /** formattedAmount: The amount of the balance formatted */ - formattedAmount: string -} - -export interface WithdrawalRequest { - /** The address of the asset */ - address: Hex - /** The amount to withdraw. Expects unformatted amount. Will use max amount if unset */ - amount?: bigint - /** The destination address of the funds. The second argument from the `withdraw(...)` function will be used as the default if left unset. */ - recipient?: Hex -} - -export interface GasOverheads { - /** fixed: fixed gas overhead */ - fixed: number - /** perUserOp: per user operation gas overhead */ - perUserOp: number - /** perUserOpWord: per user operation word gas overhead */ - perUserOpWord: number - /** zeroByte: per byte gas overhead */ - zeroByte: number - /** nonZeroByte: per non zero byte gas overhead */ - nonZeroByte: number - /** bundleSize: per signature bundleSize */ - bundleSize: number - /** sigSize: sigSize gas overhead */ - sigSize: number -} - -export type BaseSmartAccountConfig = { - /** index: helps to not conflict with other smart account instances */ - index?: bigint - /** entryPointAddress: address of the smart account entry point */ - entryPointAddress?: string - /** accountAddress: address of the smart account, potentially counterfactual */ - accountAddress?: string - /** overheads: {@link GasOverheads} */ - overheads?: Partial - /** paymaster: {@link IPaymaster} interface */ - paymaster?: IPaymaster -} - -export type BiconomyTokenPaymasterRequest = { - /** feeQuote: {@link PaymasterFeeQuote} */ - feeQuote: PaymasterFeeQuote - /** spender: The address of the spender who is paying for the transaction, this can usually be set to feeQuotesResponse.tokenPaymasterAddress */ - spender: Hex - /** maxApproval: If set to true, the paymaster will approve the maximum amount of tokens required for the transaction. Not recommended */ - maxApproval?: boolean - /* skip option to patch callData if approval is already given to the paymaster */ - skipPatchCallData?: boolean -} - -export type RequireAtLeastOne = Pick< - T, - Exclude -> & - { - [K in Keys]-?: Required> & Partial>> - }[Keys] - -export type ConditionalBundlerProps = RequireAtLeastOne< - { - bundler: IBundler - bundlerUrl: string - }, - "bundler" | "bundlerUrl" -> -export type ResolvedBundlerProps = { - bundler: IBundler -} - -export type ResolvedValidationProps = { - /** defaultValidationModule: {@link BaseValidationModule} */ - defaultValidationModule: BaseValidationModule - /** activeValidationModule: {@link BaseValidationModule}. The active validation module. Will default to the defaultValidationModule */ - activeValidationModule: BaseValidationModule - /** holder */ - holder: Holder - /** rpcUrl */ - rpcUrl: string -} - -export type ConfigurationAddresses = { - factoryAddress: Hex - k1ValidatorAddress: Hex - entryPointAddress: Hex -} - -/** - * Represents options for building a user operation. - * @typedef BuildUserOpOptions - * @property {GasOffset} [gasOffset] - Increment gas values by giving an offset, the given value will be an increment to the current estimated gas values, not an override. - * @property {ModuleInfo} [params] - Parameters relevant to the module, mostly relevant to sessions. - * @property {NonceOptions} [nonceOptions] - Options for overriding the nonce. - * @property {boolean} [forceEncodeForBatch] - Whether to encode the user operation for batch. - * @property {PaymasterUserOperationDto} [paymasterServiceData] - Options specific to transactions that involve a paymaster. - * @property {SimulationType} [simulationType] - Determine which parts of the transaction a bundler will simulate: "validation" | "validation_and_execution". - * @property {StateOverrideSet} [stateOverrideSet] - For overriding the state. - * @property {boolean} [useEmptyDeployCallData] - Set to true if the transaction is being used only to deploy the smart contract, so "0x" is set as the user operation call data. - */ -export type BuildUserOpOptions = { - gasOffset?: GasOffsetPct - params?: ModuleInfo - nonceOptions?: NonceOptions - forceEncodeForBatch?: boolean - paymasterServiceData?: PaymasterUserOperationDto - simulationType?: SimulationType - stateOverrideSet?: StateOverrideSet - dummyPndOverride?: BytesLike - useEmptyDeployCallData?: boolean - useExecutor?: boolean -} - export type NonceOptions = { /** nonceKey: The key to use for nonce */ nonceKey?: bigint @@ -314,176 +41,7 @@ export type NonceOptions = { nonceOverride?: bigint } -export type SimulationType = "validation" | "validation_and_execution" - -/** - * Represents an offset percentage value used for gas-related calculations. - * @remarks - * This type defines offset percentages for various gas-related parameters. Each percentage represents a proportion of the current estimated gas values. - * For example: - * - A value of `1` represents a 1% offset. - * - A value of `100` represents a 100% offset. - * @public - */ -/** - * Represents an object containing offset percentages for gas-related parameters. - * @typedef GasOffsetPct - * @property {number} [callGasLimitOffsetPct] - Percentage offset for the gas limit used by inner account execution. - * @property {number} [verificationGasLimitOffsetPct] - Percentage offset for the actual gas used by the validation of a UserOperation. - * @property {number} [preVerificationGasOffsetPct] - Percentage offset representing the gas overhead of a UserOperation. - * @property {number} [maxFeePerGasOffsetPct] - Percentage offset for the maximum fee per gas (similar to EIP-1559 max_fee_per_gas). - * @property {number} [maxPriorityFeePerGasOffsetPct] - Percentage offset for the maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas). - */ -export type GasOffsetPct = { - callGasLimitOffsetPct?: number - verificationGasLimitOffsetPct?: number - preVerificationGasOffsetPct?: number - maxFeePerGasOffsetPct?: number - maxPriorityFeePerGasOffsetPct?: number -} - -export type InitilizationData = { - accountIndex?: number - 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 */ - 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 */ - // biome-ignore lint/suspicious/noExplicitAny: - webhookData?: Record - /** Smart account meta data */ - smartAccountInfo?: SmartAccountData - /** the fee-paying token address */ - feeTokenAddress?: string - /** The fee quote */ - feeQuote?: PaymasterFeeQuote - /** The address of the spender. This is usually set to FeeQuotesOrDataResponse.tokenPaymasterAddress */ - spender?: Hex - /** Not recommended */ - maxApproval?: boolean - /* skip option to patch callData if approval is already given to the paymaster */ - skipPatchCallData?: boolean - } - -export type InitializeV2Data = { - accountIndex?: number -} - -export type EstimateUserOpGasParams = { - userOp: Partial - /** paymasterServiceData: Options specific to transactions that involve a paymaster */ - paymasterServiceData?: SponsorUserOperationDto -} - -export interface TransactionDetailsForUserOp { - /** target: The address of the contract to call */ - target: string - /** data: The data to send to the contract */ - data: string - /** value: The value to send to the contract */ - value?: BigNumberish - /** gasLimit: The gas limit to use for the transaction */ - gasLimit?: BigNumberish - /** maxFeePerGas: The maximum fee per gas to use for the transaction */ - maxFeePerGas?: BigNumberish - /** maxPriorityFeePerGas: The maximum priority fee per gas to use for the transaction */ - maxPriorityFeePerGas?: BigNumberish - /** nonce: The nonce to use for the transaction */ - nonce?: BigNumberish -} - -export type CounterFactualAddressParam = { - index?: bigint - validationModule?: BaseValidationModule - /** scanForUpgradedAccountsFromV1: set to true if you you want the userwho was using biconomy SA v1 to upgrade to biconomy SA v2 */ - scanForUpgradedAccountsFromV1?: boolean - /** the index of SA the EOA have generated and till which indexes the upgraded SA should scan */ - maxIndexForScan?: bigint -} - -export type QueryParamsForAddressResolver = { - eoaAddress: Hex - index: bigint - moduleAddress: Hex - moduleSetupData: Hex - maxIndexForScan?: bigint -} - -export type SmartAccountInfo = { - /** accountAddress: The address of the smart account */ - accountAddress: Hex - /** factoryAddress: The address of the smart account factory */ - factoryAddress: Hex - /** currentImplementation: The address of the current implementation */ - currentImplementation: string - /** currentVersion: The version of the smart account */ - currentVersion: string - /** factoryVersion: The version of the factory */ - factoryVersion: string - /** deploymentIndex: The index of the deployment */ - deploymentIndex: BigNumberish -} - -export type ValueOrData = RequireAtLeastOne< - { - value: BigNumberish | string - data: string - }, - "value" | "data" -> - -export type SupportedToken = Omit< - PaymasterFeeQuote, - "maxGasFeeUSD" | "usdPayment" | "maxGasFee" | "validUntil" -> & { balance: BalancePayload } - -export type Signer = LightSigner & { - // biome-ignore lint/suspicious/noExplicitAny: any is used here to allow for the ethers provider - provider: any -} -export type SupportedSignerName = "alchemy" | "ethers" | "viem" export type Service = "Bundler" | "Paymaster" - -export interface LightSigner { - getAddress(): Promise - signMessage(message: string | Uint8Array): Promise -} - -export type StateOverrideSet = { - [key: string]: { - balance?: string - nonce?: string - code?: string - state?: object - stateDiff?: object - } -} - export type BigNumberish = Hex | number | bigint export type BytesLike = Uint8Array | Hex | string @@ -511,36 +69,6 @@ export type UserOperationStruct = { } //#endregion UserOperationStruct -//#region UserOperationCallData -export type UserOperationCallData = - | { - /* the target of the call */ - target: Address - /* the data passed to the target */ - data: Hex - /* the amount of native token to send to the target (default: 0) */ - value?: bigint - } - | Hex -//#endregion UserOperationCallData - -//#region BatchUserOperationCallData -export type BatchUserOperationCallData = Exclude[] -//#endregion BatchUserOperationCallData - -export type SignTypedDataParams = Omit - -export type TransferOwnershipCompatibleModule = - | "0x0000001c5b32F37F5beA87BDD5374eB2aC54eA8e" - | "0x000000824dc138db84FD9109fc154bdad332Aa8E" - -export type ModuleInfoParams = { - moduleAddress: Address - moduleType: ModuleType - moduleSelector?: Hex - data?: Hex -} - export type EIP712DomainReturn = [ Hex, string, @@ -551,16 +79,12 @@ export type EIP712DomainReturn = [ bigint[] ] -export type NEXUS_VERSION_TYPE = "1.0.0-beta" - export type AccountMetadata = { name: string version: string chainId: bigint } -export type WithRequired = Required> - export type TypeField = { name: string type: string @@ -580,6 +104,3 @@ export type Call = { data?: Hex | undefined value?: bigint | undefined } - -export type PartiallyOptional = Omit & - Partial> diff --git a/src/sdk/clients/createBicoPaymasterClient.ts b/src/sdk/clients/createBicoPaymasterClient.ts index 8653a803..01abfcc5 100644 --- a/src/sdk/clients/createBicoPaymasterClient.ts +++ b/src/sdk/clients/createBicoPaymasterClient.ts @@ -5,6 +5,14 @@ import { createPaymasterClient } from "viem/account-abstraction" +/** + * Configuration options for creating a Bico Paymaster Client. + * @typedef {Object} BicoPaymasterClientConfig + * @property {Transport} [transport] - Optional custom transport. + * @property {string} [paymasterUrl] - URL of the paymaster service. + * @property {number} [chainId] - Chain ID for the network. + * @property {string} [apiKey] - API key for authentication. + */ type BicoPaymasterClientConfig = Omit & OneOf< | { @@ -19,6 +27,27 @@ type BicoPaymasterClientConfig = Omit & } > +/** + * Creates a Bico Paymaster Client. + * + * This function sets up a client for interacting with Biconomy's paymaster service. + * It can be configured with a custom transport, a specific paymaster URL, or with a chain ID and API key. + * + * @param {BicoPaymasterClientConfig} parameters - Configuration options for the client. + * @returns {PaymasterClient} A configured Paymaster Client instance. + * + * @example + * // Create a client with a custom transport + * const client1 = createBicoPaymasterClient({ transport: customTransport }) + * + * @example + * // Create a client with a specific paymaster URL + * const client2 = createBicoPaymasterClient({ paymasterUrl: 'https://example.com/paymaster' }) + * + * @example + * // Create a client with chain ID and API key + * const client3 = createBicoPaymasterClient({ chainId: 1, apiKey: 'your-api-key' }) + */ export const createBicoPaymasterClient = ( parameters: BicoPaymasterClientConfig ): PaymasterClient => diff --git a/src/sdk/clients/createNexusClient.ts b/src/sdk/clients/createNexusClient.ts index 99e47d1c..b99bf5d8 100644 --- a/src/sdk/clients/createNexusClient.ts +++ b/src/sdk/clients/createNexusClient.ts @@ -30,10 +30,16 @@ import { smartAccountActions } from "./decorators/smartAccount" +/** + * Parameters for sending a transaction + */ export type SendTransactionParameters = { calls: Call | Call[] } +/** + * Nexus Client type + */ export type NexusClient< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, @@ -48,15 +54,36 @@ export type NexusClient< BundlerActions & Erc7579Actions & SmartAccountActions & { + /** + * The Nexus account associated with this client + */ account: NexusAccount + /** + * Optional client for additional functionality + */ client?: client | Client | undefined + /** + * Transport configuration for the bundler + */ bundlerTransport?: BundlerClientConfig["transport"] + /** + * Optional paymaster configuration + */ paymaster?: BundlerClientConfig["paymaster"] | undefined + /** + * Optional paymaster context + */ paymasterContext?: BundlerClientConfig["paymasterContext"] | undefined + /** + * Optional user operation configuration + */ userOperation?: BundlerClientConfig["userOperation"] | undefined } > +/** + * Configuration for creating a Nexus Client + */ export type NexusClientConfig< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, @@ -113,14 +140,33 @@ export type NexusClientConfig< index?: bigint /** Active module of the account. */ activeModule?: BaseValidationModule - /** Factory address of the account. */ + /** Executor module of the account. */ executorModule?: BaseExecutionModule + /** Factory address of the account. */ factoryAddress?: Address /** Owner module */ k1ValidatorAddress?: Address } > +/** + * Creates a Nexus Client for interacting with the Nexus smart account system. + * + * @param parameters - {@link NexusClientConfig} + * @returns Nexus Client. {@link NexusClient} + * + * @example + * import { createNexusClient } from '@biconomy/sdk' + * import { http } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const nexusClient = await createNexusClient({ + * chain: mainnet, + * transport: http('https://mainnet.infura.io/v3/YOUR-PROJECT-ID'), + * bundlerTransport: http('https://api.biconomy.io'), + * holder: '0x...', + * }) + */ export async function createNexusClient( parameters: NexusClientConfig ): Promise { diff --git a/src/sdk/modules/utils/Types.ts b/src/sdk/modules/utils/Types.ts index ba755135..ba723048 100644 --- a/src/sdk/modules/utils/Types.ts +++ b/src/sdk/modules/utils/Types.ts @@ -1,5 +1,4 @@ import type { Address, Chain, Hex } from "viem" -import type { SimulationType } from "../../account/utils/Types" import type { Holder, UnknownHolder } from "../../account/utils/toHolder" export type ModuleVersion = "1.0.0-beta" // | 'V1_0_1' @@ -67,10 +66,7 @@ export type ModuleInfo = { batchSessionParams?: SessionParams[] } -export interface SendUserOpParams extends ModuleInfo { - /** "validation_and_execution" is recommended during development for improved debugging & devEx, but will add some additional latency to calls. "validation" can be used in production ro remove this latency once flows have been tested. */ - simulationType?: SimulationType -} +export interface SendUserOpParams extends ModuleInfo {} export type SignerData = { /** This is not the public as provided by viem, key but address for the given pvKey */ From a6f911ec3a9ac7b0ec3fa268c0fc0a60953263b0 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Thu, 19 Sep 2024 14:34:34 +0100 Subject: [PATCH 29/29] fix: remove executor module --- src/sdk/account/toNexusAccount.ts | 4 ---- src/sdk/clients/createNexusClient.ts | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/sdk/account/toNexusAccount.ts b/src/sdk/account/toNexusAccount.ts index 3633dce9..fab2f3f7 100644 --- a/src/sdk/account/toNexusAccount.ts +++ b/src/sdk/account/toNexusAccount.ts @@ -50,7 +50,6 @@ import { PARENT_TYPEHASH } from "./utils/Constants" -import type { BaseExecutionModule } from "../modules/base/BaseExecutionModule" import type { BaseValidationModule } from "../modules/base/BaseValidationModule" import { K1ValidatorModule } from "../modules/validators/K1ValidatorModule" import { @@ -77,8 +76,6 @@ export type ToNexusSmartAccountParameters = { index?: bigint | undefined /** Optional active validation module */ activeModule?: BaseValidationModule - /** Optional executor module */ - executorModule?: BaseExecutionModule /** Optional factory address */ factoryAddress?: Address /** Optional K1 validator address */ @@ -147,7 +144,6 @@ export const toNexusAccount = async ( holder: holder_, index = 0n, activeModule, - executorModule: _, factoryAddress = contracts.k1ValidatorFactory.address, k1ValidatorAddress = contracts.k1Validator.address, key = "nexus account", diff --git a/src/sdk/clients/createNexusClient.ts b/src/sdk/clients/createNexusClient.ts index b99bf5d8..c6637f1f 100644 --- a/src/sdk/clients/createNexusClient.ts +++ b/src/sdk/clients/createNexusClient.ts @@ -178,7 +178,6 @@ export async function createNexusClient( key = "nexus client", name = "Nexus Client", activeModule, - executorModule, factoryAddress = contracts.k1ValidatorFactory.address, k1ValidatorAddress = contracts.k1Validator.address, bundlerTransport, @@ -206,7 +205,6 @@ export async function createNexusClient( holder, index, activeModule, - executorModule, factoryAddress, k1ValidatorAddress })