Skip to content

Commit

Permalink
Allow users to specify custom action to sponsor user operation
Browse files Browse the repository at this point in the history
  • Loading branch information
plusminushalf committed Nov 9, 2023
1 parent 13c0ce6 commit 9ff308f
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 20 deletions.
9 changes: 8 additions & 1 deletion src/accounts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,11 @@ import {
privateKeyToSimpleSmartAccount
} from "./privateKeyToSimpleSmartAccount.js"

export { SignTransactionNotSupportedBySmartAccount, type PrivateKeySimpleSmartAccount, privateKeyToSimpleSmartAccount }
import { type SmartAccount } from "./types.js"

export {
SignTransactionNotSupportedBySmartAccount,
type PrivateKeySimpleSmartAccount,
privateKeyToSimpleSmartAccount,
type SmartAccount
}
44 changes: 44 additions & 0 deletions src/actions/smartAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { deployContract } from "./smartAccount/deployContract.js"

import { getChainId } from "./smartAccount/getChainId.js"

import {
type PrepareUserOperationRequestParameters,
type PrepareUserOperationRequestReturnType,
prepareUserOperationRequest
} from "./smartAccount/prepareUserOperationRequest.js"

import { sendTransaction } from "./smartAccount/sendTransaction.js"

import {
type SendUserOperationParameters,
type SendUserOperationReturnType,
sendUserOperation
} from "./smartAccount/sendUserOperation.js"

import { signMessage } from "./smartAccount/signMessage.js"

import { signTypedData } from "./smartAccount/signTypedData.js"

import {
type SponsorUserOperationParameters,
type SponsorUserOperationReturnType,
sponsorUserOperation
} from "./smartAccount/sponsorUserOperation.js"

export {
deployContract,
getChainId,
prepareUserOperationRequest,
type PrepareUserOperationRequestParameters,
type PrepareUserOperationRequestReturnType,
sendTransaction,
sendUserOperation,
type SendUserOperationParameters,
type SendUserOperationReturnType,
signMessage,
signTypedData,
sponsorUserOperation,
type SponsorUserOperationParameters,
type SponsorUserOperationReturnType
}
32 changes: 15 additions & 17 deletions src/actions/smartAccount/prepareUserOperationRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import type { SmartAccount } from "../../accounts/types.js"
import type { GetAccountParameter, PartialBy, UserOperation } from "../../types/index.js"
import { getAction } from "../../utils/getAction.js"
import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js"
import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas.js"
import { sponsorUserOperation } from "./sponsorUserOperation.js"

