Skip to content

Commit

Permalink
Merge pull request #31 from pimlicolabs/feat/account-system
Browse files Browse the repository at this point in the history
Create simple account client
  • Loading branch information
kristofgazso authored Nov 16, 2023
2 parents 0da9bae + aabe479 commit 5bc4d56
Show file tree
Hide file tree
Showing 61 changed files with 2,807 additions and 389 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-dolls-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"permissionless": patch
---

Added support for SimpleAccount management
4 changes: 2 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"formatter": {
"enabled": true,
"formatWithErrors": true,
"lineWidth": 120,
"indentSize": 4,
"lineWidth": 80,
"indentWidth": 4,
"indentStyle": "space"
},
"javascript": {
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@changesets/cli": "^2.26.2",
"@size-limit/esbuild-why": "^9.0.0",
"@size-limit/preset-small-lib": "^9.0.0",
"bun-types": "^1.0.3",
"bun-types": "^1.0.7",
"rimraf": "^5.0.1",
"simple-git-hooks": "^2.9.0",
"size-limit": "^9.0.0",
Expand Down
14 changes: 14 additions & 0 deletions src/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
type PrivateKeySimpleSmartAccount,
SignTransactionNotSupportedBySmartAccount,
privateKeyToSimpleSmartAccount
} from "./privateKeyToSimpleSmartAccount.js"

import { type SmartAccount } from "./types.js"

export {
SignTransactionNotSupportedBySmartAccount,
type PrivateKeySimpleSmartAccount,
privateKeyToSimpleSmartAccount,
type SmartAccount
}
220 changes: 220 additions & 0 deletions src/accounts/privateKeyToSimpleSmartAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import {
type Address,
BaseError,
type Chain,
type Client,
type Hex,
type Transport,
concatHex,
encodeFunctionData
} from "viem"
import { privateKeyToAccount, toAccount } from "viem/accounts"
import { getBytecode, getChainId } from "viem/actions"
import { getAccountNonce } from "../actions/public/getAccountNonce.js"
import { getSenderAddress } from "../actions/public/getSenderAddress.js"
import { getUserOperationHash } from "../utils/getUserOperationHash.js"
import { type SmartAccount } from "./types.js"

export class SignTransactionNotSupportedBySmartAccount extends BaseError {
override name = "SignTransactionNotSupportedBySmartAccount"
constructor({ docsPath }: { docsPath?: string } = {}) {
super(
[
"A smart account cannot sign or send transaction, it can only sign message or userOperation.",
"Please send user operation instead."
].join("\n"),
{
docsPath,
docsSlug: "account"
}
)
}
}

export type PrivateKeySimpleSmartAccount<
transport extends Transport = Transport,
chain extends Chain | undefined = Chain | undefined
> = SmartAccount<"privateKeySimpleSmartAccount", transport, chain>

const getAccountInitCode = async (
factoryAddress: Address,
owner: Address,
index = 0n
): Promise<Hex> => {
if (!owner) throw new Error("Owner account not found")

return concatHex([
factoryAddress,
encodeFunctionData({
abi: [
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address"
},
{
internalType: "uint256",
name: "salt",
type: "uint256"
}
],
name: "createAccount",
outputs: [
{
internalType: "contract SimpleAccount",
name: "ret",
type: "address"
}
],
stateMutability: "nonpayable",
type: "function"
}
],
functionName: "createAccount",
args: [owner, index]
}) as Hex
])
}

const getAccountAddress = async <
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined
>({
client,
factoryAddress,
entryPoint,
owner
}: {
client: Client<TTransport, TChain>
factoryAddress: Address
owner: Address
entryPoint: Address
}): Promise<Address> => {
const initCode = await getAccountInitCode(factoryAddress, owner)

return getSenderAddress(client, {
initCode,
entryPoint
})
}

