-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add installModules and uninstallModules Functions
- Loading branch information
1 parent
bd06cb2
commit 73ba48e
Showing
4 changed files
with
647 additions
and
0 deletions.
There are no files selected for viewing
244 changes: 244 additions & 0 deletions
244
packages/permissionless/actions/erc7579/installModules.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
import { | ||
http, | ||
type Chain, | ||
type Transport, | ||
encodeAbiParameters, | ||
encodePacked, | ||
isHash, | ||
zeroAddress | ||
} from "viem" | ||
import { generatePrivateKey } from "viem/accounts" | ||
import { describe, expect } from "vitest" | ||
import { testWithRpc } from "../../../permissionless-test/src/testWithRpc" | ||
import { | ||
getCoreSmartAccounts, | ||
getPimlicoPaymasterClient | ||
} from "../../../permissionless-test/src/utils" | ||
import type { SmartAccount } from "../../accounts" | ||
import { createBundlerClient } from "../../clients/createBundlerClient" | ||
import type { SmartAccountClient } from "../../clients/createSmartAccountClient" | ||
import type { ENTRYPOINT_ADDRESS_V07_TYPE } from "../../types/entrypoint" | ||
import { ENTRYPOINT_ADDRESS_V07 } from "../../utils" | ||
import { erc7579Actions } from "../erc7579" | ||
import { installModules } from "./installModules" | ||
|
||
describe.each(getCoreSmartAccounts())( | ||
"installmodules $name", | ||
({ getErc7579SmartAccountClient, name }) => { | ||
testWithRpc.skipIf(!getErc7579SmartAccountClient)( | ||
"installModules", | ||
async ({ rpc }) => { | ||
const { anvilRpc, altoRpc, paymasterRpc } = rpc | ||
|
||
if (!getErc7579SmartAccountClient) { | ||
throw new Error("getErc7579SmartAccountClient not defined") | ||
} | ||
|
||
const privateKey = generatePrivateKey() | ||
|
||
const smartClientWithoutExtend: SmartAccountClient< | ||
ENTRYPOINT_ADDRESS_V07_TYPE, | ||
Transport, | ||
Chain, | ||
SmartAccount<ENTRYPOINT_ADDRESS_V07_TYPE> | ||
> = await getErc7579SmartAccountClient({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07, | ||
privateKey: privateKey, | ||
altoRpc: altoRpc, | ||
anvilRpc: anvilRpc, | ||
paymasterClient: getPimlicoPaymasterClient({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07, | ||
paymasterRpc | ||
}) | ||
}) | ||
|
||
const smartClient = smartClientWithoutExtend.extend( | ||
erc7579Actions({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07 | ||
}) | ||
) | ||
|
||
const moduleData = encodePacked( | ||
["address"], | ||
[smartClient.account.address] | ||
) | ||
|
||
const opHash = await installModules(smartClient as any, { | ||
account: smartClient.account as any, | ||
modules: [ | ||
{ | ||
type: "executor", | ||
address: | ||
"0xc98B026383885F41d9a995f85FC480E9bb8bB891", | ||
context: | ||
name === "Kernel 7579" | ||
? encodePacked( | ||
["address", "bytes"], | ||
[ | ||
zeroAddress, | ||
encodeAbiParameters( | ||
[ | ||
{ type: "bytes" }, | ||
{ type: "bytes" } | ||
], | ||
[moduleData, "0x"] | ||
) | ||
] | ||
) | ||
: moduleData | ||
} | ||
] | ||
}) | ||
|
||
const bundlerClientV07 = createBundlerClient({ | ||
transport: http(altoRpc), | ||
entryPoint: ENTRYPOINT_ADDRESS_V07 | ||
}) | ||
|
||
expect(isHash(opHash)).toBe(true) | ||
|
||
const userOperationReceipt = | ||
await bundlerClientV07.waitForUserOperationReceipt({ | ||
hash: opHash, | ||
timeout: 100000 | ||
}) | ||
expect(userOperationReceipt).not.toBeNull() | ||
expect(userOperationReceipt?.userOpHash).toBe(opHash) | ||
expect( | ||
userOperationReceipt?.receipt.transactionHash | ||
).toBeTruthy() | ||
|
||
const receipt = await bundlerClientV07.getUserOperationReceipt({ | ||
hash: opHash | ||
}) | ||
|
||
expect(receipt?.receipt.transactionHash).toBe( | ||
userOperationReceipt?.receipt.transactionHash | ||
) | ||
|
||
const isModuleInstalled = await smartClient.isModuleInstalled({ | ||
type: "executor", | ||
address: "0xc98B026383885F41d9a995f85FC480E9bb8bB891", | ||
context: "0x" | ||
}) | ||
|
||
expect(isModuleInstalled).toBe(true) | ||
} | ||
) | ||
testWithRpc.skipIf(!getErc7579SmartAccountClient)( | ||
"installModule", | ||
async ({ rpc }) => { | ||
const { anvilRpc, altoRpc, paymasterRpc } = rpc | ||
|
||
if (!getErc7579SmartAccountClient) { | ||
throw new Error("getErc7579SmartAccountClient not defined") | ||
} | ||
|
||
const privateKey = generatePrivateKey() | ||
|
||
const smartClientWithoutExtend: SmartAccountClient< | ||
ENTRYPOINT_ADDRESS_V07_TYPE, | ||
Transport, | ||
Chain, | ||
SmartAccount<ENTRYPOINT_ADDRESS_V07_TYPE> | ||
> = await getErc7579SmartAccountClient({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07, | ||
privateKey: privateKey, | ||
altoRpc: altoRpc, | ||
anvilRpc: anvilRpc, | ||
paymasterClient: getPimlicoPaymasterClient({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07, | ||
paymasterRpc | ||
}) | ||
}) | ||
|
||
const smartClient = smartClientWithoutExtend.extend( | ||
erc7579Actions({ | ||
entryPoint: ENTRYPOINT_ADDRESS_V07 | ||
}) | ||
) | ||
|
||
await smartClient.sendTransactions({ | ||
transactions: [ | ||
{ | ||
to: smartClient.account.address, | ||
value: 0n, | ||
data: "0x" | ||
}, | ||
{ | ||
to: smartClient.account.address, | ||
value: 0n, | ||
data: "0x" | ||
} | ||
] | ||
}) | ||
|
||
const moduleData = encodePacked( | ||
["address"], | ||
[smartClient.account.address] | ||
) | ||
|
||
const opHash = await installModules(smartClient as any, { | ||
account: smartClient.account as any, | ||
modules: [ | ||
{ | ||
type: "executor", | ||
address: | ||
"0xc98B026383885F41d9a995f85FC480E9bb8bB891", | ||
context: | ||
name === "Kernel 7579" | ||
? encodePacked( | ||
["address", "bytes"], | ||
[ | ||
zeroAddress, | ||
encodeAbiParameters( | ||
[ | ||
{ type: "bytes" }, | ||
{ type: "bytes" } | ||
], | ||
[moduleData, "0x"] | ||
) | ||
] | ||
) | ||
: moduleData | ||
} | ||
] | ||
}) | ||
|
||
const bundlerClientV07 = createBundlerClient({ | ||
transport: http(altoRpc), | ||
entryPoint: ENTRYPOINT_ADDRESS_V07 | ||
}) | ||
|
||
expect(isHash(opHash)).toBe(true) | ||
|
||
const userOperationReceipt = | ||
await bundlerClientV07.waitForUserOperationReceipt({ | ||
hash: opHash, | ||
timeout: 100000 | ||
}) | ||
expect(userOperationReceipt).not.toBeNull() | ||
expect(userOperationReceipt?.userOpHash).toBe(opHash) | ||
expect( | ||
userOperationReceipt?.receipt.transactionHash | ||
).toBeTruthy() | ||
|
||
const receipt = await bundlerClientV07.getUserOperationReceipt({ | ||
hash: opHash | ||
}) | ||
|
||
expect(receipt?.receipt.transactionHash).toBe( | ||
userOperationReceipt?.receipt.transactionHash | ||
) | ||
|
||
const isModuleInstalled = await smartClient.isModuleInstalled({ | ||
type: "executor", | ||
address: "0xc98B026383885F41d9a995f85FC480E9bb8bB891", | ||
context: "0x" | ||
}) | ||
|
||
expect(isModuleInstalled).toBe(true) | ||
} | ||
) | ||
} | ||
) |
115 changes: 115 additions & 0 deletions
115
packages/permissionless/actions/erc7579/installModules.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { | ||
type Address, | ||
type Chain, | ||
type Client, | ||
type Hex, | ||
type Transport, | ||
encodeFunctionData, | ||
getAddress | ||
} from "viem" | ||
import { getAction } from "viem/utils" | ||
import type { SmartAccount } from "../../accounts/types" | ||
import type { GetAccountParameter, Prettify } from "../../types/" | ||
import type { EntryPoint } from "../../types/entrypoint" | ||
import { parseAccount } from "../../utils/" | ||
import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA" | ||
import type { Middleware } from "../smartAccount/prepareUserOperationRequest" | ||
import { sendUserOperation } from "../smartAccount/sendUserOperation" | ||
import { type ModuleType, parseModuleTypeId } from "./supportsModule" | ||
|
||
export type InstallModulesParameters< | ||
TEntryPoint extends EntryPoint, | ||
TSmartAccount extends SmartAccount<TEntryPoint> | undefined | ||
> = GetAccountParameter<TEntryPoint, TSmartAccount> & | ||
Middleware<TEntryPoint> & { | ||
modules: { | ||
type: ModuleType | ||
address: Address | ||
context: Hex | ||
}[] | ||
maxFeePerGas?: bigint | ||
maxPriorityFeePerGas?: bigint | ||
nonce?: bigint | ||
} | ||
|
||
export async function installModules< | ||
TEntryPoint extends EntryPoint, | ||
TSmartAccount extends SmartAccount<TEntryPoint> | undefined, | ||
TTransport extends Transport = Transport, | ||
TChain extends Chain | undefined = Chain | undefined | ||
>( | ||
client: Client<TTransport, TChain, TSmartAccount>, | ||
parameters: Prettify<InstallModulesParameters<TEntryPoint, TSmartAccount>> | ||
): Promise<Hex> { | ||
const { | ||
account: account_ = client.account, | ||
maxFeePerGas, | ||
maxPriorityFeePerGas, | ||
nonce, | ||
middleware, | ||
modules | ||
} = parameters | ||
|
||
if (!account_) { | ||
throw new AccountOrClientNotFoundError({ | ||
docsPath: "/docs/actions/wallet/sendTransaction" | ||
}) | ||
} | ||
|
||
const account = parseAccount(account_) as SmartAccount<TEntryPoint> | ||
|
||
const installModulesCallData = await account.encodeCallData( | ||
await Promise.all( | ||
modules.map(({ type, address, context }) => ({ | ||
to: account.address, | ||
value: BigInt(0), | ||
data: encodeFunctionData({ | ||
abi: [ | ||
{ | ||
name: "installModule", | ||
type: "function", | ||
stateMutability: "nonpayable", | ||
inputs: [ | ||
{ | ||
type: "uint256", | ||
name: "moduleTypeId" | ||
}, | ||
{ | ||
type: "address", | ||
name: "module" | ||
}, | ||
{ | ||
type: "bytes", | ||
name: "initData" | ||
} | ||
], | ||
outputs: [] | ||
} | ||
], | ||
functionName: "installModule", | ||
args: [ | ||
parseModuleTypeId(type), | ||
getAddress(address), | ||
context | ||
] | ||
}) | ||
})) | ||
) | ||
) | ||
|
||
return getAction( | ||
client, | ||
sendUserOperation<TEntryPoint>, | ||
"sendUserOperation" | ||
)({ | ||
userOperation: { | ||
sender: account.address, | ||
maxFeePerGas: maxFeePerGas || BigInt(0), | ||
maxPriorityFeePerGas: maxPriorityFeePerGas || BigInt(0), | ||
callData: installModulesCallData, | ||
nonce: nonce ? BigInt(nonce) : undefined | ||
}, | ||
account: account, | ||
middleware | ||
}) | ||
} |
Oops, something went wrong.