Skip to content

Commit

Permalink
feat: add ability to pass fully formed UserOperations to AA Actions. (#…
Browse files Browse the repository at this point in the history
…2638)

* feat: add ability to pass fully formed user ops to actions

* chore: format

* chore: tweaks
  • Loading branch information
jxom committed Aug 22, 2024
1 parent 89d11ed commit 9cbd082
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 95 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-crabs-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added \`nonceKeyManager\` as a property to \`toSmartAccount\`.
5 changes: 5 additions & 0 deletions .changeset/heavy-knives-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added ability to pass full-formed User Operations to `sendUserOperation` and `estimateUserOperationGas`.
39 changes: 37 additions & 2 deletions src/account-abstraction/accounts/toSmartAccount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { anvilMainnet } from '../../../test/src/anvil.js'
import { accounts } from '../../../test/src/constants.js'
import { deploySoladyAccount_07 } from '../../../test/src/utils.js'
import { mine, writeContract } from '../../actions/index.js'
import { pad } from '../../utils/index.js'
import { createNonceManager, pad } from '../../utils/index.js'
import { toSoladySmartAccount } from './implementations/toSoladySmartAccount.js'
import { toSmartAccount } from './toSmartAccount.js'

const client = anvilMainnet.getClient({ account: true })

Expand Down Expand Up @@ -912,6 +913,40 @@ test('default', async () => {
`)
})

test('args: nonceKeyManager', async () => {
const { factoryAddress } = await deploySoladyAccount_07()

const nonceKeyManager = createNonceManager({
source: {
get() {
return 69
},
set() {},
},
})

const implementation = await toSoladySmartAccount({
client,
factoryAddress,
owner: accounts[1].address,
})
const account = await toSmartAccount({
...implementation,
async getNonce(parameters) {
return parameters!.key as bigint
},
nonceKeyManager,
})

const nonces = await Promise.all([
account.getNonce(),
account.getNonce(),
account.getNonce(),
])

expect(nonces).toEqual([69n, 70n, 71n])
})

