-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #255 from pimlicolabs/installModules
Add installModules and uninstallModules Functions
- Loading branch information
Showing
10 changed files
with
664 additions
and
12 deletions.
There are no files selected for viewing
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,5 @@ | ||
--- | ||
"permissionless": patch | ||
--- | ||
|
||
Added uninstall modules and install modules functions |
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
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, | ||
maxPriorityFeePerGas: maxPriorityFeePerGas, | ||
callData: installModulesCallData, | ||
nonce: nonce | ||
}, | ||
account: account, | ||
middleware | ||
}) | ||
} |
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
Oops, something went wrong.