/**
* @description Creates an Simple Account from a private key.
*
* @returns A Private Key Simple Account.
*/
export async function privateKeyToSimpleSmartAccount<
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined
>(
client: Client<TTransport, TChain>,
{
privateKey,
factoryAddress,
entryPoint
}: {
privateKey: Hex
factoryAddress: Address
entryPoint: Address
}
): Promise<PrivateKeySimpleSmartAccount<TTransport, TChain>> {
const privateKeyAccount = privateKeyToAccount(privateKey)

const [accountAddress, chainId] = await Promise.all([
getAccountAddress<TTransport, TChain>({
client,
factoryAddress,
entryPoint,
owner: privateKeyAccount.address
}),
getChainId(client)
])

if (!accountAddress) throw new Error("Account address not found")

const account = toAccount({
address: accountAddress,
async signMessage({ message }) {
return privateKeyAccount.signMessage({ message })
},
async signTransaction(_, __) {
throw new SignTransactionNotSupportedBySmartAccount()
},
async signTypedData(typedData) {
return privateKeyAccount.signTypedData({ ...typedData, privateKey })
}
})

return {
...account,
client: client,
publicKey: accountAddress,
entryPoint: entryPoint,
source: "privateKeySimpleSmartAccount",
async getNonce() {
return getAccountNonce(client, {
sender: accountAddress,
entryPoint: entryPoint
})
},
async signUserOperation(userOperation) {
return account.signMessage({
message: {
raw: getUserOperationHash({
userOperation,
entryPoint: entryPoint,
chainId: chainId
})
}
})
},
async getInitCode() {
const contractCode = await getBytecode(client, {
address: accountAddress
})

if ((contractCode?.length ?? 0) > 2) return "0x"

return getAccountInitCode(factoryAddress, privateKeyAccount.address)
},
async encodeDeployCallData(_) {
throw new Error("Simple account doesn't support account deployment")
},
async encodeCallData({ to, value, data }) {
return encodeFunctionData({
abi: [
{
inputs: [
{
internalType: "address",
name: "dest",
type: "address"
},
{
internalType: "uint256",
name: "value",
type: "uint256"
},
{
internalType: "bytes",
name: "func",
type: "bytes"
}
],
name: "execute",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}
],
functionName: "execute",
args: [to, value, data]
})
},
async getDummySignature() {
return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"
}
}
}
33 changes: 33 additions & 0 deletions src/accounts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {
Abi,
Address,
Client,
GetConstructorArgs,
Hex,
LocalAccount
} from "viem"
import type { Chain, Transport } from "viem"
import { type UserOperation } from "../types/index.js"

export type SmartAccount<
Name extends string = string,
transport extends Transport = Transport,
chain extends Chain | undefined = Chain | undefined
> = LocalAccount<Name> & {
client: Client<transport, chain>
entryPoint: Address
getNonce: () => Promise<bigint>
getInitCode: () => Promise<Hex>
encodeCallData: ({
to,
value,
data
}: { to: Address; value: bigint; data: Hex }) => Promise<Hex>
getDummySignature(): Promise<Hex>
encodeDeployCallData: <TAbi extends Abi | readonly unknown[] = Abi>({
abi,
args,
bytecode
}: { abi: TAbi; bytecode: Hex } & GetConstructorArgs<TAbi>) => Promise<Hex>
signUserOperation: (UserOperation: UserOperation) => Promise<Hex>
}
2 changes: 1 addition & 1 deletion src/actions/bundler/chainId.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BundlerClient } from "../../clients/bundler.js"
import type { BundlerClient } from "../../clients/createBundlerClient.js"

/**
* Returns the supported chain id by the bundler service
Expand Down
12 changes: 9 additions & 3 deletions src/actions/bundler/estimateUserOperationGas.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type { Address } from "viem"
import type { PartialBy } from "viem/types/utils"
import type { BundlerClient } from "../../clients/bundler.js"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { UserOperation } from "../../types/userOperation.js"
import type { UserOperationWithBigIntAsHex } from "../../types/userOperation.js"
import { deepHexlify } from "../../utils/deepHexlify.js"

export type EstimateUserOperationGasParameters = {
userOperation: PartialBy<UserOperation, "callGasLimit" | "preVerificationGas" | "verificationGasLimit">
userOperation: PartialBy<
UserOperation,
"callGasLimit" | "preVerificationGas" | "verificationGasLimit"
>
entryPoint: Address
}

Expand Down Expand Up @@ -51,7 +54,10 @@ export const estimateUserOperationGas = async (

const response = await client.request({
method: "eth_estimateUserOperationGas",
params: [deepHexlify(userOperation) as UserOperationWithBigIntAsHex, entryPoint as Address]
params: [
deepHexlify(userOperation) as UserOperationWithBigIntAsHex,
entryPoint as Address
]
})

return {
Expand Down
10 changes: 8 additions & 2 deletions src/actions/bundler/getUserOperationByHash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Address, Hash } from "viem"
import type { BundlerClient } from "../../clients/bundler.js"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { UserOperation } from "../../types/userOperation.js"

export type GetUserOperationByHashParameters = {
Expand Down Expand Up @@ -49,7 +49,13 @@ export const getUserOperationByHash = async (

if (!response) return null

const { userOperation, entryPoint, transactionHash, blockHash, blockNumber } = response
const {
userOperation,
entryPoint,
transactionHash,
blockHash,
blockNumber
} = response

return {
userOperation: {
Expand Down
2 changes: 1 addition & 1 deletion src/actions/bundler/getUserOperationReceipt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Address, Hash, Hex } from "viem"
import type { BundlerClient } from "../../clients/bundler.js"
import type { BundlerClient } from "../../clients/createBundlerClient.js"
import type { TStatus } from "../../types/userOperation.js"
import { transactionReceiptStatus } from "../../utils/deepHexlify.js"

Expand Down
Loading

0 comments on commit 5bc4d56

Please sign in to comment.