export type PrepareUserOperationRequestParameters<
TAccount extends SmartAccount | undefined = SmartAccount | undefined
TAccount extends SmartAccount | undefined = SmartAccount | undefined,
> = {
userOperation: PartialBy<
UserOperation,
Expand Down Expand Up @@ -39,13 +39,12 @@ export async function prepareUserOperationRequest<

const account = parseAccount(account_) as SmartAccount

const [sender, nonce, initCode, signature, callData, paymasterAndData, gasEstimation] = await Promise.all([
const [sender, nonce, initCode, signature, callData, gasEstimation] = await Promise.all([
partialUserOperation.sender || account.address,
partialUserOperation.nonce || account.getNonce(),
partialUserOperation.initCode || account.getInitCode(),
partialUserOperation.signature || account.getDummySignature(),
partialUserOperation.callData,
partialUserOperation.paymasterAndData || "0x",
!partialUserOperation.maxFeePerGas || !partialUserOperation.maxPriorityFeePerGas
? estimateFeesPerGas(account.client)
: undefined
Expand All @@ -57,27 +56,26 @@ export async function prepareUserOperationRequest<
initCode,
signature,
callData,
paymasterAndData,
paymasterAndData: "0x",
maxFeePerGas: partialUserOperation.maxFeePerGas || gasEstimation?.maxFeePerGas || 0n,
maxPriorityFeePerGas: partialUserOperation.maxPriorityFeePerGas || gasEstimation?.maxPriorityFeePerGas || 0n,
callGasLimit: partialUserOperation.callGasLimit || 0n,
verificationGasLimit: partialUserOperation.verificationGasLimit || 0n,
preVerificationGas: partialUserOperation.preVerificationGas || 0n
}

if (!userOperation.callGasLimit || !userOperation.verificationGasLimit || !userOperation.preVerificationGas) {
const gasParameters = await getAction(
client,
estimateUserOperationGas
)({
userOperation,
entryPoint: account.entryPoint
})
const { paymasterAndData, callGasLimit, verificationGasLimit, preVerificationGas } = await getAction(
client,
sponsorUserOperation
)({
userOperation: userOperation,
account: account
})

userOperation.callGasLimit = userOperation.callGasLimit || gasParameters.callGasLimit
userOperation.verificationGasLimit = userOperation.verificationGasLimit || gasParameters.verificationGasLimit
userOperation.preVerificationGas = userOperation.preVerificationGas || gasParameters.preVerificationGas
}
userOperation.paymasterAndData = paymasterAndData
userOperation.callGasLimit = callGasLimit
userOperation.verificationGasLimit = verificationGasLimit
userOperation.preVerificationGas = preVerificationGas

return userOperation
}
54 changes: 54 additions & 0 deletions src/actions/smartAccount/sponsorUserOperation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Chain, Client, Hex, Transport } from "viem"
import { type SmartAccount } from "../../accounts/types.js"
import type { GetAccountParameter } from "../../types/index.js"
import type { UserOperation } from "../../types/userOperation.js"
import { getAction } from "../../utils/getAction.js"
import { parseAccount } from "../../utils/index.js"
import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js"
import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas.js"

export type SponsorUserOperationParameters<TAccount extends SmartAccount | undefined = SmartAccount | undefined,> = {
userOperation: UserOperation
} & GetAccountParameter<TAccount>

export type SponsorUserOperationReturnType = {
paymasterAndData: Hex
preVerificationGas: bigint
verificationGasLimit: bigint
callGasLimit: bigint
}

export async function sponsorUserOperation<TChain extends Chain | undefined, TAccount extends SmartAccount | undefined>(
client: Client<Transport, TChain, TAccount>,
args: SponsorUserOperationParameters<TAccount>
): Promise<SponsorUserOperationReturnType> {
const { account: account_ = client.account, userOperation } = args
if (!account_) throw new AccountOrClientNotFoundError()

const account = parseAccount(account_) as SmartAccount

let callGasLimit = userOperation.callGasLimit
let verificationGasLimit = userOperation.verificationGasLimit
let preVerificationGas = userOperation.preVerificationGas

if (!userOperation.callGasLimit || !userOperation.verificationGasLimit || !userOperation.preVerificationGas) {
const gasParameters = await getAction(
client,
estimateUserOperationGas
)({
userOperation,
entryPoint: account.entryPoint
})

callGasLimit = callGasLimit || gasParameters.callGasLimit
verificationGasLimit = verificationGasLimit || gasParameters.verificationGasLimit
preVerificationGas = preVerificationGas || gasParameters.preVerificationGas
}

return {
paymasterAndData: "0x",
callGasLimit,
verificationGasLimit,
preVerificationGas
}
}
6 changes: 6 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
"import": "./_esm/actions/stackup.js",
"default": "./_cjs/actions/stackup.js"
},

"./actions/smartAccount": {
"types": "./_types/actions/smartAccount.d.ts",
"import": "./_esm/actions/smartAccount.js",
"default": "./_cjs/actions/smartAccount.js"
},
"./clients": {
"types": "./_types/clients/index.d.ts",
"import": "./_esm/clients/index.js",
Expand Down
36 changes: 34 additions & 2 deletions test/simpleAccount.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { beforeAll, describe, expect, test } from "bun:test"
import dotenv from "dotenv"
import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts"
import { Address, Hex, getContract, zeroAddress } from "viem"
import { SponsorUserOperationParameters, SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js"
import { Address, Client, Hex, Transport, getContract, zeroAddress } from "viem"
import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js"
import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils.js"
import {
getEntryPoint,
getPimlicoPaymasterClient,
getPrivateKeyToSimpleSmartAccount,
getPublicClient,
getSmartAccountClient,
getTestingChain
} from "./utils.js"

dotenv.config()

Expand Down Expand Up @@ -149,4 +157,28 @@ describe("Simple Account", () => {
expect(response).toHaveLength(66)
expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/)
}, 1000000)

test("smart account client send Transaction with paymaster", async () => {
const smartAccountClient = await getSmartAccountClient()

smartAccountClient.extend((_: Client) => ({
sponsorUserOperation: async (
args: SponsorUserOperationParameters
): Promise<SponsorUserOperationReturnType> => {
const pimlicoPaymaster = getPimlicoPaymasterClient()
const { userOperation } = args
return pimlicoPaymaster.sponsorUserOperation({ userOperation, entryPoint: getEntryPoint() })
}
}))

const response = await smartAccountClient.sendTransaction({
to: zeroAddress,
value: 0n,
data: "0x"
})

expect(response).toBeString()
expect(response).toHaveLength(66)
expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/)
}, 1000000)
})

0 comments on commit 9ff308f

Please sign in to comment.