test('return value: `isDeployed`', async () => {
const { factoryAddress } = await deploySoladyAccount_07()

Expand Down Expand Up @@ -946,7 +981,7 @@ test('return value: `getFactoryArgs`', async () => {

expect(await account.getFactoryArgs()).toMatchInlineSnapshot(`
{
"factory": "0xd73bab8f06db28c87932571f87d0d2c0fdf13d94",
"factory": "0x82a9286db983093ff234cefcea1d8fa66382876b",
"factoryData": "0xf14ddffc00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000000",
}
`)
Expand Down
22 changes: 12 additions & 10 deletions src/account-abstraction/accounts/toSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ export async function toSmartAccount<
>(
implementation: implementation,
): Promise<ToSmartAccountReturnType<implementation>> {
const { extend, ...rest } = implementation
const {
extend,
nonceKeyManager = createNonceManager({
source: {
get() {
return Date.now()
},
set() {},
},
}),
...rest
} = implementation

let deployed = false

const address = await implementation.getAddress()

const nonceKeyManager = createNonceManager({
source: {
get() {
return Date.now()
},
set() {},
},
})

return {
...extend,
...rest,
Expand Down
3 changes: 3 additions & 0 deletions src/account-abstraction/accounts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Client } from '../../clients/createClient.js'
import type { Hash, Hex, SignableMessage } from '../../types/misc.js'
import type { TypedDataDefinition } from '../../types/typedData.js'
import type { Assign, ExactPartial, UnionPartialBy } from '../../types/utils.js'
import type { NonceManager } from '../../utils/nonceManager.js'
import type { EntryPointVersion } from '../types/entryPointVersion.js'
import type {
EstimateUserOperationGasReturnType,
Expand Down Expand Up @@ -102,6 +103,8 @@ export type SmartAccountImplementation<
* ```
*/
getStubSignature: () => Promise<Hex>
/** Custom nonce key manager. */
nonceKeyManager?: NonceManager | undefined
/**
* Signs a hash via the Smart Account's owner.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest'
import { ErrorsExample } from '../../../../contracts/generated.js'
import { wagmiContractConfig } from '../../../../test/src/abis.js'
import {
Expand All @@ -15,7 +15,9 @@ import { mine, writeContract } from '../../../actions/index.js'
import { http } from '../../../clients/transports/http.js'
import { pad, parseEther, parseGwei } from '../../../utils/index.js'
import { createPaymasterClient } from '../../clients/createPaymasterClient.js'
import type { UserOperation } from '../../types/userOperation.js'
import { estimateUserOperationGas } from './estimateUserOperationGas.js'
import { prepareUserOperation } from './prepareUserOperation.js'

const client = anvilMainnet.getClient({ account: true })
const bundlerClient = bundlerMainnet.getBundlerClient()
Expand Down Expand Up @@ -139,6 +141,39 @@ describe('entryPointVersion: 0.7', async () => {
`)
})

test('behavior: prepared user operation', async () => {
const request = {
...(await prepareUserOperation(bundlerClient, {
account,
calls: [
{
to: '0x0000000000000000000000000000000000000000',
value: parseEther('1'),
},
],
...fees,
})),
account: undefined,
} as const

expectTypeOf(request).toMatchTypeOf<UserOperation>()

expect(
await estimateUserOperationGas(bundlerClient, {
...request,
entryPointAddress: account.entryPoint?.address,
}),
).toMatchInlineSnapshot(`
{
"callGasLimit": 80000n,
"paymasterPostOpGasLimit": 0n,
"paymasterVerificationGasLimit": 0n,
"preVerificationGas": 51722n,
"verificationGasLimit": 259060n,
}
`)
})

test('error: insufficient funds', async () => {
await expect(() =>
estimateUserOperationGas(bundlerClient, {
Expand All @@ -160,7 +195,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761076688711039842254848
nonce: 30902162761095135455113551806464
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -198,7 +233,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761095135455113551806464
nonce: 30902162761113582199187261358080
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -235,7 +270,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761113582199187261358080
nonce: 30902162761132028943260970909696
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -273,7 +308,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761132028943260970909696
nonce: 30902162761150475687334680461312
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -314,7 +349,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761150475687334680461312
nonce: 30902162761168922431408390012928
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -355,7 +390,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761168922431408390012928
nonce: 30902162761187369175482099564544
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -396,7 +431,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761187369175482099564544
nonce: 30902162761205815919555809116160
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -437,7 +472,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761205815919555809116160
nonce: 30902162761224262663629518667776
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -478,7 +513,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761224262663629518667776
nonce: 30902162761242709407703228219392
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -521,7 +556,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761242709407703228219392
nonce: 30902162761261156151776937771008
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -564,7 +599,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761261156151776937771008
nonce: 30902162761279602895850647322624
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -642,7 +677,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0x
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761279602895850647322624
nonce: 30902162761298049639924356874240
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -689,7 +724,7 @@ describe('entryPointVersion: 0.7', async () => {
factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761298049639924356874240
nonce: 30902162761316496383998066425856
sender: 0xE911628bF8428C23f179a07b081325cAe376DE1f
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand Down Expand Up @@ -723,6 +758,37 @@ describe('entryPointVersion: 0.6', async () => {
`)
})

test('behavior: prepared user operation', async () => {
const request = {
...(await prepareUserOperation(bundlerClient, {
account,
calls: [
{
to: '0x0000000000000000000000000000000000000000',
value: parseEther('1'),
},
],
...fees,
})),
account: undefined,
} as const

expectTypeOf(request).toMatchTypeOf<UserOperation>()

expect(
await estimateUserOperationGas(bundlerClient, {
...request,
entryPointAddress: account.entryPoint.address,
}),
).toMatchInlineSnapshot(`
{
"callGasLimit": 80000n,
"preVerificationGas": 55233n,
"verificationGasLimit": 258801n,
}
`)
})

test('error: aa13', async () => {
await expect(() =>
estimateUserOperationGas(bundlerClient, {
Expand All @@ -746,7 +812,7 @@ describe('entryPointVersion: 0.6', async () => {
initCode: 0x0000000000000000000000000000000000000000deadbeef
maxFeePerGas: 15 gwei
maxPriorityFeePerGas: 2 gwei
nonce: 30902162761039795222892423151616
nonce: 30902162761058241966966132703232
paymasterAndData: 0x
sender: 0x6edf7db791fC4D438D4A683E857B2fE1a84947Ce
signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c
Expand All @@ -759,8 +825,8 @@ describe('entryPointVersion: 0.6', async () => {

test('error: account not defined', async () => {
await expect(() =>
// @ts-expect-error
estimateUserOperationGas(bundlerClient, {
// @ts-expect-error
account: undefined,
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
...fees,
Expand Down
Loading

0 comments on commit 9cbd082

Please sign in to comment.