From 0449f36846927055e91016f5250c4cf3443f5ded Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Sat, 30 Nov 2024 13:21:00 +0000 Subject: [PATCH 01/10] chore: add test and multi account --- .env.example | 4 +- src/sdk/account/toNexusAccount.test.ts | 2 +- src/sdk/account/utils/Types.ts | 1 - src/sdk/account/utils/Utils.ts | 2 +- src/sdk/account/utils/getAAError.ts | 85 --------------- src/sdk/account/utils/toSigner.test.ts | 2 +- src/sdk/account/utils/utils.test.ts | 14 ++- .../clients/createBicoBundlerClient.test.ts | 1 + .../clients/createBicoPaymasterClient.test.ts | 6 +- src/sdk/clients/createMeeClient.test.ts | 93 ++++++++++++++++ src/sdk/clients/createMeeClient.ts | 103 ++++++++++++++++++ src/sdk/clients/createNexusClient.test.ts | 3 +- .../clients/createNexusSessionClient.test.ts | 7 +- src/sdk/clients/createNexusSessionClient.ts | 7 -- src/sdk/clients/decorators/mee/Helpers.ts | 72 ++++++++++++ .../decorators/mee/decorators/index.ts | 23 ++++ .../mee/decorators/mee.decorators.test.ts | 87 +++++++++++++++ .../decorators/mee/decorators/meeAction.ts | 51 +++++++++ src/sdk/clients/decorators/mee/index.ts | 2 + src/sdk/clients/index.ts | 2 +- .../toOwnableValidator.dan.test.ts | 2 - .../smartSessions.decorators.test.ts | 3 +- .../toSmartSessionsValidator.dan.dx.test.ts | 5 +- .../toSmartSessionsValidator.dx.test.ts | 5 +- .../toSmartSessionsValidator.test.ts | 8 +- ...oSmartSessionsValidator.uni.policy.test.ts | 10 +- src/sdk/modules/utils/Types.ts | 4 +- src/sdk/modules/utils/toModule.test.ts | 9 +- src/test/README.md | 66 ++--------- src/test/playground.test.ts | 6 +- src/test/testSetup.ts | 40 +++++-- src/test/testUtils.ts | 17 ++- 32 files changed, 527 insertions(+), 215 deletions(-) delete mode 100644 src/sdk/account/utils/getAAError.ts create mode 100644 src/sdk/clients/createMeeClient.test.ts create mode 100644 src/sdk/clients/createMeeClient.ts delete mode 100644 src/sdk/clients/createNexusSessionClient.ts create mode 100644 src/sdk/clients/decorators/mee/Helpers.ts create mode 100644 src/sdk/clients/decorators/mee/decorators/index.ts create mode 100644 src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts create mode 100644 src/sdk/clients/decorators/mee/decorators/meeAction.ts create mode 100644 src/sdk/clients/decorators/mee/index.ts diff --git a/.env.example b/.env.example index b528468de..405886cfd 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,10 @@ PRIVATE_KEY= CHAIN_ID=84532 +ALT_CHAIN_ID=11155111 RPC_URL= BUNDLER_URL= BICONOMY_SDK_DEBUG=false RUN_PLAYGROUND=false PAYMASTER_URL= -PIMLICO_API_KEY= \ No newline at end of file +PIMLICO_API_KEY= +MEE_NODE_URL= \ No newline at end of file diff --git a/src/sdk/account/toNexusAccount.test.ts b/src/sdk/account/toNexusAccount.test.ts index 8c1e0a6ba..efaa5a2c3 100644 --- a/src/sdk/account/toNexusAccount.test.ts +++ b/src/sdk/account/toNexusAccount.test.ts @@ -29,7 +29,7 @@ import { afterAll, beforeAll, describe, expect, test } from "vitest" import { MockSignatureValidatorAbi } from "../../test/__contracts/abi/MockSignatureValidatorAbi" import { TokenWithPermitAbi } from "../../test/__contracts/abi/TokenWithPermitAbi" import { testAddresses } from "../../test/callDatas" -import { toNetwork } from "../../test/testSetup" +import { toNetwork, toNetworks } from "../../test/testSetup" import { fundAndDeployClients, getTestAccount, diff --git a/src/sdk/account/utils/Types.ts b/src/sdk/account/utils/Types.ts index 6c723a534..da108fa73 100644 --- a/src/sdk/account/utils/Types.ts +++ b/src/sdk/account/utils/Types.ts @@ -63,7 +63,6 @@ export type UserOpReceipt = { logs: Log[] } -export type Service = "Bundler" | "Paymaster" export type BigNumberish = Hex | number | bigint export type BytesLike = Uint8Array | Hex | string diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 839c0fc38..8ec4ad5d7 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -42,7 +42,7 @@ import type { AccountMetadata, EIP712DomainReturn } from "./Types" * @param value - The value to check * @returns True if the value is null or undefined */ -export const isNullOrUndefined = (value: any): value is undefined => { +export const isNullOrUndefined = (value: AnyData): value is undefined => { return value === null || value === undefined } diff --git a/src/sdk/account/utils/getAAError.ts b/src/sdk/account/utils/getAAError.ts deleted file mode 100644 index 8be4806c0..000000000 --- a/src/sdk/account/utils/getAAError.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { BaseError } from "viem" -import type { Service } from ".." -export type KnownError = { - name: string - regex: string - description: string - causes: string[] - solutions: string[] - docsUrl?: string -} - -export const ERRORS_URL = - "https://raw.githubusercontent.com/bcnmy/aa-errors/main/docs/errors.json" -export const DOCS_URL = "https://docs.biconomy.io/troubleshooting/commonerrors" -const UNKOWN_ERROR_CODE = "520" - -const knownErrors: KnownError[] = [] - -const matchError = (message: string): null | KnownError => - knownErrors.find( - (knownError: KnownError) => - message.toLowerCase().indexOf(knownError.regex.toLowerCase()) > -1 - ) ?? null - -const buildErrorStrings = ( - error: KnownError, - status: string, - service?: Service -): string[] => - [ - `${status}: ${error.description}\n`, - error.causes?.length - ? ["Potential cause(s): \n", ...error.causes, ""].join("\n") - : "", - error.solutions?.length - ? ["Potential solution(s): \n", ...error.solutions].join("\n") - : "", - service ? `\nSent via: ${service}` : "" - ].filter(Boolean) - -type AccountAbstractionErrorParams = { - docsSlug?: string - metaMessages?: string[] - details?: string -} - -class AccountAbstractionError extends BaseError { - override name = "AccountAbstractionError" - override version = "@biconomy/sdk" - - constructor(title: string, params: AccountAbstractionErrorParams = {}) { - super(title, params) - } -} - -export const getAAError = async ( - message: string, - httpStatus?: number, - service?: Service -) => { - if (!knownErrors.length) { - const errors = (await (await fetch(ERRORS_URL)).json()) as KnownError[] - knownErrors.push(...errors) - } - - const details: string = - `${service} - ${typeof message}` === "string" - ? message - : JSON.stringify(message) - const matchedError = matchError(details) - const status = - matchedError?.regex ?? (httpStatus ?? UNKOWN_ERROR_CODE).toString() - - const metaMessages = matchedError - ? buildErrorStrings(matchedError, status, service) - : [] - const title = matchedError ? matchedError.name : "Unknown Error" - const docsSlug = matchedError?.docsUrl ?? DOCS_URL - - return new AccountAbstractionError(title, { - docsSlug, - metaMessages, - details - }) -} diff --git a/src/sdk/account/utils/toSigner.test.ts b/src/sdk/account/utils/toSigner.test.ts index 2f288e3f8..34fd96c99 100644 --- a/src/sdk/account/utils/toSigner.test.ts +++ b/src/sdk/account/utils/toSigner.test.ts @@ -2,7 +2,7 @@ import { JsonRpcProvider, JsonRpcSigner, ethers } from "ethers" import { http, type Address, type Hex, createWalletClient } from "viem" import { privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, it } from "vitest" -import { toNetwork } from "../../../test/testSetup" +import { toNetwork, toNetworks } from "../../../test/testSetup" import { type NetworkConfig, killNetwork, pKey } from "../../../test/testUtils" import { type EthersWallet, addressEquals } from "./Utils" import { toSigner } from "./toSigner" diff --git a/src/sdk/account/utils/utils.test.ts b/src/sdk/account/utils/utils.test.ts index 986ba514f..518b4d898 100644 --- a/src/sdk/account/utils/utils.test.ts +++ b/src/sdk/account/utils/utils.test.ts @@ -1,12 +1,24 @@ import { ParamType, ethers } from "ethers" import { type AbiParameter, encodeAbiParameters } from "viem" import { generatePrivateKey } from "viem/accounts" -import { describe, expect, test } from "vitest" +import { beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../../test/testSetup" +import type { NetworkConfig } from "../../../test/testUtils" import type { EthersWallet } from "./Utils" import { toSigner } from "./toSigner" describe("utils", async () => { + let networks: NetworkConfig[] + + beforeAll(async () => { + networks = await toNetworks([ + "TESTNET_FROM_ENV_VARS", + "TESTNET_FROM_ENV_VARS" + ]) + }) + const privKey = generatePrivateKey() + test.concurrent( "should have consistent behaviour between ethers.AbiCoder.defaultAbiCoder() and viem.encodeAbiParameters()", async () => { diff --git a/src/sdk/clients/createBicoBundlerClient.test.ts b/src/sdk/clients/createBicoBundlerClient.test.ts index bab8bc0e5..0e8fb9b2c 100644 --- a/src/sdk/clients/createBicoBundlerClient.test.ts +++ b/src/sdk/clients/createBicoBundlerClient.test.ts @@ -45,6 +45,7 @@ describe("bico.bundler", async () => { nexusAccountAddress = await nexusAccount.getCounterFactualAddress() await topUp(testClient, nexusAccountAddress) }) + afterAll(async () => { await killNetwork([network?.rpcPort, network?.bundlerPort]) }) diff --git a/src/sdk/clients/createBicoPaymasterClient.test.ts b/src/sdk/clients/createBicoPaymasterClient.test.ts index 4f28c85c8..ad6c41825 100644 --- a/src/sdk/clients/createBicoPaymasterClient.test.ts +++ b/src/sdk/clients/createBicoPaymasterClient.test.ts @@ -9,7 +9,7 @@ import { createWalletClient } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { paymasterTruthy, toNetwork } from "../../test/testSetup" +import { paymasterTruthy, toNetworks } from "../../test/testSetup" import { getTestParamsForTestnet, killNetwork } from "../../test/testUtils" import type { NetworkConfig, TestnetParams } from "../../test/testUtils" import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" @@ -25,7 +25,7 @@ import { type NexusClient, createNexusClient } from "./createNexusClient" describe.runIf(paymasterTruthy())("bico.paymaster", async () => { let network: NetworkConfig - // Required for "PUBLIC_TESTNET" networks + // Required for "TESTNET_FROM_ENV_VARS" networks let testParams: TestnetParams let chain: Chain @@ -44,7 +44,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { let nexusClient: NexusClient beforeAll(async () => { - network = await toNetwork("PUBLIC_TESTNET") + ;[network] = await toNetworks("TESTNET_FROM_ENV_VARS") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/sdk/clients/createMeeClient.test.ts b/src/sdk/clients/createMeeClient.test.ts new file mode 100644 index 000000000..6e394ce95 --- /dev/null +++ b/src/sdk/clients/createMeeClient.test.ts @@ -0,0 +1,93 @@ +import { + http, + type Account, + type Address, + type Chain, + type PrivateKeyAccount, + type PublicClient, + createPublicClient +} from "viem" +import { beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../test/testSetup" +import type { NetworkConfig } from "../../test/testUtils" +import { type NexusAccount, addressEquals } from "../account" +import { toNexusAccount } from "../account/toNexusAccount" +import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS } from "../constants" +import { MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../constants" +import { createMeeClient } from "./createMeeClient" +import type { MeeClient } from "./createMeeClient" + +describe("mee.client", async () => { + let networkOne: NetworkConfig + let networkTwo: NetworkConfig + + let chainOne: Chain + let chainTwo: Chain + + let eoaAccount: Account + let recipientAddress: Address + let nexusAccountOne: NexusAccount + let nexusAccountTwo: NexusAccount + let nexusAccountAddressOne: Address + let nexusAccountAddressTwo: Address + let publicClientOne: PublicClient + let publicClientTwo: PublicClient + + let meeClient: MeeClient + + beforeAll(async () => { + ;[networkOne, networkTwo] = await toNetworks([ + "TESTNET_FROM_ENV_VARS", + "TESTNET_FROM_ALT_ENV_VARS" + ]) + + chainOne = networkOne.chain + chainTwo = networkTwo.chain + + eoaAccount = networkOne.account as PrivateKeyAccount + + recipientAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik.eth + + publicClientOne = createPublicClient({ + chain: chainOne, + transport: http() + }) + + publicClientTwo = createPublicClient({ + chain: chainTwo, + transport: http() + }) + + nexusAccountOne = await toNexusAccount({ + transport: http(), + chain: chainOne, + signer: eoaAccount, + k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS + }) + nexusAccountTwo = await toNexusAccount({ + transport: http(), + chain: chainTwo, + signer: eoaAccount, + k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS + }) + + meeClient = createMeeClient({ + accounts: [nexusAccountOne, nexusAccountTwo] + }) + }) + + test("should get chainIds from publicClients", async () => { + const chainIds = await Promise.all( + [publicClientOne, publicClientTwo].map((client) => client.getChainId()) + ) + expect(chainIds).to.equal([chainOne.id, chainTwo.id]) + }) + + test("should have matching addresses on different chains", async () => { + nexusAccountAddressOne = await nexusAccountOne.getAddress() + nexusAccountAddressTwo = await nexusAccountTwo.getAddress() + expect(addressEquals(nexusAccountAddressOne, nexusAccountAddressTwo)) + }) +}) diff --git a/src/sdk/clients/createMeeClient.ts b/src/sdk/clients/createMeeClient.ts new file mode 100644 index 000000000..b474d079a --- /dev/null +++ b/src/sdk/clients/createMeeClient.ts @@ -0,0 +1,103 @@ +import { + http, + type Client, + type ClientConfig, + type CreateClientErrorType, + type Prettify, + type RpcSchema, + type Transport, + createClient +} from "viem" +import type { AnyData, ModularSmartAccount } from "../modules" +import { HttpMethod, httpRequest } from "./decorators/mee/Helpers" +import { type MeeActions, meeActions } from "./decorators/mee/decorators" +import type { MeeRpcSchema } from "./decorators/mee/decorators/meeAction" + +export const DEFAULT_MEE_NODE = "https://biconomy.io/mee" + +export type ErrorType = Error & { name: name } + +export type MeeClientConfig< + transport extends Transport | undefined = Transport | undefined, + account extends ModularSmartAccount = ModularSmartAccount, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Pick< + ClientConfig, + "account" | "cacheTime" | "key" | "name" | "pollingInterval" | "rpcSchema" + > +> & { + /** Client that points to an Execution RPC URL. */ + client?: client | Client + transport?: transport + /** Accounts to be used for the mee client */ + accounts: account[] +} + +export type MeeClient< + transport extends Transport = Transport, + account extends ModularSmartAccount = ModularSmartAccount, + client extends Client | undefined = Client | undefined, + rpcSchema extends RpcSchema | undefined = undefined +> = Prettify< + Client< + transport, + never, + account, + rpcSchema extends RpcSchema + ? [...MeeRpcSchema, ...rpcSchema] + : MeeRpcSchema, + MeeActions + > +> & { + client: client + accounts: account[] +} + +export type CreateMeeClientErrorType = CreateClientErrorType | ErrorType + +export function createMeeClient< + account extends ModularSmartAccount = ModularSmartAccount, + transport extends Transport | undefined = Transport | undefined, + client extends Client | undefined = undefined, + rpcSchema extends RpcSchema | undefined = undefined +>( + parameters: MeeClientConfig +): MeeClient + +export function createMeeClient(parameters: MeeClientConfig): MeeClient { + const { + accounts, + client: client_, + key = "mee", + name = "Mee Client", + transport = http(DEFAULT_MEE_NODE) + } = parameters + + const client = Object.assign( + createClient({ + ...parameters, + key, + name, + transport, + type: "meeClient" + }), + { + accounts, + client: client_, + // Temporay while we wait for EIP1193 to be supported by meeNode + // Can remove this when MEE node is EIP1193 compliant + request: (body: Record) => { + console.log("request body", body) + return httpRequest({ + url: DEFAULT_MEE_NODE, + method: HttpMethod.Post, + body + }) + } + } + ) + + return client.extend(meeActions) as MeeClient +} diff --git a/src/sdk/clients/createNexusClient.test.ts b/src/sdk/clients/createNexusClient.test.ts index 08a3b7cf3..6e6aeb84e 100644 --- a/src/sdk/clients/createNexusClient.test.ts +++ b/src/sdk/clients/createNexusClient.test.ts @@ -14,11 +14,11 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { CounterAbi } from "../../test/__contracts/abi" import mockAddresses from "../../test/__contracts/mockAddresses" -import { toNetwork } from "../../test/testSetup" import { getBalance, getTestAccount, killNetwork, + toNetwork, toTestClient, topUp } from "../../test/testUtils" @@ -32,7 +32,6 @@ import { } from "../account/utils/Utils" import { getChain } from "../account/utils/getChain" import { k1ValidatorAddress } from "../constants" -import type { AnyData } from "../modules" import { type NexusClient, createNexusClient } from "./createNexusClient" describe("nexus.client", async () => { diff --git a/src/sdk/clients/createNexusSessionClient.test.ts b/src/sdk/clients/createNexusSessionClient.test.ts index 3cacf61d7..0b1fd30b7 100644 --- a/src/sdk/clients/createNexusSessionClient.test.ts +++ b/src/sdk/clients/createNexusSessionClient.test.ts @@ -22,7 +22,6 @@ import { import { toSmartSessionsValidator } from "../modules/smartSessionsValidator/toSmartSessionsValidator" import type { Module } from "../modules/utils/Types" import { type NexusClient, createNexusClient } from "./createNexusClient" -import { createNexusSessionClient } from "./createNexusSessionClient" describe("nexus.session.client", async () => { let network: NetworkConfig @@ -41,7 +40,7 @@ describe("nexus.session.client", async () => { let sessionsModule: Module beforeAll(async () => { - network = await toNetwork("BASE_SEPOLIA_FORKED") + ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl @@ -158,7 +157,7 @@ describe("nexus.session.client", async () => { functionName: "getNumber" }) - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, @@ -217,7 +216,7 @@ describe("nexus.session.client", async () => { } }) - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/clients/createNexusSessionClient.ts b/src/sdk/clients/createNexusSessionClient.ts deleted file mode 100644 index 01042e331..000000000 --- a/src/sdk/clients/createNexusSessionClient.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Address } from "viem" -import { type NexusClientConfig, createNexusClient } from "./createNexusClient" - -export type NexusSessionClientConfig = NexusClientConfig & { - accountAddress: Address -} -export const createNexusSessionClient = createNexusClient diff --git a/src/sdk/clients/decorators/mee/Helpers.ts b/src/sdk/clients/decorators/mee/Helpers.ts new file mode 100644 index 000000000..5a04c1d79 --- /dev/null +++ b/src/sdk/clients/decorators/mee/Helpers.ts @@ -0,0 +1,72 @@ +import { Logger } from "../../../account/utils/Logger" + +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 httpRequest({ + url, + method, + body +}: HttpRequest): Promise { + const stringifiedBody = JSON.stringify(body) + Logger.log("HTTP 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("HTTP Response", jsonResponse) + } catch (error) { + if (!response.ok) { + throw new Error([response.statusText, response.status].join(", ")) + } + } + + if (response.ok) { + return jsonResponse as T + } + if (jsonResponse.error) { + throw new Error(jsonResponse.error.message) + } + if (jsonResponse.message) { + throw new Error([response.status, jsonResponse.message].join(", ")) + } + if (jsonResponse.msg) { + throw new Error([response.status, jsonResponse.msg].join(", ")) + } + if (jsonResponse.data) { + throw new Error([response.status, jsonResponse.data].join(", ")) + } + if (jsonResponse.detail) { + throw new Error([response.status, jsonResponse.detail].join(", ")) + } + if (jsonResponse.message) { + throw new Error([response.status, jsonResponse.message].join(", ")) + } + if (jsonResponse.nonFieldErrors) { + throw new Error([response.status, jsonResponse.nonFieldErrors].join(", ")) + } + if (jsonResponse.delegate) { + throw new Error([response.status, jsonResponse.delegate].join(", ")) + } + throw new Error([response.status, jsonResponse.statusText].join(", ")) +} diff --git a/src/sdk/clients/decorators/mee/decorators/index.ts b/src/sdk/clients/decorators/mee/decorators/index.ts new file mode 100644 index 000000000..520a84409 --- /dev/null +++ b/src/sdk/clients/decorators/mee/decorators/index.ts @@ -0,0 +1,23 @@ +import type { Client, Transport } from "viem" +import type { + AnyData, + ModularSmartAccount +} from "../../../../modules/utils/Types" +import type { MeeClient } from "../../../createMeeClient" +import { type MeeActionParameters, meeAction } from "./meeAction" + +export type MeeActions = { + meeAction: ( + parameters: MeeActionParameters + ) => Promise +} + +export function meeActions< + transport extends Transport = Transport, + account extends ModularSmartAccount = ModularSmartAccount +>(client: Client): MeeActions { + return { + meeAction: (parameters) => + meeAction(client as MeeClient, parameters) + } +} diff --git a/src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts b/src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts new file mode 100644 index 000000000..0d02530d8 --- /dev/null +++ b/src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts @@ -0,0 +1,87 @@ +import { + http, + type Account, + type Address, + type Chain, + type PrivateKeyAccount, + type PublicClient, + createPublicClient +} from "viem" +import { beforeAll, describe, expect, test } from "vitest" +import { toNetwork } from "../../../../../test/testSetup" +import type { NetworkConfig } from "../../../../../test/testUtils" +import { + type NexusAccount, + toNexusAccount +} from "../../../../account/toNexusAccount" +import { + MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS +} from "../../../../constants" +import { type MeeClient, createMeeClient } from "../../../createMeeClient" + +describe("mee.decorators", async () => { + let networkOne: NetworkConfig + let networkTwo: NetworkConfig + + let chainOne: Chain + let chainTwo: Chain + + let eoaAccount: Account + let recipientAddress: Address + let nexusAccountOne: NexusAccount + let nexusAccountTwo: NexusAccount + let publicClientOne: PublicClient + let publicClientTwo: PublicClient + + let meeClient: MeeClient + + beforeAll(async () => { + ;[networkOne, networkTwo] = await toNetworks([ + "TESTNET_FROM_ENV_VARS", + "TESTNET_FROM_ALT_ENV_VARS" + ]) + + chainOne = networkOne.chain + chainTwo = networkTwo.chain + eoaAccount = networkOne.account as PrivateKeyAccount + + recipientAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik.eth + + publicClientOne = createPublicClient({ + chain: chainOne, + transport: http() + }) + + publicClientTwo = createPublicClient({ + chain: chainTwo, + transport: http() + }) + + nexusAccountOne = await toNexusAccount({ + transport: http(), + chain: chainOne, + signer: eoaAccount, + k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS + }) + + nexusAccountTwo = await toNexusAccount({ + transport: http(), + chain: chainTwo, + signer: eoaAccount, + k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS + }) + + meeClient = createMeeClient({ + accounts: [nexusAccountOne, nexusAccountTwo] + }) + }) + + test("should call meeAction ", async () => { + await expect(meeClient.meeAction({ testParam: 1 })).rejects.toThrow( + "Not Found, 404" + ) + }) +}) diff --git a/src/sdk/clients/decorators/mee/decorators/meeAction.ts b/src/sdk/clients/decorators/mee/decorators/meeAction.ts new file mode 100644 index 000000000..992e6a14e --- /dev/null +++ b/src/sdk/clients/decorators/mee/decorators/meeAction.ts @@ -0,0 +1,51 @@ +import type { Transport } from "viem" +import { parseAccount } from "viem/accounts" +import { AccountNotFoundError } from "../../../../account/utils/AccountNotFound" +import type { + AnyData, + ModularSmartAccount +} from "../../../../modules/utils/Types" +import type { MeeClient } from "../../../createMeeClient" + +export type MeeActionResponse = AnyData + +export type MeeRpcSchema = [ + { + Method: "mee_action" + Parameters: [] + ReturnType: MeeActionResponse + } +] + +export type MeeActionParameters< + TModularSmartAccount extends ModularSmartAccount | undefined +> = { + accounts?: TModularSmartAccount[] + testParam: number +} + +export async function meeAction< + TModularSmartAccount extends ModularSmartAccount | undefined +>( + client: MeeClient, + parameters: MeeActionParameters +): Promise { + const { accounts: accounts_ = client.accounts, testParam } = parameters + + if (!accounts_?.length) { + throw new AccountNotFoundError({ + docsPath: "/nexus-client/methods#sendtransaction" + }) + } + + const accounts = accounts_.map((account) => parseAccount(account)) + console.log({ accounts }) + + // Do something in here... + const paramsFromParameters = [testParam] as AnyData + + return await client.request({ + method: "mee_action", + params: paramsFromParameters + }) +} diff --git a/src/sdk/clients/decorators/mee/index.ts b/src/sdk/clients/decorators/mee/index.ts new file mode 100644 index 000000000..f06563064 --- /dev/null +++ b/src/sdk/clients/decorators/mee/index.ts @@ -0,0 +1,2 @@ +export * from "./decorators" +export * from "./Helpers" diff --git a/src/sdk/clients/index.ts b/src/sdk/clients/index.ts index 4e9aaddee..94d28a6d4 100644 --- a/src/sdk/clients/index.ts +++ b/src/sdk/clients/index.ts @@ -1,5 +1,5 @@ export * from "./createBicoBundlerClient" export * from "./createBicoPaymasterClient" export * from "./createNexusClient" -export * from "./createNexusSessionClient" +export * from "./createNexusClient" export * from "./decorators" diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts index d76fbe91d..c036084c4 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts @@ -1,5 +1,4 @@ import { http, type Chain, type LocalAccount } from "viem" -import type { BundlerClient } from "viem/account-abstraction" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { toNetwork } from "../../../test/testSetup" import { @@ -11,7 +10,6 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { createNexusClient } from "../../clients/createNexusClient" import { danActions } from "../../clients/decorators/dan/decorators" -import { keyGen } from "../../clients/decorators/dan/decorators/keyGen" import { ownableActions } from "./decorators" import { toOwnableValidator } from "./toOwnableValidator" diff --git a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts index d9445b028..809c5ae35 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts @@ -14,7 +14,6 @@ import { type NexusClient, createNexusClient } from "../../../clients/createNexusClient" -import { createNexusSessionClient } from "../../../clients/createNexusSessionClient" import { toSmartSessionsValidator } from "../toSmartSessionsValidator" import { smartSessionCreateActions, smartSessionUseActions } from "./" @@ -88,7 +87,7 @@ describe("modules.smartSessions.decorators", async () => { } }) - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts index a2288bf44..5a8c9ea33 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts @@ -21,7 +21,6 @@ import { type NexusClient, createNexusClient } from "../../clients/createNexusClient" -import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import { danActions } from "../../clients/decorators/dan" import type { Module } from "../utils/Types" import { parse, stringify } from "./Helpers" @@ -48,7 +47,7 @@ describe("modules.smartSessions.dan.dx", async () => { beforeAll(async () => { // Setup test network and accounts - network = await toNetwork("BASE_SEPOLIA_FORKED") + ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl eoaAccount = getTestAccount(0) @@ -147,7 +146,7 @@ describe("modules.smartSessions.dan.dx", async () => { const { moduleData, granter } = parse(zippedSessionDatum) // Initialize the smart session client's Nexus client - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: granter, signer: dappAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index c84df2611..581468620 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -23,7 +23,6 @@ import { type NexusClient, createNexusClient } from "../../clients/createNexusClient" -import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import type { Module } from "../utils/Types" import { parse, stringify } from "./Helpers" import type { CreateSessionDataParams, SessionData } from "./Types" @@ -46,7 +45,7 @@ describe("modules.smartSessions.dx", async () => { let sessionsModule: Module beforeAll(async () => { - network = await toNetwork("BASE_SEPOLIA_FORKED") + ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl @@ -163,7 +162,7 @@ describe("modules.smartSessions.dx", async () => { // Create a new Nexus client for the session // This client will be used to interact with the smart contract account using the session key - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: usersSessionData.granter, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index 98bab7cad..b80ff3d92 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -6,12 +6,10 @@ import { type LocalAccount, encodeFunctionData, pad, - toBytes, toHex } from "viem" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { MockRegistryAbi } from "../../../test/__contracts/abi" import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" import { testAddresses } from "../../../test/callDatas" import { toNetwork } from "../../../test/testSetup" @@ -26,8 +24,6 @@ import { type NexusClient, createNexusClient } from "../../clients/createNexusClient" -import { createNexusSessionClient } from "../../clients/createNexusSessionClient" -import { SIMPLE_SESSION_VALIDATOR_ADDRESS } from "../../constants" import { parseReferenceValue } from "../utils/Helpers" import type { Module } from "../utils/Types" import policies from "./Helpers" @@ -52,7 +48,7 @@ describe("modules.smartSessions", async () => { let sessionsModule: Module beforeAll(async () => { - network = await toNetwork("BASE_SEPOLIA_FORKED") + ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl @@ -247,7 +243,7 @@ describe("modules.smartSessions", async () => { functionName: "getNumber" }) - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index 7856a883d..b300c6e34 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts @@ -9,13 +9,10 @@ import { encodeFunctionData, getContract, slice, - toBytes, - toFunctionSelector, - toHex + toFunctionSelector } from "viem" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { MockRegistryAbi } from "../../../test/__contracts/abi" import { MockCalleeAbi } from "../../../test/__contracts/abi/MockCalleeAbi" import { testAddresses } from "../../../test/callDatas" import { toNetwork } from "../../../test/testSetup" @@ -30,7 +27,6 @@ import { type NexusClient, createNexusClient } from "../../clients/createNexusClient" -import { createNexusSessionClient } from "../../clients/createNexusSessionClient" import { SMART_SESSIONS_ADDRESS } from "../../constants" import type { Module } from "../utils/Types" import { isPermissionEnabled } from "./Helpers" @@ -56,7 +52,7 @@ describe("modules.smartSessions.uniPolicy", async () => { let sessionsModule: Module beforeAll(async () => { - network = await toNetwork("BASE_SEPOLIA_FORKED") + ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl @@ -274,7 +270,7 @@ describe("modules.smartSessions.uniPolicy", async () => { // timestamp: 9727001666n // }) - const smartSessionNexusClient = await createNexusSessionClient({ + const smartSessionNexusClient = await createNexusClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/modules/utils/Types.ts b/src/sdk/modules/utils/Types.ts index f040d356f..b8e457217 100644 --- a/src/sdk/modules/utils/Types.ts +++ b/src/sdk/modules/utils/Types.ts @@ -1,5 +1,6 @@ import type { Address, Assign, Chain, Hex, SignableMessage } from "viem" import type { SmartAccount } from "viem/account-abstraction" +import type { NexusSmartAccountImplementation } from "../../account/toNexusAccount" import type { Signer } from "./../../account/utils/toSigner" export type ModuleVersion = "1.0.0" // | 'V1_0_1' @@ -116,7 +117,8 @@ export type Modularity = { setModule: (module: Module) => void } -export type ModularSmartAccount = SmartAccount & Modularity +export type ModularSmartAccount = + SmartAccount & Modularity export type ModuleMeta = { address: Hex diff --git a/src/sdk/modules/utils/toModule.test.ts b/src/sdk/modules/utils/toModule.test.ts index 1095d6a84..b24cd87af 100644 --- a/src/sdk/modules/utils/toModule.test.ts +++ b/src/sdk/modules/utils/toModule.test.ts @@ -1,11 +1,4 @@ -import { - http, - type Account, - type Address, - type Chain, - type LocalAccount, - isHex -} from "viem" +import type { Chain, LocalAccount } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { toNetwork } from "../../../test/testSetup" import { diff --git a/src/test/README.md b/src/test/README.md index 0cf7d7178..d2698a565 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -9,71 +9,21 @@ - Tests are executed against locally deployed ephemeral Anvil chains (each with a different ID) with relevant contracts pre-deployed for each test. - Bundlers for testing are instantiated using [prool](https://github.com/wevm/prool), currently utilizing alto instances. We plan to switch to Biconomy's bundlers when they become available via `prool`. -### Deployment Configuration -A custom script `bun run fetch:deployment` is provided to search for the bytecode of deployed contracts from a customizable location (default: `../../nexus/deployments`). This folder is **auto-generated** in Nexus whenever a new Hardhat deployment is made, ensuring that the SDK remains up-to-date with the latest contract changes. - -The script performs the following: -- **ABIs**: Moved to `./src/__contracts/{name}Abi.ts` -- **Addresses**: Moved to `./src/addresses.ts` -- **Additional Fixtures**: Copied to `tests__/contracts` - -The script accepts a number of args from the command line: - - nexusDeploymentPath (default: `"../node_modules/nexus/deployments"`) - - chainName (default: `"anvil-55000"`) - - forSrc (default: `["K1ValidatorFactory", "Nexus", "K1Validator"]`); - -Example usage: -```bash -bun run fetch:deployment:raw --chainName=anvil-52878 -forSrc=K1Validator -forSrc=Nexus --nexusDeploymentPath=../../nexus/deployments -bun run lint --apply-unsafe -``` - -> **Note**: -> - Do not edit these files manually; they will be overridden if/when a new Nexus deployment occurs. -> - Avoid hardcoding important addresses (e.g., `const k1ValidatorAddress = "0x"`). Use `./src/addresses.ts` instead. - -## Network Scopes for Tests - -To prevent tests from conflicting with one another, tests can be scoped to different networks in different ways. - ### Global Scope -- Use by setting `const NETWORK_TYPE: TestFileNetworkType = "COMMON_LOCALHOST"` at the top of the test file. +- Use by setting `const NETWORK_TYPE: TestFileNetworkType = "COMMUNAL_ANVIL_NETWORK"` at the top of the test file. - Suitable when you're sure that tests in the file will **not** conflict with other tests using the common localhost network. ### Local Scope -- Use by setting `const NETWORK_TYPE: TestFileNetworkType = "FILE_LOCALHOST"` for test files that may conflict with others. +- Use by setting `const NETWORK_TYPE: TestFileNetworkType = "BESPOKE_ANVIL_NETWORK"` for test files that may conflict with others. - Networks scoped locally are isolated to the file in which they are used. - Tests within the same file using a local network may conflict with each other. If needed, split tests into separate files or use the Test Scope. +- `"BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA"` does a similar thing, but anvil assumes the current state of base sepolia instead. Overusing this can cause throttling issues from tenderly or the service that you are finding the forkUrl from. -### Test Scope -- A network is spun up *only* for the individual test in which it is used. Access this via the `localhostTest`/`testnetTest` helpers in the same file as `"COMMON_LOCALHOST"` or `"FILE_LOCALHOST"` network types. - -Example usage: -```ts -localhostTest("should be used in the following way", async({ config: { bundlerUrl, chain, fundedClients }}) => { - // chain, bundlerUrl spun up just in time for this test only... - expect(await fundedClients.smartAccount.getAccountAddress()).toBeTruthy(); -}); -``` - -> **Note:** -> Please avoid using multiple nested describe blocks in a single test file, as it is unnecessary and can lead to confusion regarding network scope. -> Using *many* test files is preferable, as describe blocks run in parallel. - -## Testing on Testnets or New Chains -- There is currently one area where SDK tests can be run against a remote testnet: the playground -- You can run the playground using the command: `bun run playground`. They playground is automatically ommitted from CICD. -- Additionally there are helpers for running tests on files on a public testnet: - - `const NETWORK_TYPE: TestFileNetworkType = "TESTNET"` will pick up relevant configuration from environment variables, and can be used at the top of a test file to have tests run against the specified testnet instead of the localhost - - If you want to run a single test on a public testnet *from inside a different describe block* you can use the: `testnetTest` helper: - -Example usage: -```ts -testnetTest("should be used in the following way", async({ config: { bundlerUrl, chain, account }}) => { - // chain, bundlerUrl etc taken from environment variables... - expect(account).toBeTruthy(); // from private key, please ensure it is funded if sending txs -}); -``` +### Testnet Scope +- Use by setting `const NETWORK_TYPE: TestFileNetworkType = "TESTNET_FROM_ENV_VARS"` for test files that rely on the network, private key and bundler url specified in your env vars. +- `"TESTNET_FROM_ALT_ENV_VARS"` is also available, which uses alternative env vars which you've specified. +- Networks scoped to a testnet are not isolated to the file in which they are used, they require tesnet tokens, can often fail for gas reasons, and they will add additional latency to tests. +- Avoid overusing this option > **Note:** > As testnetTest runs against a public testnet the account related to the privatekey (in your env var) must be funded, and the testnet is not 'ephemeral', meaning state is obviously persisted on the testnet after the test finishes. diff --git a/src/test/playground.test.ts b/src/test/playground.test.ts index 351722240..fd8bada97 100644 --- a/src/test/playground.test.ts +++ b/src/test/playground.test.ts @@ -15,7 +15,7 @@ import { type NexusClient, createNexusClient } from "../sdk/clients/createNexusClient" -import { toNetwork } from "./testSetup" +import { toNetworks } from "./testSetup" import { type NetworkConfig, type TestnetParams, @@ -24,7 +24,7 @@ import { describe.skipIf(!playgroundTrue())("playground", () => { let network: NetworkConfig - // Required for "PUBLIC_TESTNET" networks + // Required for "TESTNET_FROM_ENV_VARS" networks let testParams: TestnetParams // Nexus Config let chain: Chain @@ -39,7 +39,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { let nexusAccountAddress: Address beforeAll(async () => { - network = await toNetwork("PUBLIC_TESTNET") + ;[network] = await toNetworks("TESTNET_FROM_ENV_VARS") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/test/testSetup.ts b/src/test/testSetup.ts index 41f3eaef6..7bc74a8e0 100644 --- a/src/test/testSetup.ts +++ b/src/test/testSetup.ts @@ -35,26 +35,46 @@ export const testnetTest = test.extend<{ }>({ // biome-ignore lint/correctness/noEmptyPattern: Needed in vitest :/ config: async ({}, use) => { - const testNetwork = await toNetwork("PUBLIC_TESTNET") + const testNetwork = await toNetwork("TESTNET_FROM_ENV_VARS") await use(testNetwork) } }) export type TestFileNetworkType = - | "FILE_LOCALHOST" - | "COMMON_LOCALHOST" - | "PUBLIC_TESTNET" - | "BASE_SEPOLIA_FORKED" + | "BESPOKE_ANVIL_NETWORK" + | "BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA" + | "TESTNET_FROM_ENV_VARS" + | "TESTNET_FROM_ALT_ENV_VARS" + | "COMMUNAL_ANVIL_NETWORK" + +export const toNetworks = async ( + networkTypes_: TestFileNetworkType | TestFileNetworkType[] = [ + "BESPOKE_ANVIL_NETWORK" + ] +): Promise => { + const networkTypes = Array.isArray(networkTypes_) + ? networkTypes_ + : [networkTypes_] + + return await Promise.all(networkTypes.map((type) => toNetwork(type))) +} export const toNetwork = async ( - networkType: TestFileNetworkType = "FILE_LOCALHOST" + networkType: TestFileNetworkType = "BESPOKE_ANVIL_NETWORK" ): Promise => { - const forkBaseSepolia = networkType === "BASE_SEPOLIA_FORKED" - return await (networkType === "COMMON_LOCALHOST" + const forkBaseSepolia = + networkType === "BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA" + const communalAnvil = networkType === "COMMUNAL_ANVIL_NETWORK" + const testNet = [ + "TESTNET_FROM_ENV_VARS", + "TESTNET_FROM_ALT_ENV_VARS" + ].includes(networkType) + + return await (communalAnvil ? // @ts-ignore inject("globalNetwork") - : networkType === "PUBLIC_TESTNET" - ? initTestnetNetwork() + : testNet + ? initTestnetNetwork(networkType) : initLocalhostNetwork(forkBaseSepolia)) } diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 3add7b224..9fab4166c 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -1,6 +1,5 @@ import { config } from "dotenv" import getPort from "get-port" -// @ts-ignore import { type AnvilParameters, alto, anvil } from "prool/instances" import { http, @@ -39,6 +38,7 @@ import { } from "./callDatas" import * as hardhatExec from "./executables" +import type { TestFileNetworkType } from "./testSetup" config() @@ -62,6 +62,7 @@ export type NetworkConfig = Omit< > & { account?: PrivateKeyAccount paymasterUrl?: string + meeNodeUrl?: string } export const pKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" // This is a publicly available private key meant only for testing only @@ -93,18 +94,25 @@ export const killNetwork = (ids: number[]) => }) ) -export const initTestnetNetwork = async (): Promise => { +export const initTestnetNetwork = async ( + type: TestFileNetworkType = "TESTNET_FROM_ENV_VARS" +): Promise => { const privateKey = process.env.PRIVATE_KEY - const chainId = process.env.CHAIN_ID + const chainId_ = process.env.CHAIN_ID + const altChainId = process.env.ALT_CHAIN_ID const rpcUrl = process.env.RPC_URL //Optional, taken from chain (using chainId) if not provided const _bundlerUrl = process.env.BUNDLER_URL // Optional, taken from chain (using chainId) if not provided const paymasterUrl = process.env.PAYMASTER_URL // Optional + const meeNodeUrl = process.env.MEE_NODE_URL // Optional + + const chainId = type === "TESTNET_FROM_ALT_ENV_VARS" ? altChainId : chainId_ let chain: Chain if (!privateKey) throw new Error("Missing env var PRIVATE_KEY") if (!chainId) throw new Error("Missing env var CHAIN_ID") if (!paymasterUrl) console.log("Missing env var PAYMASTER_URL") + if (!meeNodeUrl) console.log("Missing env var MEE_NODE_URL") try { chain = getChain(+chainId) @@ -125,7 +133,8 @@ export const initTestnetNetwork = async (): Promise => { bundlerUrl, paymasterUrl, bundlerPort: 0, - account: holder + account: holder, + meeNodeUrl } } From abc7e95c71b4ae97267682974a0ec365c47ac2e4 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 09:09:11 +0000 Subject: [PATCH 02/10] chore: abstractions --- README.md | 4 +- src/sdk/account/toNexusAccount.test.ts | 8 +- src/sdk/account/utils/getChain.ts | 4 +- .../clients/createBicoPaymasterClient.test.ts | 7 +- src/sdk/clients/createBundlerClient.test.ts | 2 +- src/sdk/clients/createMeeClient.test.ts | 63 +++++---- src/sdk/clients/createMeeClient.ts | 122 +++++++++++++++--- .../clients/createNexusSessionClient.test.ts | 13 +- ...st.ts => createSmartAccountClient.test.ts} | 15 ++- ...sClient.ts => createSmartAccountClient.ts} | 70 ++++++---- .../dan/decorators/dan.decorators.test.ts | 7 +- .../erc7579/erc7579.decorators.test.ts | 7 +- src/sdk/clients/decorators/mee/Helpers.ts | 72 ----------- .../decorators/mee/decorators/index.ts | 23 ---- src/sdk/clients/decorators/mee/index.ts | 30 ++++- .../{decorators => }/mee.decorators.test.ts | 20 +-- ...eeAction.ts => prepareSuperTransaction.ts} | 31 ++--- .../smartAccount/account.decorators.test.ts | 7 +- src/sdk/clients/index.ts | 4 +- .../modules/k1Validator/toK1Validator.test.ts | 6 +- .../decorators/getThreshold.ts | 2 +- .../decorators/ownables.decorators.test.ts | 6 +- .../decorators/setThreshold.ts | 2 +- .../toOwnableValidator.dan.test.ts | 4 +- .../toOwnableValidator.dx.test.ts | 4 +- .../toOwnableValidator.executor.test.ts | 6 +- .../toOwnableValidator.test.ts | 6 +- .../smartSessions.decorators.test.ts | 8 +- .../toSmartSessionsValidator.dan.dx.test.ts | 8 +- .../toSmartSessionsValidator.dx.test.ts | 8 +- .../toSmartSessionsValidator.test.ts | 8 +- ...oSmartSessionsValidator.uni.policy.test.ts | 8 +- src/test/playground.test.ts | 10 +- src/test/testUtils.ts | 6 +- 34 files changed, 334 insertions(+), 267 deletions(-) rename src/sdk/clients/{createNexusClient.test.ts => createSmartAccountClient.test.ts} (96%) rename src/sdk/clients/{createNexusClient.ts => createSmartAccountClient.ts} (81%) delete mode 100644 src/sdk/clients/decorators/mee/Helpers.ts delete mode 100644 src/sdk/clients/decorators/mee/decorators/index.ts rename src/sdk/clients/decorators/mee/{decorators => }/mee.decorators.test.ts (79%) rename src/sdk/clients/decorators/mee/{decorators/meeAction.ts => prepareSuperTransaction.ts} (52%) diff --git a/README.md b/README.md index 90f7a4536..2ecfc5a53 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,10 @@ bun i ``` ```typescript -import { createNexusClient } from "@biconomy/sdk"; +import { createSmartAccountClient } from "@biconomy/sdk"; import { http } from "viem"; -const nexusClient = await createNexusClient({ +const nexusClient = await createSmartAccountClient({ signer: account, chain, transport: http(), diff --git a/src/sdk/account/toNexusAccount.test.ts b/src/sdk/account/toNexusAccount.test.ts index efaa5a2c3..d1ed7dcdd 100644 --- a/src/sdk/account/toNexusAccount.test.ts +++ b/src/sdk/account/toNexusAccount.test.ts @@ -39,8 +39,8 @@ import { import type { MasterClient, NetworkConfig } from "../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../clients/createNexusClient" + createSmartAccountClient +} from "../clients/createSmartAccountClient" import { k1ValidatorAddress } from "../constants" import type { NexusAccount } from "./toNexusAccount" import { @@ -84,7 +84,7 @@ describe("nexus.account", async () => { transport: http() }) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -100,7 +100,7 @@ describe("nexus.account", async () => { }) test("should override account address", async () => { - const newNexusClient = await createNexusClient({ + const newNexusClient = await createSmartAccountClient({ chain, transport: http(), bundlerTransport: http(bundlerUrl), diff --git a/src/sdk/account/utils/getChain.ts b/src/sdk/account/utils/getChain.ts index 2cee4fd94..729cc9da1 100644 --- a/src/sdk/account/utils/getChain.ts +++ b/src/sdk/account/utils/getChain.ts @@ -64,7 +64,7 @@ type StringOrStrings = string | string[] * * @example * - * import { getCustomChain, createNexusClient } from "@biconomy/sdk" + * import { getCustomChain, createSmartAccountClient } from "@biconomy/sdk" * * const customChain = getCustomChain( * "My Custom Chain", @@ -80,7 +80,7 @@ type StringOrStrings = string | string[] * transport: http() * }) * - * const smartAccountCustomChain = await createNexusClient({ + * const smartAccountCustomChain = await createSmartAccountClient({ * signer: walletClientWithCustomChain, * bundlerUrl, * customChain diff --git a/src/sdk/clients/createBicoPaymasterClient.test.ts b/src/sdk/clients/createBicoPaymasterClient.test.ts index ad6c41825..af28d2084 100644 --- a/src/sdk/clients/createBicoPaymasterClient.test.ts +++ b/src/sdk/clients/createBicoPaymasterClient.test.ts @@ -21,7 +21,10 @@ import { type BicoPaymasterClient, createBicoPaymasterClient } from "./createBicoPaymasterClient" -import { type NexusClient, createNexusClient } from "./createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "./createSmartAccountClient" describe.runIf(paymasterTruthy())("bico.paymaster", async () => { let network: NetworkConfig @@ -84,7 +87,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { }) nexusAccountAddress = await nexusAccount.getCounterFactualAddress() - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: account, chain, transport: http(), diff --git a/src/sdk/clients/createBundlerClient.test.ts b/src/sdk/clients/createBundlerClient.test.ts index e887c28de..63df82897 100644 --- a/src/sdk/clients/createBundlerClient.test.ts +++ b/src/sdk/clients/createBundlerClient.test.ts @@ -7,7 +7,7 @@ import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" import { safeMultiplier } from "../account/utils" import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS } from "../constants" import { MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../constants" -import type { NexusClient } from "./createNexusClient" +import type { NexusClient } from "./createSmartAccountClient" import { erc7579Actions } from "./decorators/erc7579" import { smartAccountActions } from "./decorators/smartAccount" diff --git a/src/sdk/clients/createMeeClient.test.ts b/src/sdk/clients/createMeeClient.test.ts index 6e394ce95..d493bd797 100644 --- a/src/sdk/clients/createMeeClient.test.ts +++ b/src/sdk/clients/createMeeClient.test.ts @@ -1,22 +1,20 @@ import { http, - type Account, type Address, type Chain, + type LocalAccount, type PrivateKeyAccount, type PublicClient, createPublicClient } from "viem" import { beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../test/testSetup" +import { toNetworks } from "../../test/testSetup" import type { NetworkConfig } from "../../test/testUtils" -import { type NexusAccount, addressEquals } from "../account" import { toNexusAccount } from "../account/toNexusAccount" import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS } from "../constants" import { MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../constants" import { createMeeClient } from "./createMeeClient" import type { MeeClient } from "./createMeeClient" - describe("mee.client", async () => { let networkOne: NetworkConfig let networkTwo: NetworkConfig @@ -24,12 +22,8 @@ describe("mee.client", async () => { let chainOne: Chain let chainTwo: Chain - let eoaAccount: Account + let eoaAccount: LocalAccount let recipientAddress: Address - let nexusAccountOne: NexusAccount - let nexusAccountTwo: NexusAccount - let nexusAccountAddressOne: Address - let nexusAccountAddressTwo: Address let publicClientOne: PublicClient let publicClientTwo: PublicClient @@ -58,14 +52,46 @@ describe("mee.client", async () => { transport: http() }) - nexusAccountOne = await toNexusAccount({ + meeClient = await createMeeClient({ + accountParams: { + signer: eoaAccount, + k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS, + chainList: [ + { + transport: http(), + chain: chainOne + }, + { + transport: http(), + chain: chainTwo + } + ] + } + }) + }) + + test("should get chainIds from publicClients", async () => { + const chainIds = await Promise.all( + [publicClientOne, publicClientTwo].map((client) => client.getChainId()) + ) + expect(chainIds).to.equal([chainOne.id, chainTwo.id]) + }) + + test("should have relevant meeClient properties", async () => { + expect(meeClient).toHaveProperty("accounts") + expect(typeof meeClient.prepareSuperTransaction).toBe("function") + }) + + test("should alternatively create a mee client from two distinct nexus accounts", async () => { + const nexusAccountOne = await toNexusAccount({ transport: http(), chain: chainOne, signer: eoaAccount, k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS }) - nexusAccountTwo = await toNexusAccount({ + const nexusAccountTwo = await toNexusAccount({ transport: http(), chain: chainTwo, signer: eoaAccount, @@ -73,21 +99,10 @@ describe("mee.client", async () => { factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS }) - meeClient = createMeeClient({ + const meeClient = await createMeeClient({ accounts: [nexusAccountOne, nexusAccountTwo] }) - }) - - test("should get chainIds from publicClients", async () => { - const chainIds = await Promise.all( - [publicClientOne, publicClientTwo].map((client) => client.getChainId()) - ) - expect(chainIds).to.equal([chainOne.id, chainTwo.id]) - }) - test("should have matching addresses on different chains", async () => { - nexusAccountAddressOne = await nexusAccountOne.getAddress() - nexusAccountAddressTwo = await nexusAccountTwo.getAddress() - expect(addressEquals(nexusAccountAddressOne, nexusAccountAddressTwo)) + expect(meeClient).toHaveProperty("accounts") }) }) diff --git a/src/sdk/clients/createMeeClient.ts b/src/sdk/clients/createMeeClient.ts index b474d079a..f274db83f 100644 --- a/src/sdk/clients/createMeeClient.ts +++ b/src/sdk/clients/createMeeClient.ts @@ -3,19 +3,35 @@ import { type Client, type ClientConfig, type CreateClientErrorType, + type OneOf, type Prettify, type RpcSchema, type Transport, createClient } from "viem" +import { + type ToNexusSmartAccountParameters, + toNexusAccount +} from "../account/toNexusAccount" +import { Logger } from "../account/utils/Logger" import type { AnyData, ModularSmartAccount } from "../modules" -import { HttpMethod, httpRequest } from "./decorators/mee/Helpers" -import { type MeeActions, meeActions } from "./decorators/mee/decorators" -import type { MeeRpcSchema } from "./decorators/mee/decorators/meeAction" +import { type MeeActions, meeActions } from "./decorators/mee" +import type { MeeRpcSchema } from "./decorators/mee/prepareSuperTransaction" export const DEFAULT_MEE_NODE = "https://biconomy.io/mee" export type ErrorType = Error & { name: name } +export enum HttpMethod { + Get = "get", + Post = "post", + Delete = "delete" +} +export interface HttpRequest { + url: string + method: HttpMethod + // biome-ignore lint/suspicious/noExplicitAny: + body?: Record +} export type MeeClientConfig< transport extends Transport | undefined = Transport | undefined, @@ -28,12 +44,17 @@ export type MeeClientConfig< "account" | "cacheTime" | "key" | "name" | "pollingInterval" | "rpcSchema" > > & { - /** Client that points to an Execution RPC URL. */ client?: client | Client transport?: transport - /** Accounts to be used for the mee client */ - accounts: account[] -} +} & OneOf< + | { + /** Accounts to be used for the mee client */ + accounts: account[] + } + | { + accountParams: ChainAbstractedAccountParams + } + > export type MeeClient< transport extends Transport = Transport, @@ -64,10 +85,13 @@ export function createMeeClient< rpcSchema extends RpcSchema | undefined = undefined >( parameters: MeeClientConfig -): MeeClient +): Promise> -export function createMeeClient(parameters: MeeClientConfig): MeeClient { +export async function createMeeClient( + parameters: MeeClientConfig +): Promise { const { + accountParams: accountParams_, accounts, client: client_, key = "mee", @@ -75,6 +99,12 @@ export function createMeeClient(parameters: MeeClientConfig): MeeClient { transport = http(DEFAULT_MEE_NODE) } = parameters + let accounts_ = accounts + if (!accounts_) { + if (!accountParams_) throw new Error("No account params") + accounts_ = await toAccounts(accountParams_) + } + const client = Object.assign( createClient({ ...parameters, @@ -84,20 +114,80 @@ export function createMeeClient(parameters: MeeClientConfig): MeeClient { type: "meeClient" }), { - accounts, + accounts: accounts_, client: client_, // Temporay while we wait for EIP1193 to be supported by meeNode // Can remove this when MEE node is EIP1193 compliant - request: (body: Record) => { - console.log("request body", body) - return httpRequest({ - url: DEFAULT_MEE_NODE, - method: HttpMethod.Post, - body + request: async ( + body: Record, + url = DEFAULT_MEE_NODE, + method = HttpMethod.Post + ) => { + const stringifiedBody = JSON.stringify(body) + Logger.log("HTTP 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("HTTP Response", jsonResponse) + } catch (error) { + if (!response.ok) { + throw new Error([response.statusText, response.status].join(", ")) + } + } + + if (response.ok) { + return jsonResponse as T + } + + const errorFields = [ + "error", + "message", + "msg", + "data", + "detail", + "nonFieldErrors" + ] + const firstError = errorFields.find((field) => + Boolean(jsonResponse[field]) + ) + if (firstError) { + throw new Error([response.status, firstError].join(", ")) + } + throw new Error([response.status, jsonResponse.statusText].join(", ")) } } ) return client.extend(meeActions) as MeeClient } + +export type ChainAbstractedAccountParams = Omit< + ToNexusSmartAccountParameters, + "transport" | "chain" +> & { + chainList: { + transport: ToNexusSmartAccountParameters["transport"] + chain: ToNexusSmartAccountParameters["chain"] + }[] +} +export const toAccounts = async ( + accountParams: ChainAbstractedAccountParams +): Promise => { + const { chainList, ...chainAgnosticParams } = accountParams + return await Promise.all( + chainList.map((params) => + toNexusAccount({ ...chainAgnosticParams, ...params }) + ) + ) +} diff --git a/src/sdk/clients/createNexusSessionClient.test.ts b/src/sdk/clients/createNexusSessionClient.test.ts index 0b1fd30b7..b1f0c83e6 100644 --- a/src/sdk/clients/createNexusSessionClient.test.ts +++ b/src/sdk/clients/createNexusSessionClient.test.ts @@ -4,7 +4,7 @@ import { encodeFunctionData } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { CounterAbi } from "../../test/__contracts/abi" import { testAddresses } from "../../test/callDatas" -import { toNetwork } from "../../test/testSetup" +import { toNetwork, toNetworks } from "../../test/testSetup" import { fundAndDeployClients, getTestAccount, @@ -21,7 +21,10 @@ import { } from "../modules/smartSessionsValidator/decorators" import { toSmartSessionsValidator } from "../modules/smartSessionsValidator/toSmartSessionsValidator" import type { Module } from "../modules/utils/Types" -import { type NexusClient, createNexusClient } from "./createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "./createSmartAccountClient" describe("nexus.session.client", async () => { let network: NetworkConfig @@ -50,7 +53,7 @@ describe("nexus.session.client", async () => { testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -157,7 +160,7 @@ describe("nexus.session.client", async () => { functionName: "getNumber" }) - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, @@ -216,7 +219,7 @@ describe("nexus.session.client", async () => { } }) - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/clients/createNexusClient.test.ts b/src/sdk/clients/createSmartAccountClient.test.ts similarity index 96% rename from src/sdk/clients/createNexusClient.test.ts rename to src/sdk/clients/createSmartAccountClient.test.ts index 6e6aeb84e..5a515092a 100644 --- a/src/sdk/clients/createNexusClient.test.ts +++ b/src/sdk/clients/createSmartAccountClient.test.ts @@ -14,11 +14,11 @@ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { CounterAbi } from "../../test/__contracts/abi" import mockAddresses from "../../test/__contracts/mockAddresses" +import { toNetwork } from "../../test/testSetup" import { getBalance, getTestAccount, killNetwork, - toNetwork, toTestClient, topUp } from "../../test/testUtils" @@ -32,7 +32,10 @@ import { } from "../account/utils/Utils" import { getChain } from "../account/utils/getChain" import { k1ValidatorAddress } from "../constants" -import { type NexusClient, createNexusClient } from "./createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "./createSmartAccountClient" describe("nexus.client", async () => { let network: NetworkConfig @@ -62,7 +65,7 @@ describe("nexus.client", async () => { privKey = generatePrivateKey() const account = privateKeyToAccount(privKey) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: account, chain, transport: http(), @@ -263,14 +266,14 @@ describe("nexus.client", async () => { const wallet = new Wallet(privKey) - const viemNexusClient = await createNexusClient({ + const viemNexusClient = await createSmartAccountClient({ signer: viemSigner, chain, transport: http(), bundlerTransport: http(bundlerUrl) }) - const ethersNexusClient = await createNexusClient({ + const ethersNexusClient = await createSmartAccountClient({ signer: wallet as EthersWallet, chain, transport: http(), @@ -285,7 +288,7 @@ describe("nexus.client", async () => { test("should send user operation using ethers Wallet", async () => { const ethersWallet = new ethers.Wallet(privKey) - const ethersNexusClient = await createNexusClient({ + const ethersNexusClient = await createSmartAccountClient({ signer: ethersWallet as EthersWallet, chain, transport: http(), diff --git a/src/sdk/clients/createNexusClient.ts b/src/sdk/clients/createSmartAccountClient.ts similarity index 81% rename from src/sdk/clients/createNexusClient.ts rename to src/sdk/clients/createSmartAccountClient.ts index 2756353d5..262234035 100644 --- a/src/sdk/clients/createNexusClient.ts +++ b/src/sdk/clients/createSmartAccountClient.ts @@ -21,14 +21,18 @@ import type { UserOperationRequest } from "viem/account-abstraction" -import { type NexusAccount, toNexusAccount } from "../account/toNexusAccount" +import { toNexusAccount } from "../account/toNexusAccount" import type { EthersWallet } from "../account/utils/Utils" import type { EthereumProvider } from "../account/utils/toSigner" import { k1ValidatorAddress as k1ValidatorAddress_, k1ValidatorFactoryAddress } from "../constants" -import type { AnyData, Module } from "../modules/utils/Types" +import type { + AnyData, + ModularSmartAccount, + Module +} from "../modules/utils/Types" import { createBicoBundlerClient } from "./createBicoBundlerClient" import { type Erc7579Actions, erc7579Actions } from "./decorators/erc7579" import { @@ -42,7 +46,9 @@ import { export type NexusClient< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, - account extends NexusAccount | undefined = NexusAccount | undefined, + account extends ModularSmartAccount | undefined = + | ModularSmartAccount + | undefined, client extends Client | undefined = Client | undefined, rpcSchema extends RpcSchema | undefined = undefined > = Prettify< @@ -60,13 +66,13 @@ export type NexusClient< BundlerActions > > & - BundlerActions & - Erc7579Actions & - SmartAccountActions & { + BundlerActions & + Erc7579Actions & + SmartAccountActions & { /** * The Nexus account associated with this client */ - account: NexusAccount + account: ModularSmartAccount /** * Optional client for additional functionality */ @@ -90,9 +96,9 @@ export type NexusClient< } /** - * Configuration for creating a Nexus Client + * Configuration for creating a Smart account Client */ -export type NexusClientConfig< +export type SmartAccountClientConfig< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, client extends Client | undefined = Client | undefined, @@ -100,7 +106,13 @@ export type NexusClientConfig< > = Prettify< Pick< ClientConfig, - "cacheTime" | "chain" | "key" | "name" | "pollingInterval" | "rpcSchema" + | "account" + | "cacheTime" + | "chain" + | "key" + | "name" + | "pollingInterval" + | "rpcSchema" > & { /** RPC URL. */ transport: transport @@ -158,25 +170,26 @@ export type NexusClientConfig< /** * Creates a Nexus Client for interacting with the Nexus smart account system. * - * @param parameters - {@link NexusClientConfig} + * @param parameters - {@link SmartAccountClientConfig} * @returns Nexus Client. {@link NexusClient} * * @example - * import { createNexusClient } from '@biconomy/sdk' + * import { createSmartAccountClient } from '@biconomy/sdk' * import { http } from 'viem' * import { mainnet } from 'viem/chains' * - * const nexusClient = await createNexusClient({ + * const nexusClient = await createSmartAccountClient({ * chain: mainnet, * transport: http('https://mainnet.infura.io/v3/YOUR-PROJECT-ID'), * bundlerTransport: http('https://api.biconomy.io'), * signer: '0x...', * }) */ -export async function createNexusClient( - parameters: NexusClientConfig +export async function createSmartAccountClient( + parameters: SmartAccountClientConfig ): Promise { const { + account: account_, client: client_, chain = parameters.chain ?? client_?.chain, signer, @@ -194,23 +207,25 @@ export async function createNexusClient( if (!chain) throw new Error("Missing chain") - const nexusAccount = await toNexusAccount({ - accountAddress, - transport, - chain, - signer, - index, - module, - factoryAddress, - k1ValidatorAddress - }) + const account = + account_ ?? + (await toNexusAccount({ + accountAddress, + transport, + chain, + signer, + index, + module, + factoryAddress, + k1ValidatorAddress + })) const bundler_ = createBicoBundlerClient({ ...bundlerConfig, chain, key, name, - account: nexusAccount, + account, transport: bundlerTransport }) .extend(erc7579Actions()) @@ -218,3 +233,6 @@ export async function createNexusClient( return bundler_ as unknown as NexusClient } + +// An alias for backwards compatibility +export const createNexusClient = createSmartAccountClient diff --git a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts index 4308477aa..7e412b3a8 100644 --- a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts +++ b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts @@ -11,7 +11,10 @@ import { killNetwork, toTestClient } from "../../../../../test/testUtils" -import { type NexusClient, createNexusClient } from "../../../createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "../../../createSmartAccountClient" import { DanWallet, hexToUint8Array, uuid } from "../Helpers" import { danActions } from "./" @@ -38,7 +41,7 @@ describe("dan.decorators", async () => { userThree = getTestAccount(2) testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts b/src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts index 24ee7731f..2086d9ec3 100644 --- a/src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts +++ b/src/sdk/clients/decorators/erc7579/erc7579.decorators.test.ts @@ -18,7 +18,10 @@ import { toTestClient } from "../../../../test/testUtils" import { k1ValidatorAddress } from "../../../constants" -import { type NexusClient, createNexusClient } from "../../createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "../../createSmartAccountClient" describe("erc7579.decorators", async () => { let network: NetworkConfig @@ -43,7 +46,7 @@ describe("erc7579.decorators", async () => { recipientAddress = recipient.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/clients/decorators/mee/Helpers.ts b/src/sdk/clients/decorators/mee/Helpers.ts deleted file mode 100644 index 5a04c1d79..000000000 --- a/src/sdk/clients/decorators/mee/Helpers.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Logger } from "../../../account/utils/Logger" - -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 httpRequest({ - url, - method, - body -}: HttpRequest): Promise { - const stringifiedBody = JSON.stringify(body) - Logger.log("HTTP 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("HTTP Response", jsonResponse) - } catch (error) { - if (!response.ok) { - throw new Error([response.statusText, response.status].join(", ")) - } - } - - if (response.ok) { - return jsonResponse as T - } - if (jsonResponse.error) { - throw new Error(jsonResponse.error.message) - } - if (jsonResponse.message) { - throw new Error([response.status, jsonResponse.message].join(", ")) - } - if (jsonResponse.msg) { - throw new Error([response.status, jsonResponse.msg].join(", ")) - } - if (jsonResponse.data) { - throw new Error([response.status, jsonResponse.data].join(", ")) - } - if (jsonResponse.detail) { - throw new Error([response.status, jsonResponse.detail].join(", ")) - } - if (jsonResponse.message) { - throw new Error([response.status, jsonResponse.message].join(", ")) - } - if (jsonResponse.nonFieldErrors) { - throw new Error([response.status, jsonResponse.nonFieldErrors].join(", ")) - } - if (jsonResponse.delegate) { - throw new Error([response.status, jsonResponse.delegate].join(", ")) - } - throw new Error([response.status, jsonResponse.statusText].join(", ")) -} diff --git a/src/sdk/clients/decorators/mee/decorators/index.ts b/src/sdk/clients/decorators/mee/decorators/index.ts deleted file mode 100644 index 520a84409..000000000 --- a/src/sdk/clients/decorators/mee/decorators/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Client, Transport } from "viem" -import type { - AnyData, - ModularSmartAccount -} from "../../../../modules/utils/Types" -import type { MeeClient } from "../../../createMeeClient" -import { type MeeActionParameters, meeAction } from "./meeAction" - -export type MeeActions = { - meeAction: ( - parameters: MeeActionParameters - ) => Promise -} - -export function meeActions< - transport extends Transport = Transport, - account extends ModularSmartAccount = ModularSmartAccount ->(client: Client): MeeActions { - return { - meeAction: (parameters) => - meeAction(client as MeeClient, parameters) - } -} diff --git a/src/sdk/clients/decorators/mee/index.ts b/src/sdk/clients/decorators/mee/index.ts index f06563064..0f43d52fa 100644 --- a/src/sdk/clients/decorators/mee/index.ts +++ b/src/sdk/clients/decorators/mee/index.ts @@ -1,2 +1,28 @@ -export * from "./decorators" -export * from "./Helpers" +import type { Client, Transport } from "viem" +import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" +import type { MeeClient } from "../../createMeeClient" +import { + type PrepareSuperTransactionParameters, + prepareSuperTransaction +} from "./prepareSuperTransaction" + +export type MeeActions = { + prepareSuperTransaction: ( + parameters: PrepareSuperTransactionParameters + ) => Promise + sendSuperTransaction: () => void +} + +export function meeActions< + transport extends Transport = Transport, + account extends ModularSmartAccount = ModularSmartAccount +>(client: Client): MeeActions { + return { + prepareSuperTransaction: (parameters) => + prepareSuperTransaction( + client as MeeClient, + parameters + ), + sendSuperTransaction: () => {} // etc etc + } +} diff --git a/src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts b/src/sdk/clients/decorators/mee/mee.decorators.test.ts similarity index 79% rename from src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts rename to src/sdk/clients/decorators/mee/mee.decorators.test.ts index 0d02530d8..ffb6cb539 100644 --- a/src/sdk/clients/decorators/mee/decorators/mee.decorators.test.ts +++ b/src/sdk/clients/decorators/mee/mee.decorators.test.ts @@ -8,17 +8,17 @@ import { createPublicClient } from "viem" import { beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../../../test/testSetup" -import type { NetworkConfig } from "../../../../../test/testUtils" +import { toNetworks } from "../../../../test/testSetup" +import type { NetworkConfig } from "../../../../test/testUtils" import { type NexusAccount, toNexusAccount -} from "../../../../account/toNexusAccount" +} from "../../../account/toNexusAccount" import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS -} from "../../../../constants" -import { type MeeClient, createMeeClient } from "../../../createMeeClient" +} from "../../../constants" +import { type MeeClient, createMeeClient } from "../../createMeeClient" describe("mee.decorators", async () => { let networkOne: NetworkConfig @@ -74,14 +74,14 @@ describe("mee.decorators", async () => { factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS }) - meeClient = createMeeClient({ + meeClient = await createMeeClient({ accounts: [nexusAccountOne, nexusAccountTwo] }) }) - test("should call meeAction ", async () => { - await expect(meeClient.meeAction({ testParam: 1 })).rejects.toThrow( - "Not Found, 404" - ) + test("should call prepareUserOperation ", async () => { + await expect( + meeClient.prepareSuperTransaction({ testParam: 1 }) + ).rejects.toThrow("Not Found, 404") }) }) diff --git a/src/sdk/clients/decorators/mee/decorators/meeAction.ts b/src/sdk/clients/decorators/mee/prepareSuperTransaction.ts similarity index 52% rename from src/sdk/clients/decorators/mee/decorators/meeAction.ts rename to src/sdk/clients/decorators/mee/prepareSuperTransaction.ts index 992e6a14e..a29d5406e 100644 --- a/src/sdk/clients/decorators/mee/decorators/meeAction.ts +++ b/src/sdk/clients/decorators/mee/prepareSuperTransaction.ts @@ -1,35 +1,31 @@ import type { Transport } from "viem" -import { parseAccount } from "viem/accounts" -import { AccountNotFoundError } from "../../../../account/utils/AccountNotFound" -import type { - AnyData, - ModularSmartAccount -} from "../../../../modules/utils/Types" -import type { MeeClient } from "../../../createMeeClient" +import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" +import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" +import type { MeeClient } from "../../createMeeClient" -export type MeeActionResponse = AnyData +export type PrepareSuperTransaction = AnyData export type MeeRpcSchema = [ { - Method: "mee_action" + Method: "mee_prepareSuperTransaction" Parameters: [] - ReturnType: MeeActionResponse + ReturnType: PrepareSuperTransaction } ] -export type MeeActionParameters< +export type PrepareSuperTransactionParameters< TModularSmartAccount extends ModularSmartAccount | undefined > = { accounts?: TModularSmartAccount[] testParam: number } -export async function meeAction< +export async function prepareSuperTransaction< TModularSmartAccount extends ModularSmartAccount | undefined >( client: MeeClient, - parameters: MeeActionParameters -): Promise { + parameters: PrepareSuperTransactionParameters +): Promise { const { accounts: accounts_ = client.accounts, testParam } = parameters if (!accounts_?.length) { @@ -38,14 +34,13 @@ export async function meeAction< }) } - const accounts = accounts_.map((account) => parseAccount(account)) - console.log({ accounts }) - // Do something in here... const paramsFromParameters = [testParam] as AnyData + console.log({ accounts_, paramsFromParameters }) + return await client.request({ - method: "mee_action", + method: "mee_prepareSuperTransaction", params: paramsFromParameters }) } diff --git a/src/sdk/clients/decorators/smartAccount/account.decorators.test.ts b/src/sdk/clients/decorators/smartAccount/account.decorators.test.ts index e217d3414..cad45cac2 100644 --- a/src/sdk/clients/decorators/smartAccount/account.decorators.test.ts +++ b/src/sdk/clients/decorators/smartAccount/account.decorators.test.ts @@ -12,7 +12,10 @@ import { killNetwork, toTestClient } from "../../../../test/testUtils" -import { type NexusClient, createNexusClient } from "../../createNexusClient" +import { + type NexusClient, + createSmartAccountClient +} from "../../createSmartAccountClient" describe("account.decorators", async () => { let network: NetworkConfig @@ -37,7 +40,7 @@ describe("account.decorators", async () => { recipientAddress = recipient.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/clients/index.ts b/src/sdk/clients/index.ts index 94d28a6d4..bfe41428f 100644 --- a/src/sdk/clients/index.ts +++ b/src/sdk/clients/index.ts @@ -1,5 +1,5 @@ export * from "./createBicoBundlerClient" export * from "./createBicoPaymasterClient" -export * from "./createNexusClient" -export * from "./createNexusClient" +export * from "./createSmartAccountClient" +export * from "./createSmartAccountClient" export * from "./decorators" diff --git a/src/sdk/modules/k1Validator/toK1Validator.test.ts b/src/sdk/modules/k1Validator/toK1Validator.test.ts index 8c62b9440..53425a95e 100644 --- a/src/sdk/modules/k1Validator/toK1Validator.test.ts +++ b/src/sdk/modules/k1Validator/toK1Validator.test.ts @@ -18,8 +18,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { k1ValidatorAddress } from "../../constants" import { toK1Validator } from "./toK1Validator" @@ -47,7 +47,7 @@ describe("modules.k1Validator", async () => { testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/ownableValidator/decorators/getThreshold.ts b/src/sdk/modules/ownableValidator/decorators/getThreshold.ts index dce90013e..a81d05f95 100644 --- a/src/sdk/modules/ownableValidator/decorators/getThreshold.ts +++ b/src/sdk/modules/ownableValidator/decorators/getThreshold.ts @@ -36,7 +36,7 @@ export type GetThresholdParameters< * * @example * ```typescript - * const nexusClient = createNexusClient({ ... }); + * const nexusClient = createSmartAccountClient({ ... }); * const threshold = await getThreshold(nexusClient); * console.log(`Current approval threshold: ${threshold}`); * ``` diff --git a/src/sdk/modules/ownableValidator/decorators/ownables.decorators.test.ts b/src/sdk/modules/ownableValidator/decorators/ownables.decorators.test.ts index b77c1dbaf..3abe77c20 100644 --- a/src/sdk/modules/ownableValidator/decorators/ownables.decorators.test.ts +++ b/src/sdk/modules/ownableValidator/decorators/ownables.decorators.test.ts @@ -19,8 +19,8 @@ import { } from "../../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../../clients/createNexusClient" + createSmartAccountClient +} from "../../../clients/createSmartAccountClient" import { toOwnableValidator } from "../toOwnableValidator" describe("modules.ownables.decorators", async () => { @@ -50,7 +50,7 @@ describe("modules.ownables.decorators", async () => { userThreeAddress = userThree.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/ownableValidator/decorators/setThreshold.ts b/src/sdk/modules/ownableValidator/decorators/setThreshold.ts index 5a5e20aaa..d2e46f9d2 100644 --- a/src/sdk/modules/ownableValidator/decorators/setThreshold.ts +++ b/src/sdk/modules/ownableValidator/decorators/setThreshold.ts @@ -40,7 +40,7 @@ export type SetThresholdParameters< * * @example * ```typescript - * const nexusClient = createNexusClient({ ... }); + * const nexusClient = createSmartAccountClient({ ... }); * const hash = await setThreshold(nexusClient, { * threshold: 2, * maxFeePerGas: 1000000000n diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts index c036084c4..d45c802fe 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts @@ -8,7 +8,7 @@ import { toTestClient } from "../../../test/testUtils" import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { createNexusClient } from "../../clients/createNexusClient" +import { createSmartAccountClient } from "../../clients/createSmartAccountClient" import { danActions } from "../../clients/decorators/dan/decorators" import { ownableActions } from "./decorators" import { toOwnableValidator } from "./toOwnableValidator" @@ -39,7 +39,7 @@ describe("modules.dan.dx", async () => { }) test("should demonstrate ownables module dx using a dan account", async () => { - const nexusClient = await createNexusClient({ + const nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dx.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dx.test.ts index 60782c902..6c3c3d6ac 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dx.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.dx.test.ts @@ -8,7 +8,7 @@ import { toTestClient } from "../../../test/testUtils" import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { createNexusClient } from "../../clients/createNexusClient" +import { createSmartAccountClient } from "../../clients/createSmartAccountClient" import { ownableActions } from "./decorators" import { toOwnableValidator } from "./toOwnableValidator" @@ -59,7 +59,7 @@ describe("modules.ownableValidator.dx", async () => { // Create a Nexus client for the main account (eoaAccount) // This client will be used to interact with the smart contract account - const nexusClient = await createNexusClient({ + const nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.executor.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.executor.test.ts index 48c2c3c41..b606f5598 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.executor.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.executor.test.ts @@ -28,8 +28,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { moduleActivator } from "../../clients/decorators/erc7579/moduleActivator" import { toK1Validator } from "../k1Validator/toK1Validator" import type { Module } from "../utils/Types" @@ -58,7 +58,7 @@ describe("modules.ownableExecutor", async () => { testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.test.ts index 8724302d9..c4e1a25ae 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.test.ts @@ -24,8 +24,8 @@ import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import type { NexusAccount } from "../../account" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { parseModuleTypeId } from "../../clients/decorators/erc7579/supportsModule" import { k1ValidatorAddress } from "../../constants" import type { Module } from "../utils/Types" @@ -61,7 +61,7 @@ describe("modules.ownables", async () => { userThreeAddress = userThree.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts index 809c5ae35..62eebfb91 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts @@ -12,8 +12,8 @@ import { } from "../../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../../clients/createNexusClient" + createSmartAccountClient +} from "../../../clients/createSmartAccountClient" import { toSmartSessionsValidator } from "../toSmartSessionsValidator" import { smartSessionCreateActions, smartSessionUseActions } from "./" @@ -40,7 +40,7 @@ describe("modules.smartSessions.decorators", async () => { sessionPublicKey = sessionKeyAccount.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -87,7 +87,7 @@ describe("modules.smartSessions.decorators", async () => { } }) - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts index 5a8c9ea33..2538b82f3 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts @@ -19,8 +19,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { danActions } from "../../clients/decorators/dan" import type { Module } from "../utils/Types" import { parse, stringify } from "./Helpers" @@ -62,7 +62,7 @@ describe("modules.smartSessions.dan.dx", async () => { test("should demonstrate creating a smart session using DAN", async () => { // Initialize the user's Nexus client with DAN actions - usersNexusClient = await createNexusClient({ + usersNexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -146,7 +146,7 @@ describe("modules.smartSessions.dan.dx", async () => { const { moduleData, granter } = parse(zippedSessionDatum) // Initialize the smart session client's Nexus client - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: granter, signer: dappAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index 581468620..c63561875 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -21,8 +21,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import type { Module } from "../utils/Types" import { parse, stringify } from "./Helpers" import type { CreateSessionDataParams, SessionData } from "./Types" @@ -80,7 +80,7 @@ describe("modules.smartSessions.dx", async () => { // Create a Nexus client for the main account (eoaAccount) // This client will be used to interact with the smart contract account - usersNexusClient = await createNexusClient({ + usersNexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -162,7 +162,7 @@ describe("modules.smartSessions.dx", async () => { // Create a new Nexus client for the session // This client will be used to interact with the smart contract account using the session key - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: usersSessionData.granter, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index b80ff3d92..d9a034884 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -22,8 +22,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { parseReferenceValue } from "../utils/Helpers" import type { Module } from "../utils/Types" import policies from "./Helpers" @@ -57,7 +57,7 @@ describe("modules.smartSessions", async () => { sessionPublicKey = sessionKeyAccount.address testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -243,7 +243,7 @@ describe("modules.smartSessions", async () => { functionName: "getNumber" }) - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index b300c6e34..c5a174643 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts @@ -25,8 +25,8 @@ import { import type { MasterClient, NetworkConfig } from "../../../test/testUtils" import { type NexusClient, - createNexusClient -} from "../../clients/createNexusClient" + createSmartAccountClient +} from "../../clients/createSmartAccountClient" import { SMART_SESSIONS_ADDRESS } from "../../constants" import type { Module } from "../utils/Types" import { isPermissionEnabled } from "./Helpers" @@ -62,7 +62,7 @@ describe("modules.smartSessions.uniPolicy", async () => { testClient = toTestClient(chain, getTestAccount(5)) - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), @@ -270,7 +270,7 @@ describe("modules.smartSessions.uniPolicy", async () => { // timestamp: 9727001666n // }) - const smartSessionNexusClient = await createNexusClient({ + const smartSessionNexusClient = await createSmartAccountClient({ chain, accountAddress: nexusClient.account.address, signer: sessionKeyAccount, diff --git a/src/test/playground.test.ts b/src/test/playground.test.ts index fd8bada97..49539ca8e 100644 --- a/src/test/playground.test.ts +++ b/src/test/playground.test.ts @@ -13,9 +13,9 @@ import { playgroundTrue } from "../sdk/account/utils/Utils" import { createBicoPaymasterClient } from "../sdk/clients/createBicoPaymasterClient" import { type NexusClient, - createNexusClient -} from "../sdk/clients/createNexusClient" -import { toNetworks } from "./testSetup" + createSmartAccountClient +} from "../sdk/clients/createSmartAccountClient" +import { toNetwork } from "./testSetup" import { type NetworkConfig, type TestnetParams, @@ -39,7 +39,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { let nexusAccountAddress: Address beforeAll(async () => { - ;[network] = await toNetworks("TESTNET_FROM_ENV_VARS") + network = await toNetwork("TESTNET_FROM_ENV_VARS") chain = network.chain bundlerUrl = network.bundlerUrl @@ -62,7 +62,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { }) test("should init the smart account", async () => { - nexusClient = await createNexusClient({ + nexusClient = await createSmartAccountClient({ signer: eoaAccount, chain, transport: http(), diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index 9fab4166c..d02b47194 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -23,8 +23,8 @@ import { getChain, getCustomChain, safeMultiplier } from "../sdk/account/utils" import { Logger } from "../sdk/account/utils/Logger" import { type NexusClient, - createNexusClient -} from "../sdk/clients/createNexusClient" + createSmartAccountClient +} from "../sdk/clients/createSmartAccountClient" import { ENTRYPOINT_SIMULATIONS_ADDRESS, ENTRY_POINT_ADDRESS, @@ -327,7 +327,7 @@ export const toFundedTestClients = async ({ const testClient = toTestClient(chain, getTestAccount()) - const nexus = await createNexusClient({ + const nexus = await createSmartAccountClient({ signer: account, transport: http(), bundlerTransport: http(bundlerUrl), From 63ec4af9b680008939c18929e8734f1be2a53d91 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 10:28:38 +0000 Subject: [PATCH 03/10] chore: remove unnecessary code --- src/sdk/account/utils/utils.test.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/sdk/account/utils/utils.test.ts b/src/sdk/account/utils/utils.test.ts index 518b4d898..de4e29574 100644 --- a/src/sdk/account/utils/utils.test.ts +++ b/src/sdk/account/utils/utils.test.ts @@ -1,22 +1,11 @@ import { ParamType, ethers } from "ethers" import { type AbiParameter, encodeAbiParameters } from "viem" import { generatePrivateKey } from "viem/accounts" -import { beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../test/testSetup" -import type { NetworkConfig } from "../../../test/testUtils" +import { describe, expect, test } from "vitest" import type { EthersWallet } from "./Utils" import { toSigner } from "./toSigner" describe("utils", async () => { - let networks: NetworkConfig[] - - beforeAll(async () => { - networks = await toNetworks([ - "TESTNET_FROM_ENV_VARS", - "TESTNET_FROM_ENV_VARS" - ]) - }) - const privKey = generatePrivateKey() test.concurrent( From 2028d5aaca1ff65b8c3bf638d28939deff80482e Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 10:42:13 +0000 Subject: [PATCH 04/10] chore: fix unit tests --- src/sdk/clients/createMeeClient.test.ts | 2 +- src/sdk/clients/createNexusSessionClient.test.ts | 4 ++-- .../toSmartSessionsValidator.dan.dx.test.ts | 2 +- .../toSmartSessionsValidator.dx.test.ts | 2 +- .../toSmartSessionsValidator.test.ts | 2 +- .../toSmartSessionsValidator.uni.policy.test.ts | 2 +- src/test/testUtils.ts | 8 ++++---- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sdk/clients/createMeeClient.test.ts b/src/sdk/clients/createMeeClient.test.ts index d493bd797..df670ca8d 100644 --- a/src/sdk/clients/createMeeClient.test.ts +++ b/src/sdk/clients/createMeeClient.test.ts @@ -75,7 +75,7 @@ describe("mee.client", async () => { const chainIds = await Promise.all( [publicClientOne, publicClientTwo].map((client) => client.getChainId()) ) - expect(chainIds).to.equal([chainOne.id, chainTwo.id]) + expect(chainIds).to.deep.equal([chainOne.id, chainTwo.id]) }) test("should have relevant meeClient properties", async () => { diff --git a/src/sdk/clients/createNexusSessionClient.test.ts b/src/sdk/clients/createNexusSessionClient.test.ts index b1f0c83e6..208b45ffb 100644 --- a/src/sdk/clients/createNexusSessionClient.test.ts +++ b/src/sdk/clients/createNexusSessionClient.test.ts @@ -4,7 +4,7 @@ import { encodeFunctionData } from "viem" import { afterAll, beforeAll, describe, expect, test } from "vitest" import { CounterAbi } from "../../test/__contracts/abi" import { testAddresses } from "../../test/callDatas" -import { toNetwork, toNetworks } from "../../test/testSetup" +import { toNetwork } from "../../test/testSetup" import { fundAndDeployClients, getTestAccount, @@ -43,7 +43,7 @@ describe("nexus.session.client", async () => { let sessionsModule: Module beforeAll(async () => { - ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts index 2538b82f3..a5a8a1866 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts @@ -47,7 +47,7 @@ describe("modules.smartSessions.dan.dx", async () => { beforeAll(async () => { // Setup test network and accounts - ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl eoaAccount = getTestAccount(0) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index c63561875..740f40655 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -45,7 +45,7 @@ describe("modules.smartSessions.dx", async () => { let sessionsModule: Module beforeAll(async () => { - ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index d9a034884..7b26167c7 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -48,7 +48,7 @@ describe("modules.smartSessions", async () => { let sessionsModule: Module beforeAll(async () => { - ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index c5a174643..8011bb3d7 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts @@ -52,7 +52,7 @@ describe("modules.smartSessions.uniPolicy", async () => { let sessionsModule: Module beforeAll(async () => { - ;[network] = await toNetworks("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") chain = network.chain bundlerUrl = network.bundlerUrl diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index d02b47194..9930e3937 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -21,10 +21,6 @@ import { createBundlerClient } from "viem/account-abstraction" import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts" import { getChain, getCustomChain, safeMultiplier } from "../sdk/account/utils" import { Logger } from "../sdk/account/utils/Logger" -import { - type NexusClient, - createSmartAccountClient -} from "../sdk/clients/createSmartAccountClient" import { ENTRYPOINT_SIMULATIONS_ADDRESS, ENTRY_POINT_ADDRESS, @@ -37,6 +33,10 @@ import { TEST_CONTRACTS } from "./callDatas" +import { + type NexusClient, + createSmartAccountClient +} from "../sdk/clients/createSmartAccountClient" import * as hardhatExec from "./executables" import type { TestFileNetworkType } from "./testSetup" From 7cee6ca61473089be5421ef3ce26b06d98b0c9c1 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 11:06:17 +0000 Subject: [PATCH 05/10] chore: omit failing dan tests --- .env.example | 2 +- .github/workflows/unit-tests.yml | 1 + src/sdk/account/utils/Utils.ts | 8 ++++++++ .../decorators/dan/decorators/dan.decorators.test.ts | 3 ++- .../ownableValidator/toOwnableValidator.dan.test.ts | 3 ++- .../toSmartSessionsValidator.dan.dx.test.ts | 3 ++- 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 405886cfd..44899e0af 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ ALT_CHAIN_ID=11155111 RPC_URL= BUNDLER_URL= BICONOMY_SDK_DEBUG=false -RUN_PLAYGROUND=false +RUN_DAN=false PAYMASTER_URL= PIMLICO_API_KEY= MEE_NODE_URL= \ No newline at end of file diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 7a688588a..15f312a08 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,3 +29,4 @@ jobs: BUNDLER_URL: https://api.pimlico.io/v2/84532/rpc?apikey=pim_im13GpaqtMDSiJFhXMxcVn CHAIN_ID: 84532 CI: true + RUN_DAN: false diff --git a/src/sdk/account/utils/Utils.ts b/src/sdk/account/utils/Utils.ts index 8ec4ad5d7..dab2b3220 100644 --- a/src/sdk/account/utils/Utils.ts +++ b/src/sdk/account/utils/Utils.ts @@ -357,6 +357,14 @@ export const getAccountDomainStructFields = async ( ]) } +export const danTrue = () => { + try { + return process?.env?.RUN_DAN === "true" + } catch (e) { + return false + } +} + export const playgroundTrue = () => { try { return process?.env?.RUN_PLAYGROUND === "true" diff --git a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts index 7e412b3a8..672971719 100644 --- a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts +++ b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts @@ -11,6 +11,7 @@ import { killNetwork, toTestClient } from "../../../../../test/testUtils" +import { danTrue } from "../../../../account/utils/Utils" import { type NexusClient, createSmartAccountClient @@ -18,7 +19,7 @@ import { import { DanWallet, hexToUint8Array, uuid } from "../Helpers" import { danActions } from "./" -describe("dan.decorators", async () => { +describe.runIf(danTrue())("dan.decorators", async () => { let network: NetworkConfig let chain: Chain let bundlerUrl: string diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts index d45c802fe..e84e0743a 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts @@ -8,12 +8,13 @@ import { toTestClient } from "../../../test/testUtils" import type { MasterClient, NetworkConfig } from "../../../test/testUtils" +import { danTrue } from "../../account" import { createSmartAccountClient } from "../../clients/createSmartAccountClient" import { danActions } from "../../clients/decorators/dan/decorators" import { ownableActions } from "./decorators" import { toOwnableValidator } from "./toOwnableValidator" -describe("modules.dan.dx", async () => { +describe.runIf(danTrue())("modules.dan.dx", async () => { let network: NetworkConfig let chain: Chain let bundlerUrl: string diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts index a5a8a1866..3da9ef9fc 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts @@ -17,6 +17,7 @@ import { toTestClient } from "../../../test/testUtils" import type { MasterClient, NetworkConfig } from "../../../test/testUtils" +import { danTrue } from "../../account/utils/Utils" import { type NexusClient, createSmartAccountClient @@ -32,7 +33,7 @@ import { toSmartSessionsValidator } from "./toSmartSessionsValidator" // Distributed Sessions enhance security and efficiency by storing session keys on Biconomy's Delegated Authorisation Network (DAN), // providing features like automated transaction processing and reduced exposure of private keys. -describe("modules.smartSessions.dan.dx", async () => { +describe.runIf(danTrue())("modules.smartSessions.dan.dx", async () => { let network: NetworkConfig let chain: Chain let bundlerUrl: string From b60fabb349d45e148dab5db5b0fb514a12f09da5 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 11:14:50 +0000 Subject: [PATCH 06/10] chore: remove dan --- .env.example | 1 - .github/workflows/unit-tests.yml | 1 - bun.lockb | Bin 299874 -> 296080 bytes package.json | 5 +- src/sdk/account/utils/Utils.ts | 8 - src/sdk/clients/decorators/dan/Helpers.ts | 77 ------- .../dan/decorators/dan.decorators.test.ts | 153 -------------- .../decorators/dan/decorators/index.ts | 43 ---- .../decorators/dan/decorators/keyGen.ts | 138 ------------- .../decorators/dan/decorators/sigGen.ts | 118 ----------- src/sdk/clients/decorators/dan/index.ts | 2 - src/sdk/clients/decorators/index.ts | 1 - .../toOwnableValidator.dan.test.ts | 117 ----------- .../decorators/index.ts | 21 +- .../decorators/useDistributedPermission.ts | 113 ----------- .../toSmartSessionsValidator.dan.dx.test.ts | 191 ------------------ 16 files changed, 2 insertions(+), 987 deletions(-) delete mode 100644 src/sdk/clients/decorators/dan/Helpers.ts delete mode 100644 src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts delete mode 100644 src/sdk/clients/decorators/dan/decorators/index.ts delete mode 100644 src/sdk/clients/decorators/dan/decorators/keyGen.ts delete mode 100644 src/sdk/clients/decorators/dan/decorators/sigGen.ts delete mode 100644 src/sdk/clients/decorators/dan/index.ts delete mode 100644 src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts delete mode 100644 src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts delete mode 100644 src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts diff --git a/.env.example b/.env.example index 44899e0af..8782ba2a8 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,6 @@ ALT_CHAIN_ID=11155111 RPC_URL= BUNDLER_URL= BICONOMY_SDK_DEBUG=false -RUN_DAN=false PAYMASTER_URL= PIMLICO_API_KEY= MEE_NODE_URL= \ No newline at end of file diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 15f312a08..7a688588a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,4 +29,3 @@ jobs: BUNDLER_URL: https://api.pimlico.io/v2/84532/rpc?apikey=pim_im13GpaqtMDSiJFhXMxcVn CHAIN_ID: 84532 CI: true - RUN_DAN: false diff --git a/bun.lockb b/bun.lockb index 3d7bee68438818e77d68f03f95dd490393e9107e..438ea62cbc43e0f89d7981a46a4aa0b943f668fc 100755 GIT binary patch delta 58911 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 { - try { - return process?.env?.RUN_DAN === "true" - } catch (e) { - return false - } -} - export const playgroundTrue = () => { try { return process?.env?.RUN_PLAYGROUND === "true" diff --git a/src/sdk/clients/decorators/dan/Helpers.ts b/src/sdk/clients/decorators/dan/Helpers.ts deleted file mode 100644 index 2321509a3..000000000 --- a/src/sdk/clients/decorators/dan/Helpers.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { - IBrowserWallet, - TypedData -} from "@silencelaboratories/walletprovider-sdk" -import { http, type Chain, type WalletClient, createWalletClient } from "viem" -import type { LocalAccount } from "viem/accounts" - -/** - * Implementation of IBrowserWallet for DAN (Distributed Account Network). - * Provides wallet functionality using viem's WalletClient. - */ -export class DanWallet implements IBrowserWallet { - walletClient: WalletClient - - /** - * Creates a new DanWallet instance. - * - * @param account - The local account to use for transactions - * @param chain - The blockchain chain configuration - */ - constructor(account: LocalAccount, chain: Chain) { - this.walletClient = createWalletClient({ - account, - chain, - transport: http() - }) - } - - /** - * Signs typed data according to EIP-712. - * - * @param _ - Unused parameter (kept for interface compatibility) - * @param request - The typed data to sign - * @returns A promise resolving to the signature - */ - async signTypedData(_: string, request: TypedData): Promise { - // @ts-ignore - return await this.walletClient.signTypedData(request) - } -} - -/** - * Converts a hexadecimal string to a Uint8Array. - * - * @param hex - The hexadecimal string to convert (must have even length) - * @returns A Uint8Array representation of the hex string - * @throws If the hex string has an odd number of characters - */ -export const hexToUint8Array = (hex: string): Uint8Array => { - if (hex.length % 2 !== 0) { - throw new Error("Hex string must have an even number of characters") - } - const array = new Uint8Array(hex.length / 2) - for (let i = 0; i < hex.length; i += 2) { - array[i / 2] = Number.parseInt(hex.substr(i, 2), 16) - } - return array -} - -/** - * Generates a random UUID string of specified length. - * - * @param length - The desired length of the UUID (default: 24) - * @returns A random string of the specified length - */ -export const uuid = (length = 24) => { - let result = "" - const characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - const charactersLength = characters.length - let counter = 0 - while (counter < length) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)) - counter += 1 - } - return result -} diff --git a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts b/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts deleted file mode 100644 index 672971719..000000000 --- a/src/sdk/clients/decorators/dan/decorators/dan.decorators.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { http, type Address, type Chain, type LocalAccount, isHex } from "viem" -import { verifyMessage } from "viem" -import type { UserOperation } from "viem/account-abstraction" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../../../test/testSetup" -import { - type MasterClient, - type NetworkConfig, - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../../../test/testUtils" -import { danTrue } from "../../../../account/utils/Utils" -import { - type NexusClient, - createSmartAccountClient -} from "../../../createSmartAccountClient" -import { DanWallet, hexToUint8Array, uuid } from "../Helpers" -import { danActions } from "./" - -describe.runIf(danTrue())("dan.decorators", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utils - let testClient: MasterClient - let eoaAccount: LocalAccount - let nexusAccountAddress: Address - let nexusClient: NexusClient - let userTwo: LocalAccount - let userThree: LocalAccount - - beforeAll(async () => { - network = await toNetwork() - - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - userTwo = getTestAccount(1) - userThree = getTestAccount(2) - testClient = toTestClient(chain, getTestAccount(5)) - - nexusClient = await createSmartAccountClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - nexusAccountAddress = await nexusClient.account.getCounterFactualAddress() - await fundAndDeployClients(testClient, [nexusClient]) - }) - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("DanWallet should initialize correctly", () => { - const account = getTestAccount(0) - const danWallet = new DanWallet(account, chain) - expect(danWallet.walletClient).toBeDefined() - expect(danWallet.walletClient.account).toBe(account) - expect(danWallet.walletClient.chain).toBe(chain) - }) - - test("DanWallet should sign typed data", async () => { - const account = getTestAccount(0) - const danWallet = new DanWallet(account, chain) - - const typedData = { - types: { - Test: [{ name: "test", type: "string" }] - }, - primaryType: "Test", - domain: { - name: "Test Domain", - version: "1", - chainId: 1 - }, - message: { - test: "Hello World" - } - } - - const signature = await danWallet.signTypedData("", typedData) - expect(signature).toBeDefined() - expect(isHex(signature as string)).toBe(true) - }) - - test("hexToUint8Array should convert hex string correctly", () => { - const hex = "0a0b0c" - const result = hexToUint8Array(hex) - expect(result).toBeInstanceOf(Uint8Array) - expect(result.length).toBe(3) - expect(Array.from(result)).toEqual([10, 11, 12]) - }) - - test("hexToUint8Array should throw on invalid hex string", () => { - expect(() => hexToUint8Array("0a0")).toThrow( - "Hex string must have an even number of characters" - ) - }) - - test("uuid should generate string of correct length", () => { - const length = 32 - const result = uuid(length) - expect(result.length).toBe(length) - expect(typeof result).toBe("string") - }) - - test("uuid should use default length of 24", () => { - const result = uuid() - expect(result.length).toBe(24) - }) - - test("uuid should generate unique values", () => { - const uuid1 = uuid() - const uuid2 = uuid() - expect(uuid1).not.toBe(uuid2) - }) - - test("should check that signature is verified", async () => { - const danNexusClient = nexusClient.extend(danActions()) - const keyGenData = await danNexusClient.keyGen() - - // @ts-ignore - const preparedUserOperation = (await danNexusClient.prepareUserOperation({ - calls: [{ to: userTwo.address, value: 1n }] - })) as UserOperation - - const sendUserOperationParameters = await danNexusClient.sigGen({ - keyGenData, - ...preparedUserOperation - }) - - const userOpHash = await danNexusClient?.account?.getUserOpHash( - preparedUserOperation - ) - - if (!userOpHash || !sendUserOperationParameters.signature) - throw new Error("Missing userOpHash or signature") - - const valid = await verifyMessage({ - address: keyGenData.sessionPublicKey, - message: { raw: userOpHash }, - signature: sendUserOperationParameters.signature - }) - - // Verify transaction success - expect(valid).toBe(true) - }) -}) diff --git a/src/sdk/clients/decorators/dan/decorators/index.ts b/src/sdk/clients/decorators/dan/decorators/index.ts deleted file mode 100644 index 5117e96da..000000000 --- a/src/sdk/clients/decorators/dan/decorators/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Chain, Client, Transport } from "viem" -import type { UserOperation } from "viem/account-abstraction" -import type { ModularSmartAccount } from "../../../../modules/utils/Types" -import { type KeyGenData, type KeyGenParameters, keyGen } from "./keyGen" -import { type SigGenParameters, sigGen } from "./sigGen" - -/** - * Defines the available DAN (Distributed Account Network) actions for a modular smart account. - * Provides methods for key generation, signature generation, and transaction sending. - * - * @template TModularSmartAccount - The type of modular smart account being used - */ -export type DanActions< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** Generates keys for the smart account with optional parameters */ - keyGen: (args?: KeyGenParameters) => Promise - /** Generates signatures for user operations */ - /** Generates signatures for user operations */ - sigGen: (parameters: SigGenParameters) => Promise> -} - -/** - * Creates a set of DAN-specific actions for interacting with a modular smart account. - * This function is a decorator that adds DAN functionality to a viem Client instance. - * - * @returns A function that takes a client and returns DAN-specific actions - * - * @example - * const client = createClient(...) - * const danClient = client.extend(danActions()) - */ -export function danActions() { - return < - TModularSmartAccount extends ModularSmartAccount | undefined, - chain extends Chain | undefined - >( - client: Client - ): DanActions => ({ - keyGen: (args) => keyGen(client, args), - sigGen: (parameters) => sigGen(client, parameters) - }) -} diff --git a/src/sdk/clients/decorators/dan/decorators/keyGen.ts b/src/sdk/clients/decorators/dan/decorators/keyGen.ts deleted file mode 100644 index 2d4ed4ee7..000000000 --- a/src/sdk/clients/decorators/dan/decorators/keyGen.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - EOAAuth, - NetworkSigner, - WalletProviderServiceClient, - computeAddress, - generateEphPrivateKey, - getEphPublicKey -} from "@silencelaboratories/walletprovider-sdk" -import { type Chain, type Client, type Hex, type Transport, toHex } from "viem" -import { ERROR_MESSAGES, type Signer } from "../../../../account" -import type { ModularSmartAccount } from "../../../../modules/utils/Types" -import { DanWallet, uuid } from "../Helpers" - -/** - * Constants for DAN (Distributed Account Network) configuration - */ -export const EPHEMERAL_KEY_TTL = 60 * 60 * 24 // 1 day -export const QUORUM_PARTIES = 3 -export const QUORUM_THRESHOLD = 2 -export const DEFAULT_DAN_URL = "wss://dan.staging.biconomy.io/v1" - -/** - * Response data from the key generation process - */ -export type KeyGenData = { - /** The generated public key */ - publicKey: Hex - /** Unique identifier for the generated key */ - keyId: Hex - /** EOA address derived from the session key */ - sessionPublicKey: Hex - /** Secret key of the ephemeral key pair */ - ephSK: Hex - /** Unique identifier for the ephemeral key */ - ephId: Hex -} - -/** - * Parameters for key generation - */ -export type KeyGenParameters< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** The smart account to add the owner to. If not provided, the client's account will be used */ - account?: TModularSmartAccount - /** Optional Signer, defaults to the one in the client */ - signer?: Signer - /** Secret key of the ephemeral key pair */ - ephSK: Hex -} & DanParameters - -/** - * Configuration parameters for DAN network - */ -export type DanParameters = { - /** Chain configuration */ - chain?: Chain - /** Minimum number of parties required for signing (default: 2) */ - threshold?: number - /** Total number of parties in the signing group (default: 3) */ - partiesNumber?: number - /** Duration of the ephemeral key validity in seconds (default: 24 hours) */ - duration?: number - /** URL of the wallet provider service */ - walletProviderUrl?: string - /** Unique identifier for the ephemeral key */ - ephId?: string -} - -/** - * Generates a key using the Distributed Account Network (DAN). - * - * @typeParam TModularSmartAccount - The type of the modular smart account, or undefined. - * @param client - The viem client instance. - * @param parameters - Optional parameters for key generation. - * @returns A Promise that resolves to the key generation data. - */ -export async function keyGen< - TModularSmartAccount extends ModularSmartAccount | undefined ->( - client: Client, - parameters?: KeyGenParameters -): Promise { - const { - signer: signer_ = client?.account?.client?.account as Signer, - walletProviderUrl = DEFAULT_DAN_URL, - partiesNumber = QUORUM_PARTIES, - threshold = QUORUM_THRESHOLD, - duration = EPHEMERAL_KEY_TTL, - ephId = uuid(), - chain: chain_ = client.account?.client?.chain - } = parameters ?? {} - - if (!signer_) { - throw new Error(ERROR_MESSAGES.SIGNER_REQUIRED) - } - - if (!chain_) { - throw new Error(ERROR_MESSAGES.CHAIN_NOT_FOUND) - } - - const skArr = generateEphPrivateKey() - const ephPKArr = getEphPublicKey(skArr) - const ephSK = toHex(skArr) - - const wpClient = new WalletProviderServiceClient({ - walletProviderId: "WalletProvider", - walletProviderUrl - }) - - const wallet = new DanWallet(signer_, chain_) - - const eoaAuth = new EOAAuth( - ephId, - signer_.address, - wallet, - ephPKArr, - duration - ) - - const networkSigner = new NetworkSigner( - wpClient, - threshold, - partiesNumber, - eoaAuth - ) - - const createdKey = await networkSigner.generateKey() - - const sessionPublicKey = computeAddress(createdKey.publicKey) - - return { - ...createdKey, - sessionPublicKey, - ephSK, - ephId - } as KeyGenData -} diff --git a/src/sdk/clients/decorators/dan/decorators/sigGen.ts b/src/sdk/clients/decorators/dan/decorators/sigGen.ts deleted file mode 100644 index 363348d20..000000000 --- a/src/sdk/clients/decorators/dan/decorators/sigGen.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - EphAuth, - NetworkSigner, - WalletProviderServiceClient -} from "@silencelaboratories/walletprovider-sdk" -import type { Chain, Client, Hex, PartialBy, Transport } from "viem" -import { - type PrepareUserOperationParameters, - type UserOperation, - prepareUserOperation -} from "viem/account-abstraction" -import { getAction } from "viem/utils" -import { ERROR_MESSAGES, type Signer } from "../../../../account" -import { deepHexlify } from "../../../../account/utils/deepHexlify" -import type { - AnyData, - ModularSmartAccount -} from "../../../../modules/utils/Types" -import { hexToUint8Array } from "../Helpers" -import { - DEFAULT_DAN_URL, - type DanParameters, - type KeyGenData, - QUORUM_PARTIES, - QUORUM_THRESHOLD -} from "./keyGen" - -/** - * Parameters required for signature generation - */ -export type SigGenParameters = PartialBy< - PrepareUserOperationParameters & { - /** Optional Signer, defaults to the one in the client */ - signer?: Signer - /** Data from previous key generation step */ - keyGenData: KeyGenData - } & DanParameters, - "account" -> - -export const REQUIRED_FIELDS = [ - "sender", - "nonce", - "callData", - "callGasLimit", - "verificationGasLimit", - "preVerificationGas", - "maxFeePerGas", - "maxPriorityFeePerGas", - "factoryData" -] - -export const sigGen = async < - TModularSmartAccount extends ModularSmartAccount | undefined, - chain extends Chain | undefined ->( - client: Client, - parameters: SigGenParameters -): Promise> => { - const { - walletProviderUrl = DEFAULT_DAN_URL, - partiesNumber = QUORUM_PARTIES, - threshold = QUORUM_THRESHOLD, - chain: chain_ = client.account?.client?.chain, - keyGenData: { ephSK, ephId, keyId } - } = parameters ?? {} - - if (!chain_) { - throw new Error(ERROR_MESSAGES.CHAIN_NOT_FOUND) - } - - const ephSKArr = hexToUint8Array(ephSK.slice(2)) - - const wpClient = new WalletProviderServiceClient({ - walletProviderId: "WalletProvider", - walletProviderUrl - }) - const authModule = new EphAuth(ephId, ephSKArr) - - const networkSigner = new NetworkSigner( - wpClient, - threshold, - partiesNumber, - authModule - ) - - const preparedUserOperation = await getAction( - client, - prepareUserOperation, - "prepareUserOperation" - )(parameters as PrepareUserOperationParameters) - - const userOperation = REQUIRED_FIELDS.reduce((acc, field) => { - if (field in preparedUserOperation) { - // @ts-ignore - acc[field] = preparedUserOperation[field] - } - return acc - }, {} as AnyData) - - const userOperationHexed = deepHexlify(userOperation) - - const signMessage = JSON.stringify({ - message: JSON.stringify({ - userOperation: userOperationHexed, - entryPointVersion: "v0.7.0", - entryPointAddress: "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - chainId: chain_.id - }), - requestType: "accountAbstractionTx" - }) - - const { sign, recid } = await networkSigner.signMessage(keyId, signMessage) - - const recid_hex = (27 + recid).toString(16) - const signature = `0x${sign}${recid_hex}` as Hex - return { ...userOperationHexed, signature } -} diff --git a/src/sdk/clients/decorators/dan/index.ts b/src/sdk/clients/decorators/dan/index.ts deleted file mode 100644 index f06563064..000000000 --- a/src/sdk/clients/decorators/dan/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./decorators" -export * from "./Helpers" diff --git a/src/sdk/clients/decorators/index.ts b/src/sdk/clients/decorators/index.ts index 4aff93b4d..a651fc255 100644 --- a/src/sdk/clients/decorators/index.ts +++ b/src/sdk/clients/decorators/index.ts @@ -1,4 +1,3 @@ export * from "./erc7579" export * from "./smartAccount" export * from "./bundler" -export * from "./dan" diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts deleted file mode 100644 index e84e0743a..000000000 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.dan.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { http, type Chain, type LocalAccount } from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { toNetwork } from "../../../test/testSetup" -import { - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../test/testUtils" -import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { danTrue } from "../../account" -import { createSmartAccountClient } from "../../clients/createSmartAccountClient" -import { danActions } from "../../clients/decorators/dan/decorators" -import { ownableActions } from "./decorators" -import { toOwnableValidator } from "./toOwnableValidator" - -describe.runIf(danTrue())("modules.dan.dx", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utils - let testClient: MasterClient - let eoaAccount: LocalAccount - - const recipient = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" // vitalik.eth - - beforeAll(async () => { - network = await toNetwork() - - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - - testClient = toTestClient(chain, getTestAccount(5)) - }) - - afterAll(async () => { - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should demonstrate ownables module dx using a dan account", async () => { - const nexusClient = await createSmartAccountClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - const danNexusClient = nexusClient.extend(danActions()) - - const keyGenData = await danNexusClient.keyGen() - - // Fund the account and deploy the smart contract wallet - // This is just a reminder to fund the account and deploy the smart contract wallet - await fundAndDeployClients(testClient, [nexusClient]) - - // Create an ownables module with the following configuration: - // - Threshold: 1 (requires 1 signature for approval) - // - Owners: danAccount - const ownableModule = toOwnableValidator({ - account: nexusClient.account, - signer: eoaAccount, - moduleInitArgs: { - threshold: 1n, - owners: [keyGenData.sessionPublicKey] - } - }) - - // Install the ownables module on the Nexus client's smart contract account - const hash = await nexusClient.installModule({ - module: ownableModule.moduleInitData - }) - - // Extend the Nexus client with ownable-specific actions - // This allows the client to use the new module's functionality - const ownableDanClient = nexusClient - .extend(ownableActions(ownableModule)) - .extend(danActions()) - - // Wait for the module installation transaction to be mined and check its success - await ownableDanClient.waitForUserOperationReceipt({ hash }) - - // Prepare a user operation to withdraw 1 wei to userTwo - // This demonstrates a simple transaction that requires multi-sig approval - // @ts-ignore - const withdrawalUserOp = await ownableDanClient.prepareUserOperation({ - calls: [ - { - to: recipient, // vitalik.eth - value: 1n - } - ] - }) - - // Collect signature - const { signature } = await ownableDanClient.sigGen({ - keyGenData, - ...withdrawalUserOp - }) - - if (!signature) throw new Error("Missing signature") - - // Send the user operation with the collected signatures - const userOpHash = await nexusClient.sendUserOperation({ - ...withdrawalUserOp, - signature - }) - - // Wait for the user operation to be mined and check its success - const { success: userOpSuccess } = - await ownableDanClient.waitForUserOperationReceipt({ hash: userOpHash }) - - // Verify that the multi-sig transaction was successful - expect(userOpSuccess).toBe(true) - }) -}) diff --git a/src/sdk/modules/smartSessionsValidator/decorators/index.ts b/src/sdk/modules/smartSessionsValidator/decorators/index.ts index bbbec6906..1e0a31446 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/index.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/index.ts @@ -8,11 +8,6 @@ import { grantPermission } from "./grantPermission" import { type TrustAttestersParameters, trustAttesters } from "./trustAttesters" -import { - type DanClient, - type UseDistributedPermissionParameters, - useDistributedPermission -} from "./useDistributedPermission" import { type UsePermissionParameters, usePermission } from "./usePermission" /** * Defines the shape of actions available for creating smart sessions. @@ -60,15 +55,6 @@ export type SmartSessionUseActions< usePermission: ( args: UsePermissionParameters ) => Promise - /** - * Uses a session to perform multiple actions. - * - * @param args - Parameters for using a session. - * @returns A promise that resolves to the transaction hash. - */ - useDistributedPermission: ( - args: UseDistributedPermissionParameters - ) => Promise } /** @@ -102,11 +88,7 @@ export function smartSessionUseActions( ): SmartSessionUseActions => { client?.account?.setModule(smartSessionsModule) return { - usePermission: (args) => usePermission(client, args), - useDistributedPermission: (args) => { - const danClient = client.extend(danActions()) as unknown as DanClient - return useDistributedPermission(danClient, args) - } + usePermission: (args) => usePermission(client, args) } } } @@ -114,4 +96,3 @@ export function smartSessionUseActions( export * from "./grantPermission" export * from "./trustAttesters" export * from "./usePermission" -export * from "./useDistributedPermission" diff --git a/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts b/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts deleted file mode 100644 index fa82c488a..000000000 --- a/src/sdk/modules/smartSessionsValidator/decorators/useDistributedPermission.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { Chain, Client, Hex, Transport } from "viem" -import { type BundlerClient, sendUserOperation } from "viem/account-abstraction" -import { getAction } from "viem/utils" -import { ERROR_MESSAGES } from "../../../account" -import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import type { Call } from "../../../account/utils/Types" -import type { Signer } from "../../../account/utils/toSigner" -import type { DanActions } from "../../../clients/decorators/dan/decorators" -import { parseModule } from "../../utils/Helpers" -import type { ModularSmartAccount } from "../../utils/Types" -import type { SmartSessionModule } from "../toSmartSessionsValidator" - -/** - * Parameters for using a smart session to execute actions. - * - * @template TModularSmartAccount - Type of the modular smart account, extending ModularSmartAccount or undefined. - */ -export type UseDistributedPermissionParameters< - TModularSmartAccount extends ModularSmartAccount | undefined -> = { - /** Array of executions to perform in the session. Allows for batch transactions if the session is enabled for multiple actions. */ - calls: Call[] - /** The maximum fee per gas unit the transaction is willing to pay. */ - maxFeePerGas?: bigint - /** The maximum priority fee per gas unit the transaction is willing to pay. */ - maxPriorityFeePerGas?: bigint - /** The nonce of the transaction. If not provided, it will be determined automatically. */ - nonce?: bigint - /** The modular smart account to use for the session. If not provided, the client's account will be used. */ - account?: TModularSmartAccount - /** The signer to use for the session. Defaults to the signer of the client. */ - signer?: Signer -} - -export type DanClient = Client< - Transport, - Chain | undefined, - ModularSmartAccount -> & - DanActions & - BundlerClient - -/** - * Executes actions using a smart session. - * - * This function allows for the execution of one or more actions within an enabled smart session. - * It can handle batch transactions if the session is configured for multiple actions. - * - * @template TModularSmartAccount - Type of the modular smart account, extending ModularSmartAccount or undefined. - * @param client - The client used to interact with the blockchain. - * @param parameters - Parameters for using the session, including actions to execute and optional gas settings. - * @returns A promise that resolves to the hash of the sent user operation. - * - * @throws {AccountNotFoundError} If no account is provided and the client doesn't have an associated account. - * - * @example - * ```typescript - * const result = await useDistributedPermission(nexusClient, { - * calls: [ - * { - * to: '0x1234...', - * data: '0xabcdef...' - * } - * ], - * maxFeePerGas: 1000000000n - * }); - * console.log(`Transaction hash: ${result}`); - * ``` - * - * @remarks - * - Ensure that the session is enabled and has the necessary permissions for the actions being executed. - * - For batch transactions, all actions must be permitted within the same session. - * - The function uses the `sendUserOperation` method, which is specific to account abstraction implementations. - */ -export async function useDistributedPermission< - TModularSmartAccount extends ModularSmartAccount | undefined ->( - client: DanClient, - parameters: UseDistributedPermissionParameters -): Promise { - const { account: account_ = client.account } = parameters - - if (!account_) { - throw new AccountNotFoundError({ - docsPath: "/nexus-client/methods#sendtransaction" - }) - } - - const params = { ...parameters, account: account_ } - - const preppedUserOp = await client.prepareUserOperation(params) - const sessionsModule = parseModule(client) as SmartSessionModule - const keyGenData = sessionsModule?.moduleData?.keyGenData - - if (!keyGenData) { - throw new Error(ERROR_MESSAGES.KEY_GEN_DATA_NOT_FOUND) - } - - const { signature } = await client.sigGen({ ...preppedUserOp, keyGenData }) - - if (!signature) { - throw new Error(ERROR_MESSAGES.SIGNATURE_NOT_FOUND) - } - - const extendedSignature = sessionsModule.sigGen(signature) - - return await getAction( - client, - sendUserOperation, - "sendUserOperation" - // @ts-ignore - )({ ...preppedUserOp, account: account_, signature: extendedSignature }) -} diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts deleted file mode 100644 index 3da9ef9fc..000000000 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dan.dx.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { SmartSessionMode } from "@rhinestone/module-sdk/module" -import { - http, - type Chain, - type Hex, - type LocalAccount, - encodeFunctionData -} from "viem" -import { afterAll, beforeAll, describe, expect, test } from "vitest" -import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" -import { testAddresses } from "../../../test/callDatas" -import { toNetwork } from "../../../test/testSetup" -import { - fundAndDeployClients, - getTestAccount, - killNetwork, - toTestClient -} from "../../../test/testUtils" -import type { MasterClient, NetworkConfig } from "../../../test/testUtils" -import { danTrue } from "../../account/utils/Utils" -import { - type NexusClient, - createSmartAccountClient -} from "../../clients/createSmartAccountClient" -import { danActions } from "../../clients/decorators/dan" -import type { Module } from "../utils/Types" -import { parse, stringify } from "./Helpers" -import type { CreateSessionDataParams, SessionData } from "./Types" -import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" -import { toSmartSessionsValidator } from "./toSmartSessionsValidator" - -// This test suite demonstrates how to create and use a smart session using Biconomy's Distributed Sessions (DAN). -// Distributed Sessions enhance security and efficiency by storing session keys on Biconomy's Delegated Authorisation Network (DAN), -// providing features like automated transaction processing and reduced exposure of private keys. - -describe.runIf(danTrue())("modules.smartSessions.dan.dx", async () => { - let network: NetworkConfig - let chain: Chain - let bundlerUrl: string - - // Test utilities and variables - let testClient: MasterClient - let eoaAccount: LocalAccount - let usersNexusClient: NexusClient - let dappAccount: LocalAccount - let zippedSessionDatum: string - let sessionsModule: Module - - beforeAll(async () => { - // Setup test network and accounts - network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") - chain = network.chain - bundlerUrl = network.bundlerUrl - eoaAccount = getTestAccount(0) - dappAccount = getTestAccount(7) - testClient = toTestClient(chain, getTestAccount(5)) - }) - - afterAll(async () => { - // Clean up the network after tests - await killNetwork([network?.rpcPort, network?.bundlerPort]) - }) - - test("should demonstrate creating a smart session using DAN", async () => { - // Initialize the user's Nexus client with DAN actions - usersNexusClient = await createSmartAccountClient({ - signer: eoaAccount, - chain, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - const danNexusClient = usersNexusClient.extend(danActions()) - - // Generate a session key using DAN - const keyGenData = await danNexusClient.keyGen() - const sessionPublicKey = keyGenData.sessionPublicKey - - // Fund and deploy the user's smart account - await fundAndDeployClients(testClient, [usersNexusClient]) - - // Initialize the smart sessions validator module - sessionsModule = toSmartSessionsValidator({ - account: usersNexusClient.account, - signer: eoaAccount - }) - - // Install the sessions module - const hash = await usersNexusClient.installModule({ - module: sessionsModule.moduleInitData - }) - - // Extend the Nexus client with smart session creation actions - const nexusSessionClient = usersNexusClient.extend( - smartSessionCreateActions(sessionsModule) - ) - - // Wait for the module installation to complete - const { success: installSuccess } = - await usersNexusClient.waitForUserOperationReceipt({ hash }) - - expect(installSuccess).toBe(true) - - // Define the permissions for the smart session - const sessionRequestedInfo: CreateSessionDataParams[] = [ - { - sessionPublicKey, // Public key of the session stored in DAN - actionPoliciesInfo: [ - { - contractAddress: testAddresses.Counter, - functionSelector: "0x273ea3e3" as Hex // Selector for 'incrementNumber' function - } - ] - } - ] - - // Create the smart session with the specified permissions - const createSessionsResponse = await nexusSessionClient.grantPermission({ - sessionRequestedInfo - }) - - // Wait for the permission grant operation to complete - const { success: sessionCreateSuccess } = - await usersNexusClient.waitForUserOperationReceipt({ - hash: createSessionsResponse.userOpHash - }) - - expect(installSuccess).toBe(sessionCreateSuccess) - - // Prepare the session data to be stored by the dApp. This could be saved in a Database or client side in local storage. - const sessionData: SessionData = { - granter: usersNexusClient.account.address, - sessionPublicKey, - moduleData: { - keyGenData, - permissionIds: createSessionsResponse.permissionIds, - mode: SmartSessionMode.USE - } - } - - // Serialize the session data - zippedSessionDatum = stringify(sessionData) - }, 200000) - - test("should demonstrate using a smart session using DAN", async () => { - // Parse the session data received from the user - const { moduleData, granter } = parse(zippedSessionDatum) - - // Initialize the smart session client's Nexus client - const smartSessionNexusClient = await createSmartAccountClient({ - chain, - accountAddress: granter, - signer: dappAccount, - transport: http(), - bundlerTransport: http(bundlerUrl) - }) - - // Initialize the smart sessions validator module with the received module data - const usePermissionsModule = toSmartSessionsValidator({ - account: smartSessionNexusClient.account, - signer: dappAccount, - moduleData // This includes the keyGenData - }) - - // Extend the Nexus client with smart session usage and dan actions - const danSessionClient = smartSessionNexusClient - .extend(smartSessionUseActions(usePermissionsModule)) - .extend(danActions()) - - // Use the distributed permission to execute a transaction - const userOpHash = await danSessionClient.useDistributedPermission({ - calls: [ - { - to: testAddresses.Counter, - data: encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - } - ] - }) - - // Wait for the transaction to be processed - const { success: sessionUseSuccess } = - await danSessionClient.waitForUserOperationReceipt({ - hash: userOpHash - }) - - expect(sessionUseSuccess).toBe(true) - }, 200000) // Test timeout set to 200 seconds -}) From 83d62699378e3e64dd95b4fa3bcc1e90cd36daea Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 2 Dec 2024 11:17:37 +0000 Subject: [PATCH 07/10] chore: add missing imports --- .github/workflows/unit-tests.yml | 1 + src/sdk/clients/decorators/index.ts | 1 + src/sdk/clients/index.ts | 2 +- src/sdk/modules/smartSessionsValidator/Types.ts | 3 --- .../smartSessionsValidator/decorators/index.ts | 1 - .../toSmartSessionsValidator.ts | 16 ++-------------- 6 files changed, 5 insertions(+), 19 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 7a688588a..4004c44f0 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -28,4 +28,5 @@ jobs: PIMLICO_API_KEY: ${{ secrets.PIMLICO_API_KEY }} BUNDLER_URL: https://api.pimlico.io/v2/84532/rpc?apikey=pim_im13GpaqtMDSiJFhXMxcVn CHAIN_ID: 84532 + ALT_CHAIN_ID: 11155420 CI: true diff --git a/src/sdk/clients/decorators/index.ts b/src/sdk/clients/decorators/index.ts index a651fc255..aa611ad0a 100644 --- a/src/sdk/clients/decorators/index.ts +++ b/src/sdk/clients/decorators/index.ts @@ -1,3 +1,4 @@ export * from "./erc7579" export * from "./smartAccount" export * from "./bundler" +export * from "./mee" diff --git a/src/sdk/clients/index.ts b/src/sdk/clients/index.ts index bfe41428f..8158408a6 100644 --- a/src/sdk/clients/index.ts +++ b/src/sdk/clients/index.ts @@ -1,5 +1,5 @@ export * from "./createBicoBundlerClient" export * from "./createBicoPaymasterClient" export * from "./createSmartAccountClient" -export * from "./createSmartAccountClient" +export * from "./createMeeClient" export * from "./decorators" diff --git a/src/sdk/modules/smartSessionsValidator/Types.ts b/src/sdk/modules/smartSessionsValidator/Types.ts index c650ccf06..49693cf8c 100644 --- a/src/sdk/modules/smartSessionsValidator/Types.ts +++ b/src/sdk/modules/smartSessionsValidator/Types.ts @@ -3,7 +3,6 @@ import type { SmartSessionMode } from "@rhinestone/module-sdk" import type { AbiFunction, Address, Hex, OneOf } from "viem" -import type { KeyGenData } from "../../clients/decorators/dan/decorators/keyGen" import type { AnyReferenceValue } from "../utils/Helpers" import type { Execution } from "../utils/Types" @@ -60,8 +59,6 @@ export type UsePermissionModuleData = { mode?: SmartSessionModeType /** Data for enabling the session. */ enableSessionData?: EnableSessionData - /** Key generation data for the session. */ - keyGenData?: KeyGenData /** The index of the permission ID to use for the session. Defaults to 0. */ permissionIdIndex?: number } diff --git a/src/sdk/modules/smartSessionsValidator/decorators/index.ts b/src/sdk/modules/smartSessionsValidator/decorators/index.ts index 1e0a31446..b9e94b152 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/index.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/index.ts @@ -1,5 +1,4 @@ import type { Chain, Client, Hash, Transport } from "viem" -import { danActions } from "../../../clients/decorators/dan/decorators" import type { ModularSmartAccount, Module } from "../../utils/Types" import type { GrantPermissionResponse } from "../Types" import type { SmartSessionModule } from "../toSmartSessionsValidator" diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts index 7f4ca78e6..4b662fc49 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts @@ -14,7 +14,6 @@ const DUMMY_ECDSA_SIG = "0xe8b94748580ca0b4993c9a1b86b5be851bfc076ff5ce3a1ff65bf16392acfcb800f9b4f1aef1555c7fce5599fffb17e7c635502154a0333ba21f3ae491839af51c" export type SmartSessionModule = Module & { - sigGen: (signature: Hex) => Hex moduleData?: UsePermissionModuleData } @@ -103,8 +102,7 @@ export const toSmartSessionsValidator = ( permissionIdIndex = 0, permissionIds = [], mode = SmartSessionMode.USE, - enableSessionData, - keyGenData: _ + enableSessionData } = {} } = parameters @@ -135,16 +133,6 @@ export const toSmartSessionsValidator = ( signature: await signer.signMessage({ message: { raw: userOpHash as Hex } }) - }), - extend: { - sigGen: (signature: Hex): Hex => { - return encodeSmartSessionSignature({ - mode, - permissionId: permissionIds[permissionIdIndex], - enableSessionData, - signature - }) - } - } + }) }) as SmartSessionModule } From 32f98466a5231be1fd400fd918d8df36e1891e72 Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Sat, 7 Dec 2024 10:27:41 +0000 Subject: [PATCH 08/10] chore: meeService --- ...Agent.test.ts => createMeeService.test.ts} | 19 ++++++++-------- ...{createMeeAgent.ts => createMeeService.ts} | 22 +++++++++---------- src/sdk/clients/decorators/mee/getFeeQuote.ts | 4 ++-- src/sdk/clients/decorators/mee/index.ts | 4 ++-- .../decorators/mee/mee.decorators.test.ts | 8 +++---- src/sdk/clients/index.ts | 2 +- 6 files changed, 29 insertions(+), 30 deletions(-) rename src/sdk/clients/{createMeeAgent.test.ts => createMeeService.test.ts} (85%) rename src/sdk/clients/{createMeeAgent.ts => createMeeService.ts} (82%) diff --git a/src/sdk/clients/createMeeAgent.test.ts b/src/sdk/clients/createMeeService.test.ts similarity index 85% rename from src/sdk/clients/createMeeAgent.test.ts rename to src/sdk/clients/createMeeService.test.ts index b255cce0e..1741a7c93 100644 --- a/src/sdk/clients/createMeeAgent.test.ts +++ b/src/sdk/clients/createMeeService.test.ts @@ -13,10 +13,9 @@ import type { NetworkConfig } from "../../test/testUtils" import { toNexusAccount } from "../account/toNexusAccount" import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS } from "../constants" import { MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../constants" -import { createMeeAgent } from "./createMeeAgent" -import type { MeeAgent } from "./createMeeAgent" +import { type MeeService, createMeeService } from "./createMeeService" -describe("mee.client", async () => { +describe("mee.agent", async () => { let networkOne: NetworkConfig let networkTwo: NetworkConfig @@ -28,7 +27,7 @@ describe("mee.client", async () => { let publicClientOne: PublicClient let publicClientTwo: PublicClient - let meeAgent: MeeAgent + let meeService: MeeService beforeAll(async () => { ;[networkOne, networkTwo] = await toNetworks([ @@ -53,7 +52,7 @@ describe("mee.client", async () => { transport: http() }) - meeAgent = await createMeeAgent({ + meeService = await createMeeService({ accountParams: { signer: eoaAccount, k1ValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, @@ -79,9 +78,9 @@ describe("mee.client", async () => { expect(chainIds).to.deep.equal([chainOne.id, chainTwo.id]) }) - test("should have relevant meeAgent properties", async () => { - expect(meeAgent).toHaveProperty("accounts") - expect(typeof meeAgent.getFeeQuote).toBe("function") + test("should have relevant meeService properties", async () => { + expect(meeService).toHaveProperty("accounts") + expect(typeof meeService.getFeeQuote).toBe("function") }) test("should alternatively create a mee client from two distinct nexus accounts", async () => { @@ -100,10 +99,10 @@ describe("mee.client", async () => { factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS }) - const meeAgent = await createMeeAgent({ + const meeService = await createMeeService({ accounts: [nexusAccountOne, nexusAccountTwo] }) - expect(meeAgent).toHaveProperty("accounts") + expect(meeService).toHaveProperty("accounts") }) }) diff --git a/src/sdk/clients/createMeeAgent.ts b/src/sdk/clients/createMeeService.ts similarity index 82% rename from src/sdk/clients/createMeeAgent.ts rename to src/sdk/clients/createMeeService.ts index 6fb1a1658..67cecb8a8 100644 --- a/src/sdk/clients/createMeeAgent.ts +++ b/src/sdk/clients/createMeeService.ts @@ -25,7 +25,7 @@ export type ChainAbstractedAccountParams = Omit< }[] } -export type MeeAgentParameters = { +export type MeeServiceParameters = { baseUrl?: string } & OneOf< | { @@ -35,19 +35,19 @@ export type MeeAgentParameters = { accountParams: ChainAbstractedAccountParams } > -export type ResolvedMeeAgentParameters = { +export type ResolvedMeeServiceParameters = { baseUrl?: string accounts: ModularSmartAccount[] } -export type TBaseMeeAgent = Omit -export type MeeAgent = Prettify +export type TBaseMeeService = Omit +export type MeeService = Prettify -export class BaseMeeAgent { +export class BaseMeeService { private baseUrl: string public accounts: ModularSmartAccount[] - constructor(config: ResolvedMeeAgentParameters) { + constructor(config: ResolvedMeeServiceParameters) { this.baseUrl = config.baseUrl || DEFAULT_MEE_NODE this.accounts = config.accounts || [] } @@ -100,7 +100,7 @@ export class BaseMeeAgent { throw new Error(`${response.status}, ${(response as AnyData).statusText}`) } - static async init(config: MeeAgentParameters): Promise { + static async init(config: MeeServiceParameters): Promise { let accounts: ModularSmartAccount[] = config.accounts || [] if (!config.accounts && config.accountParams) { const { chainList, ...chainAgnosticParams } = config.accountParams @@ -113,10 +113,10 @@ export class BaseMeeAgent { if (!accounts) { throw new Error("No accounts provided") } - const baseAgent = new BaseMeeAgent({ baseUrl: config.baseUrl, accounts }) - return Object.assign(baseAgent, meeActions(baseAgent)) as MeeAgent + const baseAgent = new BaseMeeService({ baseUrl: config.baseUrl, accounts }) + return Object.assign(baseAgent, meeActions(baseAgent)) as MeeService } } -// Helper function to create a MeeAgent instance -export const createMeeAgent = BaseMeeAgent.init +// Helper function to create a MeeService instance +export const createMeeService = BaseMeeService.init diff --git a/src/sdk/clients/decorators/mee/getFeeQuote.ts b/src/sdk/clients/decorators/mee/getFeeQuote.ts index 6ee651b87..3afda61ce 100644 --- a/src/sdk/clients/decorators/mee/getFeeQuote.ts +++ b/src/sdk/clients/decorators/mee/getFeeQuote.ts @@ -1,7 +1,7 @@ import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" import type { Call } from "../../../account/utils/Types" import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" -import type { BaseMeeAgent } from "../../createMeeAgent" +import type { BaseMeeService } from "../../createMeeService" export type GetFeeQuote = AnyData @@ -21,7 +21,7 @@ export type GetFeeQuoteParameters< } export async function getFeeQuote( - client: BaseMeeAgent, + client: BaseMeeService, parameters: GetFeeQuoteParameters ): Promise { const { accounts: accounts_ = client.accounts, calls } = parameters diff --git a/src/sdk/clients/decorators/mee/index.ts b/src/sdk/clients/decorators/mee/index.ts index 993db387d..023143a5c 100644 --- a/src/sdk/clients/decorators/mee/index.ts +++ b/src/sdk/clients/decorators/mee/index.ts @@ -1,5 +1,5 @@ import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" -import type { BaseMeeAgent } from "../../createMeeAgent" +import type { BaseMeeService } from "../../createMeeService" import { type GetFeeQuoteParameters, getFeeQuote } from "./getFeeQuote" export type MeeActions = { @@ -9,7 +9,7 @@ export type MeeActions = { sendSuperTransaction: () => void } -export function meeActions(client: BaseMeeAgent): MeeActions { +export function meeActions(client: BaseMeeService): MeeActions { return { getFeeQuote: (parameters) => getFeeQuote(client, parameters), sendSuperTransaction: () => {} // etc etc diff --git a/src/sdk/clients/decorators/mee/mee.decorators.test.ts b/src/sdk/clients/decorators/mee/mee.decorators.test.ts index 02fcf5ab3..f2213dc1a 100644 --- a/src/sdk/clients/decorators/mee/mee.decorators.test.ts +++ b/src/sdk/clients/decorators/mee/mee.decorators.test.ts @@ -18,7 +18,7 @@ import { MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS } from "../../../constants" -import { type MeeAgent, createMeeAgent } from "../../createMeeAgent" +import { type MeeService, createMeeService } from "../../createMeeService" describe("mee.decorators", async () => { let networkOne: NetworkConfig @@ -34,7 +34,7 @@ describe("mee.decorators", async () => { let publicClientOne: PublicClient let publicClientTwo: PublicClient - let meeAgent: MeeAgent + let meeService: MeeService beforeAll(async () => { ;[networkOne, networkTwo] = await toNetworks([ @@ -74,14 +74,14 @@ describe("mee.decorators", async () => { factoryAddress: MAINNET_ADDRESS_K1_VALIDATOR_FACTORY_ADDRESS }) - meeAgent = await createMeeAgent({ + meeService = await createMeeService({ accounts: [nexusAccountOne, nexusAccountTwo] }) }) test("should call prepareUserOperation ", async () => { await expect( - meeAgent.getFeeQuote({ + meeService.getFeeQuote({ calls: [ { to: recipientAddress, diff --git a/src/sdk/clients/index.ts b/src/sdk/clients/index.ts index be96b2e30..725d53da3 100644 --- a/src/sdk/clients/index.ts +++ b/src/sdk/clients/index.ts @@ -1,5 +1,5 @@ export * from "./createBicoBundlerClient" export * from "./createBicoPaymasterClient" export * from "./createSmartAccountClient" -export * from "./createMeeAgent" +export * from "./createMeeService" export * from "./decorators" From 7420df9c4ad609773b0f1ba8e2e7eb1bc26ec73f Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 9 Dec 2024 13:44:27 +0000 Subject: [PATCH 09/10] chore: get account params --- src/sdk/clients/createMeeService.ts | 2 +- src/sdk/clients/decorators/mee/getFeeQuote.ts | 82 +++++++++++++++++-- src/sdk/clients/decorators/mee/index.ts | 11 ++- .../decorators/mee/mee.decorators.test.ts | 8 +- src/test/playground.test.ts | 12 +-- 5 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/sdk/clients/createMeeService.ts b/src/sdk/clients/createMeeService.ts index 67cecb8a8..ed58f248c 100644 --- a/src/sdk/clients/createMeeService.ts +++ b/src/sdk/clients/createMeeService.ts @@ -7,7 +7,7 @@ import { Logger } from "../account/utils/Logger" import type { AnyData, ModularSmartAccount } from "../modules" import { type MeeActions, meeActions } from "./decorators/mee" -export const DEFAULT_MEE_NODE = "https://biconomy.io/mee" +export const DEFAULT_MEE_NODE = "https://mee-node.biconomy.io" export enum HttpMethod { Get = "get", diff --git a/src/sdk/clients/decorators/mee/getFeeQuote.ts b/src/sdk/clients/decorators/mee/getFeeQuote.ts index 3afda61ce..371e4fd6a 100644 --- a/src/sdk/clients/decorators/mee/getFeeQuote.ts +++ b/src/sdk/clients/decorators/mee/getFeeQuote.ts @@ -1,8 +1,11 @@ +import type { Address } from "viem/accounts" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" import type { Call } from "../../../account/utils/Types" import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" import type { BaseMeeService } from "../../createMeeService" +import type { Hex } from "viem" +const DEFAULT_GAS_LIMIT = 1000000n export type GetFeeQuote = AnyData export type MeeRpcSchema = [ @@ -18,13 +21,31 @@ export type GetFeeQuoteParameters< > = { accounts?: TModularSmartAccount[] calls: Call[] + paymentInfo: { + token: Hex + chainId: number + } +} + +// [callData, sender, factory, factoryData, callGasLimit, nonce] +export type AccountData = [Hex, Address, Address, Hex, string, string, number] + +export type WalletProvider = "BICO_V2" +export type GetFeeQuoteRequest = { + walletProvider: WalletProvider + userOps: AccountData[] + paymentInfo: GetFeeQuoteParameters["paymentInfo"] } export async function getFeeQuote( client: BaseMeeService, parameters: GetFeeQuoteParameters ): Promise { - const { accounts: accounts_ = client.accounts, calls } = parameters + const { + accounts: accounts_ = client.accounts, + calls, + paymentInfo + } = parameters if (!accounts_?.length) { throw new AccountNotFoundError({ @@ -32,12 +53,59 @@ export async function getFeeQuote( }) } - const callData = await Promise.all( - accounts_.map((account) => account.encodeCalls(calls)) + // Get payload data for each account + const userOps = await Promise.all( + accounts_.map((account) => { + // Initiate all asynchronous operations + const callDataPromise = account.encodeCalls(calls) + const factoryArgsPromise = account.getFactoryArgs() + const chainIdPromise = account?.client?.chain?.id + const noncePromise = account.getNonce() + const gasLimitPromise = (async () => { + try { + if (account?.userOperation?.estimateGas) { + const gas = await account.userOperation.estimateGas({ + sender: account.address, + callData: await callDataPromise, // Await callDataPromise here + nonce: await noncePromise // Await noncePromise here + }) + return gas?.callGasLimit ?? DEFAULT_GAS_LIMIT + } + return DEFAULT_GAS_LIMIT + } catch { + return DEFAULT_GAS_LIMIT + } + })() + + // Await all promises in parallel + return Promise.all([ + callDataPromise, + factoryArgsPromise, + noncePromise, + gasLimitPromise, + chainIdPromise + ]).then( + ([callData, { factory, factoryData }, nonce, callGasLimit, chainId]) => + [ + callData, + account.address, // sender + factory, + factoryData, + String(callGasLimit), + String(nonce), + chainId + ] as AccountData + ) + }) ) - return await client.request({ - method: "mee_getFeeQuote", - params: callData - }) + const request: GetFeeQuoteRequest = { + walletProvider: "BICO_V2", + userOps, + paymentInfo + } + + console.log({ request }) + + return await client.request({ method: "mee_getFeeQuote", params: request }) } diff --git a/src/sdk/clients/decorators/mee/index.ts b/src/sdk/clients/decorators/mee/index.ts index 023143a5c..a29ea2367 100644 --- a/src/sdk/clients/decorators/mee/index.ts +++ b/src/sdk/clients/decorators/mee/index.ts @@ -6,12 +6,21 @@ export type MeeActions = { getFeeQuote: ( parameters: GetFeeQuoteParameters ) => Promise + sendPreparedSuperTransaction: () => void sendSuperTransaction: () => void } export function meeActions(client: BaseMeeService): MeeActions { return { getFeeQuote: (parameters) => getFeeQuote(client, parameters), - sendSuperTransaction: () => {} // etc etc + sendPreparedSuperTransaction: () => { + // Sends a prepared super transaction with the retrievedfee quote + }, // etc etc + sendSuperTransaction: () => { + // Pairs fetching the fee quote with sending the super transaction + // getFeeQuote + // then... + // sendSuperTransactions + } } } diff --git a/src/sdk/clients/decorators/mee/mee.decorators.test.ts b/src/sdk/clients/decorators/mee/mee.decorators.test.ts index f2213dc1a..1cc278a9a 100644 --- a/src/sdk/clients/decorators/mee/mee.decorators.test.ts +++ b/src/sdk/clients/decorators/mee/mee.decorators.test.ts @@ -79,7 +79,7 @@ describe("mee.decorators", async () => { }) }) - test("should call prepareUserOperation ", async () => { + test("should call getFeeQuote ", async () => { await expect( meeService.getFeeQuote({ calls: [ @@ -87,7 +87,11 @@ describe("mee.decorators", async () => { to: recipientAddress, data: "0x" } - ] + ], + paymentInfo: { + token: "0x", + chainId: chainOne.id + } }) ).rejects.toThrow("Not Found, 404") }) diff --git a/src/test/playground.test.ts b/src/test/playground.test.ts index a4a4acb74..f397c863d 100644 --- a/src/test/playground.test.ts +++ b/src/test/playground.test.ts @@ -39,7 +39,7 @@ import { describe.skipIf(!playgroundTrue())("playground", () => { let network: NetworkConfig // Required for "TESTNET_FROM_ENV_VARS" networks - let callss: TestnetParams + let testParams: TestnetParams // Nexus Config let chain: Chain let bundlerUrl: string @@ -74,7 +74,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { transport: http() }) - callss = getTestParamsForTestnet(publicClient) + testParams = getTestParamsForTestnet(publicClient) }) test("should init the smart account", async () => { @@ -89,7 +89,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { }) : undefined, index, - ...callss, + ...testParams, k1ValidatorAddress: "0x000000000EE7335c268e8225fcce3E913B8b30FE", factoryAddress: "0x0000000000D8BA042724b13e85B5f40C715A5702" }) @@ -164,7 +164,8 @@ describe.skipIf(!playgroundTrue())("playground", () => { expect(balanceAfter - balanceBefore).toBe(1n) }) - test("should test creating and using a session", async () => { + // Skipped because on base sepolia the attestations for smart sessions have not been created yet + test.skip("should test creating and using a session", async () => { const sessionsModule = toSmartSessionsValidator({ account: nexusClient.account, signer: eoaAccount @@ -223,6 +224,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { sessionPublicKey: eoaAccount.address, moduleData: { permissionIds: createSessionsResponse.permissionIds, + action: createSessionsResponse.action, mode: SmartSessionMode.USE } } @@ -234,7 +236,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { transport: http(), bundlerTransport: http(bundlerUrl), index, - ...callss, + ...testParams, k1ValidatorAddress: "0x000000000EE7335c268e8225fcce3E913B8b30FE", factoryAddress: "0x0000000000D8BA042724b13e85B5f40C715A5702" }) From 0dd2016855b5059f1997cc487da3c7dc46d7d93f Mon Sep 17 00:00:00 2001 From: Joe Pegler Date: Mon, 9 Dec 2024 13:56:19 +0000 Subject: [PATCH 10/10] chore: testnetParams --- src/sdk/clients/createBicoPaymasterClient.test.ts | 8 ++++---- src/sdk/clients/decorators/mee/getFeeQuote.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sdk/clients/createBicoPaymasterClient.test.ts b/src/sdk/clients/createBicoPaymasterClient.test.ts index 31045a3f9..9eb9883b7 100644 --- a/src/sdk/clients/createBicoPaymasterClient.test.ts +++ b/src/sdk/clients/createBicoPaymasterClient.test.ts @@ -29,7 +29,7 @@ import { describe.runIf(paymasterTruthy())("bico.paymaster", async () => { let network: NetworkConfig // Required for "TESTNET_FROM_ENV_VARS" networks - let callss: TestnetParams + let testnetParams: TestnetParams let chain: Chain let bundlerUrl: string @@ -67,7 +67,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { transport: http() }) - callss = getTestParamsForTestnet(publicClient) + testnetParams = getTestParamsForTestnet(publicClient) paymaster = createBicoPaymasterClient({ transport: http(paymasterUrl) @@ -77,7 +77,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { signer: account, chain, transport: http(), - ...callss + ...testnetParams }) bicoBundler = createBicoBundlerClient({ @@ -93,7 +93,7 @@ describe.runIf(paymasterTruthy())("bico.paymaster", async () => { transport: http(), bundlerTransport: http(bundlerUrl), paymaster, - ...callss + ...testnetParams }) }) afterAll(async () => { diff --git a/src/sdk/clients/decorators/mee/getFeeQuote.ts b/src/sdk/clients/decorators/mee/getFeeQuote.ts index 371e4fd6a..48da5f9c3 100644 --- a/src/sdk/clients/decorators/mee/getFeeQuote.ts +++ b/src/sdk/clients/decorators/mee/getFeeQuote.ts @@ -1,9 +1,9 @@ +import type { Hex } from "viem" import type { Address } from "viem/accounts" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" import type { Call } from "../../../account/utils/Types" import type { AnyData, ModularSmartAccount } from "../../../modules/utils/Types" import type { BaseMeeService } from "../../createMeeService" -import type { Hex } from "viem" const DEFAULT_GAS_LIMIT = 1000000n export type GetFeeQuote = AnyData