Skip to content

Commit

Permalink
chore: distributed session keys (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
joepegler authored Nov 13, 2024
1 parent 1bcc8ec commit a1778c2
Show file tree
Hide file tree
Showing 17 changed files with 390 additions and 72 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @biconomy/sdk

## 0.0.10

### Patch Changes

- Added Distributed Session Keys w/ Ownable & Session examples

## 0.0.9

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biconomy/sdk",
"version": "0.0.9",
"version": "0.0.10",
"author": "Biconomy",
"repository": "github:bcnmy/sdk",
"main": "./dist/_cjs/index.js",
Expand Down
2 changes: 2 additions & 0 deletions src/sdk/account/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const DefaultGasLimit = {
}

export const ERROR_MESSAGES = {
KEY_GEN_DATA_NOT_FOUND: "Key generation data is not available",
SIGNATURE_NOT_FOUND: "Signature not found from Dan",
FAILED_COMPUTE_ACCOUNT_ADDRESS:
"Failed to compute account address. Possible reasons:\n" +
"- The factory contract does not have the function 'computeAccountAddress'\n" +
Expand Down
18 changes: 8 additions & 10 deletions src/sdk/clients/createNexusSessionClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe("nexus.session.client", async () => {
account: smartSessionNexusClient.account,
signer: sessionKeyAccount,
moduleData: {
permissionId: cachedPermissionId
permissionIds: [cachedPermissionId]
}
})

Expand All @@ -177,11 +177,10 @@ describe("nexus.session.client", async () => {
)

const userOpHash = await useSmartSessionNexusClient.usePermission({
actions: [
calls: [
{
target: testAddresses.Counter,
value: 0n,
callData: encodeFunctionData({
to: testAddresses.Counter,
data: encodeFunctionData({
abi: CounterAbi,
functionName: "incrementNumber",
args: []
Expand Down Expand Up @@ -212,7 +211,7 @@ describe("nexus.session.client", async () => {
account: nexusClient.account,
signer: sessionKeyAccount,
moduleData: {
permissionId: cachedPermissionId
permissionIds: [cachedPermissionId]
}
})

Expand Down Expand Up @@ -246,11 +245,10 @@ describe("nexus.session.client", async () => {

expect(
useSmartSessionNexusClient.usePermission({
actions: [
calls: [
{
target: testAddresses.Counter,
value: 0n,
callData: encodeFunctionData({
to: testAddresses.Counter,
data: encodeFunctionData({
abi: CounterAbi,
functionName: "decrementNumber"
})
Expand Down
4 changes: 1 addition & 3 deletions src/sdk/clients/createNexusSessionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@ import { type NexusClientConfig, createNexusClient } from "./createNexusClient"
export type NexusSessionClientConfig = NexusClientConfig & {
accountAddress: Address
}
export const createNexusSessionClient = async (
parameters: NexusSessionClientConfig
) => await createNexusClient({ ...parameters })
export const createNexusSessionClient = createNexusClient
10 changes: 4 additions & 6 deletions src/sdk/clients/decorators/dan/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ export function danActions() {
chain extends Chain | undefined
>(
client: Client<Transport, chain, TModularSmartAccount>
): DanActions<TModularSmartAccount> => {
return {
keyGen: (args) => keyGen(client, args),
sigGen: (parameters) => sigGen(client, parameters)
}
}
): DanActions<TModularSmartAccount> => ({
keyGen: (args) => keyGen(client, args),
sigGen: (parameters) => sigGen(client, parameters)
})
}
4 changes: 3 additions & 1 deletion src/sdk/modules/smartSessionsValidator/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ export type SmartSessionModeType =
*/
export type UsePermissionModuleData = {
/** The permission ID for the session. */
permissionId: Hex
permissionIds: Hex[]
/** The mode of the smart session. */
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
}

type OptionalSessionKeyData = OneOf<
Expand Down
28 changes: 25 additions & 3 deletions src/sdk/modules/smartSessionsValidator/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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"
import {
type GrantPermissionParameters,
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.
*
Expand Down Expand Up @@ -54,6 +60,15 @@ export type SmartSessionUseActions<
usePermission: (
args: UsePermissionParameters<TModularSmartAccount>
) => Promise<Hash>
/**
* 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<TModularSmartAccount>
) => Promise<Hash>
}

/**
Expand All @@ -79,17 +94,24 @@ export function smartSessionCreateActions(_: Module) {
* @param smartSessionsModule - The smart sessions module to be set on the client's account.
* @returns A function that takes a client and returns SmartSessionUseActions.
*/
export function smartSessionUseActions(smartSessionsModule: Module) {
export function smartSessionUseActions(
smartSessionsModule: SmartSessionModule
) {
return <TModularSmartAccount extends ModularSmartAccount | undefined>(
client: Client<Transport, Chain | undefined, TModularSmartAccount>
): SmartSessionUseActions<TModularSmartAccount> => {
client?.account?.setModule(smartSessionsModule)
return {
usePermission: (args) => usePermission(client, args)
usePermission: (args) => usePermission(client, args),
useDistributedPermission: (args) => {
const danClient = client.extend(danActions()) as unknown as DanClient
return useDistributedPermission(danClient, args)
}
}
}
}

export * from "./grantPermission"
export * from "./trustAttesters"
export * from "./usePermission"
export * from "./useDistributedPermission"
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe("modules.smartSessions.decorators", async () => {
account: nexusClient.account,
signer: sessionKeyAccount,
moduleData: {
permissionId: "0x"
permissionIds: []
}
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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<ModularSmartAccount> &
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<TModularSmartAccount>
): Promise<Hex> {
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 })
}
24 changes: 9 additions & 15 deletions src/sdk/modules/smartSessionsValidator/decorators/usePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { Chain, Client, Hex, Transport } from "viem"
import { sendUserOperation } from "viem/account-abstraction"
import { getAction } from "viem/utils"
import { AccountNotFoundError } from "../../../account/utils/AccountNotFound"
import type { Call } from "../../../account/utils/Types"
import type { Signer } from "../../../account/utils/toSigner"
import type { Execution, ModularSmartAccount } from "../../utils/Types"
import type { ModularSmartAccount } from "../../utils/Types"

/**
* Parameters for using a smart session to execute actions.
Expand All @@ -14,7 +15,7 @@ export type UsePermissionParameters<
TModularSmartAccount extends ModularSmartAccount | undefined
> = {
/** Array of executions to perform in the session. Allows for batch transactions if the session is enabled for multiple actions. */
actions: Execution[]
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. */
Expand Down Expand Up @@ -43,11 +44,10 @@ export type UsePermissionParameters<
* @example
* ```typescript
* const result = await usePermission(nexusClient, {
* actions: [
* calls: [
* {
* target: '0x1234...',
* value: 0n,
* callData: '0xabcdef...'
* to: '0x1234...',
* data: '0xabcdef...'
* }
* ],
* maxFeePerGas: 1000000000n
Expand All @@ -66,7 +66,7 @@ export async function usePermission<
client: Client<Transport, Chain | undefined, TModularSmartAccount>,
parameters: UsePermissionParameters<TModularSmartAccount>
): Promise<Hex> {
const { account: account_ = client.account, actions, ...rest } = parameters
const { account: account_ = client.account } = parameters

if (!account_) {
throw new AccountNotFoundError({
Expand All @@ -78,12 +78,6 @@ export async function usePermission<
client,
sendUserOperation,
"sendUserOperation"
)({
...rest,
calls: actions.map((action) => ({
to: action.target,
value: BigInt(action.value.toString()),
data: action.callData
}))
})
// @ts-ignore
)({ ...parameters, account: account_ })
}
Loading

0 comments on commit a1778c2

Please sign in to comment.