diff --git a/src/sdk/account/toNexusAccount.ts b/src/sdk/account/toNexusAccount.ts index 4100ffae..36bc58b0 100644 --- a/src/sdk/account/toNexusAccount.ts +++ b/src/sdk/account/toNexusAccount.ts @@ -53,6 +53,7 @@ import { } from "../constants" // Constants import { EntrypointAbi } from "../constants/abi" +import { getCounterFactualAddress as getCounterFactualAddress_ } from "./utils/getCounterFactualAddress" // Modules import { toK1Validator } from "../modules/k1Validator/toK1Validator" @@ -251,47 +252,15 @@ export const toNexusAccount = async ( } } - const addressFromFactory = (await publicClient.readContract({ - address: factoryAddress, - abi: [ - { - inputs: [ - { - internalType: "address", - name: "eoaOwner", - type: "address" - }, - { - internalType: "uint256", - name: "index", - type: "uint256" - }, - { - internalType: "address[]", - name: "attesters", - type: "address[]" - }, - { - internalType: "uint8", - name: "threshold", - type: "uint8" - } - ], - name: "computeAccountAddress", - outputs: [ - { - internalType: "address payable", - name: "expectedAddress", - type: "address" - } - ], - stateMutability: "view", - type: "function" - } - ], - functionName: "computeAccountAddress", - args: [signerAddress, index, attesters_, attesterThreshold] - })) as Address + const addressFromFactory = await getCounterFactualAddress_( + publicClient, + signerAddress, + false, + index, + attesters_, + attesterThreshold, + factoryAddress + ) if (!addressEquals(addressFromFactory, zeroAddress)) { _accountAddress = addressFromFactory diff --git a/src/sdk/clients/createNexusSessionClient.test.ts b/src/sdk/clients/createNexusSessionClient.test.ts index 6a70671c..2806e8cf 100644 --- a/src/sdk/clients/createNexusSessionClient.test.ts +++ b/src/sdk/clients/createNexusSessionClient.test.ts @@ -130,7 +130,7 @@ describe("nexus.session.client", async () => { nexusClient.account.getCounterFactualAddress() const createSessionsResponse = - await nexusSessionClient.grantPermissionAdvanced({ + await nexusSessionClient.grantPermissionInAdvance({ sessionRequestedInfo }) @@ -145,7 +145,8 @@ describe("nexus.session.client", async () => { moduleData: { permissionIds: createSessionsResponse.permissionIds, action: createSessionsResponse.action, - mode: SmartSessionMode.USE + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions } } diff --git a/src/sdk/modules/ownableValidator/toOwnableValidator.ts b/src/sdk/modules/ownableValidator/toOwnableValidator.ts index d7780ae5..33784c07 100644 --- a/src/sdk/modules/ownableValidator/toOwnableValidator.ts +++ b/src/sdk/modules/ownableValidator/toOwnableValidator.ts @@ -19,6 +19,7 @@ import type { ModuleParameters } from "../utils/Types" import { toModule } from "../utils/toModule" +import { getOwnableValidator } from "@rhinestone/module-sdk/module" /** * Parameters for creating an Ownable module. @@ -130,7 +131,10 @@ export const toOwnableValidator = ( type: "nexus" }) - const moduleInitData = getOwnablesModuleInitData(moduleInitArgs_) + const moduleInitData = getOwnableValidator({ + threshold: moduleInitArgs_.threshold, + owners: moduleInitArgs_.owners + }) const initData = initData_ ?? getOwnablesInitData(initArgs_) return toModule({ diff --git a/src/sdk/modules/smartSessionsValidator/Types.ts b/src/sdk/modules/smartSessionsValidator/Types.ts index 78f8d6b6..02b887e0 100644 --- a/src/sdk/modules/smartSessionsValidator/Types.ts +++ b/src/sdk/modules/smartSessionsValidator/Types.ts @@ -1,5 +1,9 @@ import type { Abi, AbiFunction, Address, Hex, OneOf } from "viem" -import type { EnableSessionData, SmartSessionMode } from "../../constants" +import type { + EnableSessionData, + Session, + SmartSessionMode +} from "../../constants" import type { AnyReferenceValue } from "../utils/Helpers" import type { Execution } from "../utils/Types" @@ -26,20 +30,22 @@ export type SessionData = { description?: string } -export type GrantPermissionAdvancedActionReturnParams = { +export type PreparePermissionResponse = { /** Array of permission IDs for the created sessions. */ permissionIds: Hex[] /** The execution object for the action. */ action: Execution + /** The sessions that were created. */ + sessions: Session[] } /** * Represents the response for creating sessions. */ -export type GrantPermissionAdvancedResponse = { +export type GrantPermissionInAdvanceResponse = { /** The hash of the user operation. */ userOpHash: Hex -} & GrantPermissionAdvancedActionReturnParams +} & PreparePermissionResponse /** * Represents the possible modes for a smart session. @@ -57,7 +63,7 @@ export type UsePermissionModuleData = { enableSessionData?: EnableSessionData /** The index of the permission ID to use for the session. Defaults to 0. */ permissionIdIndex?: number -} & GrantPermissionAdvancedActionReturnParams +} & PreparePermissionResponse type OptionalSessionKeyData = OneOf< | { diff --git a/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts b/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts index 007a8a5e..48987113 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/grantPermission.ts @@ -1,10 +1,19 @@ -import type { Chain, Client, Hex, PublicClient, Transport } from "viem" -import { sendUserOperation } from "viem/account-abstraction" +import type { Chain, Client, PublicClient, Transport } from "viem" import { getAction, parseAccount } from "viem/utils" import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" -import type { AnyData, ModularSmartAccount } from "../../utils/Types" -import type { CreateSessionDataParams } from "../Types" +import type { ModularSmartAccount } from "../../utils/Types" +import type { + CreateSessionDataParams, + PreparePermissionResponse, + SessionData +} from "../Types" import { preparePermission } from "./preparePermission" +import { + getAccount, + getEnableSessionDetails, + MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS, + SmartSessionMode +} from "../../../constants" /** * Parameters for creating sessions in a modular smart account. @@ -16,21 +25,13 @@ export type GrantPermissionParameters< > = { /** Array of session data parameters for creating multiple sessions. */ sessionRequestedInfo: CreateSessionDataParams[] - /** 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 /** Optional public client for blockchain interactions. */ publicClient?: PublicClient /** The modular smart account to create sessions for. If not provided, the client's account will be used. */ account?: TModularSmartAccount - /** Optional attesters to trust. */ - attesters?: Hex[] } -export type GrantPermissionResponse = AnyData +export type GrantPermissionResponse = SessionData["moduleData"] /** * Adds multiple sessions to the SmartSessionValidator module of a given smart account. * @@ -81,12 +82,7 @@ export async function grantPermission< client: Client, parameters: GrantPermissionParameters ): Promise { - const { - account: account_ = client.account, - maxFeePerGas, - maxPriorityFeePerGas, - nonce - } = parameters + const { account: account_ = client.account } = parameters if (!account_) { throw new AccountNotFoundError({ @@ -95,6 +91,8 @@ export async function grantPermission< } const account = parseAccount(account_) as ModularSmartAccount + const publicClient = account?.client as PublicClient + if (!account || !account.address) { throw new Error("Account not found") } @@ -105,20 +103,30 @@ export async function grantPermission< "preparePermission" )(parameters) - const userOpHash = await getAction( - client, - sendUserOperation, - "sendUserOperation" - )({ - calls: preparedPermission.calls, - maxFeePerGas, - maxPriorityFeePerGas, - nonce, - account + const nexusAccount = getAccount({ + address: account.address, + type: "nexus" + }) + + const sessionDetailsWithPermissionEnableHash = await getEnableSessionDetails({ + enableMode: SmartSessionMode.UNSAFE_ENABLE, + sessions: preparedPermission.sessions, + account: nexusAccount, + clients: [publicClient], + enableValidatorAddress: MAINNET_ADDRESS_K1_VALIDATOR_ADDRESS }) + const { permissionEnableHash, ...sessionDetails } = + sessionDetailsWithPermissionEnableHash + + sessionDetails.enableSessionData.enableSession.permissionEnableSig = + await account.signer.signMessage({ message: { raw: permissionEnableHash } }) + return { - userOpHash, - ...preparedPermission + permissionIds: preparedPermission.permissionIds, + action: preparedPermission.action, + mode: SmartSessionMode.UNSAFE_ENABLE, + sessions: preparedPermission.sessions, + enableSessionData: sessionDetails.enableSessionData } } diff --git a/src/sdk/modules/smartSessionsValidator/decorators/grantPermissionAdvanced.ts b/src/sdk/modules/smartSessionsValidator/decorators/grantPermissionInAdvance.ts similarity index 85% rename from src/sdk/modules/smartSessionsValidator/decorators/grantPermissionAdvanced.ts rename to src/sdk/modules/smartSessionsValidator/decorators/grantPermissionInAdvance.ts index ba590fd6..60628e72 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/grantPermissionAdvanced.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/grantPermissionInAdvance.ts @@ -5,16 +5,17 @@ import { AccountNotFoundError } from "../../../account/utils/AccountNotFound" import type { ModularSmartAccount } from "../../utils/Types" import type { CreateSessionDataParams, - GrantPermissionAdvancedResponse + GrantPermissionInAdvanceResponse } from "../Types" import { preparePermission } from "./preparePermission" +import type { Call } from "../../../account/utils/Types" /** * Parameters for creating sessions in a modular smart account. * * @template TModularSmartAccount - Type of the modular smart account, extending ModularSmartAccount or undefined. */ -export type GrantPermissionAdvancedParameters< +export type GrantPermissionInAdvanceParameters< TModularSmartAccount extends ModularSmartAccount | undefined > = { /** Array of session data parameters for creating multiple sessions. */ @@ -31,6 +32,8 @@ export type GrantPermissionAdvancedParameters< account?: TModularSmartAccount /** Optional attesters to trust. */ attesters?: Hex[] + /** Additional calls to be included in the user operation. */ + calls?: Call[] } /** @@ -50,9 +53,9 @@ export type GrantPermissionAdvancedParameters< * * @example * ```typescript - * import { grantPermissionAdvanced } from '@biconomy/sdk' + * import { grantPermissionInAdvance } from '@biconomy/sdk' * - * const result = await grantPermissionAdvanced(nexusClient, { + * const result = await grantPermissionInAdvance(nexusClient, { * sessionRequestedInfo: [ * { * sessionKeyData: '0x...', @@ -77,17 +80,18 @@ export type GrantPermissionAdvancedParameters< * - The number of sessions created is determined by the length of the `sessionRequestedInfo` array. * - Each session's policies and permissions are determined by the `actionPoliciesInfo` provided. */ -export async function grantPermissionAdvanced< +export async function grantPermissionInAdvance< TModularSmartAccount extends ModularSmartAccount | undefined >( client: Client, - parameters: GrantPermissionAdvancedParameters -): Promise { + parameters: GrantPermissionInAdvanceParameters +): Promise { const { account: account_ = client.account, maxFeePerGas, maxPriorityFeePerGas, - nonce + nonce, + calls: calls_ } = parameters if (!account_) { @@ -112,7 +116,13 @@ export async function grantPermissionAdvanced< sendUserOperation, "sendUserOperation" )({ - calls: preparedPermission.calls, + calls: [ + { + to: preparedPermission.action.target, + data: preparedPermission.action.callData + }, + ...(calls_ || []) + ], maxFeePerGas, maxPriorityFeePerGas, nonce, diff --git a/src/sdk/modules/smartSessionsValidator/decorators/index.ts b/src/sdk/modules/smartSessionsValidator/decorators/index.ts index 63cbc505..435ce705 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/index.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/index.ts @@ -1,18 +1,20 @@ import type { Chain, Client, Hash, Transport } from "viem" import type { ModularSmartAccount, Module } from "../../utils/Types" -import type { GrantPermissionAdvancedResponse } from "../Types" +import type { + GrantPermissionInAdvanceResponse, + PreparePermissionResponse +} from "../Types" import type { SmartSessionModule } from "../toSmartSessionsValidator" import { type GrantPermissionParameters, grantPermission } from "./grantPermission.js" import { - type GrantPermissionAdvancedParameters, - grantPermissionAdvanced -} from "./grantPermissionAdvanced.js" + type GrantPermissionInAdvanceParameters, + grantPermissionInAdvance +} from "./grantPermissionInAdvance.js" import { type PreparePermissionParameters, - type PreparePermissionResponse, preparePermission } from "./preparePermission.js" import { type TrustAttestersParameters, trustAttesters } from "./trustAttesters" @@ -27,7 +29,7 @@ export type SmartSessionCreateActions< > = { /** * Creates multiple sessions for a modular smart account. - * This differs from grantPermissionAdvanced in that it defers the moment that the permission is granted + * This differs from grantPermissionInAdvance in that it defers the moment that the permission is granted * on chain to the moment that the redemption user operation is sent/redeemed. It is also known as "ENABLE_MODE". * It is the default mode for the grantPermission function. * @@ -45,9 +47,9 @@ export type SmartSessionCreateActions< * @param args - Parameters for creating sessions. * @returns A promise that resolves to the creation response. */ - grantPermissionAdvanced: ( - args: GrantPermissionAdvancedParameters - ) => Promise + grantPermissionInAdvance: ( + args: GrantPermissionInAdvanceParameters + ) => Promise /** * Trusts attesters for a modular smart account. @@ -100,7 +102,8 @@ export function smartSessionCreateActions(_: Module) { ): SmartSessionCreateActions => { return { grantPermission: (args) => grantPermission(client, args), - grantPermissionAdvanced: (args) => grantPermissionAdvanced(client, args), + grantPermissionInAdvance: (args) => + grantPermissionInAdvance(client, args), trustAttesters: (args) => trustAttesters(client, args), preparePermission: (args) => preparePermission(client, args) } @@ -129,4 +132,4 @@ export function smartSessionUseActions( export * from "./grantPermission" export * from "./trustAttesters" export * from "./usePermission" -export * from "./grantPermissionAdvanced" +export * from "./grantPermissionInAdvance.js" diff --git a/src/sdk/modules/smartSessionsValidator/decorators/preparePermission.ts b/src/sdk/modules/smartSessionsValidator/decorators/preparePermission.ts index 72983008..1568ae39 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/preparePermission.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/preparePermission.ts @@ -31,7 +31,7 @@ import { import type { CreateSessionDataParams, FullCreateSessionDataParams, - GrantPermissionAdvancedActionReturnParams, + PreparePermissionResponse, ResolvedActionPolicyInfo } from "../Types" @@ -57,10 +57,6 @@ export type PreparePermissionParameters< account?: TModularSmartAccount } -export type PreparePermissionResponse = { - calls: Call[] -} & GrantPermissionAdvancedActionReturnParams - /** * Generates the action data for creating sessions in the SmartSessionValidator. * @@ -76,7 +72,7 @@ export const getPermissionAction = async ({ chainId: number sessionRequestedInfo: FullCreateSessionDataParams[] client: PublicClient -}): Promise => { +}): Promise => { const sessions: Session[] = [] const permissionIds: Hex[] = [] @@ -204,7 +200,8 @@ export const getPermissionAction = async ({ value: BigInt(0), callData: preparePermissionData }, - permissionIds: permissionIds + permissionIds: permissionIds, + sessions } } @@ -290,23 +287,9 @@ export async function preparePermission< sessionRequestedInfo: defaultedSessionRequestedInfo }) - if (!("action" in actionResponse)) { - throw new Error("Error getting enable sessions action") - } - - const { action } = actionResponse - - if (!("callData" in action)) { - throw new Error("Error getting enable sessions action") + if (actionResponse instanceof Error) { + throw actionResponse } - const calls: Call[] = [] - - calls.push({ - to: action.target, - value: action.value, - data: action.callData - }) - - return { ...actionResponse, calls } + return actionResponse } diff --git a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts index 4f65a82a..4f8c2f16 100644 --- a/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts +++ b/src/sdk/modules/smartSessionsValidator/decorators/smartSessions.decorators.test.ts @@ -101,8 +101,9 @@ describe("modules.smartSessions.decorators", async () => { ) expect(nexusSessionClient).toBeDefined() - expect(nexusSessionClient.grantPermissionAdvanced).toBeTypeOf("function") + expect(nexusSessionClient.grantPermissionInAdvance).toBeTypeOf("function") expect(nexusSessionClient.trustAttesters).toBeTypeOf("function") + expect(nexusSessionClient.grantPermissionInAdvance).toBeTypeOf("function") }) test("should test use smart session decorators", async () => { @@ -126,32 +127,4 @@ describe("modules.smartSessions.decorators", async () => { expect(nexusSessionClient).toBeDefined() expect(nexusSessionClient.usePermission).toBeTypeOf("function") }) - - test("should test prepare permission", async () => { - const preparePermissionResponse = await preparePermission(nexusClient, { - sessionRequestedInfo: [ - { - sessionPublicKey, // Public key of the session - // sessionValidUntil: number - // sessionValidAfter: number - // chainIds: bigint[] - actionPoliciesInfo: [ - { - abi: CounterAbi, - contractAddress: testAddresses.Counter - // validUntil?: number - // validAfter?: number - // valueLimit?: bigint - } - ] - } - ] - }) - - sessionsModule = toSmartSessionsValidator({ - account: nexusClient.account, - signer: eoaAccount, - initData: "0x" - }) - }) }) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts index f361c384..a1b19e46 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionValidator.enable.mode.test.ts @@ -9,14 +9,8 @@ import { createPublicClient, createWalletClient, encodeFunctionData, - encodePacked, getAddress } from "viem" -import { - entryPoint07Address, - getUserOperationHash, - toPackedUserOperation -} from "viem/account-abstraction" import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { beforeAll, describe, expect, test } from "vitest" import { CounterAbi } from "../../../test/__contracts/abi/CounterAbi" @@ -25,7 +19,6 @@ import { toNetwork } from "../../../test/testSetup" import { getTestParamsForTestnet } from "../../../test/testUtils" import type { NetworkConfig, TestnetParams } from "../../../test/testUtils" import { type NexusAccount, toNexusAccount } from "../../account/toNexusAccount" -import { deepHexlify } from "../../account/utils/deepHexlify" import { type NexusClient, createSmartAccountClient @@ -44,12 +37,15 @@ import { getSmartSessionsValidator, getSudoPolicy } from "../../constants" -import { generateSalt } from "./Helpers" +import { generateSalt, parse, stringify } from "./Helpers" +import { toSmartSessionsValidator } from "./toSmartSessionsValidator" +import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" +import type { SessionData } from "./Types" describe("modules.smartSessions.enable.mode.dx", async () => { let network: NetworkConfig // Required for "TESTNET_FROM_ENV_VARS" networks - let testParams: TestnetParams + let testnetParams: TestnetParams let chain: Chain let bundlerUrl: string @@ -67,6 +63,8 @@ describe("modules.smartSessions.enable.mode.dx", async () => { let sessionKeyAccount: LocalAccount let sessionPublicKey: Address + let stringifiedSessionDatum: string + beforeAll(async () => { network = await toNetwork("TESTNET_FROM_ENV_VARS") @@ -91,14 +89,14 @@ describe("modules.smartSessions.enable.mode.dx", async () => { transport: http() }) - testParams = getTestParamsForTestnet(publicClient) + testnetParams = getTestParamsForTestnet(publicClient) nexusAccount = await toNexusAccount({ index: 1n, signer: eoaAccount, chain, transport: http(), - ...testParams + ...testnetParams }) nexusAccountAddress = await nexusAccount.getCounterFactualAddress() @@ -110,44 +108,115 @@ describe("modules.smartSessions.enable.mode.dx", async () => { chain, transport: http(), bundlerTransport: http(bundlerUrl), - ...testParams + ...testnetParams }) }) - test.skip("should send a sponsored transaction", async () => { - // Get initial balance - const initialBalance = await publicClient.getBalance({ - address: nexusAccountAddress + test("should support enable mode by default", async () => { + const sessionsModule = toSmartSessionsValidator({ + account: nexusClient.account, + signer: eoaAccount + }) + + const isInstalled = await nexusClient.isModuleInstalled({ + module: sessionsModule.moduleInitData }) - if (initialBalance === 0n) { - console.log("Fund account", nexusAccountAddress) + if (!isInstalled) { + const opHash = await nexusClient.installModule({ + module: sessionsModule.moduleInitData + }) + const installReceipt = await nexusClient.waitForUserOperationReceipt({ + hash: opHash + }) + expect(installReceipt.success).toBe("true") } - // Send user operation - const hash = await nexusClient.sendTransaction({ - calls: [ + // Extend the Nexus client with smart session creation actions + const nexusSessionClient = nexusClient.extend( + smartSessionCreateActions(sessionsModule) + ) + + const moduleData = await nexusSessionClient.grantPermission({ + sessionRequestedInfo: [ { - to: recipientAddress, - value: 1n + sessionPublicKey, // Public key of the session + // sessionValidUntil: number + // sessionValidAfter: number + // chainIds: bigint[] + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter + // validUntil?: number + // validAfter?: number + // valueLimit?: bigint + } + ] } ] }) - // Wait for the transaction to be mined - const { status } = await publicClient.waitForTransactionReceipt({ hash }) - expect(status).toBe("success") - // Get final balance - const finalBalance = await publicClient.getBalance({ - address: nexusAccountAddress + const sessionData: SessionData = { + granter: nexusClient.account.address, + sessionPublicKey, + description: `Permission to increment a counter for ${testAddresses.Counter}`, + moduleData + } + + stringifiedSessionDatum = stringify(sessionData) + }) + + test("should be able to use the session data to create a new smart session client", async () => { + const usersSessionData = parse(stringifiedSessionDatum) as SessionData + + // 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 createSmartAccountClient({ + index: 1n, + accountAddress: usersSessionData.granter, + signer: eoaAccount, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl), + ...testnetParams + }) + + // Create a new smart sessions module with the session key + const usePermissionsModule = toSmartSessionsValidator({ + account: smartSessionNexusClient.account, + signer: sessionKeyAccount, + moduleData: usersSessionData.moduleData + }) + + // Extend the session client with smart session use actions + const useSmartSessionNexusClient = smartSessionNexusClient.extend( + smartSessionUseActions(usePermissionsModule) + ) + + const userOpHash = await useSmartSessionNexusClient.usePermission({ + calls: [ + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "decrementNumber" + }) + } + ] }) - // Check that the balance hasn't changed - // No gas fees were paid, so the balance should have decreased only by 1n - expect(finalBalance).toBeLessThan(initialBalance) + expect(userOpHash).toBeDefined() + + const receipt = + await useSmartSessionNexusClient.waitForUserOperationReceipt({ + hash: userOpHash + }) + + expect(receipt.success).toBe("true") }) - test("should support smart sessions enable mode", async () => { + test.skip("should support smart sessions enable mode", async () => { const uninitializedSmartSessions = getSmartSessionsValidator({}) const isInstalled = await nexusClient.isModuleInstalled({ @@ -246,8 +315,6 @@ describe("modules.smartSessions.enable.mode.dx", async () => { message: { raw: userOpHashToSign } }) - console.log(3, { sessionDetails }) - userOperation.signature = encodeSmartSessionSignature(sessionDetails) const userOpHash = await nexusClient.sendUserOperation(userOperation) @@ -256,8 +323,6 @@ describe("modules.smartSessions.enable.mode.dx", async () => { hash: userOpHash }) - console.log({ receipt }) - expect(receipt.success).toBe("true") }) }) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.advanced.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.advanced.test.ts new file mode 100644 index 00000000..5d2330bb --- /dev/null +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.advanced.test.ts @@ -0,0 +1,220 @@ +import { + http, + type Address, + type Chain, + type Hex, + type LocalAccount, + encodeFunctionData +} from "viem" +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" +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 { + type NexusClient, + createSmartAccountClient +} from "../../clients/createSmartAccountClient" +import { SmartSessionMode } from "../../constants" +import type { Module } from "../utils/Types" +import { parse, stringify } from "./Helpers" +import type { SessionData } from "./Types" +import { smartSessionCreateActions, smartSessionUseActions } from "./decorators" +import { toSmartSessionsValidator } from "./toSmartSessionsValidator" + +describe("modules.smartSessions.dx", async () => { + let network: NetworkConfig + let chain: Chain + let bundlerUrl: string + + // Test utils + let testClient: MasterClient + let eoaAccount: LocalAccount + let usersNexusClient: NexusClient + let sessionKeyAccount: LocalAccount + let sessionPublicKey: Address + + let stringifiedSessionDatum: string + let sessionsModule: Module + + beforeAll(async () => { + network = await toNetwork("BESPOKE_ANVIL_NETWORK_FORKING_BASE_SEPOLIA") + + chain = network.chain + bundlerUrl = network.bundlerUrl + eoaAccount = getTestAccount(0) + sessionKeyAccount = privateKeyToAccount(generatePrivateKey()) // Generally belongs to the dapp + sessionPublicKey = sessionKeyAccount.address + testClient = toTestClient(chain, getTestAccount(5)) + }) + + afterAll(async () => { + await killNetwork([network?.rpcPort, network?.bundlerPort]) + }) + + /** + * This test demonstrates the creation and use of a smart session from two perspectives: + * + * 1. User Perspective (first test): + * - Create a Nexus client for the user's account + * - Install the smart sessions module on the user's account + * - Create a smart session with specific permissions + * + * 2. Dapp Perspective (second test): + * - Simulate a scenario where the user has left the dapp + * - Create a new Nexus client using the session key + * - Use the session to perform actions on behalf of the user + * + * This test showcases how smart sessions enable controlled, delegated actions + * on a user's smart account, even after the user is no longer actively engaged. + */ + test("should demonstrate creating a smart session from user's perspective", async () => { + // User Perspective: Creating and setting up the smart session + + // Create a Nexus client for the main account (eoaAccount) + // This client will be used to interact with the smart contract account + usersNexusClient = await createSmartAccountClient({ + signer: eoaAccount, + chain, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + // Fund the account and deploy the smart contract wallet + await fundAndDeployClients(testClient, [usersNexusClient]) + + // Create a smart sessions module for the user's account + sessionsModule = toSmartSessionsValidator({ + account: usersNexusClient.account, + signer: eoaAccount + }) + + // Install the smart sessions module on the Nexus client's smart contract account + const hash = await usersNexusClient.installModule({ + module: sessionsModule.moduleInitData + }) + + // Wait for the module installation transaction to be mined and check its success + const { success: installSuccess } = + await usersNexusClient.waitForUserOperationReceipt({ hash }) + + // Extend the Nexus client with smart session creation actions + const nexusSessionClient = usersNexusClient.extend( + smartSessionCreateActions(sessionsModule) + ) + + expect(installSuccess).toBe(true) + + // Define the session parameters + // This includes the session key, validator, and action policies + const createSessionsResponse = + await nexusSessionClient.grantPermissionInAdvance({ + sessionRequestedInfo: [ + { + sessionPublicKey, // Public key of the session + // sessionValidUntil: number + // sessionValidAfter: number + // chainIds: bigint[] + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter + // validUntil?: number + // validAfter?: number + // valueLimit?: bigint + } + ] + } + ] + }) + + // Wait for the session creation transaction to be mined and check its success + const { success: sessionCreateSuccess } = + await usersNexusClient.waitForUserOperationReceipt({ + hash: createSessionsResponse.userOpHash + }) + + expect(sessionCreateSuccess).toBe(true) + + // Prepare the session data to be stored by the dApp. This could be saved in a Database by the dApp, or client side in local storage. + const sessionData: SessionData = { + granter: usersNexusClient.account.address, + sessionPublicKey, + description: `Session to increment a counter for ${testAddresses.Counter}`, + moduleData: { + permissionIds: createSessionsResponse.permissionIds, + action: createSessionsResponse.action, + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions + } + } + + // Zip the session data, and store it for later use by a dapp + stringifiedSessionDatum = stringify(sessionData) + }, 200000) + + test("should demonstrate using a smart session from dapp's perspective", async () => { + // Now assume the user has left the dapp and the usersNexusClient signer is no longer available + // The following code demonstrates how a dapp can use the session to act on behalf of the user + + // Unzip the session data + const usersSessionData = parse(stringifiedSessionDatum) + + // 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 createSmartAccountClient({ + chain, + accountAddress: usersSessionData.granter, + signer: sessionKeyAccount, + transport: http(), + bundlerTransport: http(bundlerUrl) + }) + + // Create a new smart sessions module with the session key + const usePermissionsModule = toSmartSessionsValidator({ + account: smartSessionNexusClient.account, + signer: sessionKeyAccount, + moduleData: usersSessionData.moduleData + }) + + // Extend the session client with smart session use actions + const useSmartSessionNexusClient = smartSessionNexusClient.extend( + smartSessionUseActions(usePermissionsModule) + ) + + // Use the session to perform an action (increment and decrement the counter using the same permissionId) + const userOpHash = await useSmartSessionNexusClient.usePermission({ + calls: [ + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "incrementNumber" + }) + }, + { + to: testAddresses.Counter, + data: encodeFunctionData({ + abi: CounterAbi, + functionName: "decrementNumber" + }) + } + ] + }) + + // Wait for the action to be mined and check its success + const { success: sessionUseSuccess } = + await useSmartSessionNexusClient.waitForUserOperationReceipt({ + hash: userOpHash + }) + + expect(sessionUseSuccess).toBe(true) + }, 200000) // Test timeout set to 60 seconds +}) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts index b07b2700..0a626e58 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.dx.test.ts @@ -112,54 +112,38 @@ describe("modules.smartSessions.dx", async () => { expect(installSuccess).toBe(true) - // Define the session parameters - // This includes the session key, validator, and action policies - const createSessionsResponse = - await nexusSessionClient.grantPermissionAdvanced({ - sessionRequestedInfo: [ - { - sessionPublicKey, // Public key of the session - // sessionValidUntil: number - // sessionValidAfter: number - // chainIds: bigint[] - actionPoliciesInfo: [ - { - abi: CounterAbi, - contractAddress: testAddresses.Counter - // validUntil?: number - // validAfter?: number - // valueLimit?: bigint - } - ] - } - ] - }) - - // Wait for the session creation transaction to be mined and check its success - const { success: sessionCreateSuccess } = - await usersNexusClient.waitForUserOperationReceipt({ - hash: createSessionsResponse.userOpHash - }) - - expect(sessionCreateSuccess).toBe(true) + const moduleData = await nexusSessionClient.grantPermission({ + sessionRequestedInfo: [ + { + sessionPublicKey, // Public key of the session + // sessionValidUntil: number + // sessionValidAfter: number + // chainIds: bigint[] + actionPoliciesInfo: [ + { + abi: CounterAbi, + contractAddress: testAddresses.Counter + // validUntil?: number + // validAfter?: number + // valueLimit?: bigint + } + ] + } + ] + }) - // Prepare the session data to be stored by the dApp. This could be saved in a Database by the dApp, or client side in local storage. const sessionData: SessionData = { granter: usersNexusClient.account.address, sessionPublicKey, - description: `Session to increment a counter for ${testAddresses.Counter}`, - moduleData: { - permissionIds: createSessionsResponse.permissionIds, - action: createSessionsResponse.action, - mode: SmartSessionMode.USE - } + description: `Permission to increment a counter for ${testAddresses.Counter}`, + moduleData } // Zip the session data, and store it for later use by a dapp stringifiedSessionDatum = stringify(sessionData) }, 200000) - test("should demonstrate using a smart session from dapp's perspective", async () => { + test.skip("should demonstrate using a smart session from dapp's perspective", async () => { // Now assume the user has left the dapp and the usersNexusClient signer is no longer available // The following code demonstrates how a dapp can use the session to act on behalf of the user @@ -187,17 +171,16 @@ describe("modules.smartSessions.dx", async () => { const useSmartSessionNexusClient = smartSessionNexusClient.extend( smartSessionUseActions(usePermissionsModule) ) - - // Use the session to perform an action (increment and decrement the counter using the same permissionId) + const byteCode = await testClient.getCode({ + address: testAddresses.Counter + }) + console.log( + "testAddresses.Counter", + testAddresses.Counter, + byteCode?.length + ) const userOpHash = await useSmartSessionNexusClient.usePermission({ calls: [ - { - to: testAddresses.Counter, - data: encodeFunctionData({ - abi: CounterAbi, - functionName: "incrementNumber" - }) - }, { to: testAddresses.Counter, data: encodeFunctionData({ @@ -208,12 +191,13 @@ describe("modules.smartSessions.dx", async () => { ] }) - // Wait for the action to be mined and check its success - const { success: sessionUseSuccess } = + expect(userOpHash).toBeDefined() + + const receipt = await useSmartSessionNexusClient.waitForUserOperationReceipt({ hash: userOpHash }) - expect(sessionUseSuccess).toBe(true) + expect(receipt.success).toBe(true) }, 200000) // Test timeout set to 60 seconds }) diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts index 85e82e65..8d85aa44 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.policies.test.ts @@ -114,7 +114,7 @@ describe("modules.smartSessions.policies", async () => { // Define the session parameters // This includes the session key, validator, and action policies const createSessionsResponse = - await nexusSessionClient.grantPermissionAdvanced({ + await nexusSessionClient.grantPermissionInAdvance({ sessionRequestedInfo: [ { sessionPublicKey, // Public key of the session diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts index 86ee5546..663cf653 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.sudo.policy.test.ts @@ -100,7 +100,7 @@ describe("modules.smartSessions.sudo.policy", async () => { ) const createSessionsResponse = - await usersNexusClient.grantPermissionAdvanced({ + await usersNexusClient.grantPermissionInAdvance({ sessionRequestedInfo: [ { sessionPublicKey, @@ -134,7 +134,8 @@ describe("modules.smartSessions.sudo.policy", async () => { moduleData: { permissionIds: createSessionsResponse.permissionIds, action: createSessionsResponse.action, - mode: SmartSessionMode.USE + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions } } diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts index 9358155c..12d0e133 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.test.ts @@ -239,10 +239,9 @@ describe("modules.smartSessions", async () => { const nexusSessionClient = nexusClient.extend( smartSessionCreateActions(sessionsModule) ) - console.log({ sessionRequestedInfo }) const createSessionsResponse = - await nexusSessionClient.grantPermissionAdvanced({ + await nexusSessionClient.grantPermissionInAdvance({ sessionRequestedInfo }) @@ -258,7 +257,8 @@ describe("modules.smartSessions", async () => { moduleData: { permissionIds: createSessionsResponse.permissionIds, action: createSessionsResponse.action, - mode: SmartSessionMode.USE + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions } } diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts index 834d77b7..e33e3f67 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.ts @@ -2,7 +2,9 @@ import { type Address, type Hex, encodePacked } from "viem" import { SMART_SESSIONS_ADDRESS, SmartSessionMode, - encodeSmartSessionSignature + encodeSmartSessionSignature, + getOwnableValidatorMockSignature, + getSmartSessionsValidator } from "../../constants" import type { ModuleMeta } from "../../modules/utils/Types" import type { ModularSmartAccount } from "../utils/Types" @@ -107,8 +109,7 @@ export const toSmartSessionsValidator = ( } = parameters const initData = initData_ ?? getUsePermissionInitData(initArgs_) - const moduleInitData = - moduleInitData_ ?? getUsePermissionModuleInitData(moduleInitArgs_) + const moduleInitData = moduleInitData_ ?? getSmartSessionsValidator({}) return toModule({ ...parameters, @@ -123,10 +124,12 @@ export const toSmartSessionsValidator = ( mode, permissionId: permissionIds[permissionIdIndex], enableSessionData, - signature: DUMMY_ECDSA_SIG + signature: getOwnableValidatorMockSignature({ + threshold: 1 + }) }), - signUserOpHash: async (userOpHash: Hex) => - encodeSmartSessionSignature({ + signUserOpHash: async (userOpHash: Hex) => { + return encodeSmartSessionSignature({ mode, permissionId: permissionIds[permissionIdIndex], enableSessionData, @@ -134,5 +137,6 @@ export const toSmartSessionsValidator = ( message: { raw: userOpHash as Hex } }) }) + } }) as SmartSessionModule } diff --git a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts index e703e09a..52cb6860 100644 --- a/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts +++ b/src/sdk/modules/smartSessionsValidator/toSmartSessionsValidator.uni.policy.test.ts @@ -210,7 +210,7 @@ describe("modules.smartSessions.uni.policy", async () => { ] const createSessionsResponse = - await smartSessionNexusClient.grantPermissionAdvanced({ + await smartSessionNexusClient.grantPermissionInAdvance({ sessionRequestedInfo }) @@ -224,7 +224,8 @@ describe("modules.smartSessions.uni.policy", async () => { moduleData: { permissionIds: createSessionsResponse.permissionIds, action: createSessionsResponse.action, - mode: SmartSessionMode.USE + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions } } diff --git a/src/test/playground.test.ts b/src/test/playground.test.ts index 55858f38..1e27ed92 100644 --- a/src/test/playground.test.ts +++ b/src/test/playground.test.ts @@ -215,7 +215,7 @@ describe.skipIf(!playgroundTrue())("playground", () => { ) const createSessionsResponse = - await nexusSessionClient.grantPermissionAdvanced({ + await nexusSessionClient.grantPermissionInAdvance({ sessionRequestedInfo }) @@ -234,7 +234,8 @@ describe.skipIf(!playgroundTrue())("playground", () => { moduleData: { permissionIds: createSessionsResponse.permissionIds, action: createSessionsResponse.action, - mode: SmartSessionMode.USE + mode: SmartSessionMode.USE, + sessions: createSessionsResponse.sessions } }