From 21aa24f230fd44c2705d616325edc896136f8242 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 2 Nov 2023 19:58:38 +0530 Subject: [PATCH 01/26] Create simple account client --- bun.lockb | Bin 143204 -> 142483 bytes package.json | 2 +- src/accounts/abis/SimpleAccount.ts | 524 ++++++++++++++++++ .../abis/SimpleSmartAccountFactory.ts | 74 +++ src/accounts/index.ts | 7 + .../privateKeyToSimpleSmartAccount.ts | 146 +++++ src/accounts/types.ts | 9 + src/actions/bundler/chainId.ts | 2 +- .../bundler/estimateUserOperationGas.ts | 2 +- src/actions/bundler/getUserOperationByHash.ts | 2 +- .../bundler/getUserOperationReceipt.ts | 2 +- src/actions/bundler/sendUserOperation.ts | 2 +- src/actions/bundler/supportedEntryPoints.ts | 2 +- .../bundler/waitForUserOperationReceipt.ts | 2 +- src/actions/public/getAccountNonce.ts | 9 +- src/actions/public/getSenderAddress.ts | 11 +- src/actions/smartAccount/deployContract.ts | 34 ++ src/actions/smartAccount/getChainId.ts | 10 + .../smartAccount/prepareTransactionRequest.ts | 19 + src/actions/smartAccount/sendTransaction.ts | 77 +++ src/actions/smartAccount/signMessage.ts | 18 + src/actions/smartAccount/signTypedData.ts | 58 ++ src/actions/smartAccount/writeContract.ts | 45 ++ .../{bundler.ts => createBundlerClient.ts} | 0 src/clients/createSmartAccountClient.ts | 62 +++ src/clients/decorators/bundler.ts | 2 +- src/clients/decorators/smartAccount.ts | 52 ++ src/index.ts | 14 +- src/package.json | 15 +- src/types/index.ts | 6 + src/utils/index.ts | 6 + src/utils/signUserOperationHashWithECDSA.ts | 6 +- test/bundlerActions.test.ts | 2 +- test/package.json | 2 +- test/simpleAccount.test.ts | 110 ++++ test/utils.ts | 30 +- tsconfig.base.json | 3 +- 37 files changed, 1332 insertions(+), 35 deletions(-) create mode 100644 src/accounts/abis/SimpleAccount.ts create mode 100644 src/accounts/abis/SimpleSmartAccountFactory.ts create mode 100644 src/accounts/index.ts create mode 100644 src/accounts/privateKeyToSimpleSmartAccount.ts create mode 100644 src/accounts/types.ts create mode 100644 src/actions/smartAccount/deployContract.ts create mode 100644 src/actions/smartAccount/getChainId.ts create mode 100644 src/actions/smartAccount/prepareTransactionRequest.ts create mode 100644 src/actions/smartAccount/sendTransaction.ts create mode 100644 src/actions/smartAccount/signMessage.ts create mode 100644 src/actions/smartAccount/signTypedData.ts create mode 100644 src/actions/smartAccount/writeContract.ts rename src/clients/{bundler.ts => createBundlerClient.ts} (100%) create mode 100644 src/clients/createSmartAccountClient.ts create mode 100644 src/clients/decorators/smartAccount.ts create mode 100644 test/simpleAccount.test.ts diff --git a/bun.lockb b/bun.lockb index 7bff1d1334042773534d2caf7ba6b8a3d81d39c4..6c2259890ad05336f5a4f311877b88c9e1f9a7d5 100755 GIT binary patch delta 1935 zcmcJQ{ZmwB6vywm^0Jo&0CDJ*du9du$(=FQ)fg&rL}`SJ-TH zI4vjo#>*4F?%KxjUq5S8ISHYHu;&n#osRE6`rg`o#rKq#{3trKyEW*B1w7>w2BYjT zM3zIdrXtAQOdyB1Ey-C8G%EZR#mb35+aLnfMTYSu;B-+d442<*n zd@IlYH+Cmg`~6>~hYpQuVs0iI{?uK#*fU;tvvcNRB`xH7r&EOGe%-Hwv4OxwTIWrGcOFCe12+s%;WJtp7s^^v~Ot)KHj=) zRMXKuoW1b;(rsstq@TAW4*12)9%p|0ujR~g%;}lc;*fXxWM}x3vnxmM6uP+U!wp_6 z$`MLqhAy`^G>jc}YrC@U;jm+JN4256-tzaw7nfU)UwUBe-Pju^eK^o@xh1+eQMcmA z@FzchQ?&l!x|IiubrCDx&)Kz){e)GRn=>S^Gfv>fa?yi)*69qXEX^5#j|xEn%Tggw zsSsQxP{ceX1idP7mCqRh0Uz$2j*Pw0S^BmFtl(R%OLM4*;bZ1O;@KiFN zu0nQ@vlKp$Hlc!|pWMWj6k~~0o>C+F3PqO28NPV5n`efN_lE7qIUGD(K61o=x;<(hvl5j zL;o6zy0NhVDzTHNa0>`6D6(Cg>Cn$a%|=ylrbj;(MO{}i&r&Q=#Z!KW*74MC&ipZs z=WGvW0T?H6_BP9+68m`S6~uQ?)Mzzli_oWMF&|aKnF0ORQPg!U>!cEOJY__57e%FL zW$+^eqv%=CWwbKH@PBlY1>tyrK^b^^(e9&EVhMcwO|Y+msWck zlTVDMSJADbZSGSx*McX2S=mts-$sI&h4sO~CDUik^r0d`s43hOhF46VM$^ZR2un<% zrlm}>gBdE=YCCA4o^7>*MgKdV9{3K`(3_0AvgUX|O|bDmZng)kidv2rEn|1>Fc%`& zGdrxu!O{xUS2zaMH`675s)FQZ}|fCeA7{3+;I<85$N^tT~D p1zT@0iW}JK-$2XM!Qxt}(zZBcG4;!Izd#^s3Km~eR)vUx;=ks>nri?6 delta 2345 zcmchZk6X=G7{Jf_)$QJ-QhrpF%CA(qU1Uj5Pcba(mXJk~ip;H>Zi;?bcEcEEtG*8h zkF-`u_87vYRpgP)vdu7)9_B|}wr9f3$k^Vu&h_{U_CELhp7;H{=e*x@&iDI0@0D9s z?blVsYM-zJwF8}>oTzA1taB`FeZRTdo^33yFP}ZC*{-JJvFq+jYKf6#mBe{S{uu}J zG`2o7(ipR2%$S^_%gHySvZ?6XpuZ=9FY5F!p_$f4R}x zniV6jt#Yn%k|W$SwRyhhCd;Q&18wHx4gK+kD!C~k$fgRtiRdZh2|ht^SAK2?v-3`8 zYz+0lqzbvo!%u$btg?$oIu2=Hq)NHTrHk5>A`M0ga?|W_Ds+On9nTz0<Wb@=8pdfjozGG(R)56Nos-*CnzaIOi@J`ur zC#bvsc4Eq9L-+&N>ldr_TN)nUZ@| z74-jd6)&^`L-_Vv?tkns|2?ut?XVl#wdK+EM-R?3pBVt;$}!@yai zt;X#8CjZWe7S;Wir4@&&e}CL&IykH9R%QH=vX$BDj<9>zyj#c3IDLEkQGQJUE?WPv zPrV9sS0kSFS$Ja4wF#p-HXBo)cEO!@e_ki#%Nt$P2_$K zn>5Yo8+#2o9aO&N$tircz+O4L!iTKmB?^e+Tdl#4|D}LPNaqu*5kx2v?6O9X!5;?sLq)kPT7Vh8f+#XKrlG;d0WKB3bvL>NC=wwv;x z%)pjRYt+4h%@jEe*l9qE&*LnWCb`q`r0&_x)F=gE{} zuIP?u6blofd7`^L`Y+I?1ttpSfc7D@X*TouK}wM%LXL>a&?ZS1%n9v}(WbK`MX)|- zuST0@y?{dordTLK{SY;y(&ST-(SrJ;;$carBwLJ(7UzsQ36&&`Pp1?Hk-`P(0SJzt**9gt{RW&m$cq~TLboO-N!EWWs+PFo7F1R8a)8Vet@RB}s+mSC(}qtN?Q*F{Zni&d&s+Sb5t1iyC#%sKbr3J<055*PS5ldBd?gR*KS~HBnE(I) diff --git a/package.json b/package.json index 63db78cc..b7b54b5f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/accounts/abis/SimpleAccount.ts b/src/accounts/abis/SimpleAccount.ts new file mode 100644 index 00000000..9676694f --- /dev/null +++ b/src/accounts/abis/SimpleAccount.ts @@ -0,0 +1,524 @@ +export const SimpleAccountAbi = [ + { + inputs: [ + { + internalType: "contract IEntryPoint", + name: "anEntryPoint", + type: "address" + } + ], + stateMutability: "nonpayable", + type: "constructor" + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "previousAdmin", + type: "address" + }, + { + indexed: false, + internalType: "address", + name: "newAdmin", + type: "address" + } + ], + name: "AdminChanged", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "beacon", + type: "address" + } + ], + name: "BeaconUpgraded", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8" + } + ], + name: "Initialized", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract IEntryPoint", + name: "entryPoint", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address" + } + ], + name: "SimpleAccountInitialized", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "implementation", + type: "address" + } + ], + name: "Upgraded", + type: "event" + }, + { + inputs: [], + name: "addDeposit", + outputs: [], + stateMutability: "payable", + type: "function" + }, + { + inputs: [], + name: "entryPoint", + outputs: [ + { + internalType: "contract IEntryPoint", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + 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" + }, + { + inputs: [ + { + internalType: "address[]", + name: "dest", + type: "address[]" + }, + { + internalType: "bytes[]", + name: "func", + type: "bytes[]" + } + ], + name: "executeBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "getNonce", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "anOwner", + type: "address" + } + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]" + }, + { + internalType: "uint256[]", + name: "", + type: "uint256[]" + }, + { + internalType: "bytes", + name: "", + type: "bytes" + } + ], + name: "onERC1155BatchReceived", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4" + } + ], + stateMutability: "pure", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + }, + { + internalType: "bytes", + name: "", + type: "bytes" + } + ], + name: "onERC1155Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4" + } + ], + stateMutability: "pure", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + }, + { + internalType: "bytes", + name: "", + type: "bytes" + } + ], + name: "onERC721Received", + outputs: [ + { + internalType: "bytes4", + name: "", + type: "bytes4" + } + ], + stateMutability: "pure", + type: "function" + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "proxiableUUID", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4" + } + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + }, + { + internalType: "bytes", + name: "", + type: "bytes" + }, + { + internalType: "bytes", + name: "", + type: "bytes" + } + ], + name: "tokensReceived", + outputs: [], + stateMutability: "pure", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address" + } + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address" + }, + { + internalType: "bytes", + name: "data", + type: "bytes" + } + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable", + type: "function" + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "sender", + type: "address" + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256" + }, + { + internalType: "bytes", + name: "initCode", + type: "bytes" + }, + { + internalType: "bytes", + name: "callData", + type: "bytes" + }, + { + internalType: "uint256", + name: "callGasLimit", + type: "uint256" + }, + { + internalType: "uint256", + name: "verificationGasLimit", + type: "uint256" + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256" + }, + { + internalType: "uint256", + name: "maxFeePerGas", + type: "uint256" + }, + { + internalType: "uint256", + name: "maxPriorityFeePerGas", + type: "uint256" + }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes" + }, + { + internalType: "bytes", + name: "signature", + type: "bytes" + } + ], + internalType: "struct UserOperation", + name: "UserOperation", + type: "tuple" + }, + { + internalType: "bytes32", + name: "userOpHash", + type: "bytes32" + }, + { + internalType: "uint256", + name: "missingAccountFunds", + type: "uint256" + } + ], + name: "validateUserOp", + outputs: [ + { + internalType: "uint256", + name: "validationData", + type: "uint256" + } + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address" + }, + { + internalType: "uint256", + name: "amount", + type: "uint256" + } + ], + name: "withdrawDepositTo", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + stateMutability: "payable", + type: "receive" + } +] as const diff --git a/src/accounts/abis/SimpleSmartAccountFactory.ts b/src/accounts/abis/SimpleSmartAccountFactory.ts new file mode 100644 index 00000000..6d742271 --- /dev/null +++ b/src/accounts/abis/SimpleSmartAccountFactory.ts @@ -0,0 +1,74 @@ +export const SimpleSmartAccountFactoryAbi = [ + { + inputs: [ + { + internalType: "contract IEntryPoint", + name: "_entryPoint", + type: "address" + } + ], + stateMutability: "nonpayable", + type: "constructor" + }, + { + inputs: [], + name: "accountImplementation", + outputs: [ + { + internalType: "contract SimpleAccount", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + 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" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + }, + { + internalType: "uint256", + name: "salt", + type: "uint256" + } + ], + name: "getAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + } +] as const diff --git a/src/accounts/index.ts b/src/accounts/index.ts new file mode 100644 index 00000000..e5d34612 --- /dev/null +++ b/src/accounts/index.ts @@ -0,0 +1,7 @@ +import { + type PrivateKeySimpleSmartAccount, + SignTransactionNotSupportedBySmartAccount, + privateKeyToSimpleSmartAccount +} from "./privateKeyToSimpleSmartAccount" + +export { SignTransactionNotSupportedBySmartAccount, type PrivateKeySimpleSmartAccount, privateKeyToSimpleSmartAccount } diff --git a/src/accounts/privateKeyToSimpleSmartAccount.ts b/src/accounts/privateKeyToSimpleSmartAccount.ts new file mode 100644 index 00000000..5fb35232 --- /dev/null +++ b/src/accounts/privateKeyToSimpleSmartAccount.ts @@ -0,0 +1,146 @@ +import { + type Address, + BaseError, + type Chain, + type Hex, + type PublicClient, + type Transport, + concatHex, + encodeFunctionData +} from "viem" +import { privateKeyToAccount, toAccount } from "viem/accounts" +import { getAccountNonce } from "../actions/public/getAccountNonce" +import { getSenderAddress } from "../actions/public/getSenderAddress" +import { SimpleAccountAbi } from "./abis/SimpleAccount" +import { SimpleSmartAccountFactoryAbi } from "./abis/SimpleSmartAccountFactory" +import { type SmartAccount } from "./types" + +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 = SmartAccount<"privateKeySimpleSmartAccount"> + +const getAccountInitCode = async (factoryAddress: Address, owner: Address, index = 0n): Promise => { + if (!owner) throw new Error("Owner account not found") + + return concatHex([ + factoryAddress, + encodeFunctionData({ + abi: SimpleSmartAccountFactoryAbi, + functionName: "createAccount", + args: [owner, index] + }) as Hex + ]) +} + +const getAccountAddress = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>({ + publicClient, + factoryAddress, + entryPoint, + owner +}: { + publicClient: PublicClient + factoryAddress: Address + owner: Address + entryPoint: Address +}): Promise
=> { + const initCode = await getAccountInitCode(factoryAddress, owner) + + return getSenderAddress(publicClient, { + 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 +>( + publicClient: PublicClient, + { + privateKey, + factoryAddress, + entryPoint + }: { + privateKey: Hex + factoryAddress: Address + entryPoint: Address + } +): Promise { + const privateKeyAccount = privateKeyToAccount(privateKey) + + const accountAddress = await getAccountAddress({ + publicClient, + factoryAddress, + entryPoint, + owner: privateKeyAccount.address + }) + + 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, + publicKey: accountAddress, + entryPoint: entryPoint, + source: "privateKeySimpleSmartAccount", + async getNonce() { + return getAccountNonce(publicClient, { + sender: accountAddress, + entryPoint: entryPoint + }) + }, + async getInitCode() { + const contractCode = await publicClient.getBytecode({ + address: accountAddress + }) + + if ((contractCode?.length ?? 0) > 2) return "0x" + + return getAccountInitCode(factoryAddress, privateKeyAccount.address) + }, + async encodeCallData({ to, value, data }) { + return encodeFunctionData({ + abi: SimpleAccountAbi, + functionName: "execute", + args: [to, value, data] + }) + }, + async getDummySignature() { + return "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" + } + } +} diff --git a/src/accounts/types.ts b/src/accounts/types.ts new file mode 100644 index 00000000..1de0b4e6 --- /dev/null +++ b/src/accounts/types.ts @@ -0,0 +1,9 @@ +import type { Address, Hex, LocalAccount } from "viem" + +export type SmartAccount = LocalAccount & { + entryPoint: Address + getNonce: () => Promise + getInitCode: () => Promise + encodeCallData: ({ to, value, data }: { to: Address; value: bigint; data: Hex }) => Promise + getDummySignature(): Promise +} diff --git a/src/actions/bundler/chainId.ts b/src/actions/bundler/chainId.ts index 3d0ab0e4..370c6a70 100644 --- a/src/actions/bundler/chainId.ts +++ b/src/actions/bundler/chainId.ts @@ -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 diff --git a/src/actions/bundler/estimateUserOperationGas.ts b/src/actions/bundler/estimateUserOperationGas.ts index da625303..7a7502a7 100644 --- a/src/actions/bundler/estimateUserOperationGas.ts +++ b/src/actions/bundler/estimateUserOperationGas.ts @@ -1,6 +1,6 @@ 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" diff --git a/src/actions/bundler/getUserOperationByHash.ts b/src/actions/bundler/getUserOperationByHash.ts index 6279bc56..c6069ef9 100644 --- a/src/actions/bundler/getUserOperationByHash.ts +++ b/src/actions/bundler/getUserOperationByHash.ts @@ -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 = { diff --git a/src/actions/bundler/getUserOperationReceipt.ts b/src/actions/bundler/getUserOperationReceipt.ts index 1a3f128e..8b43e503 100644 --- a/src/actions/bundler/getUserOperationReceipt.ts +++ b/src/actions/bundler/getUserOperationReceipt.ts @@ -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" diff --git a/src/actions/bundler/sendUserOperation.ts b/src/actions/bundler/sendUserOperation.ts index 8cd8feff..142c736b 100644 --- a/src/actions/bundler/sendUserOperation.ts +++ b/src/actions/bundler/sendUserOperation.ts @@ -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, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" diff --git a/src/actions/bundler/supportedEntryPoints.ts b/src/actions/bundler/supportedEntryPoints.ts index be44a76f..25d1a4b1 100644 --- a/src/actions/bundler/supportedEntryPoints.ts +++ b/src/actions/bundler/supportedEntryPoints.ts @@ -1,5 +1,5 @@ import type { Address } from "viem" -import type { BundlerClient } from "../../clients/bundler.js" +import type { BundlerClient } from "../../clients/createBundlerClient.js" /** * Returns the supported entrypoints by the bundler service diff --git a/src/actions/bundler/waitForUserOperationReceipt.ts b/src/actions/bundler/waitForUserOperationReceipt.ts index 3fddf546..b5df7214 100644 --- a/src/actions/bundler/waitForUserOperationReceipt.ts +++ b/src/actions/bundler/waitForUserOperationReceipt.ts @@ -1,5 +1,5 @@ import { BaseError, type Chain, type Hash, stringify } from "viem" -import type { BundlerClient } from "../../clients/bundler.js" +import type { BundlerClient } from "../../clients/createBundlerClient.js" import { observe } from "../../utils/observe.js" import { type GetUserOperationReceiptReturnType, getUserOperationReceipt } from "./getUserOperationReceipt.js" diff --git a/src/actions/public/getAccountNonce.ts b/src/actions/public/getAccountNonce.ts index 2759a596..fe76381d 100644 --- a/src/actions/public/getAccountNonce.ts +++ b/src/actions/public/getAccountNonce.ts @@ -1,4 +1,4 @@ -import type { Address, PublicClient } from "viem" +import type { Address, Chain, PublicClient, Transport } from "viem" export type GetAccountNonceParams = { sender: Address; entryPoint: Address; key?: bigint } @@ -28,8 +28,11 @@ export type GetAccountNonceParams = { sender: Address; entryPoint: Address; key? * * // Return 0n */ -export const getAccountNonce = async ( - publicClient: PublicClient, +export const getAccountNonce = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + publicClient: PublicClient, { sender, entryPoint, key = BigInt(0) }: GetAccountNonceParams ): Promise => { return await publicClient.readContract({ diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 7c6af6a9..265146ee 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -1,10 +1,12 @@ import { type Address, BaseError, + type Chain, type ContractFunctionExecutionErrorType, type ContractFunctionRevertedErrorType, type Hex, - type PublicClient + type PublicClient, + type Transport } from "viem" export type GetSenderAddressParams = { initCode: Hex; entryPoint: Address } @@ -50,8 +52,11 @@ export class InvalidEntryPointError extends BaseError { * * // Return '0x7a88a206ba40b37a8c07a2b5688cf8b287318b63' */ -export const getSenderAddress = async ( - publicClient: PublicClient, +export const getSenderAddress = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + publicClient: PublicClient, { initCode, entryPoint }: GetSenderAddressParams ): Promise
=> { try { diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts new file mode 100644 index 00000000..06e15023 --- /dev/null +++ b/src/actions/smartAccount/deployContract.ts @@ -0,0 +1,34 @@ +import { + type Abi, + type Chain, + type Client, + type DeployContractParameters, + type DeployContractReturnType, + type SendTransactionParameters, + type Transport, + encodeDeployData +} from "viem" +import type { SmartAccount } from "../../accounts/types" +import type { BundlerActions } from "../../clients/decorators/bundler" +import type { BundlerRpcSchema } from "../../types/bundler" +import { sendTransaction } from "./sendTransaction" + +export function deployContract< + const TAbi extends Abi | readonly unknown[], + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined, + TChainOverride extends Chain | undefined +>( + walletClient: Client, + { abi, args, bytecode, ...request }: DeployContractParameters +): Promise { + const calldata = encodeDeployData({ + abi, + args, + bytecode + } as unknown as DeployContractParameters) + return sendTransaction(walletClient, { + ...request, + data: calldata + } as unknown as SendTransactionParameters) +} diff --git a/src/actions/smartAccount/getChainId.ts b/src/actions/smartAccount/getChainId.ts new file mode 100644 index 00000000..994bd690 --- /dev/null +++ b/src/actions/smartAccount/getChainId.ts @@ -0,0 +1,10 @@ +import { type Chain, type Client, type GetChainIdReturnType, type Transport } from "viem" +import { type SmartAccount } from "../../accounts/types" +import { type BundlerActions } from "../../clients/decorators/bundler" +import { type BundlerRpcSchema } from "../../types/bundler" + +export async function getChainId( + client: Client +): Promise { + return client.chainId() +} diff --git a/src/actions/smartAccount/prepareTransactionRequest.ts b/src/actions/smartAccount/prepareTransactionRequest.ts new file mode 100644 index 00000000..e8e7cb88 --- /dev/null +++ b/src/actions/smartAccount/prepareTransactionRequest.ts @@ -0,0 +1,19 @@ +import type { + Account, + Chain, + Client, + PrepareTransactionRequestParameters, + PrepareTransactionRequestReturnType, + Transport +} from "viem" + +export async function prepareTransactionRequest< + TChain extends Chain | undefined, + TAccount extends Account | undefined, + TChainOverride extends Chain | undefined +>( + _: Client, + __: PrepareTransactionRequestParameters +): Promise> { + throw new Error("Not implemented") +} diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts new file mode 100644 index 00000000..b7b16cc7 --- /dev/null +++ b/src/actions/smartAccount/sendTransaction.ts @@ -0,0 +1,77 @@ +import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" +import { type Hex } from "viem" +import { type SmartAccount } from "../../accounts/types" +import { type BundlerActions } from "../../clients/decorators/bundler" +import { type UserOperation } from "../../types" +import { type BundlerRpcSchema } from "../../types/bundler" +import { AccountOrClientNotFoundError, getUserOperationHash, parseAccount } from "../../utils" + +export async function sendTransaction< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined, + TChainOverride extends Chain | undefined +>( + client: Client, + args: SendTransactionParameters +): Promise { + const { account: account_ = client.account, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = args + + if (!account_) + throw new AccountOrClientNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + + const account = parseAccount(account_) as SmartAccount + + if (!to) throw new Error("Missing to address") + + if (account.type !== "local") { + throw new Error("RPC account type not supported") + } + + const userOperation: UserOperation = { + sender: account.address, + nonce: await account.getNonce(), + initCode: await account.getInitCode(), + callData: await account.encodeCallData({ + to, + value: value || 0n, + data: data || "0x" + }), + paymasterAndData: "0x" as Hex, + signature: await account.getDummySignature(), + maxFeePerGas: maxFeePerGas || 0n, + maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, + callGasLimit: 0n, + verificationGasLimit: 0n, + preVerificationGas: 0n + } + + const gasParameters = await client.estimateUserOperationGas({ + userOperation, + entryPoint: account.entryPoint + }) + + userOperation.callGasLimit = gasParameters.callGasLimit + userOperation.verificationGasLimit = gasParameters.verificationGasLimit + userOperation.preVerificationGas = gasParameters.preVerificationGas + + userOperation.signature = await account.signMessage({ + message: { + raw: getUserOperationHash({ + userOperation, + entryPoint: account.entryPoint, + chainId: await client.chainId() + }) + } + }) + + const userOpHash = await client.sendUserOperation({ + userOperation: userOperation, + entryPoint: account.entryPoint + }) + + const userOperationReceipt = await client.waitForUserOperationReceipt({ hash: userOpHash }) + + return userOperationReceipt?.receipt.transactionHash +} diff --git a/src/actions/smartAccount/signMessage.ts b/src/actions/smartAccount/signMessage.ts new file mode 100644 index 00000000..6e13bc28 --- /dev/null +++ b/src/actions/smartAccount/signMessage.ts @@ -0,0 +1,18 @@ +import type { Chain, Client, SignMessageParameters, SignMessageReturnType, Transport } from "viem" +import { type SmartAccount } from "../../accounts/types.js" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" + +export async function signMessage( + client: Client, + { account: account_ = client.account, message }: SignMessageParameters +): Promise { + if (!account_) + throw new AccountOrClientNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + + const account = parseAccount(account_) + if (account.type === "local") return account.signMessage({ message }) + + throw new Error("Sign message is not supported by this account") +} diff --git a/src/actions/smartAccount/signTypedData.ts b/src/actions/smartAccount/signTypedData.ts new file mode 100644 index 00000000..a62278fc --- /dev/null +++ b/src/actions/smartAccount/signTypedData.ts @@ -0,0 +1,58 @@ +import { + type Chain, + type Client, + type SignTypedDataParameters, + type SignTypedDataReturnType, + type Transport, + type TypedData, + type TypedDataDefinition, + getTypesForEIP712Domain, + validateTypedData +} from "viem" +import { type SmartAccount } from "../../accounts/types" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils" + +export async function signTypedData< + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string, + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined +>( + client: Client, + { + account: account_ = client.account, + domain, + message, + primaryType, + types: types_ + }: SignTypedDataParameters +): Promise { + if (!account_) + throw new AccountOrClientNotFoundError({ + docsPath: "/docs/actions/wallet/signMessage" + }) + + const account = parseAccount(account_) + + const types = { + EIP712Domain: getTypesForEIP712Domain({ domain }), + ...(types_ as TTypedData) + } + + validateTypedData({ + domain, + message, + primaryType, + types + } as TypedDataDefinition) + + if (account.type === "local") + return account.signTypedData({ + domain, + primaryType, + types, + message + } as TypedDataDefinition) + + throw new Error("Sign type message is not supported by this account") +} diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts new file mode 100644 index 00000000..cbaaa369 --- /dev/null +++ b/src/actions/smartAccount/writeContract.ts @@ -0,0 +1,45 @@ +import { + type Abi, + type Chain, + type Client, + type EncodeFunctionDataParameters, + type SendTransactionParameters, + type Transport, + type WriteContractParameters, + type WriteContractReturnType, + encodeFunctionData +} from "viem" +import { type SmartAccount } from "../../accounts/types" +import { type BundlerActions } from "../../clients/decorators/bundler" +import { type BundlerRpcSchema } from "../../types/bundler" +import { sendTransaction } from "./sendTransaction" + +export async function writeContract< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined, + const TAbi extends Abi | readonly unknown[], + TFunctionName extends string, + TChainOverride extends Chain | undefined = undefined +>( + client: Client, + { + abi, + address, + args, + dataSuffix, + functionName, + ...request + }: WriteContractParameters +): Promise { + const data = encodeFunctionData({ + abi, + args, + functionName + } as unknown as EncodeFunctionDataParameters) + const hash = await sendTransaction(client, { + data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, + to: address, + ...request + } as unknown as SendTransactionParameters) + return hash +} diff --git a/src/clients/bundler.ts b/src/clients/createBundlerClient.ts similarity index 100% rename from src/clients/bundler.ts rename to src/clients/createBundlerClient.ts diff --git a/src/clients/createSmartAccountClient.ts b/src/clients/createSmartAccountClient.ts new file mode 100644 index 00000000..ad5c3594 --- /dev/null +++ b/src/clients/createSmartAccountClient.ts @@ -0,0 +1,62 @@ +import type { Chain, Client, ClientConfig, ParseAccount, Transport, WalletClientConfig } from "viem" +import { createClient } from "viem" +import { type SmartAccount } from "../accounts/types" +import type { Prettify } from "../types" +import { type BundlerRpcSchema } from "../types/bundler" +import { type BundlerActions, bundlerActions } from "./decorators/bundler" +import { type SmartAccountActions, smartAccountActions } from "./decorators/smartAccount" + +export type SmartAccountClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends SmartAccount | undefined = SmartAccount | undefined +> = Prettify>> + +export type SmartAccountClientConfig< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined +> = Prettify< + Pick< + ClientConfig, + "account" | "cacheTime" | "chain" | "key" | "name" | "pollingInterval" | "transport" + > +> + +/** + * Creates a EIP-4337 compliant Bundler Client with a given [Transport](https://viem.sh/docs/clients/intro.html) configured for a [Chain](https://viem.sh/docs/clients/chains.html). + * + * - Docs: https://docs.pimlico.io/permissionless/reference/clients/smartAccountClient + * + * A Bundler Client is an interface to "erc 4337" [JSON-RPC API](https://eips.ethereum.org/EIPS/eip-4337#rpc-methods-eth-namespace) methods such as sending user operation, estimating gas for a user operation, get user operation receipt, etc through Bundler Actions. + * + * @param config - {@link WalletClientConfig} + * @returns A Bundler Client. {@link SmartAccountClient} + * + * @example + * import { createPublicClient, http } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const smartAccountClient = createSmartAccountClient({ + * chain: mainnet, + * transport: http(BUNDLER_URL), + * }) + */ +export const createSmartAccountClient = < + TTransport extends Transport, + TChain extends Chain | undefined = undefined, + TSmartAccount extends SmartAccount | undefined = undefined +>( + parameters: SmartAccountClientConfig +): SmartAccountClient> => { + const { key = "Account", name = "Smart Account Client", transport } = parameters + const client = createClient({ + ...parameters, + key, + name, + transport: (opts) => transport({ ...opts, retryCount: 0 }), + type: "smartAccountClient" + }).extend(bundlerActions) + + return client.extend(smartAccountActions) +} diff --git a/src/clients/decorators/bundler.ts b/src/clients/decorators/bundler.ts index 0f9b6701..7f1a2a06 100644 --- a/src/clients/decorators/bundler.ts +++ b/src/clients/decorators/bundler.ts @@ -22,7 +22,7 @@ import { type WaitForUserOperationReceiptParameters, waitForUserOperationReceipt } from "../../actions/bundler/waitForUserOperationReceipt.js" -import type { BundlerClient } from "../bundler.js" +import type { BundlerClient } from "../createBundlerClient.js" export type BundlerActions = { /** diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts new file mode 100644 index 00000000..3249e91d --- /dev/null +++ b/src/clients/decorators/smartAccount.ts @@ -0,0 +1,52 @@ +import type { Abi, Chain, Client, Transport, TypedData } from "viem" +import type { SmartAccount } from "../../accounts/types.js" +import { deployContract } from "../../actions/smartAccount/deployContract.js" +import { getChainId } from "../../actions/smartAccount/getChainId.js" +import { sendTransaction } from "../../actions/smartAccount/sendTransaction.js" +import { signMessage } from "../../actions/smartAccount/signMessage.js" +import { signTypedData } from "../../actions/smartAccount/signTypedData.js" +import { type BundlerRpcSchema } from "../../types/bundler.js" +import { type BundlerActions } from "./bundler.js" + +export type SmartAccountActions< + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { + getChainId: () => ReturnType + sendTransaction: ( + args: Parameters>[1] + ) => ReturnType> + signMessage: ( + args: Parameters>[1] + ) => ReturnType> + signTypedData: ( + args: Parameters>[1] + ) => ReturnType> + deployContract: ( + args: Parameters>[1] + ) => ReturnType> +} + +export const smartAccountActions = < + TTransport extends Transport, + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client +): SmartAccountActions => ({ + // addChain: (args) => addChain(client, args), + deployContract: (args) => deployContract(client, args), + // getAddresses: () => getAddresses(client), + getChainId: () => getChainId(client), + // getPermissions: () => getPermissions(client), + // prepareTransactionRequest: (args) => prepareTransactionRequest(client as any, args as any), + // requestAddresses: () => requestAddresses(client), + // requestPermissions: (args) => requestPermissions(client, args), + // sendRawTransaction: (args) => sendRawTransaction(client, args), + sendTransaction: (args) => sendTransaction(client, args), + signMessage: (args) => signMessage(client, args), + // signTransaction: (args) => signTransaction(client, args), + signTypedData: (args) => signTypedData(client, args) + // switchChain: (args) => switchChain(client, args), + // watchAsset: (args) => watchAsset(client, args), +}) diff --git a/src/index.ts b/src/index.ts index 310dcf6a..309c0e7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,9 +26,12 @@ import { } from "./actions/bundler/waitForUserOperationReceipt.js" import type { GetAccountNonceParams } from "./actions/public/getAccountNonce.js" import { getAccountNonce } from "./actions/public/getAccountNonce.js" -import { type BundlerClient, createBundlerClient } from "./clients/bundler.js" +import { type BundlerClient, createBundlerClient } from "./clients/createBundlerClient.js" +import { createSmartAccountClient } from "./clients/createSmartAccountClient.js" +import { type SmartAccountClient, type SmartAccountClientConfig } from "./clients/createSmartAccountClient.js" import type { BundlerActions } from "./clients/decorators/bundler.js" import { bundlerActions } from "./clients/decorators/bundler.js" +import { type SmartAccountActions, smartAccountActions } from "./clients/decorators/smartAccount.js" export type { SendUserOperationParameters, @@ -42,7 +45,10 @@ export type { GetAccountNonceParams, BundlerClient, BundlerActions, - WaitForUserOperationReceiptParameters + WaitForUserOperationReceiptParameters, + SmartAccountClient, + SmartAccountClientConfig, + SmartAccountActions } export { @@ -57,7 +63,9 @@ export { waitForUserOperationReceipt, createBundlerClient, bundlerActions, - WaitForUserOperationReceiptTimeoutError + WaitForUserOperationReceiptTimeoutError, + createSmartAccountClient, + smartAccountActions } import type { UserOperation } from "./types/userOperation.js" diff --git a/src/package.json b/src/package.json index 766cd04c..638f37f5 100644 --- a/src/package.json +++ b/src/package.json @@ -9,13 +9,7 @@ "type": "module", "sideEffects": false, "description": "A utility library for working with ERC-4337", - "keywords": [ - "ethereum", - "erc-4337", - "eip-4337", - "paymaster", - "bundler" - ], + "keywords": ["ethereum", "erc-4337", "eip-4337", "paymaster", "bundler"], "license": "MIT", "exports": { ".": { @@ -23,6 +17,11 @@ "import": "./_esm/index.js", "default": "./_cjs/index.js" }, + "./accounts": { + "types": "./_types/accounts/index.d.ts", + "import": "./_esm/accounts/index.js", + "default": "./_cjs/accounts/index.js" + }, "./actions": { "types": "./_types/actions/index.d.ts", "import": "./_esm/actions/index.js", @@ -60,6 +59,6 @@ } }, "peerDependencies": { - "viem": "^1.14.0" + "viem": "^1.17.0" } } diff --git a/src/types/index.ts b/src/types/index.ts index 5387c10c..13df3fe3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,9 @@ import type { UserOperation } from "./userOperation.js" export type { UserOperation } + +export type Prettify = { + [K in keyof T]: T[K] +} & {} + +export type PartialBy = Omit & Partial> diff --git a/src/utils/index.ts b/src/utils/index.ts index dda63cb3..88dc9a8b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,12 @@ +import type { Account, Address } from "viem" import { type GetUserOperationHashParams, getUserOperationHash } from "./getUserOperationHash.js" import { AccountOrClientNotFoundError, signUserOperationHashWithECDSA } from "./signUserOperationHashWithECDSA.js" +export function parseAccount(account: Address | Account): Account { + if (typeof account === "string") return { address: account, type: "json-rpc" } + return account +} + export { getUserOperationHash, type GetUserOperationHashParams, diff --git a/src/utils/signUserOperationHashWithECDSA.ts b/src/utils/signUserOperationHashWithECDSA.ts index 45367afd..4955734c 100644 --- a/src/utils/signUserOperationHashWithECDSA.ts +++ b/src/utils/signUserOperationHashWithECDSA.ts @@ -10,11 +10,7 @@ import { } from "viem" import type { UserOperation } from "../types/userOperation.js" import { getUserOperationHash } from "./getUserOperationHash.js" - -function parseAccount(account: Address | Account): Account { - if (typeof account === "string") return { address: account, type: "json-rpc" } - return account -} +import { parseAccount } from "./index.js" type IsUndefined = [undefined] extends [T] ? true : false diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index 17065406..0ab31eb8 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -151,7 +151,7 @@ describe("BUNDLER ACTIONS", () => { const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) - await expect(async () => { + expect(async () => { await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash, timeout: 100 }) }).toThrow(new WaitForUserOperationReceiptTimeoutError({ hash: userOpHash })) }) diff --git a/test/package.json b/test/package.json index 2b4356cb..d6c2a535 100644 --- a/test/package.json +++ b/test/package.json @@ -4,6 +4,6 @@ "type": "module", "dependencies": { "dotenv": "^16.3.1", - "viem": "1.14.0" + "viem": "1.17.0" } } diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts new file mode 100644 index 00000000..5fd1ccb4 --- /dev/null +++ b/test/simpleAccount.test.ts @@ -0,0 +1,110 @@ +import dotenv from "dotenv" +import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" +import { Address, Hex, zeroAddress } from "viem" +import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils" +import { beforeAll, describe, expect, test } from "bun:test" + +dotenv.config() + +let testPrivateKey: Hex +let factoryAddress: Address + +beforeAll(() => { + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex + factoryAddress = process.env.FACTORY_ADDRESS as Address +}) + +describe("Simple Account", () => { + test("Simple Account address", async () => { + const simpleSmartAccount = await getPrivateKeyToSimpleSmartAccount() + + expect(simpleSmartAccount.address).toBeString() + expect(simpleSmartAccount.address).toHaveLength(42) + expect(simpleSmartAccount.address).toMatch(/^0x[0-9a-fA-F]{40}$/) + + expect(async () => { + await simpleSmartAccount.signTransaction({ + to: zeroAddress, + value: 0n, + data: "0x" + }) + }).toThrow(new SignTransactionNotSupportedBySmartAccount()) + }) + + test("Smart account client chain id", async () => { + const smartAccountClient = await getSmartAccountClient() + + const chain = getTestingChain() + + const chainId = await smartAccountClient.getChainId() + + expect(chainId).toBeNumber() + expect(chainId).toBeGreaterThan(0) + + expect(chainId).toEqual(chain.id) + }) + + test("Smart account client signMessage", async () => { + const smartAccountClient = await getSmartAccountClient() + + const response = await smartAccountClient.signMessage({ + message: "hello world" + }) + + expect(response).toBeString() + expect(response).toHaveLength(132) + expect(response).toMatch(/^0x[0-9a-fA-F]{130}$/) + }) + + test("Smart account client signTypedData", async () => { + const smartAccountClient = await getSmartAccountClient() + + const response = await smartAccountClient.signTypedData({ + domain: { + chainId: 1, + name: "Test", + verifyingContract: zeroAddress + }, + primaryType: "Test", + types: { + Test: [ + { + name: "test", + type: "string" + } + ] + }, + message: { + test: "hello world" + } + }) + + expect(response).toBeString() + expect(response).toHaveLength(132) + expect(response).toMatch(/^0x[0-9a-fA-F]{130}$/) + }) + + test("Smart account client", async () => { + const smartAccountClient = await getSmartAccountClient() + + const publicClient = await getPublicClient() + + const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + + const response = await smartAccountClient.sendTransaction({ + to: zeroAddress, + value: 0n, + data: "0x", + maxFeePerGas, + maxPriorityFeePerGas + }) + + console.log(response) + }, 1000000) +}) diff --git a/test/utils.ts b/test/utils.ts index 4481770a..ee9ff5ee 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,4 +1,5 @@ -import { createBundlerClient } from "permissionless" +import { createBundlerClient, createSmartAccountClient } from "permissionless" +import { privateKeyToSimpleSmartAccount } from "permissionless/accounts" import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" import { http, Address, Hex, createPublicClient, createWalletClient } from "viem" import { privateKeyToAccount } from "viem/accounts" @@ -19,6 +20,33 @@ export const getTestingChain = () => { return goerli } +export const getPrivateKeyToSimpleSmartAccount = async () => { + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + + const publicClient = await getPublicClient() + + return await privateKeyToSimpleSmartAccount(publicClient, { + entryPoint: getEntryPoint(), + factoryAddress: getFactoryAddress(), + privateKey: process.env.TEST_PRIVATE_KEY as Hex + }) +} + +export const getSmartAccountClient = async () => { + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + const pimlicoApiKey = process.env.PIMLICO_API_KEY + const chain = getTestingChain() + + return createSmartAccountClient({ + account: await getPrivateKeyToSimpleSmartAccount(), + chain, + transport: http( + `${process.env.PIMLICO_BUNDLER_RPC_HOST}/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` + ) + }) +} + export const getEoaWalletClient = () => { return createWalletClient({ account: getPrivateKeyAccount(), diff --git a/tsconfig.base.json b/tsconfig.base.json index e74910f3..0ecd2a72 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -35,6 +35,7 @@ "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped. ], // Skip type checking for node modules - "skipLibCheck": true + "skipLibCheck": true, + "types": ["bun-types"] } } \ No newline at end of file From cb2797ceb01b1ac41842d7aeb5f72f47cc198005 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Fri, 3 Nov 2023 14:02:44 +0530 Subject: [PATCH 02/26] Make esm compatible --- biome.json | 2 +- bun.lockb | Bin 142483 -> 143727 bytes src/accounts/abis/SimpleAccount.ts | 524 ------------------ .../abis/SimpleSmartAccountFactory.ts | 74 --- src/accounts/index.ts | 2 +- .../privateKeyToSimpleSmartAccount.ts | 69 ++- src/accounts/types.ts | 11 +- src/actions/smartAccount/deployContract.ts | 8 +- src/actions/smartAccount/getChainId.ts | 6 +- src/actions/smartAccount/sendTransaction.ts | 23 +- src/actions/smartAccount/signTypedData.ts | 10 +- src/actions/smartAccount/writeContract.ts | 8 +- src/clients/createSmartAccountClient.ts | 10 +- src/clients/decorators/smartAccount.ts | 11 +- src/utils/deepHexlify.test.ts | 2 +- src/utils/deepHexlify.ts | 11 +- src/utils/observe.ts | 6 +- test/bundlerActions.test.ts | 2 +- test/index.test.ts | 2 +- test/pimlicoActions.test.ts | 2 +- test/simpleAccount.test.ts | 10 +- 21 files changed, 131 insertions(+), 662 deletions(-) delete mode 100644 src/accounts/abis/SimpleAccount.ts delete mode 100644 src/accounts/abis/SimpleSmartAccountFactory.ts diff --git a/biome.json b/biome.json index 0eba5238..6e21bf81 100644 --- a/biome.json +++ b/biome.json @@ -33,7 +33,7 @@ "enabled": true, "formatWithErrors": true, "lineWidth": 120, - "indentSize": 4, + "indentWidth": 4, "indentStyle": "space" }, "javascript": { diff --git a/bun.lockb b/bun.lockb index 6c2259890ad05336f5a4f311877b88c9e1f9a7d5..de58d8d5cd00aafe4941ecad66970c4595d92d68 100755 GIT binary patch delta 39252 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 KI?=!X)cb!02?<&N delta 38116 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 +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 => { if (!owner) throw new Error("Owner account not found") @@ -39,7 +40,32 @@ const getAccountInitCode = async (factoryAddress: Address, owner: Address, index return concatHex([ factoryAddress, encodeFunctionData({ - abi: SimpleSmartAccountFactoryAbi, + 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 @@ -87,7 +113,7 @@ export async function privateKeyToSimpleSmartAccount< factoryAddress: Address entryPoint: Address } -): Promise { +): Promise> { const privateKeyAccount = privateKeyToAccount(privateKey) const accountAddress = await getAccountAddress({ @@ -114,6 +140,7 @@ export async function privateKeyToSimpleSmartAccount< return { ...account, + publicCLient: publicClient, publicKey: accountAddress, entryPoint: entryPoint, source: "privateKeySimpleSmartAccount", @@ -134,7 +161,31 @@ export async function privateKeyToSimpleSmartAccount< }, async encodeCallData({ to, value, data }) { return encodeFunctionData({ - abi: SimpleAccountAbi, + 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] }) diff --git a/src/accounts/types.ts b/src/accounts/types.ts index 1de0b4e6..4c0f460b 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -1,6 +1,13 @@ -import type { Address, Hex, LocalAccount } from "viem" +import type { Address, Hex, LocalAccount, PublicClient } from "viem" +import { Chain } from "viem" +import { Transport } from "viem" -export type SmartAccount = LocalAccount & { +export type SmartAccount< + Name extends string = string, + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined +> = LocalAccount & { + publicCLient: PublicClient entryPoint: Address getNonce: () => Promise getInitCode: () => Promise diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index 06e15023..c2177e78 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -8,10 +8,10 @@ import { type Transport, encodeDeployData } from "viem" -import type { SmartAccount } from "../../accounts/types" -import type { BundlerActions } from "../../clients/decorators/bundler" -import type { BundlerRpcSchema } from "../../types/bundler" -import { sendTransaction } from "./sendTransaction" +import type { SmartAccount } from "../../accounts/types.js" +import type { BundlerActions } from "../../clients/decorators/bundler.js" +import type { BundlerRpcSchema } from "../../types/bundler.js" +import { sendTransaction } from "./sendTransaction.js" export function deployContract< const TAbi extends Abi | readonly unknown[], diff --git a/src/actions/smartAccount/getChainId.ts b/src/actions/smartAccount/getChainId.ts index 994bd690..a46e7ea2 100644 --- a/src/actions/smartAccount/getChainId.ts +++ b/src/actions/smartAccount/getChainId.ts @@ -1,7 +1,7 @@ import { type Chain, type Client, type GetChainIdReturnType, type Transport } from "viem" -import { type SmartAccount } from "../../accounts/types" -import { type BundlerActions } from "../../clients/decorators/bundler" -import { type BundlerRpcSchema } from "../../types/bundler" +import { type SmartAccount } from "../../accounts/types.js" +import { type BundlerActions } from "../../clients/decorators/bundler.js" +import { type BundlerRpcSchema } from "../../types/bundler.js" export async function getChainId( client: Client diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index b7b16cc7..65767414 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,10 +1,10 @@ import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" import { type Hex } from "viem" -import { type SmartAccount } from "../../accounts/types" -import { type BundlerActions } from "../../clients/decorators/bundler" -import { type UserOperation } from "../../types" -import { type BundlerRpcSchema } from "../../types/bundler" -import { AccountOrClientNotFoundError, getUserOperationHash, parseAccount } from "../../utils" +import { type SmartAccount } from "../../accounts/types.js" +import { type BundlerActions } from "../../clients/decorators/bundler.js" +import { type BundlerRpcSchema } from "../../types/bundler.js" +import { type UserOperation } from "../../types/index.js" +import { AccountOrClientNotFoundError, getUserOperationHash, parseAccount } from "../../utils/index.js" export async function sendTransaction< TChain extends Chain | undefined, @@ -16,10 +16,11 @@ export async function sendTransaction< ): Promise { const { account: account_ = client.account, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = args - if (!account_) + if (!account_) { throw new AccountOrClientNotFoundError({ docsPath: "/docs/actions/wallet/sendTransaction" }) + } const account = parseAccount(account_) as SmartAccount @@ -29,6 +30,8 @@ export async function sendTransaction< throw new Error("RPC account type not supported") } + const gasEstimation = await account.publicCLient.estimateFeesPerGas() + const userOperation: UserOperation = { sender: account.address, nonce: await account.getNonce(), @@ -40,8 +43,8 @@ export async function sendTransaction< }), paymasterAndData: "0x" as Hex, signature: await account.getDummySignature(), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, + maxFeePerGas: maxFeePerGas || gasEstimation.maxFeePerGas || 0n, + maxPriorityFeePerGas: maxPriorityFeePerGas || gasEstimation.maxPriorityFeePerGas || 0n, callGasLimit: 0n, verificationGasLimit: 0n, preVerificationGas: 0n @@ -71,7 +74,9 @@ export async function sendTransaction< entryPoint: account.entryPoint }) - const userOperationReceipt = await client.waitForUserOperationReceipt({ hash: userOpHash }) + const userOperationReceipt = await client.waitForUserOperationReceipt({ + hash: userOpHash + }) return userOperationReceipt?.receipt.transactionHash } diff --git a/src/actions/smartAccount/signTypedData.ts b/src/actions/smartAccount/signTypedData.ts index a62278fc..2f14f148 100644 --- a/src/actions/smartAccount/signTypedData.ts +++ b/src/actions/smartAccount/signTypedData.ts @@ -9,8 +9,8 @@ import { getTypesForEIP712Domain, validateTypedData } from "viem" -import { type SmartAccount } from "../../accounts/types" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils" +import { type SmartAccount } from "../../accounts/types.js" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" export async function signTypedData< const TTypedData extends TypedData | { [key: string]: unknown }, @@ -27,10 +27,11 @@ export async function signTypedData< types: types_ }: SignTypedDataParameters ): Promise { - if (!account_) + if (!account_) { throw new AccountOrClientNotFoundError({ docsPath: "/docs/actions/wallet/signMessage" }) + } const account = parseAccount(account_) @@ -46,13 +47,14 @@ export async function signTypedData< types } as TypedDataDefinition) - if (account.type === "local") + if (account.type === "local") { return account.signTypedData({ domain, primaryType, types, message } as TypedDataDefinition) + } throw new Error("Sign type message is not supported by this account") } diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index cbaaa369..25976544 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -9,10 +9,10 @@ import { type WriteContractReturnType, encodeFunctionData } from "viem" -import { type SmartAccount } from "../../accounts/types" -import { type BundlerActions } from "../../clients/decorators/bundler" -import { type BundlerRpcSchema } from "../../types/bundler" -import { sendTransaction } from "./sendTransaction" +import { type SmartAccount } from "../../accounts/types.js" +import { type BundlerActions } from "../../clients/decorators/bundler.js" +import { type BundlerRpcSchema } from "../../types/bundler.js" +import { sendTransaction } from "./sendTransaction.js" export async function writeContract< TChain extends Chain | undefined, diff --git a/src/clients/createSmartAccountClient.ts b/src/clients/createSmartAccountClient.ts index ad5c3594..744b0d29 100644 --- a/src/clients/createSmartAccountClient.ts +++ b/src/clients/createSmartAccountClient.ts @@ -1,10 +1,10 @@ import type { Chain, Client, ClientConfig, ParseAccount, Transport, WalletClientConfig } from "viem" import { createClient } from "viem" -import { type SmartAccount } from "../accounts/types" -import type { Prettify } from "../types" -import { type BundlerRpcSchema } from "../types/bundler" -import { type BundlerActions, bundlerActions } from "./decorators/bundler" -import { type SmartAccountActions, smartAccountActions } from "./decorators/smartAccount" +import { type SmartAccount } from "../accounts/types.js" +import { type BundlerRpcSchema } from "../types/bundler.js" +import type { Prettify } from "../types/index.js" +import { type BundlerActions, bundlerActions } from "./decorators/bundler.js" +import { type SmartAccountActions, smartAccountActions } from "./decorators/smartAccount.js" export type SmartAccountClient< transport extends Transport = Transport, diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index 3249e91d..df43d6d7 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -5,6 +5,7 @@ import { getChainId } from "../../actions/smartAccount/getChainId.js" import { sendTransaction } from "../../actions/smartAccount/sendTransaction.js" import { signMessage } from "../../actions/smartAccount/signMessage.js" import { signTypedData } from "../../actions/smartAccount/signTypedData.js" +import { writeContract } from "../../actions/smartAccount/writeContract.js" import { type BundlerRpcSchema } from "../../types/bundler.js" import { type BundlerActions } from "./bundler.js" @@ -25,6 +26,13 @@ export type SmartAccountActions< deployContract: ( args: Parameters>[1] ) => ReturnType> + writeContract: < + const TAbi extends Abi | readonly unknown[], + TFunctionName extends string, + TChainOverride extends Chain | undefined = undefined + >( + args: Parameters>[1] + ) => ReturnType> } export const smartAccountActions = < @@ -46,7 +54,8 @@ export const smartAccountActions = < sendTransaction: (args) => sendTransaction(client, args), signMessage: (args) => signMessage(client, args), // signTransaction: (args) => signTransaction(client, args), - signTypedData: (args) => signTypedData(client, args) + signTypedData: (args) => signTypedData(client, args), // switchChain: (args) => switchChain(client, args), // watchAsset: (args) => watchAsset(client, args), + writeContract: (args) => writeContract(client, args) }) diff --git a/src/utils/deepHexlify.test.ts b/src/utils/deepHexlify.test.ts index 1796ca3b..dfffa44c 100644 --- a/src/utils/deepHexlify.test.ts +++ b/src/utils/deepHexlify.test.ts @@ -1,6 +1,6 @@ +import { beforeAll, expect, test } from "bun:test" import dotenv from "dotenv" import { deepHexlify } from "./deepHexlify.js" -import { beforeAll, expect, test } from "bun:test" dotenv.config() diff --git a/src/utils/deepHexlify.ts b/src/utils/deepHexlify.ts index 13bfa3bc..5bd585c5 100644 --- a/src/utils/deepHexlify.ts +++ b/src/utils/deepHexlify.ts @@ -20,11 +20,8 @@ export function deepHexlify(obj: any): any { if (Array.isArray(obj)) { return obj.map((member) => deepHexlify(member)) } - return Object.keys(obj).reduce( - (set, key) => ({ - ...set, - [key]: deepHexlify(obj[key]) - }), - {} - ) + return Object.keys(obj).reduce((set, key) => { + set[key] = deepHexlify(obj[key]) + return set + }, {}) } diff --git a/src/utils/observe.ts b/src/utils/observe.ts index 60368417..69572ccd 100644 --- a/src/utils/observe.ts +++ b/src/utils/observe.ts @@ -7,7 +7,7 @@ type Callbacks = Record export const listenersCache = /*#__PURE__*/ new Map() export const cleanupCache = /*#__PURE__*/ new Map void>() -type EmitFunction = (emit: TCallbacks) => MaybePromise void)> +type EmitFunction = (emit: TCallbacks) => MaybePromise void)> let callbackCount = 0 @@ -50,7 +50,9 @@ export function observe( emit[key] = ((...args: Parameters>) => { const listeners = getListeners() if (listeners.length === 0) return - listeners.forEach((listener) => listener.fns[key]?.(...args)) + for (const listener of listeners) { + listener.fns[key]?.(...args) + } }) as TCallbacks[Extract] } diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index 0ab31eb8..843b5292 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -1,10 +1,10 @@ +import { beforeAll, beforeEach, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { BundlerClient, WaitForUserOperationReceiptTimeoutError } from "permissionless" import { getUserOperationHash } from "permissionless/utils" import { http, Address, Hex } from "viem" import { buildUserOp } from "./userOp" import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils" -import { beforeAll, beforeEach, describe, expect, test } from "bun:test" dotenv.config() diff --git a/test/index.test.ts b/test/index.test.ts index bbaf83b1..20d17768 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,3 +1,4 @@ +import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { UserOperation, createBundlerClient, getSenderAddress, getUserOperationHash } from "permissionless" import { signUserOperationHashWithECDSA } from "permissionless" @@ -13,7 +14,6 @@ import { getPublicClient, getTestingChain } from "./utils" -import { beforeAll, describe, expect, test } from "bun:test" dotenv.config() const pimlicoApiKey = process.env.PIMLICO_API_KEY diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index 4b61fc85..fb8d5671 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -1,3 +1,4 @@ +import { beforeAll, beforeEach, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { PimlicoBundlerClient, @@ -16,7 +17,6 @@ import { getPublicClient, getTestingChain } from "./utils" -import { beforeAll, beforeEach, describe, expect, test } from "bun:test" dotenv.config() diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 5fd1ccb4..0bf27766 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -1,8 +1,8 @@ +import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { Address, Hex, zeroAddress } from "viem" import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils" -import { beforeAll, describe, expect, test } from "bun:test" dotenv.config() @@ -93,16 +93,10 @@ describe("Simple Account", () => { test("Smart account client", async () => { const smartAccountClient = await getSmartAccountClient() - const publicClient = await getPublicClient() - - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() - const response = await smartAccountClient.sendTransaction({ to: zeroAddress, value: 0n, - data: "0x", - maxFeePerGas, - maxPriorityFeePerGas + data: "0x" }) console.log(response) From 8bcbb174105e1e8e54f76fb0aad32532158c72ea Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Fri, 3 Nov 2023 08:33:18 +0000 Subject: [PATCH 03/26] chore: format --- bun.lockb | Bin 143727 -> 143002 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index de58d8d5cd00aafe4941ecad66970c4595d92d68..6179398be68f0956b329905aae896ee8bf5016d0 100755 GIT binary patch delta 27067 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 Date: Fri, 3 Nov 2023 14:48:33 +0530 Subject: [PATCH 04/26] Remove usage of public client --- bun.lockb | Bin 143727 -> 143002 bytes .../privateKeyToSimpleSmartAccount.ts | 19 +++++++++-------- src/accounts/types.ts | 7 +++--- src/actions/public/getAccountNonce.ts | 19 +++++++++++------ src/actions/public/getSenderAddress.ts | 11 +++++----- src/actions/smartAccount/sendTransaction.ts | 3 ++- src/clients/decorators/smartAccount.ts | 20 +++++++++--------- src/utils/deepHexlify.ts | 12 +++++++---- src/utils/observe.ts | 3 ++- 9 files changed, 53 insertions(+), 41 deletions(-) diff --git a/bun.lockb b/bun.lockb index de58d8d5cd00aafe4941ecad66970c4595d92d68..8b1096e300a7327e30ae72a7eb72257b9989c86d 100755 GIT binary patch delta 32825 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 LS26S`eeVAQ#LLmp diff --git a/src/accounts/privateKeyToSimpleSmartAccount.ts b/src/accounts/privateKeyToSimpleSmartAccount.ts index c3829064..fe8b4a8b 100644 --- a/src/accounts/privateKeyToSimpleSmartAccount.ts +++ b/src/accounts/privateKeyToSimpleSmartAccount.ts @@ -2,13 +2,14 @@ import { type Address, BaseError, type Chain, + type Client, type Hex, - type PublicClient, type Transport, concatHex, encodeFunctionData } from "viem" import { privateKeyToAccount, toAccount } from "viem/accounts" +import { getBytecode } from "viem/actions" import { getAccountNonce } from "../actions/public/getAccountNonce.js" import { getSenderAddress } from "../actions/public/getSenderAddress.js" import { type SmartAccount } from "./types.js" @@ -76,19 +77,19 @@ const getAccountAddress = async < TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >({ - publicClient, + client, factoryAddress, entryPoint, owner }: { - publicClient: PublicClient + client: Client factoryAddress: Address owner: Address entryPoint: Address }): Promise
=> { const initCode = await getAccountInitCode(factoryAddress, owner) - return getSenderAddress(publicClient, { + return getSenderAddress(client, { initCode, entryPoint }) @@ -103,7 +104,7 @@ export async function privateKeyToSimpleSmartAccount< TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >( - publicClient: PublicClient, + client: Client, { privateKey, factoryAddress, @@ -117,7 +118,7 @@ export async function privateKeyToSimpleSmartAccount< const privateKeyAccount = privateKeyToAccount(privateKey) const accountAddress = await getAccountAddress({ - publicClient, + client, factoryAddress, entryPoint, owner: privateKeyAccount.address @@ -140,18 +141,18 @@ export async function privateKeyToSimpleSmartAccount< return { ...account, - publicCLient: publicClient, + client: client, publicKey: accountAddress, entryPoint: entryPoint, source: "privateKeySimpleSmartAccount", async getNonce() { - return getAccountNonce(publicClient, { + return getAccountNonce(client, { sender: accountAddress, entryPoint: entryPoint }) }, async getInitCode() { - const contractCode = await publicClient.getBytecode({ + const contractCode = await getBytecode(client, { address: accountAddress }) diff --git a/src/accounts/types.ts b/src/accounts/types.ts index 4c0f460b..a2b179d3 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -1,13 +1,12 @@ -import type { Address, Hex, LocalAccount, PublicClient } from "viem" -import { Chain } from "viem" -import { Transport } from "viem" +import type { Address, Client, Hex, LocalAccount } from "viem" +import type { Chain, Transport } from "viem" export type SmartAccount< Name extends string = string, transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined > = LocalAccount & { - publicCLient: PublicClient + client: Client entryPoint: Address getNonce: () => Promise getInitCode: () => Promise diff --git a/src/actions/public/getAccountNonce.ts b/src/actions/public/getAccountNonce.ts index fe76381d..ad4b59ce 100644 --- a/src/actions/public/getAccountNonce.ts +++ b/src/actions/public/getAccountNonce.ts @@ -1,13 +1,18 @@ -import type { Address, Chain, PublicClient, Transport } from "viem" +import type { Address, Chain, Client, Transport } from "viem" +import { readContract } from "viem/actions" -export type GetAccountNonceParams = { sender: Address; entryPoint: Address; key?: bigint } +export type GetAccountNonceParams = { + sender: Address + entryPoint: Address + key?: bigint +} /** * Returns the nonce of the account with the entry point. * * - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/getAccountNonce * - * @param publicClient {@link PublicClient} that you created using viem's createPublicClient. + * @param client {@link client} that you created using viem's createPublicClient. * @param args {@link GetAccountNonceParams} address, entryPoint & key * @returns bigint nonce * @@ -15,12 +20,12 @@ export type GetAccountNonceParams = { sender: Address; entryPoint: Address; key? * import { createPublicClient } from "viem" * import { getAccountNonce } from "permissionless/actions" * - * const publicClient = createPublicClient({ + * const client = createPublicClient({ * chain: goerli, * transport: http("https://goerli.infura.io/v3/your-infura-key") * }) * - * const nonce = await getAccountNonce(publicClient, { + * const nonce = await getAccountNonce(client, { * address, * entryPoint, * key @@ -32,10 +37,10 @@ export const getAccountNonce = async < TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >( - publicClient: PublicClient, + client: Client, { sender, entryPoint, key = BigInt(0) }: GetAccountNonceParams ): Promise => { - return await publicClient.readContract({ + return await readContract(client, { address: entryPoint, abi: [ { diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 265146ee..316067f7 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -2,13 +2,15 @@ import { type Address, BaseError, type Chain, + type Client, type ContractFunctionExecutionErrorType, type ContractFunctionRevertedErrorType, type Hex, - type PublicClient, type Transport } from "viem" +import { simulateContract } from "viem/actions" + export type GetSenderAddressParams = { initCode: Hex; entryPoint: Address } export class InvalidEntryPointError extends BaseError { @@ -31,8 +33,7 @@ export class InvalidEntryPointError extends BaseError { * * - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/getSenderAddress * - * - * @param publicClient {@link PublicClient} that you created using viem's createPublicClient. + * @param client {@link Client} that you created using viem's createPublicClient. * @param args {@link GetSenderAddressParams} initCode & entryPoint * @returns Sender's Address * @@ -56,11 +57,11 @@ export const getSenderAddress = async < TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined >( - publicClient: PublicClient, + client: Client, { initCode, entryPoint }: GetSenderAddressParams ): Promise
=> { try { - await publicClient.simulateContract({ + await simulateContract(client, { address: entryPoint, abi: [ { diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index 65767414..e1a6487c 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,5 +1,6 @@ import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" import { type Hex } from "viem" +import { estimateFeesPerGas } from "viem/actions" import { type SmartAccount } from "../../accounts/types.js" import { type BundlerActions } from "../../clients/decorators/bundler.js" import { type BundlerRpcSchema } from "../../types/bundler.js" @@ -30,7 +31,7 @@ export async function sendTransaction< throw new Error("RPC account type not supported") } - const gasEstimation = await account.publicCLient.estimateFeesPerGas() + const gasEstimation = await estimateFeesPerGas(account.client) const userOperation: UserOperation = { sender: account.address, diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index df43d6d7..9c522980 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -5,7 +5,7 @@ import { getChainId } from "../../actions/smartAccount/getChainId.js" import { sendTransaction } from "../../actions/smartAccount/sendTransaction.js" import { signMessage } from "../../actions/smartAccount/signMessage.js" import { signTypedData } from "../../actions/smartAccount/signTypedData.js" -import { writeContract } from "../../actions/smartAccount/writeContract.js" +// import { writeContract } from "../../actions/smartAccount/writeContract.js" import { type BundlerRpcSchema } from "../../types/bundler.js" import { type BundlerActions } from "./bundler.js" @@ -26,13 +26,13 @@ export type SmartAccountActions< deployContract: ( args: Parameters>[1] ) => ReturnType> - writeContract: < - const TAbi extends Abi | readonly unknown[], - TFunctionName extends string, - TChainOverride extends Chain | undefined = undefined - >( - args: Parameters>[1] - ) => ReturnType> + // writeContract: < + // const TAbi extends Abi | readonly unknown[], + // TFunctionName extends string, + // TChainOverride extends Chain | undefined = undefined + // >( + // args: Parameters>[1] + // ) => ReturnType> } export const smartAccountActions = < @@ -54,8 +54,8 @@ export const smartAccountActions = < sendTransaction: (args) => sendTransaction(client, args), signMessage: (args) => signMessage(client, args), // signTransaction: (args) => signTransaction(client, args), - signTypedData: (args) => signTypedData(client, args), + signTypedData: (args) => signTypedData(client, args) // switchChain: (args) => switchChain(client, args), // watchAsset: (args) => watchAsset(client, args), - writeContract: (args) => writeContract(client, args) + // writeContract: (args) => writeContract(client, args) }) diff --git a/src/utils/deepHexlify.ts b/src/utils/deepHexlify.ts index 5bd585c5..e250f210 100644 --- a/src/utils/deepHexlify.ts +++ b/src/utils/deepHexlify.ts @@ -20,8 +20,12 @@ export function deepHexlify(obj: any): any { if (Array.isArray(obj)) { return obj.map((member) => deepHexlify(member)) } - return Object.keys(obj).reduce((set, key) => { - set[key] = deepHexlify(obj[key]) - return set - }, {}) + return Object.keys(obj).reduce( + // biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type + (set: any, key: string) => { + set[key] = deepHexlify(obj[key]) + return set + }, + {} + ) } diff --git a/src/utils/observe.ts b/src/utils/observe.ts index 69572ccd..91121dca 100644 --- a/src/utils/observe.ts +++ b/src/utils/observe.ts @@ -7,7 +7,8 @@ type Callbacks = Record export const listenersCache = /*#__PURE__*/ new Map() export const cleanupCache = /*#__PURE__*/ new Map void>() -type EmitFunction = (emit: TCallbacks) => MaybePromise void)> +// biome-ignore lint/suspicious/noConfusingVoidType: it's a recursive function, so it's hard to type +type EmitFunction = (emit: TCallbacks) => MaybePromise void)> let callbackCount = 0 From 6fa41fe6848c38e9fa64f8c0c7a298fe85082fbd Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 6 Nov 2023 12:29:22 +0530 Subject: [PATCH 05/26] Add write contract with viem 1.18.4 --- bun.lockb | Bin 143002 -> 143002 bytes src/clients/decorators/smartAccount.ts | 20 ++++++++++---------- src/package.json | 2 +- test/package.json | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bun.lockb b/bun.lockb index 520716a5bd09223a609e2c266da73575e681f699..58e2a843af910c5b7bb261b1fdba4e271cbdb722 100755 GIT binary patch delta 297 zcmbPrmt)pljtP1S<*Y~5^j39F%UF4%UGt9I)kLOGhwtBg&^BHCkl$pz_?;X5yu@XE zMBc8S!@4;TN`>&^r!8LcDsYYa!#xET3lWkKhyt6f@;k#6K`%eWo5j=Y$Ct_2Ku4JsVopC1Bhk>;yR!!U;tT+X?q|i<8G$u0i28?+j)2x zYf@PAfl4E{_ZBeD=V$p31k()~73oWa$6^ delta 272 zcmbPrmt)pljtP1S$KN}>kdOM|zDjUa^}+RF_K8RCJUG+Zt+OHAvgY)}<-r^Myu>F@ z5a$pn)?;U7WIzBAfAbdc2N8^dlLIxiCrNN@K2e}M#o^_dGoEEvpHQ=DKgV?+2%FV};MOc%!iA-flTfTUs<%g9vPj2OK@hMtr3T-!K zWxT>X*+7z;lLcxU%jCdpVO|gqw2HVf( args: Parameters>[1] ) => ReturnType> - // writeContract: < - // const TAbi extends Abi | readonly unknown[], - // TFunctionName extends string, - // TChainOverride extends Chain | undefined = undefined - // >( - // args: Parameters>[1] - // ) => ReturnType> + writeContract: < + const TAbi extends Abi | readonly unknown[], + TFunctionName extends string, + TChainOverride extends Chain | undefined = undefined + >( + args: Parameters>[1] + ) => ReturnType> } export const smartAccountActions = < @@ -54,8 +54,8 @@ export const smartAccountActions = < sendTransaction: (args) => sendTransaction(client, args), signMessage: (args) => signMessage(client, args), // signTransaction: (args) => signTransaction(client, args), - signTypedData: (args) => signTypedData(client, args) + signTypedData: (args) => signTypedData(client, args), // switchChain: (args) => switchChain(client, args), // watchAsset: (args) => watchAsset(client, args), - // writeContract: (args) => writeContract(client, args) + writeContract: (args) => writeContract(client, args) }) diff --git a/src/package.json b/src/package.json index 638f37f5..d2a0bb60 100644 --- a/src/package.json +++ b/src/package.json @@ -59,6 +59,6 @@ } }, "peerDependencies": { - "viem": "^1.17.0" + "viem": "^1.18.4" } } diff --git a/test/package.json b/test/package.json index d6c2a535..d07d7731 100644 --- a/test/package.json +++ b/test/package.json @@ -4,6 +4,6 @@ "type": "module", "dependencies": { "dotenv": "^16.3.1", - "viem": "1.17.0" + "viem": "1.18.4" } } From a18bb09ee6cd46e5423ee552532f388a872723cf Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 6 Nov 2023 14:38:55 +0530 Subject: [PATCH 06/26] Remove generics for getSenderAddress --- src/actions/public/getSenderAddress.ts | 11 +++-------- test/simpleAccount.test.ts | 6 ++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 316067f7..335c325d 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -1,12 +1,10 @@ import { type Address, BaseError, - type Chain, type Client, type ContractFunctionExecutionErrorType, type ContractFunctionRevertedErrorType, - type Hex, - type Transport + type Hex } from "viem" import { simulateContract } from "viem/actions" @@ -53,11 +51,8 @@ export class InvalidEntryPointError extends BaseError { * * // Return '0x7a88a206ba40b37a8c07a2b5688cf8b287318b63' */ -export const getSenderAddress = async < - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined ->( - client: Client, +export const getSenderAddress = async ( + client: Client, { initCode, entryPoint }: GetSenderAddressParams ): Promise
=> { try { diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 0bf27766..93113946 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -90,7 +90,7 @@ describe("Simple Account", () => { expect(response).toMatch(/^0x[0-9a-fA-F]{130}$/) }) - test("Smart account client", async () => { + test("Smart account client send transaction", async () => { const smartAccountClient = await getSmartAccountClient() const response = await smartAccountClient.sendTransaction({ @@ -99,6 +99,8 @@ describe("Simple Account", () => { data: "0x" }) - console.log(response) + expect(response).toBeString() + expect(response).toHaveLength(66) + expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/) }, 1000000) }) From d6743a04250c0a60a7ae9c1a80d3c5e264b58c17 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Wed, 8 Nov 2023 12:40:21 +0530 Subject: [PATCH 07/26] Add deploy contract function --- bun.lockb | Bin 143002 -> 143506 bytes .../privateKeyToSimpleSmartAccount.ts | 32 ++++-- src/accounts/types.ts | 9 +- src/actions/bundler/sendUserOperation.ts | 2 - src/actions/public/getSenderAddress.ts | 11 +- src/actions/smartAccount/deployContract.ts | 51 +++++++--- .../smartAccount/prepareTransactionRequest.ts | 19 ---- .../prepareUserOperationRequest.ts | 80 +++++++++++++++ src/actions/smartAccount/sendTransaction.ts | 59 +++-------- src/actions/smartAccount/sendUserOperation.ts | 50 +++++++++ src/clients/decorators/smartAccount.ts | 10 -- src/package.json | 2 +- src/types/index.ts | 15 +++ src/utils/signUserOperationHashWithECDSA.ts | 19 ++-- test/abis/Greeter.ts | 96 ++++++++++++++++++ test/index.test.ts | 28 +++-- test/package.json | 5 +- test/simpleAccount.test.ts | 40 ++++++-- test/utils.ts | 1 + 19 files changed, 396 insertions(+), 133 deletions(-) delete mode 100644 src/actions/smartAccount/prepareTransactionRequest.ts create mode 100644 src/actions/smartAccount/prepareUserOperationRequest.ts create mode 100644 src/actions/smartAccount/sendUserOperation.ts create mode 100644 test/abis/Greeter.ts diff --git a/bun.lockb b/bun.lockb index 58e2a843af910c5b7bb261b1fdba4e271cbdb722..5b6a819cd0e59b6ba05f5e3ccd55a4c632c1dd09 100755 GIT binary patch delta 2278 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 J3y%NGzX1lHQDp!C diff --git a/src/accounts/privateKeyToSimpleSmartAccount.ts b/src/accounts/privateKeyToSimpleSmartAccount.ts index fe8b4a8b..21ff93ec 100644 --- a/src/accounts/privateKeyToSimpleSmartAccount.ts +++ b/src/accounts/privateKeyToSimpleSmartAccount.ts @@ -9,9 +9,10 @@ import { encodeFunctionData } from "viem" import { privateKeyToAccount, toAccount } from "viem/accounts" -import { getBytecode } from "viem/actions" +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 { @@ -117,12 +118,15 @@ export async function privateKeyToSimpleSmartAccount< ): Promise> { const privateKeyAccount = privateKeyToAccount(privateKey) - const accountAddress = await getAccountAddress({ - client, - factoryAddress, - entryPoint, - owner: privateKeyAccount.address - }) + const [accountAddress, chainId] = await Promise.all([ + getAccountAddress({ + client, + factoryAddress, + entryPoint, + owner: privateKeyAccount.address + }), + getChainId(client) + ]) if (!accountAddress) throw new Error("Account address not found") @@ -151,6 +155,17 @@ export async function privateKeyToSimpleSmartAccount< 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 @@ -160,6 +175,9 @@ export async function privateKeyToSimpleSmartAccount< 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: [ diff --git a/src/accounts/types.ts b/src/accounts/types.ts index a2b179d3..f0affb64 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -1,5 +1,6 @@ -import type { Address, Client, Hex, LocalAccount } from "viem" +import type { Abi, Address, Client, GetConstructorArgs, Hex, LocalAccount } from "viem" import type { Chain, Transport } from "viem" +import { type UserOperation } from "../types" export type SmartAccount< Name extends string = string, @@ -12,4 +13,10 @@ export type SmartAccount< getInitCode: () => Promise encodeCallData: ({ to, value, data }: { to: Address; value: bigint; data: Hex }) => Promise getDummySignature(): Promise + encodeDeployCallData: ({ + abi, + args, + bytecode + }: { abi: TAbi; bytecode: Hex } & GetConstructorArgs) => Promise + signUserOperation: (UserOperation: UserOperation) => Promise } diff --git a/src/actions/bundler/sendUserOperation.ts b/src/actions/bundler/sendUserOperation.ts index 142c736b..1ba5813c 100644 --- a/src/actions/bundler/sendUserOperation.ts +++ b/src/actions/bundler/sendUserOperation.ts @@ -17,7 +17,6 @@ export type SendUserOperationParameters = { * @param args {@link SendUserOperationParameters}. * @returns UserOpHash that you can use to track user operation as {@link Hash}. * - * * @example * import { createClient } from "viem" * import { sendUserOperation } from "permissionless/actions" @@ -33,7 +32,6 @@ export type SendUserOperationParameters = { * }) * * // Return '0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34' - * */ export const sendUserOperation = async (client: BundlerClient, args: SendUserOperationParameters): Promise => { const { userOperation, entryPoint } = args diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 335c325d..316067f7 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -1,10 +1,12 @@ import { type Address, BaseError, + type Chain, type Client, type ContractFunctionExecutionErrorType, type ContractFunctionRevertedErrorType, - type Hex + type Hex, + type Transport } from "viem" import { simulateContract } from "viem/actions" @@ -51,8 +53,11 @@ export class InvalidEntryPointError extends BaseError { * * // Return '0x7a88a206ba40b37a8c07a2b5688cf8b287318b63' */ -export const getSenderAddress = async ( - client: Client, +export const getSenderAddress = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined +>( + client: Client, { initCode, entryPoint }: GetSenderAddressParams ): Promise
=> { try { diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index c2177e78..0c2bb377 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -4,31 +4,52 @@ import { type Client, type DeployContractParameters, type DeployContractReturnType, - type SendTransactionParameters, - type Transport, - encodeDeployData + type Transport } from "viem" import type { SmartAccount } from "../../accounts/types.js" import type { BundlerActions } from "../../clients/decorators/bundler.js" import type { BundlerRpcSchema } from "../../types/bundler.js" -import { sendTransaction } from "./sendTransaction.js" +import { parseAccount } from "../../utils/index.js" +import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" +import { sendUserOperation } from "./sendUserOperation.js" -export function deployContract< +export async function deployContract< const TAbi extends Abi | readonly unknown[], TChain extends Chain | undefined, TAccount extends SmartAccount | undefined, TChainOverride extends Chain | undefined >( - walletClient: Client, + client: Client, { abi, args, bytecode, ...request }: DeployContractParameters ): Promise { - const calldata = encodeDeployData({ - abi, - args, - bytecode - } as unknown as DeployContractParameters) - return sendTransaction(walletClient, { - ...request, - data: calldata - } as unknown as SendTransactionParameters) + const { account: account_ = client.account } = request + + if (!account_) { + throw new AccountOrClientNotFoundError({ + docsPath: "/docs/actions/wallet/sendTransaction" + }) + } + + const account = parseAccount(account_) as SmartAccount + + const userOpHash = await sendUserOperation(client, { + userOperation: { + sender: account.address, + paymasterAndData: "0x", + maxFeePerGas: request.maxFeePerGas || 0n, + maxPriorityFeePerGas: request.maxPriorityFeePerGas || 0n, + callData: await account.encodeDeployCallData({ + abi, + args, + bytecode + } as unknown as DeployContractParameters) + }, + account: account + }) + + const userOperationReceipt = await client.waitForUserOperationReceipt({ + hash: userOpHash + }) + + return userOperationReceipt?.receipt.transactionHash } diff --git a/src/actions/smartAccount/prepareTransactionRequest.ts b/src/actions/smartAccount/prepareTransactionRequest.ts deleted file mode 100644 index e8e7cb88..00000000 --- a/src/actions/smartAccount/prepareTransactionRequest.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { - Account, - Chain, - Client, - PrepareTransactionRequestParameters, - PrepareTransactionRequestReturnType, - Transport -} from "viem" - -export async function prepareTransactionRequest< - TChain extends Chain | undefined, - TAccount extends Account | undefined, - TChainOverride extends Chain | undefined ->( - _: Client, - __: PrepareTransactionRequestParameters -): Promise> { - throw new Error("Not implemented") -} diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts new file mode 100644 index 00000000..39d086c1 --- /dev/null +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -0,0 +1,80 @@ +import type { Chain, Client, Transport } from "viem" +import { estimateFeesPerGas } from "viem/actions" +import type { SmartAccount } from "../../accounts/types" +import type { BundlerActions } from "../../clients/decorators/bundler" +import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" +import type { BundlerRpcSchema } from "../../types/bundler" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils" + +export type PrepareUserOperationRequestParameters< + TAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { + userOperation: PartialBy< + UserOperation, + | "nonce" + | "sender" + | "initCode" + | "callGasLimit" + | "verificationGasLimit" + | "preVerificationGas" + | "maxFeePerGas" + | "maxPriorityFeePerGas" + | "paymasterAndData" + | "signature" + > +} & GetAccountParameter + +export type PrepareUserOperationRequestReturnType = UserOperation + +export async function prepareUserOperationRequest< + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client, + args: PrepareUserOperationRequestParameters +): Promise { + const { account: account_ = client.account, userOperation: partialUserOperation } = args + if (!account_) throw new AccountOrClientNotFoundError() + + const account = parseAccount(account_) as SmartAccount + + const [sender, nonce, initCode, signature, callData, paymasterAndData, 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 + ]) + + const userOperation: UserOperation = { + sender, + nonce, + initCode, + signature, + callData, + paymasterAndData, + 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 client.estimateUserOperationGas({ + userOperation, + entryPoint: account.entryPoint + }) + + userOperation.callGasLimit = userOperation.callGasLimit || gasParameters.callGasLimit + userOperation.verificationGasLimit = userOperation.verificationGasLimit || gasParameters.verificationGasLimit + userOperation.preVerificationGas = userOperation.preVerificationGas || gasParameters.preVerificationGas + } + + return userOperation +} diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index e1a6487c..fa15794f 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,11 +1,9 @@ import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" -import { type Hex } from "viem" -import { estimateFeesPerGas } from "viem/actions" import { type SmartAccount } from "../../accounts/types.js" import { type BundlerActions } from "../../clients/decorators/bundler.js" import { type BundlerRpcSchema } from "../../types/bundler.js" -import { type UserOperation } from "../../types/index.js" -import { AccountOrClientNotFoundError, getUserOperationHash, parseAccount } from "../../utils/index.js" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" +import { sendUserOperation } from "./sendUserOperation.js" export async function sendTransaction< TChain extends Chain | undefined, @@ -31,48 +29,19 @@ export async function sendTransaction< throw new Error("RPC account type not supported") } - const gasEstimation = await estimateFeesPerGas(account.client) - - const userOperation: UserOperation = { - sender: account.address, - nonce: await account.getNonce(), - initCode: await account.getInitCode(), - callData: await account.encodeCallData({ - to, - value: value || 0n, - data: data || "0x" - }), - paymasterAndData: "0x" as Hex, - signature: await account.getDummySignature(), - maxFeePerGas: maxFeePerGas || gasEstimation.maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || gasEstimation.maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } - - const gasParameters = await client.estimateUserOperationGas({ - userOperation, - entryPoint: account.entryPoint - }) - - userOperation.callGasLimit = gasParameters.callGasLimit - userOperation.verificationGasLimit = gasParameters.verificationGasLimit - userOperation.preVerificationGas = gasParameters.preVerificationGas - - userOperation.signature = await account.signMessage({ - message: { - raw: getUserOperationHash({ - userOperation, - entryPoint: account.entryPoint, - chainId: await client.chainId() + const userOpHash = await sendUserOperation(client, { + userOperation: { + sender: account.address, + paymasterAndData: "0x", + maxFeePerGas: maxFeePerGas || 0n, + maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, + callData: await account.encodeCallData({ + to, + value: value || 0n, + data: data || "0x" }) - } - }) - - const userOpHash = await client.sendUserOperation({ - userOperation: userOperation, - entryPoint: account.entryPoint + }, + account: account }) const userOperationReceipt = await client.waitForUserOperationReceipt({ diff --git a/src/actions/smartAccount/sendUserOperation.ts b/src/actions/smartAccount/sendUserOperation.ts new file mode 100644 index 00000000..37631b9b --- /dev/null +++ b/src/actions/smartAccount/sendUserOperation.ts @@ -0,0 +1,50 @@ +import type { Chain, Client, Hex, Transport } from "viem" +import type { SmartAccount } from "../../accounts/types" +import type { BundlerActions } from "../../clients/decorators/bundler" +import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" +import type { BundlerRpcSchema } from "../../types/bundler" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils" +import { prepareUserOperationRequest } from "./prepareUserOperationRequest" + +export type SendUserOperationParameters = { + userOperation: PartialBy< + UserOperation, + | "nonce" + | "sender" + | "initCode" + | "signature" + | "callGasLimit" + | "maxFeePerGas" + | "maxPriorityFeePerGas" + | "preVerificationGas" + | "verificationGasLimit" + | "paymasterAndData" + > +} & GetAccountParameter + +export type SendUserOperationReturnType = Hex + +export async function sendUserOperation< + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined +>( + client: Client, + args: SendUserOperationParameters +): Promise { + const { account: account_ = client.account } = args + if (!account_) throw new AccountOrClientNotFoundError() + + const account = parseAccount(account_) as SmartAccount + + const userOperation = await prepareUserOperationRequest(client, args) + + userOperation.signature = await account.signUserOperation(userOperation) + + const userOpHash = await client.sendUserOperation({ + userOperation: userOperation, + entryPoint: account.entryPoint + }) + + return userOpHash +} diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index df43d6d7..cc51b62b 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -42,20 +42,10 @@ export const smartAccountActions = < >( client: Client ): SmartAccountActions => ({ - // addChain: (args) => addChain(client, args), deployContract: (args) => deployContract(client, args), - // getAddresses: () => getAddresses(client), getChainId: () => getChainId(client), - // getPermissions: () => getPermissions(client), - // prepareTransactionRequest: (args) => prepareTransactionRequest(client as any, args as any), - // requestAddresses: () => requestAddresses(client), - // requestPermissions: (args) => requestPermissions(client, args), - // sendRawTransaction: (args) => sendRawTransaction(client, args), sendTransaction: (args) => sendTransaction(client, args), signMessage: (args) => signMessage(client, args), - // signTransaction: (args) => signTransaction(client, args), signTypedData: (args) => signTypedData(client, args), - // switchChain: (args) => switchChain(client, args), - // watchAsset: (args) => watchAsset(client, args), writeContract: (args) => writeContract(client, args) }) diff --git a/src/package.json b/src/package.json index d2a0bb60..9c0b4162 100644 --- a/src/package.json +++ b/src/package.json @@ -59,6 +59,6 @@ } }, "peerDependencies": { - "viem": "^1.18.4" + "viem": "0.0.0-jxom-fix-extract-chain-params.20231106T094653" } } diff --git a/src/types/index.ts b/src/types/index.ts index 13df3fe3..d84ae26e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,7 +1,22 @@ +import type { Account, Chain, Client, Transport } from "viem" +import type { SmartAccount } from "../accounts/types.js" import type { UserOperation } from "./userOperation.js" export type { UserOperation } +type IsUndefined = [undefined] extends [T] ? true : false + +export type GetAccountParameterWithClient< + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends Account | undefined = Account | undefined +> = IsUndefined extends true + ? { account: Account; client?: Client } + : { client: Client; account?: Account } + +export type GetAccountParameter = + IsUndefined extends true ? { account: SmartAccount } : { account?: SmartAccount } + export type Prettify = { [K in keyof T]: T[K] } & {} diff --git a/src/utils/signUserOperationHashWithECDSA.ts b/src/utils/signUserOperationHashWithECDSA.ts index 4955734c..874fe3ba 100644 --- a/src/utils/signUserOperationHashWithECDSA.ts +++ b/src/utils/signUserOperationHashWithECDSA.ts @@ -8,25 +8,16 @@ import { type Hex, type Transport } from "viem" +import { type GetAccountParameterWithClient } from "../types/index.js" import type { UserOperation } from "../types/userOperation.js" import { getUserOperationHash } from "./getUserOperationHash.js" import { parseAccount } from "./index.js" -type IsUndefined = [undefined] extends [T] ? true : false - -type GetAccountParameter< - TTransport extends Transport = Transport, - TChain extends Chain | undefined = Chain | undefined, - TAccount extends Account | undefined = Account | undefined -> = IsUndefined extends true - ? { account: Account; client?: undefined } - : { client: Client; account?: undefined } - export type signUserOperationHashWithECDSAParams< TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends Account | undefined = Account | undefined -> = GetAccountParameter & +> = GetAccountParameterWithClient & ( | { hash: Hash @@ -103,7 +94,11 @@ export const signUserOperationHashWithECDSA = async < if (hash) { userOperationHash = hash } else { - userOperationHash = getUserOperationHash({ userOperation, chainId, entryPoint }) + userOperationHash = getUserOperationHash({ + userOperation, + chainId, + entryPoint + }) } const account = parseAccount(account_) diff --git a/test/abis/Greeter.ts b/test/abis/Greeter.ts new file mode 100644 index 00000000..756e69db --- /dev/null +++ b/test/abis/Greeter.ts @@ -0,0 +1,96 @@ +export const GreeterBytecode = + "0x60806040523480156200001157600080fd5b50620000776040518060600160405280602281526020016200133f602291396040518060400160405280600581526020017f48656c6c6f000000000000000000000000000000000000000000000000000000815250620000c460201b620003cc1760201c565b6040518060400160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525060009081620000bd91906200040d565b50620005be565b620001668282604051602401620000dd92919062000583565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506200016a60201b60201c565b5050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200021557607f821691505b6020821081036200022b576200022a620001cd565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620002957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000256565b620002a1868362000256565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620002ee620002e8620002e284620002b9565b620002c3565b620002b9565b9050919050565b6000819050919050565b6200030a83620002cd565b620003226200031982620002f5565b84845462000263565b825550505050565b600090565b620003396200032a565b62000346818484620002ff565b505050565b5b818110156200036e57620003626000826200032f565b6001810190506200034c565b5050565b601f821115620003bd57620003878162000231565b620003928462000246565b81016020851015620003a2578190505b620003ba620003b18562000246565b8301826200034b565b50505b505050565b600082821c905092915050565b6000620003e260001984600802620003c2565b1980831691505092915050565b6000620003fd8383620003cf565b9150826002028217905092915050565b620004188262000193565b67ffffffffffffffff8111156200043457620004336200019e565b5b620004408254620001fc565b6200044d82828562000372565b600060209050601f83116001811462000485576000841562000470578287015190505b6200047c8582620003ef565b865550620004ec565b601f198416620004958662000231565b60005b82811015620004bf5784890151825560018201915060208501945060208101905062000498565b86831015620004df5784890151620004db601f891682620003cf565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b60005b838110156200052557808201518184015260208101905062000508565b60008484015250505050565b6000601f19601f8301169050919050565b60006200054f8262000193565b6200055b8185620004f4565b93506200056d81856020860162000505565b620005788162000531565b840191505092915050565b600060408201905081810360008301526200059f818562000542565b90508181036020830152620005b5818462000542565b90509392505050565b610d7180620005ce6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80636250ce311461005c578063a413686214610078578063c3023ff414610094578063cd20c713146100b0578063cfae3217146100e0575b600080fd5b61007660048036038101906100719190610671565b6100fe565b005b610092600480360381019061008d91906107f7565b610183565b005b6100ae60048036038101906100a99190610840565b610243565b005b6100ca60048036038101906100c59190610840565b610315565b6040516100d7919061088f565b60405180910390f35b6100e861033a565b6040516100f59190610929565b60405180910390f35b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050565b610230604051806060016040528060238152602001610d1960239139600080546101ac9061097a565b80601f01602080910402602001604051908101604052809291908181526020018280546101d89061097a565b80156102255780601f106101fa57610100808354040283529160200191610225565b820191906000526020600020905b81548152906001019060200180831161020857829003601f168201915b505050505083610468565b806000908161023f9190610b57565b5050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506102cd81610507565b60008114610310576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161030790610c75565b60405180910390fd5b505050565b6001602052816000526040600020602052806000526040600020600091509150505481565b6060600080546103499061097a565b80601f01602080910402602001604051908101604052809291908181526020018280546103759061097a565b80156103c25780601f10610397576101008083540402835291602001916103c2565b820191906000526020600020905b8154815290600101906020018083116103a557829003601f168201915b5050505050905090565b61046482826040516024016103e2929190610c95565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b5050565b61050283838360405160240161048093929190610ccc565b6040516020818303038152906040527f2ced7cef000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b505050565b61059d8160405160240161051b919061088f565b6040516020818303038152906040527ff82c50f1000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b50565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610608826105dd565b9050919050565b610618816105fd565b811461062357600080fd5b50565b6000813590506106358161060f565b92915050565b6000819050919050565b61064e8161063b565b811461065957600080fd5b50565b60008135905061066b81610645565b92915050565b60008060408385031215610688576106876105d3565b5b600061069685828601610626565b92505060206106a78582860161065c565b9150509250929050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610704826106bb565b810181811067ffffffffffffffff82111715610723576107226106cc565b5b80604052505050565b60006107366105c9565b905061074282826106fb565b919050565b600067ffffffffffffffff821115610762576107616106cc565b5b61076b826106bb565b9050602081019050919050565b82818337600083830152505050565b600061079a61079584610747565b61072c565b9050828152602081018484840111156107b6576107b56106b6565b5b6107c1848285610778565b509392505050565b600082601f8301126107de576107dd6106b1565b5b81356107ee848260208601610787565b91505092915050565b60006020828403121561080d5761080c6105d3565b5b600082013567ffffffffffffffff81111561082b5761082a6105d8565b5b610837848285016107c9565b91505092915050565b60008060408385031215610857576108566105d3565b5b600061086585828601610626565b925050602061087685828601610626565b9150509250929050565b6108898161063b565b82525050565b60006020820190506108a46000830184610880565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156108e45780820151818401526020810190506108c9565b60008484015250505050565b60006108fb826108aa565b61090581856108b5565b93506109158185602086016108c6565b61091e816106bb565b840191505092915050565b6000602082019050818103600083015261094381846108f0565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061099257607f821691505b6020821081036109a5576109a461094b565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302610a0d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826109d0565b610a1786836109d0565b95508019841693508086168417925050509392505050565b6000819050919050565b6000610a54610a4f610a4a8461063b565b610a2f565b61063b565b9050919050565b6000819050919050565b610a6e83610a39565b610a82610a7a82610a5b565b8484546109dd565b825550505050565b600090565b610a97610a8a565b610aa2818484610a65565b505050565b5b81811015610ac657610abb600082610a8f565b600181019050610aa8565b5050565b601f821115610b0b57610adc816109ab565b610ae5846109c0565b81016020851015610af4578190505b610b08610b00856109c0565b830182610aa7565b50505b505050565b600082821c905092915050565b6000610b2e60001984600802610b10565b1980831691505092915050565b6000610b478383610b1d565b9150826002028217905092915050565b610b60826108aa565b67ffffffffffffffff811115610b7957610b786106cc565b5b610b83825461097a565b610b8e828285610aca565b600060209050601f831160018114610bc15760008415610baf578287015190505b610bb98582610b3b565b865550610c21565b601f198416610bcf866109ab565b60005b82811015610bf757848901518255600182019150602085019450602081019050610bd2565b86831015610c145784890151610c10601f891682610b1d565b8355505b6001600288020188555050505b505050505050565b7f4e6f20476173206c696d69740000000000000000000000000000000000000000600082015250565b6000610c5f600c836108b5565b9150610c6a82610c29565b602082019050919050565b60006020820190508181036000830152610c8e81610c52565b9050919050565b60006040820190508181036000830152610caf81856108f0565b90508181036020830152610cc381846108f0565b90509392505050565b60006060820190508181036000830152610ce681866108f0565b90508181036020830152610cfa81856108f0565b90508181036040830152610d0e81846108f0565b905094935050505056fe4368616e67696e67206772656574696e672066726f6d202725732720746f2027257327a26469706673582212208a9d4f617c5917088dbed912c8d8cb47f8edd6cf21b2c4643b7ead8977183a5964736f6c634300081200334465706c6f79696e67206120477265657465722077697468206772656574696e673a" + +export const GreeterAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor" + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address" + }, + { + internalType: "address", + name: "", + type: "address" + } + ], + name: "approvedGasLimit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "safe", + type: "address" + }, + { + internalType: "address", + name: "token", + type: "address" + } + ], + name: "getApprovedGasLimit", + outputs: [], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "greet", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address" + }, + { + internalType: "uint256", + name: "gasLimit", + type: "uint256" + } + ], + name: "setApprovedGasLimit", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "string", + name: "_greeting", + type: "string" + } + ], + name: "setGreeting", + outputs: [], + stateMutability: "nonpayable", + type: "function" + } +] diff --git a/test/index.test.ts b/test/index.test.ts index 20d17768..18725ffb 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,9 +1,7 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" -import { UserOperation, createBundlerClient, getSenderAddress, getUserOperationHash } from "permissionless" -import { signUserOperationHashWithECDSA } from "permissionless" -import { InvalidEntryPointError } from "permissionless/actions" -import { http } from "viem" +import { UserOperation, getSenderAddress, getUserOperationHash } from "permissionless" +import { signUserOperationHashWithECDSA } from "permissionless/utils" import { buildUserOp, getAccountInitCode } from "./userOp" import { getBundlerClient, @@ -90,7 +88,11 @@ describe("test public actions and utils", () => { userOperation.verificationGasLimit = gasParameters.verificationGasLimit userOperation.preVerificationGas = gasParameters.preVerificationGas - const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOpHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) expect(userOpHash).toBeString() expect(userOpHash).toStartWith("0x") @@ -123,7 +125,11 @@ describe("test public actions and utils", () => { userOperation.verificationGasLimit = gasParameters.verificationGasLimit userOperation.preVerificationGas = gasParameters.preVerificationGas - const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOpHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) userOperation.signature = await signUserOperationHashWithECDSA({ client: eoaWalletClient, @@ -136,9 +142,15 @@ describe("test public actions and utils", () => { expect(userOperation.signature).toBeString() expect(userOperation.signature).toStartWith("0x") - const signature = await signUserOperationHashWithECDSA({ client: eoaWalletClient, hash: userOpHash }) + const signature = await signUserOperationHashWithECDSA({ + client: eoaWalletClient, + hash: userOpHash + }) - await signUserOperationHashWithECDSA({ account: eoaWalletClient.account, hash: userOpHash }) + await signUserOperationHashWithECDSA({ + account: eoaWalletClient.account, + hash: userOpHash + }) await signUserOperationHashWithECDSA({ account: eoaWalletClient.account, diff --git a/test/package.json b/test/package.json index d07d7731..20f19196 100644 --- a/test/package.json +++ b/test/package.json @@ -2,8 +2,11 @@ "name": "test", "private": true, "type": "module", + "devDependencies": { + "bun-types": "^1.0.7" + }, "dependencies": { "dotenv": "^16.3.1", - "viem": "1.18.4" + "viem": "0.0.0-jxom-fix-extract-chain-params.20231106T094653" } } diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 93113946..88b3620c 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -2,7 +2,8 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { Address, Hex, zeroAddress } from "viem" -import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils" +import { GreeterAbi, GreeterBytecode } from "./abis/Greeter" +import { getPrivateKeyToSimpleSmartAccount, getSmartAccountClient, getTestingChain } from "./utils" dotenv.config() @@ -10,12 +11,24 @@ let testPrivateKey: Hex let factoryAddress: Address beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.STACKUP_API_KEY) { + throw new Error("STACKUP_API_KEY environment variable not set") + } + if (!process.env.FACTORY_ADDRESS) { + throw new Error("FACTORY_ADDRESS environment variable not set") + } + if (!process.env.TEST_PRIVATE_KEY) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } + if (!process.env.RPC_URL) { + throw new Error("RPC_URL environment variable not set") + } + if (!process.env.ENTRYPOINT_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex factoryAddress = process.env.FACTORY_ADDRESS as Address }) @@ -90,15 +103,24 @@ describe("Simple Account", () => { expect(response).toMatch(/^0x[0-9a-fA-F]{130}$/) }) - test("Smart account client send transaction", async () => { + test("smart account client write & deploy contract", async () => { const smartAccountClient = await getSmartAccountClient() + expect(async () => { + await smartAccountClient.deployContract({ + abi: GreeterAbi, + bytecode: GreeterBytecode + }) + }).toThrow("Simple account doesn't support account deployment") + }) + + test("Smart account client send transaction", async () => { + const smartAccountClient = await getSmartAccountClient() 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}$/) diff --git a/test/utils.ts b/test/utils.ts index ee9ff5ee..440e5589 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -62,6 +62,7 @@ export const getEntryPoint = () => { export const getPublicClient = async () => { if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + const publicClient = createPublicClient({ transport: http(process.env.RPC_URL as string) }) From ccab51296a36b5a53e6de16fd0757180730ba67c Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Wed, 8 Nov 2023 14:37:35 +0530 Subject: [PATCH 08/26] Use getAction to get action from clients --- .../bundler/waitForUserOperationReceipt.ts | 3 +- .../pimlico/getUserOperationGasPrice.ts | 13 +++++--- src/actions/pimlico/getUserOperationStatus.ts | 12 +++++--- src/actions/pimlico/sponsorUserOperation.ts | 12 +++++--- src/actions/public/getAccountNonce.ts | 6 +++- src/actions/public/getSenderAddress.ts | 6 +++- src/actions/smartAccount/deployContract.ts | 16 ++++++---- src/actions/smartAccount/getChainId.ts | 8 ++--- .../prepareUserOperationRequest.ts | 11 ++++--- src/actions/smartAccount/sendTransaction.ts | 28 ++++++++++------- src/actions/smartAccount/sendUserOperation.ts | 14 +++++---- src/actions/smartAccount/writeContract.ts | 10 ++++--- src/clients/createSmartAccountClient.ts | 5 ++-- src/clients/decorators/smartAccount.ts | 4 +-- src/utils/getAction.ts | 14 +++++++++ test/simpleAccount.test.ts | 30 +++++++++++++++++-- test/stackupActions.test.ts | 8 ----- 17 files changed, 136 insertions(+), 64 deletions(-) create mode 100644 src/utils/getAction.ts diff --git a/src/actions/bundler/waitForUserOperationReceipt.ts b/src/actions/bundler/waitForUserOperationReceipt.ts index b5df7214..bb0c7452 100644 --- a/src/actions/bundler/waitForUserOperationReceipt.ts +++ b/src/actions/bundler/waitForUserOperationReceipt.ts @@ -2,6 +2,7 @@ import { BaseError, type Chain, type Hash, stringify } from "viem" import type { BundlerClient } from "../../clients/createBundlerClient.js" import { observe } from "../../utils/observe.js" import { type GetUserOperationReceiptReturnType, getUserOperationReceipt } from "./getUserOperationReceipt.js" +import { getAction } from "../../utils/getAction.js" export class WaitForUserOperationReceiptTimeoutError extends BaseError { override name = "WaitForUserOperationReceiptTimeoutError" @@ -64,7 +65,7 @@ export const waitForUserOperationReceipt = ( _unobserve() } - const _userOperationReceipt = await getUserOperationReceipt(bundlerClient, { hash }) + const _userOperationReceipt = await getAction(bundlerClient, getUserOperationReceipt)({ hash }) if (_userOperationReceipt !== null) { userOperationReceipt = _userOperationReceipt diff --git a/src/actions/pimlico/getUserOperationGasPrice.ts b/src/actions/pimlico/getUserOperationGasPrice.ts index 07bc4b60..646aefc3 100644 --- a/src/actions/pimlico/getUserOperationGasPrice.ts +++ b/src/actions/pimlico/getUserOperationGasPrice.ts @@ -1,4 +1,5 @@ -import type { PimlicoBundlerClient } from "../../clients/pimlico.js" +import type { Account, Chain, Client, Transport } from "viem" +import type { PimlicoBundlerRpcSchema } from "../../types/pimlico" export type GetUserOperationGasPriceReturnType = { slow: { @@ -20,7 +21,7 @@ export type GetUserOperationGasPriceReturnType = { * * - Docs: https://docs.pimlico.io/permissionless/reference/pimlico-bundler-actions/getUserOperationGasPrice * - * @param client {@link PimlicoBundlerClient} that you created using viem's createClient whose transport url is pointing to the Pimlico's bundler. + * @param client that you created using viem's createClient whose transport url is pointing to the Pimlico's bundler. * @returns slow, standard & fast values for maxFeePerGas & maxPriorityFeePerGas * * @@ -36,8 +37,12 @@ export type GetUserOperationGasPriceReturnType = { * await getUserOperationGasPrice(bundlerClient) * */ -export const getUserOperationGasPrice = async ( - client: PimlicoBundlerClient +export const getUserOperationGasPrice = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends Account | undefined = Account | undefined +>( + client: Client ): Promise => { const gasPrices = await client.request({ method: "pimlico_getUserOperationGasPrice", diff --git a/src/actions/pimlico/getUserOperationStatus.ts b/src/actions/pimlico/getUserOperationStatus.ts index 457ffc83..29ea34dc 100644 --- a/src/actions/pimlico/getUserOperationStatus.ts +++ b/src/actions/pimlico/getUserOperationStatus.ts @@ -1,6 +1,6 @@ -import type { Hash } from "viem" +import type { Account, Chain, Client, Hash, Transport } from "viem" import type { PimlicoBundlerClient } from "../../clients/pimlico.js" -import type { PimlicoUserOperationStatus } from "../../types/pimlico.js" +import type { PimlicoBundlerRpcSchema, PimlicoUserOperationStatus } from "../../types/pimlico.js" export type GetUserOperationStatusParameters = { hash: Hash @@ -30,8 +30,12 @@ export type GetUserOperationStatusReturnType = PimlicoUserOperationStatus * await getUserOperationStatus(bundlerClient, { hash: userOpHash }) * */ -export const getUserOperationStatus = async ( - client: PimlicoBundlerClient, +export const getUserOperationStatus = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends Account | undefined = Account | undefined +>( + client: Client, { hash }: GetUserOperationStatusParameters ): Promise => { return client.request({ diff --git a/src/actions/pimlico/sponsorUserOperation.ts b/src/actions/pimlico/sponsorUserOperation.ts index 045d8386..d4b0851e 100644 --- a/src/actions/pimlico/sponsorUserOperation.ts +++ b/src/actions/pimlico/sponsorUserOperation.ts @@ -1,8 +1,8 @@ -import type { Address, Hex } from "viem" +import type { Account, Address, Chain, Client, Hex, Transport } from "viem" import type { PartialBy } from "viem/types/utils" -import type { PimlicoPaymasterClient } from "../../clients/pimlico.js" import type { UserOperation, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" +import type { PimlicoPaymasterRpcSchema } from "../../types/pimlico.js" export type SponsorUserOperationParameters = { userOperation: PartialBy< @@ -44,8 +44,12 @@ export type SponsorUserOperationReturnType = { * }}) * */ -export const sponsorUserOperation = async ( - client: PimlicoPaymasterClient, +export const sponsorUserOperation = async < + TTransport extends Transport = Transport, + TChain extends Chain | undefined = Chain | undefined, + TAccount extends Account | undefined = Account | undefined +>( + client: Client, args: SponsorUserOperationParameters ): Promise => { const response = await client.request({ diff --git a/src/actions/public/getAccountNonce.ts b/src/actions/public/getAccountNonce.ts index ad4b59ce..fe0aa62d 100644 --- a/src/actions/public/getAccountNonce.ts +++ b/src/actions/public/getAccountNonce.ts @@ -1,5 +1,6 @@ import type { Address, Chain, Client, Transport } from "viem" import { readContract } from "viem/actions" +import { getAction } from "../../utils/getAction" export type GetAccountNonceParams = { sender: Address @@ -40,7 +41,10 @@ export const getAccountNonce = async < client: Client, { sender, entryPoint, key = BigInt(0) }: GetAccountNonceParams ): Promise => { - return await readContract(client, { + return await getAction( + client, + readContract + )({ address: entryPoint, abi: [ { diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 316067f7..b2db4b03 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -10,6 +10,7 @@ import { } from "viem" import { simulateContract } from "viem/actions" +import { getAction } from "../../utils/getAction" export type GetSenderAddressParams = { initCode: Hex; entryPoint: Address } @@ -61,7 +62,10 @@ export const getSenderAddress = async < { initCode, entryPoint }: GetSenderAddressParams ): Promise
=> { try { - await simulateContract(client, { + await getAction( + client, + simulateContract + )({ address: entryPoint, abi: [ { diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index 0c2bb377..58fb2bfb 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -7,11 +7,11 @@ import { type Transport } from "viem" import type { SmartAccount } from "../../accounts/types.js" -import type { BundlerActions } from "../../clients/decorators/bundler.js" -import type { BundlerRpcSchema } from "../../types/bundler.js" import { parseAccount } from "../../utils/index.js" import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" import { sendUserOperation } from "./sendUserOperation.js" +import { getAction } from "../../utils/getAction.js" +import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" export async function deployContract< const TAbi extends Abi | readonly unknown[], @@ -19,7 +19,7 @@ export async function deployContract< TAccount extends SmartAccount | undefined, TChainOverride extends Chain | undefined >( - client: Client, + client: Client, { abi, args, bytecode, ...request }: DeployContractParameters ): Promise { const { account: account_ = client.account } = request @@ -32,7 +32,10 @@ export async function deployContract< const account = parseAccount(account_) as SmartAccount - const userOpHash = await sendUserOperation(client, { + const userOpHash = await getAction( + client, + sendUserOperation + )({ userOperation: { sender: account.address, paymasterAndData: "0x", @@ -47,7 +50,10 @@ export async function deployContract< account: account }) - const userOperationReceipt = await client.waitForUserOperationReceipt({ + const userOperationReceipt = await getAction( + client, + waitForUserOperationReceipt + )({ hash: userOpHash }) diff --git a/src/actions/smartAccount/getChainId.ts b/src/actions/smartAccount/getChainId.ts index a46e7ea2..13b638cb 100644 --- a/src/actions/smartAccount/getChainId.ts +++ b/src/actions/smartAccount/getChainId.ts @@ -1,10 +1,10 @@ import { type Chain, type Client, type GetChainIdReturnType, type Transport } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { type BundlerActions } from "../../clients/decorators/bundler.js" -import { type BundlerRpcSchema } from "../../types/bundler.js" +import { getAction } from "../../utils/getAction.js" +import { chainId } from "../bundler/chainId.js" export async function getChainId( - client: Client + client: Client ): Promise { - return client.chainId() + return getAction(client, chainId)([]) } diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts index 39d086c1..e7641c40 100644 --- a/src/actions/smartAccount/prepareUserOperationRequest.ts +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -1,10 +1,10 @@ import type { Chain, Client, Transport } from "viem" import { estimateFeesPerGas } from "viem/actions" import type { SmartAccount } from "../../accounts/types" -import type { BundlerActions } from "../../clients/decorators/bundler" import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" -import type { BundlerRpcSchema } from "../../types/bundler" import { AccountOrClientNotFoundError, parseAccount } from "../../utils" +import { getAction } from "../../utils/getAction" +import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas" export type PrepareUserOperationRequestParameters< TAccount extends SmartAccount | undefined = SmartAccount | undefined @@ -31,7 +31,7 @@ export async function prepareUserOperationRequest< TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartAccount | undefined = SmartAccount | undefined >( - client: Client, + client: Client, args: PrepareUserOperationRequestParameters ): Promise { const { account: account_ = client.account, userOperation: partialUserOperation } = args @@ -66,7 +66,10 @@ export async function prepareUserOperationRequest< } if (!userOperation.callGasLimit || !userOperation.verificationGasLimit || !userOperation.preVerificationGas) { - const gasParameters = await client.estimateUserOperationGas({ + const gasParameters = await getAction( + client, + estimateUserOperationGas + )({ userOperation, entryPoint: account.entryPoint }) diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index fa15794f..b19a3bd6 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,16 +1,16 @@ import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { type BundlerActions } from "../../clients/decorators/bundler.js" -import { type BundlerRpcSchema } from "../../types/bundler.js" import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" import { sendUserOperation } from "./sendUserOperation.js" +import { getAction } from "../../utils/getAction.js" +import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" export async function sendTransaction< TChain extends Chain | undefined, TAccount extends SmartAccount | undefined, TChainOverride extends Chain | undefined >( - client: Client, + client: Client, args: SendTransactionParameters ): Promise { const { account: account_ = client.account, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = args @@ -29,22 +29,30 @@ export async function sendTransaction< throw new Error("RPC account type not supported") } - const userOpHash = await sendUserOperation(client, { + const callData = await account.encodeCallData({ + to, + value: value || 0n, + data: data || "0x" + }) + + const userOpHash = await getAction( + client, + sendUserOperation + )({ userOperation: { sender: account.address, paymasterAndData: "0x", maxFeePerGas: maxFeePerGas || 0n, maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callData: await account.encodeCallData({ - to, - value: value || 0n, - data: data || "0x" - }) + callData: callData }, account: account }) - const userOperationReceipt = await client.waitForUserOperationReceipt({ + const userOperationReceipt = await getAction( + client, + waitForUserOperationReceipt + )({ hash: userOpHash }) diff --git a/src/actions/smartAccount/sendUserOperation.ts b/src/actions/smartAccount/sendUserOperation.ts index 37631b9b..b09d72aa 100644 --- a/src/actions/smartAccount/sendUserOperation.ts +++ b/src/actions/smartAccount/sendUserOperation.ts @@ -1,10 +1,10 @@ import type { Chain, Client, Hex, Transport } from "viem" import type { SmartAccount } from "../../accounts/types" -import type { BundlerActions } from "../../clients/decorators/bundler" import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" -import type { BundlerRpcSchema } from "../../types/bundler" import { AccountOrClientNotFoundError, parseAccount } from "../../utils" import { prepareUserOperationRequest } from "./prepareUserOperationRequest" +import { getAction } from "../../utils/getAction" +import { sendUserOperation as sendUserOperationBundler } from "../bundler/sendUserOperation" export type SendUserOperationParameters = { userOperation: PartialBy< @@ -29,7 +29,7 @@ export async function sendUserOperation< TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartAccount | undefined = SmartAccount | undefined >( - client: Client, + client: Client, args: SendUserOperationParameters ): Promise { const { account: account_ = client.account } = args @@ -37,11 +37,15 @@ export async function sendUserOperation< const account = parseAccount(account_) as SmartAccount - const userOperation = await prepareUserOperationRequest(client, args) + const userOperation = await getAction(client, prepareUserOperationRequest)(args) userOperation.signature = await account.signUserOperation(userOperation) - const userOpHash = await client.sendUserOperation({ + const userOpHash = await getAction( + client, + sendUserOperationBundler, + "sendUserOperation" + )({ userOperation: userOperation, entryPoint: account.entryPoint }) diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index 25976544..e9c787d6 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -10,9 +10,8 @@ import { encodeFunctionData } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { type BundlerActions } from "../../clients/decorators/bundler.js" -import { type BundlerRpcSchema } from "../../types/bundler.js" import { sendTransaction } from "./sendTransaction.js" +import { getAction } from "../../utils/getAction.js" export async function writeContract< TChain extends Chain | undefined, @@ -21,7 +20,7 @@ export async function writeContract< TFunctionName extends string, TChainOverride extends Chain | undefined = undefined >( - client: Client, + client: Client, { abi, address, @@ -36,7 +35,10 @@ export async function writeContract< args, functionName } as unknown as EncodeFunctionDataParameters) - const hash = await sendTransaction(client, { + const hash = await getAction( + client, + sendTransaction + )({ data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, to: address, ...request diff --git a/src/clients/createSmartAccountClient.ts b/src/clients/createSmartAccountClient.ts index 744b0d29..ec997391 100644 --- a/src/clients/createSmartAccountClient.ts +++ b/src/clients/createSmartAccountClient.ts @@ -3,14 +3,13 @@ import { createClient } from "viem" import { type SmartAccount } from "../accounts/types.js" import { type BundlerRpcSchema } from "../types/bundler.js" import type { Prettify } from "../types/index.js" -import { type BundlerActions, bundlerActions } from "./decorators/bundler.js" import { type SmartAccountActions, smartAccountActions } from "./decorators/smartAccount.js" export type SmartAccountClient< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined -> = Prettify>> +> = Prettify>> export type SmartAccountClientConfig< transport extends Transport = Transport, @@ -56,7 +55,7 @@ export const createSmartAccountClient = < name, transport: (opts) => transport({ ...opts, retryCount: 0 }), type: "smartAccountClient" - }).extend(bundlerActions) + }) return client.extend(smartAccountActions) } diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index cc51b62b..1e1f0052 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -6,8 +6,6 @@ import { sendTransaction } from "../../actions/smartAccount/sendTransaction.js" import { signMessage } from "../../actions/smartAccount/signMessage.js" import { signTypedData } from "../../actions/smartAccount/signTypedData.js" import { writeContract } from "../../actions/smartAccount/writeContract.js" -import { type BundlerRpcSchema } from "../../types/bundler.js" -import { type BundlerActions } from "./bundler.js" export type SmartAccountActions< TChain extends Chain | undefined = Chain | undefined, @@ -40,7 +38,7 @@ export const smartAccountActions = < TChain extends Chain | undefined = Chain | undefined, TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined >( - client: Client + client: Client ): SmartAccountActions => ({ deployContract: (args) => deployContract(client, args), getChainId: () => getChainId(client), diff --git a/src/utils/getAction.ts b/src/utils/getAction.ts new file mode 100644 index 00000000..6abd0f01 --- /dev/null +++ b/src/utils/getAction.ts @@ -0,0 +1,14 @@ +import type { Client } from "viem" + +export function getAction( + client: Client, + action: (_: any, params: params) => returnType, + actionName: string = action.name +) { + return (params: params): returnType => + ( + client as Client & { + [key: string]: (params: params) => returnType + } + )[actionName]?.(params) ?? action(client, params) +} diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 88b3620c..231a3ddb 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -1,9 +1,9 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import { Address, Hex, zeroAddress } from "viem" +import { Address, Hex, getContract, zeroAddress } from "viem" import { GreeterAbi, GreeterBytecode } from "./abis/Greeter" -import { getPrivateKeyToSimpleSmartAccount, getSmartAccountClient, getTestingChain } from "./utils" +import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils" dotenv.config() @@ -29,6 +29,10 @@ beforeAll(() => { if (!process.env.ENTRYPOINT_ADDRESS) { throw new Error("ENTRYPOINT_ADDRESS environment variable not set") } + + if (!process.env.GREETER_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex factoryAddress = process.env.FACTORY_ADDRESS as Address }) @@ -103,7 +107,7 @@ describe("Simple Account", () => { expect(response).toMatch(/^0x[0-9a-fA-F]{130}$/) }) - test("smart account client write & deploy contract", async () => { + test("smart account client deploy contract", async () => { const smartAccountClient = await getSmartAccountClient() expect(async () => { @@ -114,6 +118,26 @@ describe("Simple Account", () => { }).toThrow("Simple account doesn't support account deployment") }) + test("Smart account write contract", async () => { + const greeterContract = getContract({ + abi: GreeterAbi, + address: process.env.GREETER_ADDRESS as Address, + publicClient: await getPublicClient(), + walletClient: await getSmartAccountClient() + }) + + const oldGreet = await greeterContract.read.greet() + + expect(oldGreet).toBeString() + + const txHash = await greeterContract.write.setGreeting(["hello world"]) + + const newGreet = await greeterContract.read.greet() + + expect(newGreet).toBeString() + expect(newGreet).toEqual("hello world") + }, 1000000) + test("Smart account client send transaction", async () => { const smartAccountClient = await getSmartAccountClient() const response = await smartAccountClient.sendTransaction({ diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index 11eeef85..40316881 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -9,9 +9,7 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay const entryPoint = getEntryPoint() const chain = getTestingChain() - console.log("STACKUP ACTIONS:: ======= TESTING STACKUP PAYMASTER ACTIONS =======") const supportedPaymasters = await stackupBundlerClient.accounts({ entryPoint }) - console.log("PAYMASTER ADDRESSES: ", supportedPaymasters) const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() @@ -41,12 +39,8 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - console.log(userOperation, "STACKUP ACTIONS:: ============= USER OPERATION =============") - const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) - console.log(userOperationHash, "STACKUP ACTIONS:: ============= USER OPERATION HASH =============") - const signedUserOperation: UserOperation = { ...userOperation, signature: await eoaWalletClient.signMessage({ @@ -54,14 +48,12 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay message: { raw: userOperationHash } }) } - console.log(signedUserOperation, "STACKUP ACTIONS:: ============= SIGNED USER OPERATION HASH =============") const userOpHash = await stackupBundlerClient.sendUserOperation({ userOperation: signedUserOperation, entryPoint: entryPoint as Address }) - console.log("userOpHash", userOpHash) await stackupBundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) From 72e37aef8106c360f1ce96210cf62aa71a97cdc5 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Wed, 8 Nov 2023 14:38:20 +0530 Subject: [PATCH 09/26] Lint --- src/actions/bundler/waitForUserOperationReceipt.ts | 2 +- src/actions/pimlico/sponsorUserOperation.ts | 2 +- src/actions/smartAccount/deployContract.ts | 4 ++-- src/actions/smartAccount/sendTransaction.ts | 4 ++-- src/actions/smartAccount/sendUserOperation.ts | 2 +- src/actions/smartAccount/writeContract.ts | 2 +- src/utils/getAction.ts | 1 + 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/actions/bundler/waitForUserOperationReceipt.ts b/src/actions/bundler/waitForUserOperationReceipt.ts index bb0c7452..eab421a7 100644 --- a/src/actions/bundler/waitForUserOperationReceipt.ts +++ b/src/actions/bundler/waitForUserOperationReceipt.ts @@ -1,8 +1,8 @@ import { BaseError, type Chain, type Hash, stringify } from "viem" import type { BundlerClient } from "../../clients/createBundlerClient.js" +import { getAction } from "../../utils/getAction.js" import { observe } from "../../utils/observe.js" import { type GetUserOperationReceiptReturnType, getUserOperationReceipt } from "./getUserOperationReceipt.js" -import { getAction } from "../../utils/getAction.js" export class WaitForUserOperationReceiptTimeoutError extends BaseError { override name = "WaitForUserOperationReceiptTimeoutError" diff --git a/src/actions/pimlico/sponsorUserOperation.ts b/src/actions/pimlico/sponsorUserOperation.ts index d4b0851e..a516e130 100644 --- a/src/actions/pimlico/sponsorUserOperation.ts +++ b/src/actions/pimlico/sponsorUserOperation.ts @@ -1,8 +1,8 @@ import type { Account, Address, Chain, Client, Hex, Transport } from "viem" import type { PartialBy } from "viem/types/utils" +import type { PimlicoPaymasterRpcSchema } from "../../types/pimlico.js" import type { UserOperation, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" -import type { PimlicoPaymasterRpcSchema } from "../../types/pimlico.js" export type SponsorUserOperationParameters = { userOperation: PartialBy< diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index 58fb2bfb..622ea0f3 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -7,11 +7,11 @@ import { type Transport } from "viem" import type { SmartAccount } from "../../accounts/types.js" +import { getAction } from "../../utils/getAction.js" import { parseAccount } from "../../utils/index.js" import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" -import { sendUserOperation } from "./sendUserOperation.js" -import { getAction } from "../../utils/getAction.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" +import { sendUserOperation } from "./sendUserOperation.js" export async function deployContract< const TAbi extends Abi | readonly unknown[], diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index b19a3bd6..465be855 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,9 +1,9 @@ import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" -import { sendUserOperation } from "./sendUserOperation.js" import { getAction } from "../../utils/getAction.js" +import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" +import { sendUserOperation } from "./sendUserOperation.js" export async function sendTransaction< TChain extends Chain | undefined, diff --git a/src/actions/smartAccount/sendUserOperation.ts b/src/actions/smartAccount/sendUserOperation.ts index b09d72aa..d432cdae 100644 --- a/src/actions/smartAccount/sendUserOperation.ts +++ b/src/actions/smartAccount/sendUserOperation.ts @@ -2,9 +2,9 @@ import type { Chain, Client, Hex, Transport } from "viem" import type { SmartAccount } from "../../accounts/types" import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" import { AccountOrClientNotFoundError, parseAccount } from "../../utils" -import { prepareUserOperationRequest } from "./prepareUserOperationRequest" import { getAction } from "../../utils/getAction" import { sendUserOperation as sendUserOperationBundler } from "../bundler/sendUserOperation" +import { prepareUserOperationRequest } from "./prepareUserOperationRequest" export type SendUserOperationParameters = { userOperation: PartialBy< diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index e9c787d6..2c011665 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -10,8 +10,8 @@ import { encodeFunctionData } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { sendTransaction } from "./sendTransaction.js" import { getAction } from "../../utils/getAction.js" +import { sendTransaction } from "./sendTransaction.js" export async function writeContract< TChain extends Chain | undefined, diff --git a/src/utils/getAction.ts b/src/utils/getAction.ts index 6abd0f01..9022a162 100644 --- a/src/utils/getAction.ts +++ b/src/utils/getAction.ts @@ -2,6 +2,7 @@ import type { Client } from "viem" export function getAction( client: Client, + // biome-ignore lint/suspicious/noExplicitAny: it's a recursive function, so it's hard to type action: (_: any, params: params) => returnType, actionName: string = action.name ) { From a7caff50dd31e8e09d8c43a2b1d92a010054cafd Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Wed, 8 Nov 2023 15:28:12 +0530 Subject: [PATCH 10/26] Esm support changes --- src/accounts/types.ts | 2 +- src/actions/pimlico/getUserOperationGasPrice.ts | 2 +- src/actions/public/getAccountNonce.ts | 2 +- src/actions/public/getSenderAddress.ts | 2 +- .../smartAccount/prepareUserOperationRequest.ts | 10 +++++----- src/actions/smartAccount/sendUserOperation.ts | 12 ++++++------ test/bundlerActions.test.ts | 4 ++-- test/index.test.ts | 4 ++-- test/pimlicoActions.test.ts | 4 ++-- test/simpleAccount.test.ts | 4 ++-- test/stackupActions.test.ts | 4 ++-- test/userOp.ts | 6 +++--- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/accounts/types.ts b/src/accounts/types.ts index f0affb64..c5d0928e 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -1,6 +1,6 @@ import type { Abi, Address, Client, GetConstructorArgs, Hex, LocalAccount } from "viem" import type { Chain, Transport } from "viem" -import { type UserOperation } from "../types" +import { type UserOperation } from "../types/index.js" export type SmartAccount< Name extends string = string, diff --git a/src/actions/pimlico/getUserOperationGasPrice.ts b/src/actions/pimlico/getUserOperationGasPrice.ts index 646aefc3..72a3475d 100644 --- a/src/actions/pimlico/getUserOperationGasPrice.ts +++ b/src/actions/pimlico/getUserOperationGasPrice.ts @@ -1,5 +1,5 @@ import type { Account, Chain, Client, Transport } from "viem" -import type { PimlicoBundlerRpcSchema } from "../../types/pimlico" +import type { PimlicoBundlerRpcSchema } from "../../types/pimlico.js" export type GetUserOperationGasPriceReturnType = { slow: { diff --git a/src/actions/public/getAccountNonce.ts b/src/actions/public/getAccountNonce.ts index fe0aa62d..af50bb43 100644 --- a/src/actions/public/getAccountNonce.ts +++ b/src/actions/public/getAccountNonce.ts @@ -1,6 +1,6 @@ import type { Address, Chain, Client, Transport } from "viem" import { readContract } from "viem/actions" -import { getAction } from "../../utils/getAction" +import { getAction } from "../../utils/getAction.js" export type GetAccountNonceParams = { sender: Address diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index b2db4b03..13092b64 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -10,7 +10,7 @@ import { } from "viem" import { simulateContract } from "viem/actions" -import { getAction } from "../../utils/getAction" +import { getAction } from "../../utils/getAction.js" export type GetSenderAddressParams = { initCode: Hex; entryPoint: Address } diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts index e7641c40..e8f14a5d 100644 --- a/src/actions/smartAccount/prepareUserOperationRequest.ts +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -1,10 +1,10 @@ import type { Chain, Client, Transport } from "viem" import { estimateFeesPerGas } from "viem/actions" -import type { SmartAccount } from "../../accounts/types" -import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils" -import { getAction } from "../../utils/getAction" -import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas" +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" export type PrepareUserOperationRequestParameters< TAccount extends SmartAccount | undefined = SmartAccount | undefined diff --git a/src/actions/smartAccount/sendUserOperation.ts b/src/actions/smartAccount/sendUserOperation.ts index d432cdae..58a1fd74 100644 --- a/src/actions/smartAccount/sendUserOperation.ts +++ b/src/actions/smartAccount/sendUserOperation.ts @@ -1,10 +1,10 @@ import type { Chain, Client, Hex, Transport } from "viem" -import type { SmartAccount } from "../../accounts/types" -import type { GetAccountParameter, PartialBy, UserOperation } from "../../types" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils" -import { getAction } from "../../utils/getAction" -import { sendUserOperation as sendUserOperationBundler } from "../bundler/sendUserOperation" -import { prepareUserOperationRequest } from "./prepareUserOperationRequest" +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 { sendUserOperation as sendUserOperationBundler } from "../bundler/sendUserOperation.js" +import { prepareUserOperationRequest } from "./prepareUserOperationRequest.js" export type SendUserOperationParameters = { userOperation: PartialBy< diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index 843b5292..77083818 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -3,8 +3,8 @@ import dotenv from "dotenv" import { BundlerClient, WaitForUserOperationReceiptTimeoutError } from "permissionless" import { getUserOperationHash } from "permissionless/utils" import { http, Address, Hex } from "viem" -import { buildUserOp } from "./userOp" -import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils" +import { buildUserOp } from "./userOp.js" +import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils.js" dotenv.config() diff --git a/test/index.test.ts b/test/index.test.ts index 18725ffb..509420a4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { UserOperation, getSenderAddress, getUserOperationHash } from "permissionless" import { signUserOperationHashWithECDSA } from "permissionless/utils" -import { buildUserOp, getAccountInitCode } from "./userOp" +import { buildUserOp, getAccountInitCode } from "./userOp.js" import { getBundlerClient, getEntryPoint, @@ -11,7 +11,7 @@ import { getPrivateKeyAccount, getPublicClient, getTestingChain -} from "./utils" +} from "./utils.js" dotenv.config() const pimlicoApiKey = process.env.PIMLICO_API_KEY diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index fb8d5671..7f045fde 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -8,7 +8,7 @@ import { } from "permissionless/clients/pimlico" import { getUserOperationHash } from "permissionless/utils" import { http } from "viem" -import { buildUserOp } from "./userOp" +import { buildUserOp } from "./userOp.js" import { getEntryPoint, getEoaWalletClient, @@ -16,7 +16,7 @@ import { getPimlicoPaymasterClient, getPublicClient, getTestingChain -} from "./utils" +} from "./utils.js" dotenv.config() diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 231a3ddb..fd5d5bee 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -2,8 +2,8 @@ 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 { GreeterAbi, GreeterBytecode } from "./abis/Greeter" -import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils" +import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js" +import { getPrivateKeyToSimpleSmartAccount, getPublicClient, getSmartAccountClient, getTestingChain } from "./utils.js" dotenv.config() diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index 40316881..e096d80f 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -2,8 +2,8 @@ import { BundlerClient, UserOperation } from "permissionless" import { StackupPaymasterClient } from "permissionless/clients/stackup" import { getUserOperationHash } from "permissionless/utils" import { Address } from "viem" -import { buildUserOp } from "./userOp" -import { getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils" +import { buildUserOp } from "./userOp.js" +import { getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils.js" export const testStackupBundlerActions = async (stackupBundlerClient: StackupPaymasterClient) => { const entryPoint = getEntryPoint() diff --git a/test/userOp.ts b/test/userOp.ts index 2135b614..973b558b 100644 --- a/test/userOp.ts +++ b/test/userOp.ts @@ -1,9 +1,9 @@ import { UserOperation, getAccountNonce, getSenderAddress } from "permissionless" import { Address, Hex, WalletClient, concatHex, encodeFunctionData, zeroAddress } from "viem" import { PartialBy } from "viem/types/utils" -import { SimpleAccountAbi } from "./abis/SimpleAccount" -import { SimpleAccountFactoryAbi } from "./abis/SimpleAccountFactory" -import { getDummySignature, getEntryPoint, getFactoryAddress, getPublicClient, isAccountDeployed } from "./utils" +import { SimpleAccountAbi } from "./abis/SimpleAccount.js" +import { SimpleAccountFactoryAbi } from "./abis/SimpleAccountFactory.js" +import { getDummySignature, getEntryPoint, getFactoryAddress, getPublicClient, isAccountDeployed } from "./utils.js" const getInitCode = async (factoryAddress: Address, owner: WalletClient) => { const accountAddress = await getAccountAddress(factoryAddress, owner) From 2eed7319d3754a09130fdec07bc3fd54de655c5a Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 9 Nov 2023 12:45:57 +0530 Subject: [PATCH 11/26] release canary on pr --- .github/workflows/on-pull-request.yml | 35 ++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 2b1cd013..d8c3a0ff 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -55,4 +55,37 @@ jobs: uses: andresz1/size-limit-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} - package_manager: bun \ No newline at end of file + package_manager: bun + + + canary: + name: Release canary + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install dependencies + uses: ./.github/actions/install-dependencies + + - name: Setup .npmrc file + uses: actions/setup-node@v3 + with: + registry-url: 'https://registry.npmjs.org' + + - name: Set version + run: | + jq --arg prop "workspaces" 'del(.[$prop])' package.json > package.tmp.json && rm package.json && cp package.tmp.json package.json && rm package.tmp.json + cd src + npm --no-git-tag-version version 0.0.0 + npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S') + + - name: Build + run: bun run build + + - name: Publish to npm + run: cd src && npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 437a26048774f57b397dc8a818ed1b47a8678b4e Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 9 Nov 2023 12:48:53 +0530 Subject: [PATCH 12/26] Add changeset --- .changeset/great-bats-itch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/great-bats-itch.md diff --git a/.changeset/great-bats-itch.md b/.changeset/great-bats-itch.md new file mode 100644 index 00000000..fc4d78e4 --- /dev/null +++ b/.changeset/great-bats-itch.md @@ -0,0 +1,5 @@ +--- +"permissionless": patch +--- + +Account system From fe06e024d0502cdb43b7568b62bd5a2a3d15d85c Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 9 Nov 2023 12:52:38 +0530 Subject: [PATCH 13/26] Revert "release canary on pr" This reverts commit 2eed7319d3754a09130fdec07bc3fd54de655c5a. --- .github/workflows/on-pull-request.yml | 35 +-------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index d8c3a0ff..2b1cd013 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -55,37 +55,4 @@ jobs: uses: andresz1/size-limit-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} - package_manager: bun - - - canary: - name: Release canary - runs-on: ubuntu-latest - timeout-minutes: 5 - - steps: - - name: Clone repository - uses: actions/checkout@v3 - - - name: Install dependencies - uses: ./.github/actions/install-dependencies - - - name: Setup .npmrc file - uses: actions/setup-node@v3 - with: - registry-url: 'https://registry.npmjs.org' - - - name: Set version - run: | - jq --arg prop "workspaces" 'del(.[$prop])' package.json > package.tmp.json && rm package.json && cp package.tmp.json package.json && rm package.tmp.json - cd src - npm --no-git-tag-version version 0.0.0 - npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S') - - - name: Build - run: bun run build - - - name: Publish to npm - run: cd src && npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + package_manager: bun \ No newline at end of file From 13c0ce647c041b9de591ee0551c4ec98b1994660 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 9 Nov 2023 12:52:59 +0530 Subject: [PATCH 14/26] Revert "Add changeset" This reverts commit 437a26048774f57b397dc8a818ed1b47a8678b4e. --- .changeset/great-bats-itch.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/great-bats-itch.md diff --git a/.changeset/great-bats-itch.md b/.changeset/great-bats-itch.md deleted file mode 100644 index fc4d78e4..00000000 --- a/.changeset/great-bats-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"permissionless": patch ---- - -Account system From 9ff308f600819e50c4348d6a48fd5b15c4378619 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 9 Nov 2023 14:08:07 +0530 Subject: [PATCH 15/26] Allow users to specify custom action to sponsor user operation --- src/accounts/index.ts | 9 +++- src/actions/smartAccount.ts | 44 +++++++++++++++ .../prepareUserOperationRequest.ts | 32 ++++++----- .../smartAccount/sponsorUserOperation.ts | 54 +++++++++++++++++++ src/package.json | 6 +++ test/simpleAccount.test.ts | 36 ++++++++++++- 6 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 src/actions/smartAccount.ts create mode 100644 src/actions/smartAccount/sponsorUserOperation.ts diff --git a/src/accounts/index.ts b/src/accounts/index.ts index ac83d372..dec18329 100644 --- a/src/accounts/index.ts +++ b/src/accounts/index.ts @@ -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 +} diff --git a/src/actions/smartAccount.ts b/src/actions/smartAccount.ts new file mode 100644 index 00000000..70ed67ed --- /dev/null +++ b/src/actions/smartAccount.ts @@ -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 +} diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts index e8f14a5d..3ba892dd 100644 --- a/src/actions/smartAccount/prepareUserOperationRequest.ts +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -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, @@ -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 @@ -57,7 +56,7 @@ 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, @@ -65,19 +64,18 @@ export async function prepareUserOperationRequest< 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 } diff --git a/src/actions/smartAccount/sponsorUserOperation.ts b/src/actions/smartAccount/sponsorUserOperation.ts new file mode 100644 index 00000000..b95a2c53 --- /dev/null +++ b/src/actions/smartAccount/sponsorUserOperation.ts @@ -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 = { + userOperation: UserOperation +} & GetAccountParameter + +export type SponsorUserOperationReturnType = { + paymasterAndData: Hex + preVerificationGas: bigint + verificationGasLimit: bigint + callGasLimit: bigint +} + +export async function sponsorUserOperation( + client: Client, + args: SponsorUserOperationParameters +): Promise { + 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 + } +} diff --git a/src/package.json b/src/package.json index 9c0b4162..5abbb12d 100644 --- a/src/package.json +++ b/src/package.json @@ -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", diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index fd5d5bee..d4d38359 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -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() @@ -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 => { + 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) }) From a7c79ad37365843060d1930cdc28a5f06a86ac65 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Sat, 11 Nov 2023 12:44:55 +0530 Subject: [PATCH 16/26] Move to mnemonic instead of private key for tests --- src/utils/deepHexlify.test.ts | 1 - test/bundlerActions.test.ts | 40 +++++++++++++---- test/index.test.ts | 21 ++++++--- test/pimlicoActions.test.ts | 27 +++++++++--- test/simpleAccount.test.ts | 61 ++++++++++++-------------- test/stackupActions.test.ts | 10 ++++- test/utils.ts | 82 +++++++++++++++++++++++++++-------- 7 files changed, 165 insertions(+), 77 deletions(-) diff --git a/src/utils/deepHexlify.test.ts b/src/utils/deepHexlify.test.ts index dfffa44c..269507f4 100644 --- a/src/utils/deepHexlify.test.ts +++ b/src/utils/deepHexlify.test.ts @@ -8,7 +8,6 @@ beforeAll(() => { if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index 77083818..e40d658e 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -9,12 +9,21 @@ import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, g dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.STACKUP_API_KEY) { + throw new Error("STACKUP_API_KEY environment variable not set") + } + if (!process.env.FACTORY_ADDRESS) { + throw new Error("FACTORY_ADDRESS environment variable not set") + } + if (!process.env.RPC_URL) { + throw new Error("RPC_URL environment variable not set") + } + if (!process.env.ENTRYPOINT_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } }) describe("BUNDLER ACTIONS", () => { @@ -93,7 +102,13 @@ describe("BUNDLER ACTIONS", () => { userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, - message: { raw: getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) } + message: { + raw: getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) + } }) const userOpHash = await bundlerClient.sendUserOperation({ @@ -149,10 +164,17 @@ describe("BUNDLER ACTIONS", () => { userOperation.verificationGasLimit = gasParameters.verificationGasLimit userOperation.preVerificationGas = gasParameters.preVerificationGas - const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOpHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) expect(async () => { - await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash, timeout: 100 }) + await bundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + timeout: 100 + }) }).toThrow(new WaitForUserOperationReceiptTimeoutError({ hash: userOpHash })) }) }) diff --git a/test/index.test.ts b/test/index.test.ts index 509420a4..bceae279 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -17,12 +17,21 @@ dotenv.config() const pimlicoApiKey = process.env.PIMLICO_API_KEY beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.STACKUP_API_KEY) { + throw new Error("STACKUP_API_KEY environment variable not set") + } + if (!process.env.FACTORY_ADDRESS) { + throw new Error("FACTORY_ADDRESS environment variable not set") + } + if (!process.env.RPC_URL) { + throw new Error("RPC_URL environment variable not set") + } + if (!process.env.ENTRYPOINT_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } }) describe("test public actions and utils", () => { diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index 7f045fde..7676149d 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -21,12 +21,21 @@ import { dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.STACKUP_API_KEY) { + throw new Error("STACKUP_API_KEY environment variable not set") + } + if (!process.env.FACTORY_ADDRESS) { + throw new Error("FACTORY_ADDRESS environment variable not set") + } + if (!process.env.RPC_URL) { + throw new Error("RPC_URL environment variable not set") + } + if (!process.env.ENTRYPOINT_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } }) const pimlicoApiKey = process.env.PIMLICO_API_KEY @@ -144,7 +153,11 @@ describe("Pimlico Actions tests", () => { userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOperationHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index d4d38359..cf5ac17d 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { SponsorUserOperationParameters, SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js" -import { Address, Client, Hex, Transport, getContract, zeroAddress } from "viem" +import { Address, Client, getContract, zeroAddress } from "viem" import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js" import { getEntryPoint, @@ -15,9 +15,6 @@ import { dotenv.config() -let testPrivateKey: Hex -let factoryAddress: Address - beforeAll(() => { if (!process.env.PIMLICO_API_KEY) { throw new Error("PIMLICO_API_KEY environment variable not set") @@ -28,9 +25,6 @@ beforeAll(() => { if (!process.env.FACTORY_ADDRESS) { throw new Error("FACTORY_ADDRESS environment variable not set") } - if (!process.env.TEST_PRIVATE_KEY) { - throw new Error("TEST_PRIVATE_KEY environment variable not set") - } if (!process.env.RPC_URL) { throw new Error("RPC_URL environment variable not set") } @@ -41,8 +35,6 @@ beforeAll(() => { if (!process.env.GREETER_ADDRESS) { throw new Error("ENTRYPOINT_ADDRESS environment variable not set") } - testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex - factoryAddress = process.env.FACTORY_ADDRESS as Address }) describe("Simple Account", () => { @@ -126,6 +118,33 @@ describe("Simple Account", () => { }).toThrow("Simple account doesn't support account deployment") }) + test("smart account client send Transaction with paymaster", async () => { + const smartAccountClient = await getSmartAccountClient() + + const smartAccountClientNew = smartAccountClient.extend((_: Client) => ({ + sponsorUserOperation: async ( + args: SponsorUserOperationParameters + ): Promise => { + const pimlicoPaymaster = getPimlicoPaymasterClient() + const { userOperation } = args + return pimlicoPaymaster.sponsorUserOperation({ + userOperation, + entryPoint: getEntryPoint() + }) + } + })) + + const response = await smartAccountClientNew.sendTransaction({ + to: zeroAddress, + value: 0n, + data: "0x" + }) + + expect(response).toBeString() + expect(response).toHaveLength(66) + expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/) + }, 1000000) + test("Smart account write contract", async () => { const greeterContract = getContract({ abi: GreeterAbi, @@ -157,28 +176,4 @@ 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 => { - 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) }) diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index e096d80f..d97669cb 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -9,7 +9,9 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay const entryPoint = getEntryPoint() const chain = getTestingChain() - const supportedPaymasters = await stackupBundlerClient.accounts({ entryPoint }) + const supportedPaymasters = await stackupBundlerClient.accounts({ + entryPoint + }) const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() @@ -39,7 +41,11 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOperationHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) const signedUserOperation: UserOperation = { ...userOperation, diff --git a/test/utils.ts b/test/utils.ts index 440e5589..b1cde10f 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,19 +1,21 @@ +import fs from "fs" import { createBundlerClient, createSmartAccountClient } from "permissionless" import { privateKeyToSimpleSmartAccount } from "permissionless/accounts" import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" -import { http, Address, Hex, createPublicClient, createWalletClient } from "viem" -import { privateKeyToAccount } from "viem/accounts" +import { http, Address, Hex, createPublicClient, createWalletClient, toHex } from "viem" +import { mnemonicToAccount } from "viem/accounts" import { goerli } from "viem/chains" export const getFactoryAddress = () => { - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.FACTORY_ADDRESS) { + throw new Error("FACTORY_ADDRESS environment variable not set") + } const factoryAddress = process.env.FACTORY_ADDRESS as Address return factoryAddress } export const getPrivateKeyAccount = () => { - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - return privateKeyToAccount(process.env.TEST_PRIVATE_KEY as Hex) + return mnemonicToAccount(getMnemonic()) } export const getTestingChain = () => { @@ -21,20 +23,30 @@ export const getTestingChain = () => { } export const getPrivateKeyToSimpleSmartAccount = async () => { - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - const publicClient = await getPublicClient() + const mnemonicAccount = getPrivateKeyAccount() + + const hdKey = mnemonicAccount.getHdKey().derive(`m/44'/60'/0'/0/0`) + + if (!hdKey.privateKey) throw new Error("hdkey not found") + + const privateKey = toHex(hdKey.privateKey) + return await privateKeyToSimpleSmartAccount(publicClient, { entryPoint: getEntryPoint(), factoryAddress: getFactoryAddress(), - privateKey: process.env.TEST_PRIVATE_KEY as Hex + privateKey: privateKey }) } export const getSmartAccountClient = async () => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + } const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -56,12 +68,16 @@ export const getEoaWalletClient = () => { } export const getEntryPoint = () => { - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) { + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + } return process.env.ENTRYPOINT_ADDRESS as Address } export const getPublicClient = async () => { - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.RPC_URL) { + throw new Error("RPC_URL environment variable not set") + } const publicClient = createPublicClient({ transport: http(process.env.RPC_URL as string) @@ -69,14 +85,20 @@ export const getPublicClient = async () => { const chainId = await publicClient.getChainId() - if (chainId !== getTestingChain().id) throw new Error("Testing Chain ID not supported by RPC URL") + if (chainId !== getTestingChain().id) { + throw new Error("Testing Chain ID not supported by RPC URL") + } return publicClient } export const getBundlerClient = () => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + } const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -90,8 +112,12 @@ export const getBundlerClient = () => { } export const getPimlicoBundlerClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + } + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -105,8 +131,12 @@ export const getPimlicoBundlerClient = () => { } export const getPimlicoPaymasterClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + } + if (!process.env.PIMLICO_API_KEY) { + throw new Error("PIMLICO_API_KEY environment variable not set") + } const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -138,3 +168,17 @@ export const getDummySignature = (): Hex => { export const getOldUserOpHash = (): Hex => { return "0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34" } + +export const getMnemonic = (): string => { + const mnemonicFileName = process.env.MNEMONIC_FILE ?? `${process.env.HOME}/.secret/testnet-mnemonic.txt` + + let mnemonic = process.env.MNEMONIC + + if (fs.existsSync(mnemonicFileName)) { + mnemonic = fs.readFileSync(mnemonicFileName, "ascii") + } + if (!mnemonic) { + throw new Error("No mnemonic found") + } + return mnemonic +} From 395fb77a73ce51623d40ded47bc01615244eea40 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Sat, 11 Nov 2023 14:00:46 +0530 Subject: [PATCH 17/26] Revert "Move to mnemonic instead of private key for tests" This reverts commit a7c79ad37365843060d1930cdc28a5f06a86ac65. --- src/utils/deepHexlify.test.ts | 1 + test/bundlerActions.test.ts | 40 ++++------------- test/index.test.ts | 21 +++------ test/pimlicoActions.test.ts | 27 +++--------- test/simpleAccount.test.ts | 61 ++++++++++++++------------ test/stackupActions.test.ts | 10 +---- test/utils.ts | 82 ++++++++--------------------------- 7 files changed, 77 insertions(+), 165 deletions(-) diff --git a/src/utils/deepHexlify.test.ts b/src/utils/deepHexlify.test.ts index 269507f4..dfffa44c 100644 --- a/src/utils/deepHexlify.test.ts +++ b/src/utils/deepHexlify.test.ts @@ -8,6 +8,7 @@ beforeAll(() => { if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index e40d658e..77083818 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -9,21 +9,12 @@ import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, g dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } - if (!process.env.STACKUP_API_KEY) { - throw new Error("STACKUP_API_KEY environment variable not set") - } - if (!process.env.FACTORY_ADDRESS) { - throw new Error("FACTORY_ADDRESS environment variable not set") - } - if (!process.env.RPC_URL) { - throw new Error("RPC_URL environment variable not set") - } - if (!process.env.ENTRYPOINT_ADDRESS) { - throw new Error("ENTRYPOINT_ADDRESS environment variable not set") - } + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) describe("BUNDLER ACTIONS", () => { @@ -102,13 +93,7 @@ describe("BUNDLER ACTIONS", () => { userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, - message: { - raw: getUserOperationHash({ - userOperation, - entryPoint, - chainId: chain.id - }) - } + message: { raw: getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) } }) const userOpHash = await bundlerClient.sendUserOperation({ @@ -164,17 +149,10 @@ describe("BUNDLER ACTIONS", () => { userOperation.verificationGasLimit = gasParameters.verificationGasLimit userOperation.preVerificationGas = gasParameters.preVerificationGas - const userOpHash = getUserOperationHash({ - userOperation, - entryPoint, - chainId: chain.id - }) + const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) expect(async () => { - await bundlerClient.waitForUserOperationReceipt({ - hash: userOpHash, - timeout: 100 - }) + await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash, timeout: 100 }) }).toThrow(new WaitForUserOperationReceiptTimeoutError({ hash: userOpHash })) }) }) diff --git a/test/index.test.ts b/test/index.test.ts index bceae279..509420a4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -17,21 +17,12 @@ dotenv.config() const pimlicoApiKey = process.env.PIMLICO_API_KEY beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } - if (!process.env.STACKUP_API_KEY) { - throw new Error("STACKUP_API_KEY environment variable not set") - } - if (!process.env.FACTORY_ADDRESS) { - throw new Error("FACTORY_ADDRESS environment variable not set") - } - if (!process.env.RPC_URL) { - throw new Error("RPC_URL environment variable not set") - } - if (!process.env.ENTRYPOINT_ADDRESS) { - throw new Error("ENTRYPOINT_ADDRESS environment variable not set") - } + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) describe("test public actions and utils", () => { diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index 7676149d..7f045fde 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -21,21 +21,12 @@ import { dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } - if (!process.env.STACKUP_API_KEY) { - throw new Error("STACKUP_API_KEY environment variable not set") - } - if (!process.env.FACTORY_ADDRESS) { - throw new Error("FACTORY_ADDRESS environment variable not set") - } - if (!process.env.RPC_URL) { - throw new Error("RPC_URL environment variable not set") - } - if (!process.env.ENTRYPOINT_ADDRESS) { - throw new Error("ENTRYPOINT_ADDRESS environment variable not set") - } + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) const pimlicoApiKey = process.env.PIMLICO_API_KEY @@ -153,11 +144,7 @@ describe("Pimlico Actions tests", () => { userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - const userOperationHash = getUserOperationHash({ - userOperation, - entryPoint, - chainId: chain.id - }) + const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index cf5ac17d..d4d38359 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" import { SponsorUserOperationParameters, SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js" -import { Address, Client, getContract, zeroAddress } from "viem" +import { Address, Client, Hex, Transport, getContract, zeroAddress } from "viem" import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js" import { getEntryPoint, @@ -15,6 +15,9 @@ import { dotenv.config() +let testPrivateKey: Hex +let factoryAddress: Address + beforeAll(() => { if (!process.env.PIMLICO_API_KEY) { throw new Error("PIMLICO_API_KEY environment variable not set") @@ -25,6 +28,9 @@ beforeAll(() => { if (!process.env.FACTORY_ADDRESS) { throw new Error("FACTORY_ADDRESS environment variable not set") } + if (!process.env.TEST_PRIVATE_KEY) { + throw new Error("TEST_PRIVATE_KEY environment variable not set") + } if (!process.env.RPC_URL) { throw new Error("RPC_URL environment variable not set") } @@ -35,6 +41,8 @@ beforeAll(() => { if (!process.env.GREETER_ADDRESS) { throw new Error("ENTRYPOINT_ADDRESS environment variable not set") } + testPrivateKey = process.env.TEST_PRIVATE_KEY as Hex + factoryAddress = process.env.FACTORY_ADDRESS as Address }) describe("Simple Account", () => { @@ -118,33 +126,6 @@ describe("Simple Account", () => { }).toThrow("Simple account doesn't support account deployment") }) - test("smart account client send Transaction with paymaster", async () => { - const smartAccountClient = await getSmartAccountClient() - - const smartAccountClientNew = smartAccountClient.extend((_: Client) => ({ - sponsorUserOperation: async ( - args: SponsorUserOperationParameters - ): Promise => { - const pimlicoPaymaster = getPimlicoPaymasterClient() - const { userOperation } = args - return pimlicoPaymaster.sponsorUserOperation({ - userOperation, - entryPoint: getEntryPoint() - }) - } - })) - - const response = await smartAccountClientNew.sendTransaction({ - to: zeroAddress, - value: 0n, - data: "0x" - }) - - expect(response).toBeString() - expect(response).toHaveLength(66) - expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/) - }, 1000000) - test("Smart account write contract", async () => { const greeterContract = getContract({ abi: GreeterAbi, @@ -176,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 => { + 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) }) diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index d97669cb..e096d80f 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -9,9 +9,7 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay const entryPoint = getEntryPoint() const chain = getTestingChain() - const supportedPaymasters = await stackupBundlerClient.accounts({ - entryPoint - }) + const supportedPaymasters = await stackupBundlerClient.accounts({ entryPoint }) const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() @@ -41,11 +39,7 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - const userOperationHash = getUserOperationHash({ - userOperation, - entryPoint, - chainId: chain.id - }) + const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) const signedUserOperation: UserOperation = { ...userOperation, diff --git a/test/utils.ts b/test/utils.ts index b1cde10f..440e5589 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,21 +1,19 @@ -import fs from "fs" import { createBundlerClient, createSmartAccountClient } from "permissionless" import { privateKeyToSimpleSmartAccount } from "permissionless/accounts" import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" -import { http, Address, Hex, createPublicClient, createWalletClient, toHex } from "viem" -import { mnemonicToAccount } from "viem/accounts" +import { http, Address, Hex, createPublicClient, createWalletClient } from "viem" +import { privateKeyToAccount } from "viem/accounts" import { goerli } from "viem/chains" export const getFactoryAddress = () => { - if (!process.env.FACTORY_ADDRESS) { - throw new Error("FACTORY_ADDRESS environment variable not set") - } + if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") const factoryAddress = process.env.FACTORY_ADDRESS as Address return factoryAddress } export const getPrivateKeyAccount = () => { - return mnemonicToAccount(getMnemonic()) + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + return privateKeyToAccount(process.env.TEST_PRIVATE_KEY as Hex) } export const getTestingChain = () => { @@ -23,30 +21,20 @@ export const getTestingChain = () => { } export const getPrivateKeyToSimpleSmartAccount = async () => { - const publicClient = await getPublicClient() - - const mnemonicAccount = getPrivateKeyAccount() - - const hdKey = mnemonicAccount.getHdKey().derive(`m/44'/60'/0'/0/0`) + if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!hdKey.privateKey) throw new Error("hdkey not found") - - const privateKey = toHex(hdKey.privateKey) + const publicClient = await getPublicClient() return await privateKeyToSimpleSmartAccount(publicClient, { entryPoint: getEntryPoint(), factoryAddress: getFactoryAddress(), - privateKey: privateKey + privateKey: process.env.TEST_PRIVATE_KEY as Hex }) } export const getSmartAccountClient = async () => { - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { - throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - } + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -68,16 +56,12 @@ export const getEoaWalletClient = () => { } export const getEntryPoint = () => { - if (!process.env.ENTRYPOINT_ADDRESS) { - throw new Error("ENTRYPOINT_ADDRESS environment variable not set") - } + if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") return process.env.ENTRYPOINT_ADDRESS as Address } export const getPublicClient = async () => { - if (!process.env.RPC_URL) { - throw new Error("RPC_URL environment variable not set") - } + if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") const publicClient = createPublicClient({ transport: http(process.env.RPC_URL as string) @@ -85,20 +69,14 @@ export const getPublicClient = async () => { const chainId = await publicClient.getChainId() - if (chainId !== getTestingChain().id) { - throw new Error("Testing Chain ID not supported by RPC URL") - } + if (chainId !== getTestingChain().id) throw new Error("Testing Chain ID not supported by RPC URL") return publicClient } export const getBundlerClient = () => { - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { - throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - } + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -112,12 +90,8 @@ export const getBundlerClient = () => { } export const getPimlicoBundlerClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { - throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - } - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -131,12 +105,8 @@ export const getPimlicoBundlerClient = () => { } export const getPimlicoPaymasterClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) { - throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - } - if (!process.env.PIMLICO_API_KEY) { - throw new Error("PIMLICO_API_KEY environment variable not set") - } + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -168,17 +138,3 @@ export const getDummySignature = (): Hex => { export const getOldUserOpHash = (): Hex => { return "0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34" } - -export const getMnemonic = (): string => { - const mnemonicFileName = process.env.MNEMONIC_FILE ?? `${process.env.HOME}/.secret/testnet-mnemonic.txt` - - let mnemonic = process.env.MNEMONIC - - if (fs.existsSync(mnemonicFileName)) { - mnemonic = fs.readFileSync(mnemonicFileName, "ascii") - } - if (!mnemonic) { - throw new Error("No mnemonic found") - } - return mnemonic -} From 52275c98c3fb331f17f0480040fc727f352dcf32 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 14:29:56 +0530 Subject: [PATCH 18/26] Add sponsorUserOperationMiddleware --- biome.json | 2 +- .../privateKeyToSimpleSmartAccount.ts | 6 +- src/accounts/types.ts | 15 +- .../bundler/estimateUserOperationGas.ts | 10 +- src/actions/bundler/getUserOperationByHash.ts | 8 +- src/actions/bundler/sendUserOperation.ts | 15 +- src/actions/bundler/supportedEntryPoints.ts | 4 +- .../bundler/waitForUserOperationReceipt.ts | 70 +++++--- src/actions/index.ts | 5 +- src/actions/pimlico.ts | 10 +- .../pimlico/getUserOperationGasPrice.ts | 4 +- src/actions/pimlico/getUserOperationStatus.ts | 5 +- src/actions/pimlico/sponsorUserOperation.ts | 15 +- src/actions/public/getSenderAddress.ts | 11 +- src/actions/smartAccount.ts | 17 +- src/actions/smartAccount/deployContract.ts | 14 +- src/actions/smartAccount/getChainId.ts | 14 +- .../prepareUserOperationRequest.ts | 109 +++++++++---- src/actions/smartAccount/sendTransaction.ts | 42 ++++- src/actions/smartAccount/sendUserOperation.ts | 28 +++- src/actions/smartAccount/signMessage.ts | 23 ++- src/actions/smartAccount/signTypedData.ts | 5 +- .../smartAccount/sponsorUserOperation.ts | 54 ------- src/actions/smartAccount/writeContract.ts | 36 ++++- src/actions/stackup/sponsorUserOperation.ts | 16 +- src/clients/createBundlerClient.ts | 17 +- src/clients/createSmartAccountClient.ts | 48 +++++- src/clients/decorators/bundler.ts | 26 ++- src/clients/decorators/pimlico.ts | 24 ++- src/clients/decorators/smartAccount.ts | 116 ++++++++++---- src/clients/decorators/stackup.ts | 16 +- src/clients/pimlico.ts | 23 ++- src/clients/stackup.ts | 19 ++- src/index.ts | 15 +- src/types/bundler.ts | 5 +- src/types/index.ts | 7 +- src/types/pimlico.ts | 14 +- src/types/stackup.ts | 5 +- src/utils/deepHexlify.test.ts | 18 ++- src/utils/getUserOperationHash.ts | 12 +- src/utils/index.ts | 13 +- src/utils/observe.ts | 20 ++- src/utils/signUserOperationHashWithECDSA.ts | 12 +- test/bundlerActions.test.ts | 83 +++++++--- test/index.test.ts | 40 +++-- test/pimlicoActions.test.ts | 150 ++++++++++++------ test/simpleAccount.test.ts | 61 ++++++- test/stackupActions.test.ts | 51 ++++-- test/userOp.ts | 51 +++++- test/utils.ts | 79 ++++++--- 50 files changed, 1075 insertions(+), 388 deletions(-) delete mode 100644 src/actions/smartAccount/sponsorUserOperation.ts diff --git a/biome.json b/biome.json index 6e21bf81..9cc93617 100644 --- a/biome.json +++ b/biome.json @@ -32,7 +32,7 @@ "formatter": { "enabled": true, "formatWithErrors": true, - "lineWidth": 120, + "lineWidth": 80, "indentWidth": 4, "indentStyle": "space" }, diff --git a/src/accounts/privateKeyToSimpleSmartAccount.ts b/src/accounts/privateKeyToSimpleSmartAccount.ts index 21ff93ec..758ad72e 100644 --- a/src/accounts/privateKeyToSimpleSmartAccount.ts +++ b/src/accounts/privateKeyToSimpleSmartAccount.ts @@ -36,7 +36,11 @@ export type PrivateKeySimpleSmartAccount< chain extends Chain | undefined = Chain | undefined > = SmartAccount<"privateKeySimpleSmartAccount", transport, chain> -const getAccountInitCode = async (factoryAddress: Address, owner: Address, index = 0n): Promise => { +const getAccountInitCode = async ( + factoryAddress: Address, + owner: Address, + index = 0n +): Promise => { if (!owner) throw new Error("Owner account not found") return concatHex([ diff --git a/src/accounts/types.ts b/src/accounts/types.ts index c5d0928e..c7de6d15 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -1,4 +1,11 @@ -import type { Abi, Address, Client, GetConstructorArgs, Hex, LocalAccount } from "viem" +import type { + Abi, + Address, + Client, + GetConstructorArgs, + Hex, + LocalAccount +} from "viem" import type { Chain, Transport } from "viem" import { type UserOperation } from "../types/index.js" @@ -11,7 +18,11 @@ export type SmartAccount< entryPoint: Address getNonce: () => Promise getInitCode: () => Promise - encodeCallData: ({ to, value, data }: { to: Address; value: bigint; data: Hex }) => Promise + encodeCallData: ({ + to, + value, + data + }: { to: Address; value: bigint; data: Hex }) => Promise getDummySignature(): Promise encodeDeployCallData: ({ abi, diff --git a/src/actions/bundler/estimateUserOperationGas.ts b/src/actions/bundler/estimateUserOperationGas.ts index 7a7502a7..88944ac3 100644 --- a/src/actions/bundler/estimateUserOperationGas.ts +++ b/src/actions/bundler/estimateUserOperationGas.ts @@ -6,7 +6,10 @@ import type { UserOperationWithBigIntAsHex } from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" export type EstimateUserOperationGasParameters = { - userOperation: PartialBy + userOperation: PartialBy< + UserOperation, + "callGasLimit" | "preVerificationGas" | "verificationGasLimit" + > entryPoint: Address } @@ -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 { diff --git a/src/actions/bundler/getUserOperationByHash.ts b/src/actions/bundler/getUserOperationByHash.ts index c6069ef9..e3898b8a 100644 --- a/src/actions/bundler/getUserOperationByHash.ts +++ b/src/actions/bundler/getUserOperationByHash.ts @@ -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: { diff --git a/src/actions/bundler/sendUserOperation.ts b/src/actions/bundler/sendUserOperation.ts index 1ba5813c..e396d1d5 100644 --- a/src/actions/bundler/sendUserOperation.ts +++ b/src/actions/bundler/sendUserOperation.ts @@ -1,6 +1,9 @@ import type { Address, Hash } from "viem" import type { BundlerClient } from "../../clients/createBundlerClient.js" -import type { UserOperation, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" +import type { + UserOperation, + UserOperationWithBigIntAsHex +} from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" export type SendUserOperationParameters = { @@ -33,11 +36,17 @@ export type SendUserOperationParameters = { * * // Return '0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34' */ -export const sendUserOperation = async (client: BundlerClient, args: SendUserOperationParameters): Promise => { +export const sendUserOperation = async ( + client: BundlerClient, + args: SendUserOperationParameters +): Promise => { const { userOperation, entryPoint } = args return client.request({ method: "eth_sendUserOperation", - params: [deepHexlify(userOperation) as UserOperationWithBigIntAsHex, entryPoint as Address] + params: [ + deepHexlify(userOperation) as UserOperationWithBigIntAsHex, + entryPoint as Address + ] }) } diff --git a/src/actions/bundler/supportedEntryPoints.ts b/src/actions/bundler/supportedEntryPoints.ts index 25d1a4b1..adc5f8d4 100644 --- a/src/actions/bundler/supportedEntryPoints.ts +++ b/src/actions/bundler/supportedEntryPoints.ts @@ -23,7 +23,9 @@ import type { BundlerClient } from "../../clients/createBundlerClient.js" * // Return ['0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789'] * */ -export const supportedEntryPoints = async (client: BundlerClient): Promise => { +export const supportedEntryPoints = async ( + client: BundlerClient +): Promise => { return client.request({ method: "eth_supportedEntryPoints", params: [] diff --git a/src/actions/bundler/waitForUserOperationReceipt.ts b/src/actions/bundler/waitForUserOperationReceipt.ts index eab421a7..5e8bcd19 100644 --- a/src/actions/bundler/waitForUserOperationReceipt.ts +++ b/src/actions/bundler/waitForUserOperationReceipt.ts @@ -2,12 +2,17 @@ import { BaseError, type Chain, type Hash, stringify } from "viem" import type { BundlerClient } from "../../clients/createBundlerClient.js" import { getAction } from "../../utils/getAction.js" import { observe } from "../../utils/observe.js" -import { type GetUserOperationReceiptReturnType, getUserOperationReceipt } from "./getUserOperationReceipt.js" +import { + type GetUserOperationReceiptReturnType, + getUserOperationReceipt +} from "./getUserOperationReceipt.js" export class WaitForUserOperationReceiptTimeoutError extends BaseError { override name = "WaitForUserOperationReceiptTimeoutError" constructor({ hash }: { hash: Hash }) { - super(`Timed out while waiting for transaction with hash "${hash}" to be confirmed.`) + super( + `Timed out while waiting for transaction with hash "${hash}" to be confirmed.` + ) } } @@ -46,36 +51,57 @@ export type WaitForUserOperationReceiptParameters = { */ export const waitForUserOperationReceipt = ( bundlerClient: BundlerClient, - { hash, pollingInterval = bundlerClient.pollingInterval, timeout }: WaitForUserOperationReceiptParameters + { + hash, + pollingInterval = bundlerClient.pollingInterval, + timeout + }: WaitForUserOperationReceiptParameters ): Promise => { - const observerId = stringify(["waitForUserOperationReceipt", bundlerClient.uid, hash]) + const observerId = stringify([ + "waitForUserOperationReceipt", + bundlerClient.uid, + hash + ]) let userOperationReceipt: GetUserOperationReceiptReturnType return new Promise((resolve, reject) => { if (timeout) { - setTimeout(() => reject(new WaitForUserOperationReceiptTimeoutError({ hash })), timeout) + setTimeout( + () => + reject( + new WaitForUserOperationReceiptTimeoutError({ hash }) + ), + timeout + ) } - const _unobserve = observe(observerId, { resolve, reject }, async (emit) => { - const _removeInterval = setInterval(async () => { - const done = (fn: () => void) => { - clearInterval(_removeInterval) - fn() - _unobserve() - } + const _unobserve = observe( + observerId, + { resolve, reject }, + async (emit) => { + const _removeInterval = setInterval(async () => { + const done = (fn: () => void) => { + clearInterval(_removeInterval) + fn() + _unobserve() + } - const _userOperationReceipt = await getAction(bundlerClient, getUserOperationReceipt)({ hash }) + const _userOperationReceipt = await getAction( + bundlerClient, + getUserOperationReceipt + )({ hash }) - if (_userOperationReceipt !== null) { - userOperationReceipt = _userOperationReceipt - } + if (_userOperationReceipt !== null) { + userOperationReceipt = _userOperationReceipt + } - if (userOperationReceipt) { - done(() => emit.resolve(userOperationReceipt)) - return - } - }, pollingInterval) - }) + if (userOperationReceipt) { + done(() => emit.resolve(userOperationReceipt)) + return + } + }, pollingInterval) + } + ) }) } diff --git a/src/actions/index.ts b/src/actions/index.ts index 5d30baf8..a2f0737b 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -11,7 +11,10 @@ import type { import type { SendUserOperationParameters } from "./bundler/sendUserOperation.js" import type { GetSenderAddressParams } from "./public/getSenderAddress.js" -import { InvalidEntryPointError, getSenderAddress } from "./public/getSenderAddress.js" +import { + InvalidEntryPointError, + getSenderAddress +} from "./public/getSenderAddress.js" import { chainId } from "./bundler/chainId.js" import { estimateUserOperationGas } from "./bundler/estimateUserOperationGas.js" diff --git a/src/actions/pimlico.ts b/src/actions/pimlico.ts index e5d994dd..3ccaf459 100644 --- a/src/actions/pimlico.ts +++ b/src/actions/pimlico.ts @@ -13,8 +13,14 @@ import { sponsorUserOperation } from "./pimlico/sponsorUserOperation.js" -import type { PimlicoBundlerActions, PimlicoPaymasterClientActions } from "../clients/decorators/pimlico.js" -import { pimlicoBundlerActions, pimlicoPaymasterActions } from "../clients/decorators/pimlico.js" +import type { + PimlicoBundlerActions, + PimlicoPaymasterClientActions +} from "../clients/decorators/pimlico.js" +import { + pimlicoBundlerActions, + pimlicoPaymasterActions +} from "../clients/decorators/pimlico.js" export type { GetUserOperationGasPriceReturnType, diff --git a/src/actions/pimlico/getUserOperationGasPrice.ts b/src/actions/pimlico/getUserOperationGasPrice.ts index 72a3475d..b12094c9 100644 --- a/src/actions/pimlico/getUserOperationGasPrice.ts +++ b/src/actions/pimlico/getUserOperationGasPrice.ts @@ -56,7 +56,9 @@ export const getUserOperationGasPrice = async < }, standard: { maxFeePerGas: BigInt(gasPrices.standard.maxFeePerGas), - maxPriorityFeePerGas: BigInt(gasPrices.standard.maxPriorityFeePerGas) + maxPriorityFeePerGas: BigInt( + gasPrices.standard.maxPriorityFeePerGas + ) }, fast: { maxFeePerGas: BigInt(gasPrices.fast.maxFeePerGas), diff --git a/src/actions/pimlico/getUserOperationStatus.ts b/src/actions/pimlico/getUserOperationStatus.ts index 29ea34dc..e68a8d60 100644 --- a/src/actions/pimlico/getUserOperationStatus.ts +++ b/src/actions/pimlico/getUserOperationStatus.ts @@ -1,6 +1,9 @@ import type { Account, Chain, Client, Hash, Transport } from "viem" import type { PimlicoBundlerClient } from "../../clients/pimlico.js" -import type { PimlicoBundlerRpcSchema, PimlicoUserOperationStatus } from "../../types/pimlico.js" +import type { + PimlicoBundlerRpcSchema, + PimlicoUserOperationStatus +} from "../../types/pimlico.js" export type GetUserOperationStatusParameters = { hash: Hash diff --git a/src/actions/pimlico/sponsorUserOperation.ts b/src/actions/pimlico/sponsorUserOperation.ts index a516e130..7e9fe42f 100644 --- a/src/actions/pimlico/sponsorUserOperation.ts +++ b/src/actions/pimlico/sponsorUserOperation.ts @@ -1,13 +1,19 @@ import type { Account, Address, Chain, Client, Hex, Transport } from "viem" import type { PartialBy } from "viem/types/utils" import type { PimlicoPaymasterRpcSchema } from "../../types/pimlico.js" -import type { UserOperation, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" +import type { + UserOperation, + UserOperationWithBigIntAsHex +} from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" export type SponsorUserOperationParameters = { userOperation: PartialBy< UserOperation, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" | "paymasterAndData" + | "callGasLimit" + | "preVerificationGas" + | "verificationGasLimit" + | "paymasterAndData" > entryPoint: Address } @@ -54,7 +60,10 @@ export const sponsorUserOperation = async < ): Promise => { const response = await client.request({ method: "pm_sponsorUserOperation", - params: [deepHexlify(args.userOperation) as UserOperationWithBigIntAsHex, args.entryPoint] + params: [ + deepHexlify(args.userOperation) as UserOperationWithBigIntAsHex, + args.entryPoint + ] }) return { diff --git a/src/actions/public/getSenderAddress.ts b/src/actions/public/getSenderAddress.ts index 13092b64..42f639bf 100644 --- a/src/actions/public/getSenderAddress.ts +++ b/src/actions/public/getSenderAddress.ts @@ -17,7 +17,10 @@ export type GetSenderAddressParams = { initCode: Hex; entryPoint: Address } export class InvalidEntryPointError extends BaseError { override name = "InvalidEntryPointError" - constructor({ cause, entryPoint }: { cause?: BaseError; entryPoint?: Address } = {}) { + constructor({ + cause, + entryPoint + }: { cause?: BaseError; entryPoint?: Address } = {}) { super( `The entry point address (\`entryPoint\`${ entryPoint ? ` = ${entryPoint}` : "" @@ -102,7 +105,11 @@ export const getSenderAddress = async < if (err.cause.name === "ContractFunctionRevertedError") { const revertError = err.cause as ContractFunctionRevertedErrorType const errorName = revertError.data?.errorName ?? "" - if (errorName === "SenderAddressResult" && revertError.data?.args && revertError.data?.args[0]) { + if ( + errorName === "SenderAddressResult" && + revertError.data?.args && + revertError.data?.args[0] + ) { return revertError.data?.args[0] as Address } } diff --git a/src/actions/smartAccount.ts b/src/actions/smartAccount.ts index 70ed67ed..ecbab9c0 100644 --- a/src/actions/smartAccount.ts +++ b/src/actions/smartAccount.ts @@ -5,10 +5,14 @@ import { getChainId } from "./smartAccount/getChainId.js" import { type PrepareUserOperationRequestParameters, type PrepareUserOperationRequestReturnType, + type SponsorUserOperationMiddleware, prepareUserOperationRequest } from "./smartAccount/prepareUserOperationRequest.js" -import { sendTransaction } from "./smartAccount/sendTransaction.js" +import { + type SendTransactionWithPaymasterParameters, + sendTransaction +} from "./smartAccount/sendTransaction.js" import { type SendUserOperationParameters, @@ -20,12 +24,6 @@ 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, @@ -38,7 +36,6 @@ export { type SendUserOperationReturnType, signMessage, signTypedData, - sponsorUserOperation, - type SponsorUserOperationParameters, - type SponsorUserOperationReturnType + type SendTransactionWithPaymasterParameters, + type SponsorUserOperationMiddleware } diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index 622ea0f3..bde91fbe 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -20,7 +20,12 @@ export async function deployContract< TChainOverride extends Chain | undefined >( client: Client, - { abi, args, bytecode, ...request }: DeployContractParameters + { + abi, + args, + bytecode, + ...request + }: DeployContractParameters ): Promise { const { account: account_ = client.account } = request @@ -45,7 +50,12 @@ export async function deployContract< abi, args, bytecode - } as unknown as DeployContractParameters) + } as unknown as DeployContractParameters< + TAbi, + TChain, + TAccount, + TChainOverride + >) }, account: account }) diff --git a/src/actions/smartAccount/getChainId.ts b/src/actions/smartAccount/getChainId.ts index 13b638cb..e6964866 100644 --- a/src/actions/smartAccount/getChainId.ts +++ b/src/actions/smartAccount/getChainId.ts @@ -1,10 +1,16 @@ -import { type Chain, type Client, type GetChainIdReturnType, type Transport } from "viem" +import { + type Chain, + type Client, + type GetChainIdReturnType, + type Transport +} from "viem" import { type SmartAccount } from "../../accounts/types.js" import { getAction } from "../../utils/getAction.js" import { chainId } from "../bundler/chainId.js" -export async function getChainId( - client: Client -): Promise { +export async function getChainId< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined +>(client: Client): Promise { return getAction(client, chainId)([]) } diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts index 3ba892dd..ece3a637 100644 --- a/src/actions/smartAccount/prepareUserOperationRequest.ts +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -1,10 +1,26 @@ -import type { Chain, Client, Transport } from "viem" +import type { Chain, Client, Hex, Transport } from "viem" import { estimateFeesPerGas } from "viem/actions" import type { SmartAccount } from "../../accounts/types.js" -import type { GetAccountParameter, PartialBy, UserOperation } from "../../types/index.js" +import type { + GetAccountParameter, + PartialBy, + UserOperation +} from "../../types/index.js" import { getAction } from "../../utils/getAction.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" -import { sponsorUserOperation } from "./sponsorUserOperation.js" +import { + AccountOrClientNotFoundError, + parseAccount +} from "../../utils/index.js" +import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas.js" + +export type SponsorUserOperationMiddleware = { + sponsorUserOperation?: (userOperation: UserOperation) => Promise<{ + paymasterAndData: Hex + preVerificationGas: bigint + verificationGasLimit: bigint + callGasLimit: bigint + }> +} export type PrepareUserOperationRequestParameters< TAccount extends SmartAccount | undefined = SmartAccount | undefined, @@ -22,7 +38,8 @@ export type PrepareUserOperationRequestParameters< | "paymasterAndData" | "signature" > -} & GetAccountParameter +} & GetAccountParameter & + SponsorUserOperationMiddleware export type PrepareUserOperationRequestReturnType = UserOperation @@ -34,21 +51,27 @@ export async function prepareUserOperationRequest< client: Client, args: PrepareUserOperationRequestParameters ): Promise { - const { account: account_ = client.account, userOperation: partialUserOperation } = args + const { + account: account_ = client.account, + userOperation: partialUserOperation, + sponsorUserOperation + } = args if (!account_) throw new AccountOrClientNotFoundError() const account = parseAccount(account_) as SmartAccount - 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.maxFeePerGas || !partialUserOperation.maxPriorityFeePerGas - ? estimateFeesPerGas(account.client) - : undefined - ]) + 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.maxFeePerGas || + !partialUserOperation.maxPriorityFeePerGas + ? estimateFeesPerGas(account.client) + : undefined + ]) const userOperation: UserOperation = { sender, @@ -57,25 +80,53 @@ export async function prepareUserOperationRequest< signature, callData, paymasterAndData: "0x", - maxFeePerGas: partialUserOperation.maxFeePerGas || gasEstimation?.maxFeePerGas || 0n, - maxPriorityFeePerGas: partialUserOperation.maxPriorityFeePerGas || gasEstimation?.maxPriorityFeePerGas || 0n, + maxFeePerGas: + partialUserOperation.maxFeePerGas || + gasEstimation?.maxFeePerGas || + 0n, + maxPriorityFeePerGas: + partialUserOperation.maxPriorityFeePerGas || + gasEstimation?.maxPriorityFeePerGas || + 0n, callGasLimit: partialUserOperation.callGasLimit || 0n, verificationGasLimit: partialUserOperation.verificationGasLimit || 0n, preVerificationGas: partialUserOperation.preVerificationGas || 0n } - const { paymasterAndData, callGasLimit, verificationGasLimit, preVerificationGas } = await getAction( - client, - sponsorUserOperation - )({ - userOperation: userOperation, - account: account - }) + if (sponsorUserOperation) { + const { + callGasLimit, + verificationGasLimit, + preVerificationGas, + paymasterAndData + } = await sponsorUserOperation(userOperation) + userOperation.paymasterAndData = paymasterAndData + userOperation.callGasLimit = userOperation.callGasLimit || callGasLimit + userOperation.verificationGasLimit = + userOperation.verificationGasLimit || verificationGasLimit + userOperation.preVerificationGas = + userOperation.preVerificationGas || preVerificationGas + } else if ( + !userOperation.callGasLimit || + !userOperation.verificationGasLimit || + !userOperation.preVerificationGas + ) { + const gasParameters = await getAction( + client, + estimateUserOperationGas + )({ + userOperation, + entryPoint: account.entryPoint + }) - userOperation.paymasterAndData = paymasterAndData - userOperation.callGasLimit = callGasLimit - userOperation.verificationGasLimit = verificationGasLimit - userOperation.preVerificationGas = preVerificationGas + userOperation.callGasLimit = + userOperation.callGasLimit || gasParameters.callGasLimit + userOperation.verificationGasLimit = + userOperation.verificationGasLimit || + gasParameters.verificationGasLimit + userOperation.preVerificationGas = + userOperation.preVerificationGas || gasParameters.preVerificationGas + } return userOperation } diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index 465be855..94fb72c2 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -1,19 +1,50 @@ -import type { Chain, Client, SendTransactionParameters, SendTransactionReturnType, Transport } from "viem" +import type { + Chain, + Client, + SendTransactionParameters, + SendTransactionReturnType, + Transport +} from "viem" import { type SmartAccount } from "../../accounts/types.js" import { getAction } from "../../utils/getAction.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" +import { + AccountOrClientNotFoundError, + parseAccount +} from "../../utils/index.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" +import { type SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" import { sendUserOperation } from "./sendUserOperation.js" +export type SendTransactionWithPaymasterParameters< + TChain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined, + TChainOverride extends Chain | undefined = Chain | undefined +> = SendTransactionParameters & + SponsorUserOperationMiddleware + export async function sendTransaction< TChain extends Chain | undefined, TAccount extends SmartAccount | undefined, TChainOverride extends Chain | undefined >( client: Client, - args: SendTransactionParameters + args: SendTransactionWithPaymasterParameters< + TChain, + TAccount, + TChainOverride + > ): Promise { - const { account: account_ = client.account, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = args + const { + account: account_ = client.account, + data, + maxFeePerGas, + maxPriorityFeePerGas, + to, + value, + sponsorUserOperation + } = args + + console.log(sponsorUserOperation, "sponsorUserOperation") if (!account_) { throw new AccountOrClientNotFoundError({ @@ -46,7 +77,8 @@ export async function sendTransaction< maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, callData: callData }, - account: account + account: account, + sponsorUserOperation }) const userOperationReceipt = await getAction( diff --git a/src/actions/smartAccount/sendUserOperation.ts b/src/actions/smartAccount/sendUserOperation.ts index 58a1fd74..b9c5d05f 100644 --- a/src/actions/smartAccount/sendUserOperation.ts +++ b/src/actions/smartAccount/sendUserOperation.ts @@ -1,12 +1,24 @@ import type { Chain, Client, Hex, Transport } from "viem" import type { SmartAccount } from "../../accounts/types.js" -import type { GetAccountParameter, PartialBy, UserOperation } from "../../types/index.js" +import type { + GetAccountParameter, + PartialBy, + UserOperation +} from "../../types/index.js" import { getAction } from "../../utils/getAction.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" +import { + AccountOrClientNotFoundError, + parseAccount +} from "../../utils/index.js" import { sendUserOperation as sendUserOperationBundler } from "../bundler/sendUserOperation.js" -import { prepareUserOperationRequest } from "./prepareUserOperationRequest.js" +import { + type SponsorUserOperationMiddleware, + prepareUserOperationRequest +} from "./prepareUserOperationRequest.js" -export type SendUserOperationParameters = { +export type SendUserOperationParameters< + TAccount extends SmartAccount | undefined = SmartAccount | undefined +> = { userOperation: PartialBy< UserOperation, | "nonce" @@ -20,7 +32,8 @@ export type SendUserOperationParameters -} & GetAccountParameter +} & GetAccountParameter & + SponsorUserOperationMiddleware export type SendUserOperationReturnType = Hex @@ -37,7 +50,10 @@ export async function sendUserOperation< const account = parseAccount(account_) as SmartAccount - const userOperation = await getAction(client, prepareUserOperationRequest)(args) + const userOperation = await getAction( + client, + prepareUserOperationRequest + )(args) userOperation.signature = await account.signUserOperation(userOperation) diff --git a/src/actions/smartAccount/signMessage.ts b/src/actions/smartAccount/signMessage.ts index 6e13bc28..cfb4ce73 100644 --- a/src/actions/smartAccount/signMessage.ts +++ b/src/actions/smartAccount/signMessage.ts @@ -1,10 +1,25 @@ -import type { Chain, Client, SignMessageParameters, SignMessageReturnType, Transport } from "viem" +import type { + Chain, + Client, + SignMessageParameters, + SignMessageReturnType, + Transport +} from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" +import { + AccountOrClientNotFoundError, + parseAccount +} from "../../utils/index.js" -export async function signMessage( +export async function signMessage< + TChain extends Chain | undefined, + TAccount extends SmartAccount | undefined +>( client: Client, - { account: account_ = client.account, message }: SignMessageParameters + { + account: account_ = client.account, + message + }: SignMessageParameters ): Promise { if (!account_) throw new AccountOrClientNotFoundError({ diff --git a/src/actions/smartAccount/signTypedData.ts b/src/actions/smartAccount/signTypedData.ts index 2f14f148..51ebcf80 100644 --- a/src/actions/smartAccount/signTypedData.ts +++ b/src/actions/smartAccount/signTypedData.ts @@ -10,7 +10,10 @@ import { validateTypedData } from "viem" import { type SmartAccount } from "../../accounts/types.js" -import { AccountOrClientNotFoundError, parseAccount } from "../../utils/index.js" +import { + AccountOrClientNotFoundError, + parseAccount +} from "../../utils/index.js" export async function signTypedData< const TTypedData extends TypedData | { [key: string]: unknown }, diff --git a/src/actions/smartAccount/sponsorUserOperation.ts b/src/actions/smartAccount/sponsorUserOperation.ts deleted file mode 100644 index b95a2c53..00000000 --- a/src/actions/smartAccount/sponsorUserOperation.ts +++ /dev/null @@ -1,54 +0,0 @@ -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 = { - userOperation: UserOperation -} & GetAccountParameter - -export type SponsorUserOperationReturnType = { - paymasterAndData: Hex - preVerificationGas: bigint - verificationGasLimit: bigint - callGasLimit: bigint -} - -export async function sponsorUserOperation( - client: Client, - args: SponsorUserOperationParameters -): Promise { - 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 - } -} diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index 2c011665..4347c726 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -3,7 +3,6 @@ import { type Chain, type Client, type EncodeFunctionDataParameters, - type SendTransactionParameters, type Transport, type WriteContractParameters, type WriteContractReturnType, @@ -11,7 +10,26 @@ import { } from "viem" import { type SmartAccount } from "../../accounts/types.js" import { getAction } from "../../utils/getAction.js" -import { sendTransaction } from "./sendTransaction.js" +import { type SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" +import { + type SendTransactionWithPaymasterParameters, + sendTransaction +} from "./sendTransaction.js" + +export type WriteContractWithPaymasterParameters< + TChain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined, + TAbi extends Abi | readonly unknown[] = Abi | readonly unknown[], + TFunctionName extends string = string, + TChainOverride extends Chain | undefined = undefined +> = WriteContractParameters< + TAbi, + TFunctionName, + TChain, + TAccount, + TChainOverride +> & + SponsorUserOperationMiddleware export async function writeContract< TChain extends Chain | undefined, @@ -28,7 +46,13 @@ export async function writeContract< dataSuffix, functionName, ...request - }: WriteContractParameters + }: WriteContractWithPaymasterParameters< + TChain, + TAccount, + TAbi, + TFunctionName, + TChainOverride + > ): Promise { const data = encodeFunctionData({ abi, @@ -42,6 +66,10 @@ export async function writeContract< data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, to: address, ...request - } as unknown as SendTransactionParameters) + } as SendTransactionWithPaymasterParameters< + TChain, + TAccount, + TChainOverride + >) return hash } diff --git a/src/actions/stackup/sponsorUserOperation.ts b/src/actions/stackup/sponsorUserOperation.ts index 4f48d78f..6c26d4f6 100644 --- a/src/actions/stackup/sponsorUserOperation.ts +++ b/src/actions/stackup/sponsorUserOperation.ts @@ -2,13 +2,19 @@ import type { Address, Hex } from "viem" import type { PartialBy } from "viem/types/utils" import { type StackupPaymasterClient } from "../../clients/stackup.js" import type { StackupPaymasterContext } from "../../types/stackup.js" -import type { UserOperation, UserOperationWithBigIntAsHex } from "../../types/userOperation.js" +import type { + UserOperation, + UserOperationWithBigIntAsHex +} from "../../types/userOperation.js" import { deepHexlify } from "../../utils/deepHexlify.js" export type SponsorUserOperationParameters = { userOperation: PartialBy< UserOperation, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" | "paymasterAndData" + | "callGasLimit" + | "preVerificationGas" + | "verificationGasLimit" + | "paymasterAndData" > entryPoint: Address context: StackupPaymasterContext @@ -52,7 +58,11 @@ export const sponsorUserOperation = async ( ): Promise => { const response = await client.request({ method: "pm_sponsorUserOperation", - params: [deepHexlify(args.userOperation) as UserOperationWithBigIntAsHex, args.entryPoint, args.context] + params: [ + deepHexlify(args.userOperation) as UserOperationWithBigIntAsHex, + args.entryPoint, + args.context + ] }) return { diff --git a/src/clients/createBundlerClient.ts b/src/clients/createBundlerClient.ts index 75264197..4bbd9af0 100644 --- a/src/clients/createBundlerClient.ts +++ b/src/clients/createBundlerClient.ts @@ -1,9 +1,17 @@ -import type { Account, Chain, Client, PublicClientConfig, Transport } from "viem" +import type { + Account, + Chain, + Client, + PublicClientConfig, + Transport +} from "viem" import { createClient } from "viem" import type { BundlerRpcSchema } from "../types/bundler.js" import { type BundlerActions, bundlerActions } from "./decorators/bundler.js" -export type BundlerClient = Client< +export type BundlerClient< + TChain extends Chain | undefined = Chain | undefined +> = Client< Transport, TChain, Account | undefined, @@ -29,7 +37,10 @@ export type BundlerClient * transport: http(BUNDLER_URL), * }) */ -export const createBundlerClient = ( +export const createBundlerClient = < + transport extends Transport, + chain extends Chain | undefined = undefined +>( parameters: PublicClientConfig ): BundlerClient => { const { key = "public", name = "Bundler Client" } = parameters diff --git a/src/clients/createSmartAccountClient.ts b/src/clients/createSmartAccountClient.ts index ec997391..b970418b 100644 --- a/src/clients/createSmartAccountClient.ts +++ b/src/clients/createSmartAccountClient.ts @@ -1,15 +1,34 @@ -import type { Chain, Client, ClientConfig, ParseAccount, Transport, WalletClientConfig } from "viem" +import type { + Chain, + Client, + ClientConfig, + ParseAccount, + Transport, + WalletClientConfig +} from "viem" import { createClient } from "viem" import { type SmartAccount } from "../accounts/types.js" +import { type SponsorUserOperationMiddleware } from "../actions/smartAccount/prepareUserOperationRequest.js" import { type BundlerRpcSchema } from "../types/bundler.js" import type { Prettify } from "../types/index.js" -import { type SmartAccountActions, smartAccountActions } from "./decorators/smartAccount.js" +import { + type SmartAccountActions, + smartAccountActions +} from "./decorators/smartAccount.js" export type SmartAccountClient< transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartAccount | undefined = SmartAccount | undefined -> = Prettify>> +> = Prettify< + Client< + transport, + chain, + account, + BundlerRpcSchema, + SmartAccountActions + > +> export type SmartAccountClientConfig< transport extends Transport = Transport, @@ -18,7 +37,13 @@ export type SmartAccountClientConfig< > = Prettify< Pick< ClientConfig, - "account" | "cacheTime" | "chain" | "key" | "name" | "pollingInterval" | "transport" + | "account" + | "cacheTime" + | "chain" + | "key" + | "name" + | "pollingInterval" + | "transport" > > @@ -46,9 +71,14 @@ export const createSmartAccountClient = < TChain extends Chain | undefined = undefined, TSmartAccount extends SmartAccount | undefined = undefined >( - parameters: SmartAccountClientConfig + parameters: SmartAccountClientConfig & + SponsorUserOperationMiddleware ): SmartAccountClient> => { - const { key = "Account", name = "Smart Account Client", transport } = parameters + const { + key = "Account", + name = "Smart Account Client", + transport + } = parameters const client = createClient({ ...parameters, key, @@ -57,5 +87,9 @@ export const createSmartAccountClient = < type: "smartAccountClient" }) - return client.extend(smartAccountActions) + return client.extend( + smartAccountActions({ + sponsorUserOperation: parameters.sponsorUserOperation + }) + ) } diff --git a/src/clients/decorators/bundler.ts b/src/clients/decorators/bundler.ts index 7f1a2a06..92539eaf 100644 --- a/src/clients/decorators/bundler.ts +++ b/src/clients/decorators/bundler.ts @@ -16,7 +16,10 @@ import { type GetUserOperationReceiptReturnType, getUserOperationReceipt } from "../../actions/bundler/getUserOperationReceipt.js" -import { type SendUserOperationParameters, sendUserOperation } from "../../actions/bundler/sendUserOperation.js" +import { + type SendUserOperationParameters, + sendUserOperation +} from "../../actions/bundler/sendUserOperation.js" import { supportedEntryPoints } from "../../actions/bundler/supportedEntryPoints.js" import { type WaitForUserOperationReceiptParameters, @@ -76,7 +79,9 @@ export type BundlerActions = { * * // Return {preVerificationGas: 43492n, verificationGasLimit: 59436n, callGasLimit: 9000n} */ - estimateUserOperationGas: (args: EstimateUserOperationGasParameters) => Promise + estimateUserOperationGas: ( + args: EstimateUserOperationGasParameters + ) => Promise /** * * Returns the supported entrypoints by the bundler service @@ -141,7 +146,9 @@ export type BundlerActions = { * await bundlerClient.getUserOperationByHash(userOpHash) * */ - getUserOperationByHash: (args: GetUserOperationByHashParameters) => Promise + getUserOperationByHash: ( + args: GetUserOperationByHashParameters + ) => Promise /** * * Returns the user operation receipt from userOpHash @@ -194,18 +201,21 @@ export type BundlerActions = { } const bundlerActions = (client: Client): BundlerActions => ({ - sendUserOperation: async (args: SendUserOperationParameters): Promise => - sendUserOperation(client as BundlerClient, args), + sendUserOperation: async ( + args: SendUserOperationParameters + ): Promise => sendUserOperation(client as BundlerClient, args), estimateUserOperationGas: (args: EstimateUserOperationGasParameters) => estimateUserOperationGas(client as BundlerClient, args), - supportedEntryPoints: (): Promise => supportedEntryPoints(client as BundlerClient), + supportedEntryPoints: (): Promise => + supportedEntryPoints(client as BundlerClient), chainId: () => chainId(client as BundlerClient), getUserOperationByHash: (args: GetUserOperationByHashParameters) => getUserOperationByHash(client as BundlerClient, args), getUserOperationReceipt: (args: GetUserOperationReceiptParameters) => getUserOperationReceipt(client as BundlerClient, args), - waitForUserOperationReceipt: (args: WaitForUserOperationReceiptParameters) => - waitForUserOperationReceipt(client as BundlerClient, args) + waitForUserOperationReceipt: ( + args: WaitForUserOperationReceiptParameters + ) => waitForUserOperationReceipt(client as BundlerClient, args) }) export { bundlerActions } diff --git a/src/clients/decorators/pimlico.ts b/src/clients/decorators/pimlico.ts index 3f76193b..03d91c96 100644 --- a/src/clients/decorators/pimlico.ts +++ b/src/clients/decorators/pimlico.ts @@ -13,7 +13,10 @@ import { type SponsorUserOperationReturnType, sponsorUserOperation } from "../../actions/pimlico/sponsorUserOperation.js" -import type { PimlicoBundlerClient, PimlicoPaymasterClient } from "../pimlico.js" +import type { + PimlicoBundlerClient, + PimlicoPaymasterClient +} from "../pimlico.js" export type PimlicoBundlerActions = { /** @@ -55,11 +58,16 @@ export type PimlicoBundlerActions = { * * await bundlerClient.getUserOperationStatus({ hash: userOpHash }) */ - getUserOperationStatus: (args: GetUserOperationStatusParameters) => Promise + getUserOperationStatus: ( + args: GetUserOperationStatusParameters + ) => Promise } -export const pimlicoBundlerActions = (client: Client): PimlicoBundlerActions => ({ - getUserOperationGasPrice: async () => getUserOperationGasPrice(client as PimlicoBundlerClient), +export const pimlicoBundlerActions = ( + client: Client +): PimlicoBundlerActions => ({ + getUserOperationGasPrice: async () => + getUserOperationGasPrice(client as PimlicoBundlerClient), getUserOperationStatus: async (args: GetUserOperationStatusParameters) => getUserOperationStatus(client as PimlicoBundlerClient, args) }) @@ -88,10 +96,14 @@ export type PimlicoPaymasterClientActions = { * }}) * */ - sponsorUserOperation: (args: SponsorUserOperationParameters) => Promise + sponsorUserOperation: ( + args: SponsorUserOperationParameters + ) => Promise } -export const pimlicoPaymasterActions = (client: Client): PimlicoPaymasterClientActions => ({ +export const pimlicoPaymasterActions = ( + client: Client +): PimlicoPaymasterClientActions => ({ sponsorUserOperation: async (args: SponsorUserOperationParameters) => sponsorUserOperation(client as PimlicoPaymasterClient, args) }) diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index 1e1f0052..24679424 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -1,11 +1,26 @@ -import type { Abi, Chain, Client, Transport, TypedData } from "viem" +import type { + Abi, + Chain, + Client, + SendTransactionParameters, + Transport, + TypedData, + WriteContractParameters +} from "viem" import type { SmartAccount } from "../../accounts/types.js" import { deployContract } from "../../actions/smartAccount/deployContract.js" import { getChainId } from "../../actions/smartAccount/getChainId.js" -import { sendTransaction } from "../../actions/smartAccount/sendTransaction.js" +import { type SponsorUserOperationMiddleware } from "../../actions/smartAccount/prepareUserOperationRequest.js" +import { + type SendTransactionWithPaymasterParameters, + sendTransaction +} from "../../actions/smartAccount/sendTransaction.js" import { signMessage } from "../../actions/smartAccount/signMessage.js" import { signTypedData } from "../../actions/smartAccount/signTypedData.js" -import { writeContract } from "../../actions/smartAccount/writeContract.js" +import { + type WriteContractWithPaymasterParameters, + writeContract +} from "../../actions/smartAccount/writeContract.js" export type SmartAccountActions< TChain extends Chain | undefined = Chain | undefined, @@ -13,37 +28,84 @@ export type SmartAccountActions< > = { getChainId: () => ReturnType sendTransaction: ( - args: Parameters>[1] - ) => ReturnType> + args: SendTransactionParameters + ) => ReturnType< + typeof sendTransaction + > signMessage: ( args: Parameters>[1] ) => ReturnType> - signTypedData: ( - args: Parameters>[1] - ) => ReturnType> - deployContract: ( - args: Parameters>[1] - ) => ReturnType> + signTypedData: < + const TTypedData extends TypedData | { [key: string]: unknown }, + TPrimaryType extends string + >( + args: Parameters< + typeof signTypedData< + TTypedData, + TPrimaryType, + TChain, + TSmartAccount + > + >[1] + ) => ReturnType< + typeof signTypedData + > + deployContract: < + const TAbi extends Abi | readonly unknown[], + TChainOverride extends Chain | undefined = undefined + >( + args: Parameters< + typeof deployContract + >[1] + ) => ReturnType< + typeof deployContract + > writeContract: < const TAbi extends Abi | readonly unknown[], TFunctionName extends string, TChainOverride extends Chain | undefined = undefined >( - args: Parameters>[1] - ) => ReturnType> + args: WriteContractParameters< + TAbi, + TFunctionName, + TChain, + TSmartAccount, + TChainOverride + > + ) => ReturnType< + typeof writeContract< + TChain, + TSmartAccount, + TAbi, + TFunctionName, + TChainOverride + > + > } -export const smartAccountActions = < - TTransport extends Transport, - TChain extends Chain | undefined = Chain | undefined, - TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined ->( - client: Client -): SmartAccountActions => ({ - deployContract: (args) => deployContract(client, args), - getChainId: () => getChainId(client), - sendTransaction: (args) => sendTransaction(client, args), - signMessage: (args) => signMessage(client, args), - signTypedData: (args) => signTypedData(client, args), - writeContract: (args) => writeContract(client, args) -}) +export const smartAccountActions = + ({ sponsorUserOperation }: SponsorUserOperationMiddleware) => + < + TTransport extends Transport, + TChain extends Chain | undefined = Chain | undefined, + TSmartAccount extends SmartAccount | undefined = + | SmartAccount + | undefined + >( + client: Client + ): SmartAccountActions => ({ + deployContract: (args) => deployContract(client, args), + getChainId: () => getChainId(client), + sendTransaction: (args) => + sendTransaction(client, { + ...args, + sponsorUserOperation + } as SendTransactionWithPaymasterParameters), + signMessage: (args) => signMessage(client, args), + signTypedData: (args) => signTypedData(client, args), + writeContract: (args) => + writeContract(client, { + ...args, + sponsorUserOperation + } as WriteContractWithPaymasterParameters) + }) diff --git a/src/clients/decorators/stackup.ts b/src/clients/decorators/stackup.ts index e50001c4..27e0620c 100644 --- a/src/clients/decorators/stackup.ts +++ b/src/clients/decorators/stackup.ts @@ -1,5 +1,8 @@ import type { Address, Client } from "viem" -import { type AccountsParameters, accounts } from "../../actions/stackup/accounts.js" +import { + type AccountsParameters, + accounts +} from "../../actions/stackup/accounts.js" import { type SponsorUserOperationParameters, type SponsorUserOperationReturnType, @@ -31,7 +34,9 @@ export type StackupPaymasterClientActions = { * }}) * */ - sponsorUserOperation: (args: SponsorUserOperationParameters) => Promise + sponsorUserOperation: ( + args: SponsorUserOperationParameters + ) => Promise /** * Returns all the Paymaster addresses associated with an EntryPoint that’s owned by this service. @@ -58,8 +63,11 @@ export type StackupPaymasterClientActions = { accounts: (args: AccountsParameters) => Promise } -export const stackupPaymasterActions = (client: Client): StackupPaymasterClientActions => ({ +export const stackupPaymasterActions = ( + client: Client +): StackupPaymasterClientActions => ({ sponsorUserOperation: async (args: SponsorUserOperationParameters) => sponsorUserOperation(client as StackupPaymasterClient, args), - accounts: async (args: AccountsParameters) => accounts(client as StackupPaymasterClient, args) + accounts: async (args: AccountsParameters) => + accounts(client as StackupPaymasterClient, args) }) diff --git a/src/clients/pimlico.ts b/src/clients/pimlico.ts index 99dff902..a935ab67 100644 --- a/src/clients/pimlico.ts +++ b/src/clients/pimlico.ts @@ -1,6 +1,15 @@ -import type { Account, Chain, Client, PublicClientConfig, Transport } from "viem" +import type { + Account, + Chain, + Client, + PublicClientConfig, + Transport +} from "viem" import { createClient } from "viem" -import type { PimlicoBundlerRpcSchema, PimlicoPaymasterRpcSchema } from "../types/pimlico.js" +import type { + PimlicoBundlerRpcSchema, + PimlicoPaymasterRpcSchema +} from "../types/pimlico.js" import { type BundlerActions, bundlerActions } from "./decorators/bundler.js" import { type PimlicoBundlerActions, @@ -44,7 +53,10 @@ export type PimlicoPaymasterClient = Client< * transport: http("https://api.pimlico.io/v1/goerli/rpc?apikey=YOUR_API_KEY_HERE"), * }) */ -export const createPimlicoBundlerClient = ( +export const createPimlicoBundlerClient = < + transport extends Transport, + chain extends Chain | undefined = undefined +>( parameters: PublicClientConfig ): PimlicoBundlerClient => { const { key = "public", name = "Pimlico Bundler Client" } = parameters @@ -76,7 +88,10 @@ export const createPimlicoBundlerClient = ( +export const createPimlicoPaymasterClient = < + transport extends Transport, + chain extends Chain | undefined = undefined +>( parameters: PublicClientConfig ): PimlicoPaymasterClient => { const { key = "public", name = "Pimlico Paymaster Client" } = parameters diff --git a/src/clients/stackup.ts b/src/clients/stackup.ts index 949e9da9..05469e05 100644 --- a/src/clients/stackup.ts +++ b/src/clients/stackup.ts @@ -1,7 +1,17 @@ -import { type Account, type Chain, type Client, type PublicClientConfig, type Transport, createClient } from "viem" +import { + type Account, + type Chain, + type Client, + type PublicClientConfig, + type Transport, + createClient +} from "viem" import type { StackupPaymasterRpcSchema } from "../types/stackup.js" import { type BundlerActions, bundlerActions } from "./decorators/bundler.js" -import { type StackupPaymasterClientActions, stackupPaymasterActions } from "./decorators/stackup.js" +import { + type StackupPaymasterClientActions, + stackupPaymasterActions +} from "./decorators/stackup.js" export type StackupPaymasterClient = Client< Transport, @@ -30,7 +40,10 @@ export type StackupPaymasterClient = Client< * transport: http("https://api.stackup.sh/v1/paymaster/YOUR_API_KEY_HERE"), * }) */ -export const createStackupPaymasterClient = ( +export const createStackupPaymasterClient = < + transport extends Transport, + chain extends Chain | undefined = undefined +>( parameters: PublicClientConfig ): StackupPaymasterClient => { const { key = "public", name = "Stackup Paymaster Client" } = parameters diff --git a/src/index.ts b/src/index.ts index 309c0e7b..fe1d1d14 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,12 +26,21 @@ import { } from "./actions/bundler/waitForUserOperationReceipt.js" import type { GetAccountNonceParams } from "./actions/public/getAccountNonce.js" import { getAccountNonce } from "./actions/public/getAccountNonce.js" -import { type BundlerClient, createBundlerClient } from "./clients/createBundlerClient.js" +import { + type BundlerClient, + createBundlerClient +} from "./clients/createBundlerClient.js" import { createSmartAccountClient } from "./clients/createSmartAccountClient.js" -import { type SmartAccountClient, type SmartAccountClientConfig } from "./clients/createSmartAccountClient.js" +import { + type SmartAccountClient, + type SmartAccountClientConfig +} from "./clients/createSmartAccountClient.js" import type { BundlerActions } from "./clients/decorators/bundler.js" import { bundlerActions } from "./clients/decorators/bundler.js" -import { type SmartAccountActions, smartAccountActions } from "./clients/decorators/smartAccount.js" +import { + type SmartAccountActions, + smartAccountActions +} from "./clients/decorators/smartAccount.js" export type { SendUserOperationParameters, diff --git a/src/types/bundler.ts b/src/types/bundler.ts index fa2b92ac..ca1a599b 100644 --- a/src/types/bundler.ts +++ b/src/types/bundler.ts @@ -5,7 +5,10 @@ import type { UserOperationWithBigIntAsHex } from "./userOperation.js" export type BundlerRpcSchema = [ { Method: "eth_sendUserOperation" - Parameters: [userOperation: UserOperationWithBigIntAsHex, entryPoint: Address] + Parameters: [ + userOperation: UserOperationWithBigIntAsHex, + entryPoint: Address + ] ReturnType: Hash }, { diff --git a/src/types/index.ts b/src/types/index.ts index d84ae26e..1a558cbc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -14,8 +14,11 @@ export type GetAccountParameterWithClient< ? { account: Account; client?: Client } : { client: Client; account?: Account } -export type GetAccountParameter = - IsUndefined extends true ? { account: SmartAccount } : { account?: SmartAccount } +export type GetAccountParameter< + TAccount extends SmartAccount | undefined = SmartAccount | undefined +> = IsUndefined extends true + ? { account: SmartAccount } + : { account?: SmartAccount } export type Prettify = { [K in keyof T]: T[K] diff --git a/src/types/pimlico.ts b/src/types/pimlico.ts index fd5dd4a3..cc921189 100644 --- a/src/types/pimlico.ts +++ b/src/types/pimlico.ts @@ -18,7 +18,14 @@ type PimlicoUserOperationGasPriceWithBigIntAsHex = { } export type PimlicoUserOperationStatus = { - status: "not_found" | "not_submitted" | "submitted" | "rejected" | "reverted" | "included" | "failed" + status: + | "not_found" + | "not_submitted" + | "submitted" + | "rejected" + | "reverted" + | "included" + | "failed" transactionHash: Hash | null } @@ -41,7 +48,10 @@ export type PimlicoPaymasterRpcSchema = [ Parameters: [ userOperation: PartialBy< UserOperationWithBigIntAsHex, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" | "paymasterAndData" + | "callGasLimit" + | "preVerificationGas" + | "verificationGasLimit" + | "paymasterAndData" >, entryPoint: Address ] diff --git a/src/types/stackup.ts b/src/types/stackup.ts index 1cc0290f..52bc3695 100644 --- a/src/types/stackup.ts +++ b/src/types/stackup.ts @@ -16,7 +16,10 @@ export type StackupPaymasterRpcSchema = [ Parameters: [ userOperation: PartialBy< UserOperationWithBigIntAsHex, - "callGasLimit" | "preVerificationGas" | "verificationGasLimit" | "paymasterAndData" + | "callGasLimit" + | "preVerificationGas" + | "verificationGasLimit" + | "paymasterAndData" >, entryPoint: Address, context: StackupPaymasterContext diff --git a/src/utils/deepHexlify.test.ts b/src/utils/deepHexlify.test.ts index dfffa44c..e57dd201 100644 --- a/src/utils/deepHexlify.test.ts +++ b/src/utils/deepHexlify.test.ts @@ -5,12 +5,18 @@ import { deepHexlify } from "./deepHexlify.js" dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) + throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) + throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) + throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) test("Test deep Hexlify", async () => { diff --git a/src/utils/getUserOperationHash.ts b/src/utils/getUserOperationHash.ts index 5e7352f8..3bd141e2 100644 --- a/src/utils/getUserOperationHash.ts +++ b/src/utils/getUserOperationHash.ts @@ -35,7 +35,11 @@ function packUserOp({ userOperation }: { userOperation: UserOperation }): Hex { ) } -export type GetUserOperationHashParams = { userOperation: UserOperation; entryPoint: Address; chainId: number } +export type GetUserOperationHashParams = { + userOperation: UserOperation + entryPoint: Address + chainId: number +} /** * @@ -58,7 +62,11 @@ export type GetUserOperationHashParams = { userOperation: UserOperation; entryPo * // Returns "0xe9fad2cd67f9ca1d0b7a6513b2a42066784c8df938518da2b51bb8cc9a89ea34" * */ -export const getUserOperationHash = ({ userOperation, entryPoint, chainId }: GetUserOperationHashParams): Hash => { +export const getUserOperationHash = ({ + userOperation, + entryPoint, + chainId +}: GetUserOperationHashParams): Hash => { const encoded = encodeAbiParameters( [{ type: "bytes32" }, { type: "address" }, { type: "uint256" }], [keccak256(packUserOp({ userOperation })), entryPoint, BigInt(chainId)] diff --git a/src/utils/index.ts b/src/utils/index.ts index 88dc9a8b..250ebc45 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,9 +1,16 @@ import type { Account, Address } from "viem" -import { type GetUserOperationHashParams, getUserOperationHash } from "./getUserOperationHash.js" -import { AccountOrClientNotFoundError, signUserOperationHashWithECDSA } from "./signUserOperationHashWithECDSA.js" +import { + type GetUserOperationHashParams, + getUserOperationHash +} from "./getUserOperationHash.js" +import { + AccountOrClientNotFoundError, + signUserOperationHashWithECDSA +} from "./signUserOperationHashWithECDSA.js" export function parseAccount(account: Address | Account): Account { - if (typeof account === "string") return { address: account, type: "json-rpc" } + if (typeof account === "string") + return { address: account, type: "json-rpc" } return account } diff --git a/src/utils/observe.ts b/src/utils/observe.ts index 91121dca..b4b719a8 100644 --- a/src/utils/observe.ts +++ b/src/utils/observe.ts @@ -4,11 +4,16 @@ import type { MaybePromise } from "viem/types/utils" type Callback = ((...args: any[]) => any) | undefined type Callbacks = Record -export const listenersCache = /*#__PURE__*/ new Map() +export const listenersCache = /*#__PURE__*/ new Map< + string, + { id: number; fns: Callbacks }[] +>() export const cleanupCache = /*#__PURE__*/ new Map void>() -// biome-ignore lint/suspicious/noConfusingVoidType: it's a recursive function, so it's hard to type -type EmitFunction = (emit: TCallbacks) => MaybePromise void)> +type EmitFunction = ( + emit: TCallbacks + // biome-ignore lint/suspicious/noConfusingVoidType: +) => MaybePromise void)> let callbackCount = 0 @@ -42,13 +47,18 @@ export function observe( } const listeners = getListeners() - listenersCache.set(observerId, [...listeners, { id: callbackId, fns: callbacks }]) + listenersCache.set(observerId, [ + ...listeners, + { id: callbackId, fns: callbacks } + ]) if (listeners && listeners.length > 0) return unwatch const emit: TCallbacks = {} as TCallbacks for (const key in callbacks) { - emit[key] = ((...args: Parameters>) => { + emit[key] = (( + ...args: Parameters> + ) => { const listeners = getListeners() if (listeners.length === 0) return for (const listener of listeners) { diff --git a/src/utils/signUserOperationHashWithECDSA.ts b/src/utils/signUserOperationHashWithECDSA.ts index 874fe3ba..a77896bb 100644 --- a/src/utils/signUserOperationHashWithECDSA.ts +++ b/src/utils/signUserOperationHashWithECDSA.ts @@ -83,10 +83,15 @@ export const signUserOperationHashWithECDSA = async < userOperation, chainId, entryPoint -}: signUserOperationHashWithECDSAParams): Promise => { +}: signUserOperationHashWithECDSAParams< + TTransport, + TChain, + TAccount +>): Promise => { if (!account_) throw new AccountOrClientNotFoundError({ - docsPath: "/permissionless/reference/utils/signUserOperationHashWithECDSA" + docsPath: + "/permissionless/reference/utils/signUserOperationHashWithECDSA" }) let userOperationHash: Hash @@ -112,7 +117,8 @@ export const signUserOperationHashWithECDSA = async < if (!client) throw new AccountOrClientNotFoundError({ - docsPath: "/permissionless/reference/utils/signUserOperationHashWithECDSA" + docsPath: + "/permissionless/reference/utils/signUserOperationHashWithECDSA" }) return client.request({ diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index 77083818..e3886fd1 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -1,20 +1,35 @@ import { beforeAll, beforeEach, describe, expect, test } from "bun:test" import dotenv from "dotenv" -import { BundlerClient, WaitForUserOperationReceiptTimeoutError } from "permissionless" +import { + BundlerClient, + WaitForUserOperationReceiptTimeoutError +} from "permissionless" import { getUserOperationHash } from "permissionless/utils" import { http, Address, Hex } from "viem" import { buildUserOp } from "./userOp.js" -import { getBundlerClient, getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils.js" +import { + getBundlerClient, + getEntryPoint, + getEoaWalletClient, + getPublicClient, + getTestingChain +} from "./utils.js" dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) + throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) + throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) + throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) describe("BUNDLER ACTIONS", () => { @@ -44,7 +59,8 @@ describe("BUNDLER ACTIONS", () => { test("Estimate user operation gas", async () => { const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -68,7 +84,8 @@ describe("BUNDLER ACTIONS", () => { test("Sending user operation", async () => { const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -93,7 +110,13 @@ describe("BUNDLER ACTIONS", () => { userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, - message: { raw: getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) } + message: { + raw: getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) + } }) const userOpHash = await bundlerClient.sendUserOperation({ @@ -104,29 +127,40 @@ describe("BUNDLER ACTIONS", () => { expect(userOpHash).toBeString() expect(userOpHash).toStartWith("0x") - const userOperationReceipt = await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) + const userOperationReceipt = + await bundlerClient.waitForUserOperationReceipt({ + hash: userOpHash + }) expect(userOperationReceipt).not.toBeNull() expect(userOperationReceipt?.userOpHash).toBe(userOpHash) expect(userOperationReceipt?.receipt.transactionHash).not.toBeEmpty() expect(userOperationReceipt?.receipt.transactionHash).not.toBeNull() - expect(userOperationReceipt?.receipt.transactionHash).not.toBeUndefined() + expect( + userOperationReceipt?.receipt.transactionHash + ).not.toBeUndefined() - const userOperationFromUserOpHash = await bundlerClient.getUserOperationByHash({ hash: userOpHash }) + const userOperationFromUserOpHash = + await bundlerClient.getUserOperationByHash({ hash: userOpHash }) expect(userOperationFromUserOpHash).not.toBeNull() expect(userOperationFromUserOpHash?.entryPoint).toBe(entryPoint) - expect(userOperationFromUserOpHash?.transactionHash).toBe(userOperationReceipt?.receipt.transactionHash) + expect(userOperationFromUserOpHash?.transactionHash).toBe( + userOperationReceipt?.receipt.transactionHash + ) for (const key in userOperationFromUserOpHash?.userOperation) { - expect(userOperationFromUserOpHash?.userOperation[key]).toBe(userOperation[key]) + expect(userOperationFromUserOpHash?.userOperation[key]).toBe( + userOperation[key] + ) } }, 100000) test("wait for user operation receipt fail", async () => { const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -149,10 +183,19 @@ describe("BUNDLER ACTIONS", () => { userOperation.verificationGasLimit = gasParameters.verificationGasLimit userOperation.preVerificationGas = gasParameters.preVerificationGas - const userOpHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOpHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) expect(async () => { - await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash, timeout: 100 }) - }).toThrow(new WaitForUserOperationReceiptTimeoutError({ hash: userOpHash })) + await bundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + timeout: 100 + }) + }).toThrow( + new WaitForUserOperationReceiptTimeoutError({ hash: userOpHash }) + ) }) }) diff --git a/test/index.test.ts b/test/index.test.ts index 509420a4..60e9a174 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,10 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" -import { UserOperation, getSenderAddress, getUserOperationHash } from "permissionless" +import { + UserOperation, + getSenderAddress, + getUserOperationHash +} from "permissionless" import { signUserOperationHashWithECDSA } from "permissionless/utils" import { buildUserOp, getAccountInitCode } from "./userOp.js" import { @@ -17,12 +21,18 @@ dotenv.config() const pimlicoApiKey = process.env.PIMLICO_API_KEY beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) + throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) + throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) + throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) describe("test public actions and utils", () => { @@ -30,7 +40,10 @@ describe("test public actions and utils", () => { const eoaWalletClient = getEoaWalletClient() const factoryAddress = getFactoryAddress() - const initCode = await getAccountInitCode(factoryAddress, eoaWalletClient) + const initCode = await getAccountInitCode( + factoryAddress, + eoaWalletClient + ) const publicClient = await getPublicClient() const entryPoint = getEntryPoint() @@ -49,7 +62,10 @@ describe("test public actions and utils", () => { const eoaWalletClient = getEoaWalletClient() const factoryAddress = getFactoryAddress() - const initCode = await getAccountInitCode(factoryAddress, eoaWalletClient) + const initCode = await getAccountInitCode( + factoryAddress, + eoaWalletClient + ) const publicClient = await getPublicClient() const entryPoint = "0x0000000" @@ -68,7 +84,8 @@ describe("test public actions and utils", () => { const entryPoint = getEntryPoint() const bundlerClient = getBundlerClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -102,7 +119,8 @@ describe("test public actions and utils", () => { const bundlerClient = getBundlerClient() const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation: UserOperation = { ...(await buildUserOp(eoaWalletClient)), diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index 7f045fde..a56c8a40 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -21,12 +21,18 @@ import { dotenv.config() beforeAll(() => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.STACKUP_API_KEY) throw new Error("STACKUP_API_KEY environment variable not set") - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.STACKUP_API_KEY) + throw new Error("STACKUP_API_KEY environment variable not set") + if (!process.env.FACTORY_ADDRESS) + throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.RPC_URL) + throw new Error("RPC_URL environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") }) const pimlicoApiKey = process.env.PIMLICO_API_KEY @@ -42,7 +48,8 @@ describe("Pimlico Actions tests", () => { describe("Pimlico Bundler actions", () => { test("fetch gas price", async () => { - const gasPrice = await pimlicoBundlerClient.getUserOperationGasPrice() + const gasPrice = + await pimlicoBundlerClient.getUserOperationGasPrice() expect(gasPrice).not.toBeUndefined() expect(gasPrice).not.toBeNull() @@ -60,19 +67,25 @@ describe("Pimlico Actions tests", () => { expect(gasPrice.slow.maxFeePerGas).toBeGreaterThan(BigInt(0)) expect(typeof gasPrice.slow.maxPriorityFeePerGas).toBe("bigint") - expect(gasPrice.slow.maxPriorityFeePerGas).toBeGreaterThan(BigInt(0)) + expect(gasPrice.slow.maxPriorityFeePerGas).toBeGreaterThan( + BigInt(0) + ) expect(typeof gasPrice.standard.maxFeePerGas).toBe("bigint") expect(gasPrice.standard.maxFeePerGas).toBeGreaterThan(BigInt(0)) expect(typeof gasPrice.standard.maxPriorityFeePerGas).toBe("bigint") - expect(gasPrice.standard.maxPriorityFeePerGas).toBeGreaterThan(BigInt(0)) + expect(gasPrice.standard.maxPriorityFeePerGas).toBeGreaterThan( + BigInt(0) + ) expect(typeof gasPrice.fast.maxFeePerGas).toBe("bigint") expect(gasPrice.fast.maxFeePerGas).toBeGreaterThan(BigInt(0)) expect(typeof gasPrice.fast.maxPriorityFeePerGas).toBe("bigint") - expect(gasPrice.fast.maxPriorityFeePerGas).toBeGreaterThan(BigInt(0)) + expect(gasPrice.fast.maxPriorityFeePerGas).toBeGreaterThan( + BigInt(0) + ) }) test("fetch user operation status", async () => {}) @@ -82,7 +95,8 @@ describe("Pimlico Actions tests", () => { test("Fetching paymaster and data", async () => { const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -95,26 +109,43 @@ describe("Pimlico Actions tests", () => { const entryPoint = getEntryPoint() - const sponsorUserOperationPaymasterAndData = await pimlicoPaymasterClient.sponsorUserOperation({ - userOperation: userOperation, - entryPoint: entryPoint - }) + const sponsorUserOperationPaymasterAndData = + await pimlicoPaymasterClient.sponsorUserOperation({ + userOperation: userOperation, + entryPoint: entryPoint + }) expect(sponsorUserOperationPaymasterAndData).not.toBeNull() expect(sponsorUserOperationPaymasterAndData).not.toBeUndefined() expect(sponsorUserOperationPaymasterAndData).not.toBeUndefined() - expect(typeof sponsorUserOperationPaymasterAndData.callGasLimit).toBe("bigint") - expect(sponsorUserOperationPaymasterAndData.callGasLimit).toBeGreaterThan(BigInt(0)) - - expect(typeof sponsorUserOperationPaymasterAndData.preVerificationGas).toBe("bigint") - expect(sponsorUserOperationPaymasterAndData.preVerificationGas).toBeGreaterThan(BigInt(0)) - - expect(typeof sponsorUserOperationPaymasterAndData.verificationGasLimit).toBe("bigint") - expect(sponsorUserOperationPaymasterAndData.verificationGasLimit).toBeGreaterThan(BigInt(0)) - - expect(sponsorUserOperationPaymasterAndData.paymasterAndData).not.toBeEmpty() - expect(sponsorUserOperationPaymasterAndData.paymasterAndData).toStartWith("0x") + expect( + typeof sponsorUserOperationPaymasterAndData.callGasLimit + ).toBe("bigint") + expect( + sponsorUserOperationPaymasterAndData.callGasLimit + ).toBeGreaterThan(BigInt(0)) + + expect( + typeof sponsorUserOperationPaymasterAndData.preVerificationGas + ).toBe("bigint") + expect( + sponsorUserOperationPaymasterAndData.preVerificationGas + ).toBeGreaterThan(BigInt(0)) + + expect( + typeof sponsorUserOperationPaymasterAndData.verificationGasLimit + ).toBe("bigint") + expect( + sponsorUserOperationPaymasterAndData.verificationGasLimit + ).toBeGreaterThan(BigInt(0)) + + expect( + sponsorUserOperationPaymasterAndData.paymasterAndData + ).not.toBeEmpty() + expect( + sponsorUserOperationPaymasterAndData.paymasterAndData + ).toStartWith("0x") }) test("Sending user op with paymaster and data", async () => { @@ -123,7 +154,8 @@ describe("Pimlico Actions tests", () => { const chain = getTestingChain() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -134,18 +166,27 @@ describe("Pimlico Actions tests", () => { preVerificationGas: 0n } - const sponsorUserOperationPaymasterAndData = await pimlicoPaymasterClient.sponsorUserOperation({ - userOperation: userOperation, - entryPoint: entryPoint + const sponsorUserOperationPaymasterAndData = + await pimlicoPaymasterClient.sponsorUserOperation({ + userOperation: userOperation, + entryPoint: entryPoint + }) + + userOperation.paymasterAndData = + sponsorUserOperationPaymasterAndData.paymasterAndData + userOperation.callGasLimit = + sponsorUserOperationPaymasterAndData.callGasLimit + userOperation.verificationGasLimit = + sponsorUserOperationPaymasterAndData.verificationGasLimit + userOperation.preVerificationGas = + sponsorUserOperationPaymasterAndData.preVerificationGas + + const userOperationHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id }) - userOperation.paymasterAndData = sponsorUserOperationPaymasterAndData.paymasterAndData - userOperation.callGasLimit = sponsorUserOperationPaymasterAndData.callGasLimit - userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit - userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas - - const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) - userOperation.signature = await eoaWalletClient.signMessage({ account: eoaWalletClient.account, message: { raw: userOperationHash } @@ -159,35 +200,48 @@ describe("Pimlico Actions tests", () => { expect(userOpHash).toBeString() expect(userOpHash).toStartWith("0x") - const userOperationReceipt = await pimlicoBundlerClient.waitForUserOperationReceipt({ - hash: userOpHash - }) + const userOperationReceipt = + await pimlicoBundlerClient.waitForUserOperationReceipt({ + hash: userOpHash + }) expect(userOperationReceipt).not.toBeNull() expect(userOperationReceipt?.userOpHash).toBe(userOpHash) - expect(userOperationReceipt?.receipt.transactionHash).not.toBeEmpty() + expect( + userOperationReceipt?.receipt.transactionHash + ).not.toBeEmpty() expect(userOperationReceipt?.receipt.transactionHash).not.toBeNull() - expect(userOperationReceipt?.receipt.transactionHash).not.toBeUndefined() + expect( + userOperationReceipt?.receipt.transactionHash + ).not.toBeUndefined() - const userOperationFromUserOpHash = await pimlicoBundlerClient.getUserOperationByHash({ hash: userOpHash }) + const userOperationFromUserOpHash = + await pimlicoBundlerClient.getUserOperationByHash({ + hash: userOpHash + }) expect(userOperationFromUserOpHash).not.toBeNull() expect(userOperationFromUserOpHash?.entryPoint).toBe(entryPoint) - expect(userOperationFromUserOpHash?.transactionHash).toBe(userOperationReceipt?.receipt.transactionHash) + expect(userOperationFromUserOpHash?.transactionHash).toBe( + userOperationReceipt?.receipt.transactionHash + ) // for (const key in userOperationFromUserOpHash?.userOperation) { // expect(userOperationFromUserOpHash?.userOperation[key]).toBe(userOperation[key]) // } - const userOperationStatus = await pimlicoBundlerClient.getUserOperationStatus({ - hash: userOpHash - }) + const userOperationStatus = + await pimlicoBundlerClient.getUserOperationStatus({ + hash: userOpHash + }) expect(userOperationStatus).not.toBeNull() expect(userOperationStatus).not.toBeUndefined() expect(userOperationStatus.status).toBe("included") - expect(userOperationStatus.transactionHash).toBe(userOperationReceipt?.receipt.transactionHash) + expect(userOperationStatus.transactionHash).toBe( + userOperationReceipt?.receipt.transactionHash + ) }, 100000) }) }) diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index d4d38359..6456e3ce 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -1,10 +1,21 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import { SponsorUserOperationParameters, SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js" -import { Address, Client, Hex, Transport, getContract, zeroAddress } from "viem" +import { SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js" +import { UserOperation } from "permissionless/index.js" +import { + Address, + Client, + Hex, + Transport, + decodeEventLog, + getContract, + zeroAddress +} from "viem" +import { EntryPointAbi } from "./abis/EntryPoint.js" import { GreeterAbi, GreeterBytecode } from "./abis/Greeter.js" import { + getBundlerClient, getEntryPoint, getPimlicoPaymasterClient, getPrivateKeyToSimpleSmartAccount, @@ -140,6 +151,9 @@ describe("Simple Account", () => { const txHash = await greeterContract.write.setGreeting(["hello world"]) + expect(txHash).toBeString() + expect(txHash).toHaveLength(66) + const newGreet = await greeterContract.read.greet() expect(newGreet).toBeString() @@ -159,17 +173,21 @@ describe("Simple Account", () => { }, 1000000) test("smart account client send Transaction with paymaster", async () => { - const smartAccountClient = await getSmartAccountClient() + const publicClient = await getPublicClient() - smartAccountClient.extend((_: Client) => ({ + const bundlerClient = getBundlerClient() + + const smartAccountClient = await getSmartAccountClient({ sponsorUserOperation: async ( - args: SponsorUserOperationParameters + userOperation: UserOperation ): Promise => { const pimlicoPaymaster = getPimlicoPaymasterClient() - const { userOperation } = args - return pimlicoPaymaster.sponsorUserOperation({ userOperation, entryPoint: getEntryPoint() }) + return pimlicoPaymaster.sponsorUserOperation({ + userOperation, + entryPoint: getEntryPoint() + }) } - })) + }) const response = await smartAccountClient.sendTransaction({ to: zeroAddress, @@ -180,5 +198,32 @@ describe("Simple Account", () => { expect(response).toBeString() expect(response).toHaveLength(66) expect(response).toMatch(/^0x[0-9a-fA-F]{64}$/) + + const transactionReceipt = await publicClient.waitForTransactionReceipt( + { + hash: response + } + ) + + let eventFound = false + + for (const log of transactionReceipt.logs) { + const event = decodeEventLog({ + abi: EntryPointAbi, + ...log + }) + if (event.eventName === "UserOperationEvent") { + eventFound = true + const userOperation = + await bundlerClient.getUserOperationByHash({ + hash: event.args.userOpHash + }) + expect(userOperation?.userOperation.paymasterAndData).not.toBe( + "0x" + ) + } + } + + expect(eventFound).toBeTrue() }, 1000000) }) diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index e096d80f..81daf605 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -3,18 +3,28 @@ import { StackupPaymasterClient } from "permissionless/clients/stackup" import { getUserOperationHash } from "permissionless/utils" import { Address } from "viem" import { buildUserOp } from "./userOp.js" -import { getEntryPoint, getEoaWalletClient, getPublicClient, getTestingChain } from "./utils.js" +import { + getEntryPoint, + getEoaWalletClient, + getPublicClient, + getTestingChain +} from "./utils.js" -export const testStackupBundlerActions = async (stackupBundlerClient: StackupPaymasterClient) => { +export const testStackupBundlerActions = async ( + stackupBundlerClient: StackupPaymasterClient +) => { const entryPoint = getEntryPoint() const chain = getTestingChain() - const supportedPaymasters = await stackupBundlerClient.accounts({ entryPoint }) + const supportedPaymasters = await stackupBundlerClient.accounts({ + entryPoint + }) const eoaWalletClient = getEoaWalletClient() const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() const userOperation: UserOperation = { ...(await buildUserOp(eoaWalletClient)), @@ -26,20 +36,29 @@ export const testStackupBundlerActions = async (stackupBundlerClient: StackupPay preVerificationGas: 0n } - const sponsorUserOperationPaymasterAndData = await stackupBundlerClient.sponsorUserOperation({ - userOperation: userOperation, - entryPoint: entryPoint as Address, - context: { - type: "payg" - } - }) + const sponsorUserOperationPaymasterAndData = + await stackupBundlerClient.sponsorUserOperation({ + userOperation: userOperation, + entryPoint: entryPoint as Address, + context: { + type: "payg" + } + }) - userOperation.paymasterAndData = sponsorUserOperationPaymasterAndData.paymasterAndData - userOperation.callGasLimit = sponsorUserOperationPaymasterAndData.callGasLimit - userOperation.verificationGasLimit = sponsorUserOperationPaymasterAndData.verificationGasLimit - userOperation.preVerificationGas = sponsorUserOperationPaymasterAndData.preVerificationGas + userOperation.paymasterAndData = + sponsorUserOperationPaymasterAndData.paymasterAndData + userOperation.callGasLimit = + sponsorUserOperationPaymasterAndData.callGasLimit + userOperation.verificationGasLimit = + sponsorUserOperationPaymasterAndData.verificationGasLimit + userOperation.preVerificationGas = + sponsorUserOperationPaymasterAndData.preVerificationGas - const userOperationHash = getUserOperationHash({ userOperation, entryPoint, chainId: chain.id }) + const userOperationHash = getUserOperationHash({ + userOperation, + entryPoint, + chainId: chain.id + }) const signedUserOperation: UserOperation = { ...userOperation, diff --git a/test/userOp.ts b/test/userOp.ts index 973b558b..6dd7f088 100644 --- a/test/userOp.ts +++ b/test/userOp.ts @@ -1,9 +1,26 @@ -import { UserOperation, getAccountNonce, getSenderAddress } from "permissionless" -import { Address, Hex, WalletClient, concatHex, encodeFunctionData, zeroAddress } from "viem" +import { + UserOperation, + getAccountNonce, + getSenderAddress +} from "permissionless" +import { + Address, + Hex, + WalletClient, + concatHex, + encodeFunctionData, + zeroAddress +} from "viem" import { PartialBy } from "viem/types/utils" import { SimpleAccountAbi } from "./abis/SimpleAccount.js" import { SimpleAccountFactoryAbi } from "./abis/SimpleAccountFactory.js" -import { getDummySignature, getEntryPoint, getFactoryAddress, getPublicClient, isAccountDeployed } from "./utils.js" +import { + getDummySignature, + getEntryPoint, + getFactoryAddress, + getPublicClient, + isAccountDeployed +} from "./utils.js" const getInitCode = async (factoryAddress: Address, owner: WalletClient) => { const accountAddress = await getAccountAddress(factoryAddress, owner) @@ -14,7 +31,11 @@ const getInitCode = async (factoryAddress: Address, owner: WalletClient) => { return getAccountInitCode(factoryAddress, owner) } -export const getAccountInitCode = async (factoryAddress: Address, owner: WalletClient, index = 0n): Promise => { +export const getAccountInitCode = async ( + factoryAddress: Address, + owner: WalletClient, + index = 0n +): Promise => { if (!owner.account) throw new Error("Owner account not found") return concatHex([ factoryAddress, @@ -26,7 +47,10 @@ export const getAccountInitCode = async (factoryAddress: Address, owner: WalletC ]) } -const getAccountAddress = async (factoryAddress: Address, owner: WalletClient): Promise
=> { +const getAccountAddress = async ( + factoryAddress: Address, + owner: WalletClient +): Promise
=> { const initCode = await getAccountInitCode(factoryAddress, owner) const publicClient = await getPublicClient() const entryPoint = getEntryPoint() @@ -37,7 +61,11 @@ const getAccountAddress = async (factoryAddress: Address, owner: WalletClient): }) } -const encodeExecute = async (target: Hex, value: bigint, data: Hex): Promise<`0x${string}`> => { +const encodeExecute = async ( + target: Hex, + value: bigint, + data: Hex +): Promise<`0x${string}`> => { return encodeFunctionData({ abi: SimpleAccountAbi, functionName: "execute", @@ -57,7 +85,10 @@ export const buildUserOp = async (eoaWalletClient: WalletClient) => { const publicClient = await getPublicClient() const entryPoint = getEntryPoint() - const accountAddress = await getAccountAddress(factoryAddress, eoaWalletClient) + const accountAddress = await getAccountAddress( + factoryAddress, + eoaWalletClient + ) if (!accountAddress) throw new Error("Account address not found") @@ -68,7 +99,11 @@ export const buildUserOp = async (eoaWalletClient: WalletClient) => { const userOperation: PartialBy< UserOperation, - "maxFeePerGas" | "maxPriorityFeePerGas" | "callGasLimit" | "verificationGasLimit" | "preVerificationGas" + | "maxFeePerGas" + | "maxPriorityFeePerGas" + | "callGasLimit" + | "verificationGasLimit" + | "preVerificationGas" > = { sender: accountAddress, nonce: nonce, diff --git a/test/utils.ts b/test/utils.ts index 440e5589..f721335b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,18 +1,30 @@ import { createBundlerClient, createSmartAccountClient } from "permissionless" import { privateKeyToSimpleSmartAccount } from "permissionless/accounts" -import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" -import { http, Address, Hex, createPublicClient, createWalletClient } from "viem" +import { SponsorTransactionParameters } from "permissionless/actions/smartAccount" +import { + createPimlicoBundlerClient, + createPimlicoPaymasterClient +} from "permissionless/clients/pimlico" +import { + http, + Address, + Hex, + createPublicClient, + createWalletClient +} from "viem" import { privateKeyToAccount } from "viem/accounts" import { goerli } from "viem/chains" export const getFactoryAddress = () => { - if (!process.env.FACTORY_ADDRESS) throw new Error("FACTORY_ADDRESS environment variable not set") + if (!process.env.FACTORY_ADDRESS) + throw new Error("FACTORY_ADDRESS environment variable not set") const factoryAddress = process.env.FACTORY_ADDRESS as Address return factoryAddress } export const getPrivateKeyAccount = () => { - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") return privateKeyToAccount(process.env.TEST_PRIVATE_KEY as Hex) } @@ -21,7 +33,8 @@ export const getTestingChain = () => { } export const getPrivateKeyToSimpleSmartAccount = async () => { - if (!process.env.TEST_PRIVATE_KEY) throw new Error("TEST_PRIVATE_KEY environment variable not set") + if (!process.env.TEST_PRIVATE_KEY) + throw new Error("TEST_PRIVATE_KEY environment variable not set") const publicClient = await getPublicClient() @@ -32,9 +45,13 @@ export const getPrivateKeyToSimpleSmartAccount = async () => { }) } -export const getSmartAccountClient = async () => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") +export const getSmartAccountClient = async ({ + sponsorUserOperation +}: SponsorTransactionParameters = {}) => { + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -42,8 +59,11 @@ export const getSmartAccountClient = async () => { account: await getPrivateKeyToSimpleSmartAccount(), chain, transport: http( - `${process.env.PIMLICO_BUNDLER_RPC_HOST}/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` - ) + `${ + process.env.PIMLICO_BUNDLER_RPC_HOST + }/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` + ), + sponsorUserOperation }) } @@ -56,12 +76,14 @@ export const getEoaWalletClient = () => { } export const getEntryPoint = () => { - if (!process.env.ENTRYPOINT_ADDRESS) throw new Error("ENTRYPOINT_ADDRESS environment variable not set") + if (!process.env.ENTRYPOINT_ADDRESS) + throw new Error("ENTRYPOINT_ADDRESS environment variable not set") return process.env.ENTRYPOINT_ADDRESS as Address } export const getPublicClient = async () => { - if (!process.env.RPC_URL) throw new Error("RPC_URL environment variable not set") + if (!process.env.RPC_URL) + throw new Error("RPC_URL environment variable not set") const publicClient = createPublicClient({ transport: http(process.env.RPC_URL as string) @@ -69,14 +91,17 @@ export const getPublicClient = async () => { const chainId = await publicClient.getChainId() - if (chainId !== getTestingChain().id) throw new Error("Testing Chain ID not supported by RPC URL") + if (chainId !== getTestingChain().id) + throw new Error("Testing Chain ID not supported by RPC URL") return publicClient } export const getBundlerClient = () => { - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -84,14 +109,18 @@ export const getBundlerClient = () => { return createBundlerClient({ chain: chain, transport: http( - `${process.env.PIMLICO_BUNDLER_RPC_HOST}/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` + `${ + process.env.PIMLICO_BUNDLER_RPC_HOST + }/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` ) }) } export const getPimlicoBundlerClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -99,14 +128,18 @@ export const getPimlicoBundlerClient = () => { return createPimlicoBundlerClient({ chain: chain, transport: http( - `${process.env.PIMLICO_BUNDLER_RPC_HOST}/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` + `${ + process.env.PIMLICO_BUNDLER_RPC_HOST + }/v1/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` ) }) } export const getPimlicoPaymasterClient = () => { - if (!process.env.PIMLICO_BUNDLER_RPC_HOST) throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") - if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") + if (!process.env.PIMLICO_BUNDLER_RPC_HOST) + throw new Error("PIMLICO_BUNDLER_RPC_HOST environment variable not set") + if (!process.env.PIMLICO_API_KEY) + throw new Error("PIMLICO_API_KEY environment variable not set") const pimlicoApiKey = process.env.PIMLICO_API_KEY const chain = getTestingChain() @@ -114,7 +147,9 @@ export const getPimlicoPaymasterClient = () => { return createPimlicoPaymasterClient({ chain: chain, transport: http( - `${process.env.PIMLICO_BUNDLER_RPC_HOST}/v2/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` + `${ + process.env.PIMLICO_BUNDLER_RPC_HOST + }/v2/${chain.name.toLowerCase()}/rpc?apikey=${pimlicoApiKey}` ) }) } From 2c75e6f9b3a5115f77e12f1359ff3f9a2b3665ac Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 14:57:20 +0530 Subject: [PATCH 19/26] Add documentation --- src/actions/smartAccount.ts | 3 - src/actions/smartAccount/deployContract.ts | 28 ++ src/actions/smartAccount/getChainId.ts | 16 -- src/actions/smartAccount/sendTransaction.ts | 48 +++- src/actions/smartAccount/signMessage.ts | 46 ++++ src/actions/smartAccount/signTypedData.ts | 98 +++++++ src/actions/smartAccount/writeContract.ts | 51 ++++ src/clients/decorators/smartAccount.ts | 276 +++++++++++++++++++- test/bundlerActions.test.ts | 40 +-- test/index.test.ts | 27 +- test/pimlicoActions.test.ts | 22 +- test/simpleAccount.test.ts | 21 +- test/stackupActions.test.ts | 15 +- test/userOp.ts | 14 +- test/utils.ts | 4 +- 15 files changed, 575 insertions(+), 134 deletions(-) delete mode 100644 src/actions/smartAccount/getChainId.ts diff --git a/src/actions/smartAccount.ts b/src/actions/smartAccount.ts index ecbab9c0..94e03b48 100644 --- a/src/actions/smartAccount.ts +++ b/src/actions/smartAccount.ts @@ -1,7 +1,5 @@ import { deployContract } from "./smartAccount/deployContract.js" -import { getChainId } from "./smartAccount/getChainId.js" - import { type PrepareUserOperationRequestParameters, type PrepareUserOperationRequestReturnType, @@ -26,7 +24,6 @@ import { signTypedData } from "./smartAccount/signTypedData.js" export { deployContract, - getChainId, prepareUserOperationRequest, type PrepareUserOperationRequestParameters, type PrepareUserOperationRequestReturnType, diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index bde91fbe..8f77c9d6 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -13,6 +13,34 @@ import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashW import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" import { sendUserOperation } from "./sendUserOperation.js" +/** + * Deploys a contract to the network, given bytecode and constructor arguments. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/deployContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/deploying-contracts + * + * @param client - Client to use + * @param parameters - {@link DeployContractParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link DeployContractReturnType} + * + * @example + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { deployContract } from 'viem/contract' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await deployContract(client, { + * abi: [], + * account: '0x…, + * bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', + * }) + */ export async function deployContract< const TAbi extends Abi | readonly unknown[], TChain extends Chain | undefined, diff --git a/src/actions/smartAccount/getChainId.ts b/src/actions/smartAccount/getChainId.ts deleted file mode 100644 index e6964866..00000000 --- a/src/actions/smartAccount/getChainId.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - type Chain, - type Client, - type GetChainIdReturnType, - type Transport -} from "viem" -import { type SmartAccount } from "../../accounts/types.js" -import { getAction } from "../../utils/getAction.js" -import { chainId } from "../bundler/chainId.js" - -export async function getChainId< - TChain extends Chain | undefined, - TAccount extends SmartAccount | undefined ->(client: Client): Promise { - return getAction(client, chainId)([]) -} diff --git a/src/actions/smartAccount/sendTransaction.ts b/src/actions/smartAccount/sendTransaction.ts index 94fb72c2..abfc22d1 100644 --- a/src/actions/smartAccount/sendTransaction.ts +++ b/src/actions/smartAccount/sendTransaction.ts @@ -22,6 +22,52 @@ export type SendTransactionWithPaymasterParameters< > = SendTransactionParameters & SponsorUserOperationMiddleware +/** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param client - Client to use + * @param parameters - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await sendTransaction(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { sendTransaction } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await sendTransaction(client, { + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ export async function sendTransaction< TChain extends Chain | undefined, TAccount extends SmartAccount | undefined, @@ -44,8 +90,6 @@ export async function sendTransaction< sponsorUserOperation } = args - console.log(sponsorUserOperation, "sponsorUserOperation") - if (!account_) { throw new AccountOrClientNotFoundError({ docsPath: "/docs/actions/wallet/sendTransaction" diff --git a/src/actions/smartAccount/signMessage.ts b/src/actions/smartAccount/signMessage.ts index cfb4ce73..4146d7ab 100644 --- a/src/actions/smartAccount/signMessage.ts +++ b/src/actions/smartAccount/signMessage.ts @@ -11,6 +11,52 @@ import { parseAccount } from "../../utils/index.js" +/** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param client - Client to use + * @param parameters - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, custom } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signMessage } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signMessage(client, { + * message: 'hello world', + * }) + */ export async function signMessage< TChain extends Chain | undefined, TAccount extends SmartAccount | undefined diff --git a/src/actions/smartAccount/signTypedData.ts b/src/actions/smartAccount/signTypedData.ts index 51ebcf80..0ddfe27c 100644 --- a/src/actions/smartAccount/signTypedData.ts +++ b/src/actions/smartAccount/signTypedData.ts @@ -15,6 +15,104 @@ import { parseAccount } from "../../utils/index.js" +/** + * Signs typed data and calculates an Ethereum-specific signature in [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712): `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))` + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param parameters - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await signTypedData(client, { + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * import { signTypedData } from 'viem/wallet' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await signTypedData(client, { + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + */ export async function signTypedData< const TTypedData extends TypedData | { [key: string]: unknown }, TPrimaryType extends string, diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index 4347c726..d4d0e239 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -16,6 +16,57 @@ import { sendTransaction } from "./sendTransaction.js" +/** + * Executes a write function on a contract. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/writeContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/writing-to-contracts + * + * A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, and hence a [Transaction](https://viem.sh/docs/glossary/terms.html) is needed to be broadcast in order to change the state. + * + * Internally, uses a [Wallet Client](https://viem.sh/docs/clients/wallet.html) to call the [`sendTransaction` action](https://viem.sh/docs/actions/wallet/sendTransaction.html) with [ABI-encoded `data`](https://viem.sh/docs/contract/encodeFunctionData.html). + * + * __Warning: The `write` internally sends a transaction – it does not validate if the contract write will succeed (the contract may throw an error). It is highly recommended to [simulate the contract write with `contract.simulate`](https://viem.sh/docs/contract/writeContract.html#usage) before you execute it.__ + * + * @param client - Client to use + * @param parameters - {@link WriteContractParameters} + * @returns A [Transaction Hash](https://viem.sh/docs/glossary/terms.html#hash). {@link WriteContractReturnType} + * + * @example + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * import { writeContract } from 'viem/contract' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await writeContract(client, { + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * }) + * + * @example + * // With Validation + * import { createWalletClient, http, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * import { simulateContract, writeContract } from 'viem/contract' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: http(), + * }) + * const { request } = await simulateContract(client, { + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * } + * const hash = await writeContract(client, request) + */ export type WriteContractWithPaymasterParameters< TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartAccount | undefined = SmartAccount | undefined, diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index 24679424..ed9decfc 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -9,8 +9,11 @@ import type { } from "viem" import type { SmartAccount } from "../../accounts/types.js" import { deployContract } from "../../actions/smartAccount/deployContract.js" -import { getChainId } from "../../actions/smartAccount/getChainId.js" -import { type SponsorUserOperationMiddleware } from "../../actions/smartAccount/prepareUserOperationRequest.js" +import { + type PrepareUserOperationRequestReturnType, + type SponsorUserOperationMiddleware, + prepareUserOperationRequest +} from "../../actions/smartAccount/prepareUserOperationRequest.js" import { type SendTransactionWithPaymasterParameters, sendTransaction @@ -26,15 +29,196 @@ export type SmartAccountActions< TChain extends Chain | undefined = Chain | undefined, TSmartAccount extends SmartAccount | undefined = SmartAccount | undefined > = { - getChainId: () => ReturnType + /** + * Creates, signs, and sends a new transaction to the network. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/actions/wallet/sendTransaction.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/transactions/sending-transactions + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_sendTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) + * - Local Accounts: [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) + * + * @param args - {@link SendTransactionParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link SendTransactionReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.sendTransaction({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await client.sendTransaction({ + * to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', + * value: 1000000000000000000n, + * }) + */ sendTransaction: ( args: SendTransactionParameters ) => ReturnType< typeof sendTransaction > + /** + * Calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signMessage.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`personal_sign`](https://docs.metamask.io/guide/signing-data.html#personal-sign) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * With the calculated signature, you can: + * - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage.html) to verify the signature, + * - use [`recoverMessageAddress`](https://viem.sh/docs/utilities/recoverMessageAddress.html) to recover the signing address from a signature. + * + * @param args - {@link SignMessageParameters} + * @returns The signed message. {@link SignMessageReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signMessage({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * message: 'hello world', + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.signMessage({ + * message: 'hello world', + * }) + */ signMessage: ( args: Parameters>[1] ) => ReturnType> + /** + * Signs typed data and calculates an Ethereum-specific signature in [EIP-191 format](https://eips.ethereum.org/EIPS/eip-191): `keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))`. + * + * - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html + * - JSON-RPC Methods: + * - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4) + * - Local Accounts: Signs locally. No JSON-RPC request. + * + * @param client - Client to use + * @param args - {@link SignTypedDataParameters} + * @returns The signed data. {@link SignTypedDataReturnType} + * + * @example + * import { createWalletClient, custom } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const signature = await client.signTypedData({ + * account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + * + * @example + * // Account Hoisting + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const signature = await client.signTypedData({ + * domain: { + * name: 'Ether Mail', + * version: '1', + * chainId: 1, + * verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + * }, + * types: { + * Person: [ + * { name: 'name', type: 'string' }, + * { name: 'wallet', type: 'address' }, + * ], + * Mail: [ + * { name: 'from', type: 'Person' }, + * { name: 'to', type: 'Person' }, + * { name: 'contents', type: 'string' }, + * ], + * }, + * primaryType: 'Mail', + * message: { + * from: { + * name: 'Cow', + * wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + * }, + * to: { + * name: 'Bob', + * wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + * }, + * contents: 'Hello, Bob!', + * }, + * }) + */ signTypedData: < const TTypedData extends TypedData | { [key: string]: unknown }, TPrimaryType extends string @@ -50,6 +234,32 @@ export type SmartAccountActions< ) => ReturnType< typeof signTypedData > + /** + * Deploys a contract to the network, given bytecode and constructor arguments. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/deployContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/deploying-contracts + * + * @param args - {@link DeployContractParameters} + * @returns The [Transaction](https://viem.sh/docs/glossary/terms.html#transaction) hash. {@link DeployContractReturnType} + * + * @example + * import { createWalletClient, http } from 'viem' + * import { privateKeyToAccount } from 'viem/accounts' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * account: privateKeyToAccount('0x…'), + * chain: mainnet, + * transport: http(), + * }) + * const hash = await client.deployContract({ + * abi: [], + * account: '0x…, + * bytecode: '0x608060405260405161083e38038061083e833981016040819052610...', + * }) + */ deployContract: < const TAbi extends Abi | readonly unknown[], TChainOverride extends Chain | undefined = undefined @@ -60,6 +270,54 @@ export type SmartAccountActions< ) => ReturnType< typeof deployContract > + /** + * Executes a write function on a contract. + * This function also allows you to sponsor this transaction if sender is a smartAccount + * + * - Docs: https://viem.sh/docs/contract/writeContract.html + * - Examples: https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/contracts/writing-to-contracts + * + * A "write" function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, and hence a [Transaction](https://viem.sh/docs/glossary/terms.html) is needed to be broadcast in order to change the state. + * + * Internally, uses a [Wallet Client](https://viem.sh/docs/clients/wallet.html) to call the [`sendTransaction` action](https://viem.sh/docs/actions/wallet/sendTransaction.html) with [ABI-encoded `data`](https://viem.sh/docs/contract/encodeFunctionData.html). + * + * __Warning: The `write` internally sends a transaction – it does not validate if the contract write will succeed (the contract may throw an error). It is highly recommended to [simulate the contract write with `contract.simulate`](https://viem.sh/docs/contract/writeContract.html#usage) before you execute it.__ + * + * @param args - {@link WriteContractParameters} + * @returns A [Transaction Hash](https://viem.sh/docs/glossary/terms.html#hash). {@link WriteContractReturnType} + * + * @example + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const hash = await client.writeContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * }) + * + * @example + * // With Validation + * import { createWalletClient, custom, parseAbi } from 'viem' + * import { mainnet } from 'viem/chains' + * + * const client = createWalletClient({ + * chain: mainnet, + * transport: custom(window.ethereum), + * }) + * const { request } = await client.simulateContract({ + * address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + * abi: parseAbi(['function mint(uint32 tokenId) nonpayable']), + * functionName: 'mint', + * args: [69420], + * } + * const hash = await client.writeContract(request) + */ writeContract: < const TAbi extends Abi | readonly unknown[], TFunctionName extends string, @@ -81,6 +339,15 @@ export type SmartAccountActions< TChainOverride > > + prepareUserOperationRequest: ( + args: Parameters< + typeof prepareUserOperationRequest< + TTransport, + TChain, + TSmartAccount + > + >[1] + ) => Promise } export const smartAccountActions = @@ -94,8 +361,9 @@ export const smartAccountActions = >( client: Client ): SmartAccountActions => ({ + prepareUserOperationRequest: (args) => + prepareUserOperationRequest(client, args), deployContract: (args) => deployContract(client, args), - getChainId: () => getChainId(client), sendTransaction: (args) => sendTransaction(client, { ...args, diff --git a/test/bundlerActions.test.ts b/test/bundlerActions.test.ts index e3886fd1..586bd952 100644 --- a/test/bundlerActions.test.ts +++ b/test/bundlerActions.test.ts @@ -58,18 +58,8 @@ describe("BUNDLER ACTIONS", () => { test("Estimate user operation gas", async () => { const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + + const userOperation = await buildUserOp(eoaWalletClient) const gasParameters = await bundlerClient.estimateUserOperationGas({ userOperation, @@ -83,18 +73,7 @@ describe("BUNDLER ACTIONS", () => { test("Sending user operation", async () => { const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const entryPoint = getEntryPoint() const chain = getTestingChain() @@ -158,18 +137,7 @@ describe("BUNDLER ACTIONS", () => { test("wait for user operation receipt fail", async () => { const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const entryPoint = getEntryPoint() const chain = getTestingChain() diff --git a/test/index.test.ts b/test/index.test.ts index 60e9a174..0099a9bb 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -79,22 +79,10 @@ describe("test public actions and utils", () => { test("getUserOperationHash", async () => { const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() const chain = getTestingChain() const entryPoint = getEntryPoint() const bundlerClient = getBundlerClient() - - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const gasParameters = await bundlerClient.estimateUserOperationGas({ userOperation, @@ -118,18 +106,7 @@ describe("test public actions and utils", () => { test("signUserOperationHashWithECDSA", async () => { const bundlerClient = getBundlerClient() const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation: UserOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const entryPoint = getEntryPoint() const chain = getTestingChain() diff --git a/test/pimlicoActions.test.ts b/test/pimlicoActions.test.ts index a56c8a40..0f5bafe9 100644 --- a/test/pimlicoActions.test.ts +++ b/test/pimlicoActions.test.ts @@ -6,6 +6,7 @@ import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico" +import { UserOperation } from "permissionless/index.js" import { getUserOperationHash } from "permissionless/utils" import { http } from "viem" import { buildUserOp } from "./userOp.js" @@ -98,8 +99,10 @@ describe("Pimlico Actions tests", () => { const { maxFeePerGas, maxPriorityFeePerGas } = await publicClient.estimateFeesPerGas() - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), + const partialUserOp = await buildUserOp(eoaWalletClient) + + const userOperation: UserOperation = { + ...partialUserOp, maxFeePerGas: maxFeePerGas || 0n, maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, callGasLimit: 0n, @@ -146,25 +149,14 @@ describe("Pimlico Actions tests", () => { expect( sponsorUserOperationPaymasterAndData.paymasterAndData ).toStartWith("0x") - }) + }, 100000) test("Sending user op with paymaster and data", async () => { const entryPoint = getEntryPoint() const eoaWalletClient = getEoaWalletClient() const chain = getTestingChain() - const publicClient = await getPublicClient() - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const sponsorUserOperationPaymasterAndData = await pimlicoPaymasterClient.sponsorUserOperation({ diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index 6456e3ce..c3bc854f 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -1,7 +1,6 @@ import { beforeAll, describe, expect, test } from "bun:test" import dotenv from "dotenv" import { SignTransactionNotSupportedBySmartAccount } from "permissionless/accounts" -import { SponsorUserOperationReturnType } from "permissionless/actions/smartAccount.js" import { UserOperation } from "permissionless/index.js" import { Address, @@ -73,19 +72,6 @@ describe("Simple Account", () => { }).toThrow(new SignTransactionNotSupportedBySmartAccount()) }) - test("Smart account client chain id", async () => { - const smartAccountClient = await getSmartAccountClient() - - const chain = getTestingChain() - - const chainId = await smartAccountClient.getChainId() - - expect(chainId).toBeNumber() - expect(chainId).toBeGreaterThan(0) - - expect(chainId).toEqual(chain.id) - }) - test("Smart account client signMessage", async () => { const smartAccountClient = await getSmartAccountClient() @@ -180,7 +166,12 @@ describe("Simple Account", () => { const smartAccountClient = await getSmartAccountClient({ sponsorUserOperation: async ( userOperation: UserOperation - ): Promise => { + ): Promise<{ + paymasterAndData: Hex + preVerificationGas: bigint + verificationGasLimit: bigint + callGasLimit: bigint + }> => { const pimlicoPaymaster = getPimlicoPaymasterClient() return pimlicoPaymaster.sponsorUserOperation({ userOperation, diff --git a/test/stackupActions.test.ts b/test/stackupActions.test.ts index 81daf605..2d60e05d 100644 --- a/test/stackupActions.test.ts +++ b/test/stackupActions.test.ts @@ -21,20 +21,7 @@ export const testStackupBundlerActions = async ( }) const eoaWalletClient = getEoaWalletClient() - const publicClient = await getPublicClient() - - const { maxFeePerGas, maxPriorityFeePerGas } = - await publicClient.estimateFeesPerGas() - - const userOperation: UserOperation = { - ...(await buildUserOp(eoaWalletClient)), - maxFeePerGas: maxFeePerGas || 0n, - maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, - paymasterAndData: "0x", - callGasLimit: 0n, - verificationGasLimit: 0n, - preVerificationGas: 0n - } + const userOperation = await buildUserOp(eoaWalletClient) const sponsorUserOperationPaymasterAndData = await stackupBundlerClient.sponsorUserOperation({ diff --git a/test/userOp.ts b/test/userOp.ts index 6dd7f088..475727f6 100644 --- a/test/userOp.ts +++ b/test/userOp.ts @@ -73,7 +73,9 @@ const encodeExecute = async ( }) } -export const buildUserOp = async (eoaWalletClient: WalletClient) => { +export const buildUserOp = async ( + eoaWalletClient: WalletClient +): Promise => { await new Promise((resolve) => { setTimeout(() => { // wait for prev user op to be added to make sure ew get correct nonce @@ -97,6 +99,9 @@ export const buildUserOp = async (eoaWalletClient: WalletClient) => { entryPoint: entryPoint }) + const { maxFeePerGas, maxPriorityFeePerGas } = + await publicClient.estimateFeesPerGas() + const userOperation: PartialBy< UserOperation, | "maxFeePerGas" @@ -110,7 +115,12 @@ export const buildUserOp = async (eoaWalletClient: WalletClient) => { initCode: await getInitCode(factoryAddress, eoaWalletClient), callData: await encodeExecute(zeroAddress as Hex, 0n, "0x" as Hex), paymasterAndData: "0x" as Hex, - signature: getDummySignature() + signature: getDummySignature(), + maxFeePerGas: maxFeePerGas || 0n, + maxPriorityFeePerGas: maxPriorityFeePerGas || 0n, + callGasLimit: 0n, + verificationGasLimit: 0n, + preVerificationGas: 0n } return userOperation diff --git a/test/utils.ts b/test/utils.ts index f721335b..792803ed 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,6 +1,6 @@ import { createBundlerClient, createSmartAccountClient } from "permissionless" import { privateKeyToSimpleSmartAccount } from "permissionless/accounts" -import { SponsorTransactionParameters } from "permissionless/actions/smartAccount" +import { SponsorUserOperationMiddleware } from "permissionless/actions/smartAccount" import { createPimlicoBundlerClient, createPimlicoPaymasterClient @@ -47,7 +47,7 @@ export const getPrivateKeyToSimpleSmartAccount = async () => { export const getSmartAccountClient = async ({ sponsorUserOperation -}: SponsorTransactionParameters = {}) => { +}: SponsorUserOperationMiddleware = {}) => { if (!process.env.PIMLICO_API_KEY) throw new Error("PIMLICO_API_KEY environment variable not set") if (!process.env.PIMLICO_BUNDLER_RPC_HOST) From c6e0d2f1e309e25761847bb25ab1bace08cec4ff Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 15:16:21 +0530 Subject: [PATCH 20/26] Allow sponsoring of deployContract too --- src/actions/smartAccount/deployContract.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index 8f77c9d6..ce13555a 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -12,6 +12,7 @@ import { parseAccount } from "../../utils/index.js" import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" import { sendUserOperation } from "./sendUserOperation.js" +import { SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" /** * Deploys a contract to the network, given bytecode and constructor arguments. @@ -52,8 +53,10 @@ export async function deployContract< abi, args, bytecode, + sponsorUserOperation, ...request - }: DeployContractParameters + }: DeployContractParameters & + SponsorUserOperationMiddleware ): Promise { const { account: account_ = client.account } = request @@ -85,7 +88,8 @@ export async function deployContract< TChainOverride >) }, - account: account + account: account, + sponsorUserOperation }) const userOperationReceipt = await getAction( From df1b94265013c29b9c50c5aaa3bb40b2af3e50eb Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 09:46:45 +0000 Subject: [PATCH 21/26] chore: format --- src/actions/smartAccount/deployContract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index ce13555a..0f72dde5 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -11,8 +11,8 @@ import { getAction } from "../../utils/getAction.js" import { parseAccount } from "../../utils/index.js" import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" -import { sendUserOperation } from "./sendUserOperation.js" import { SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" +import { sendUserOperation } from "./sendUserOperation.js" /** * Deploys a contract to the network, given bytecode and constructor arguments. From 3edfd9afe3c5c39b72ca6f65f7285112eb2ea198 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 22:40:21 +0530 Subject: [PATCH 22/26] Fix deployContract --- src/actions/smartAccount.ts | 6 +++++- src/actions/smartAccount/deployContract.ts | 13 ++++++++++--- src/clients/decorators/smartAccount.ts | 21 ++++++++++++++++----- src/package.json | 2 +- test/package.json | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/actions/smartAccount.ts b/src/actions/smartAccount.ts index 94e03b48..e7c54020 100644 --- a/src/actions/smartAccount.ts +++ b/src/actions/smartAccount.ts @@ -1,4 +1,7 @@ -import { deployContract } from "./smartAccount/deployContract.js" +import { + type DeployContractParametersWithPaymaster, + deployContract +} from "./smartAccount/deployContract.js" import { type PrepareUserOperationRequestParameters, @@ -24,6 +27,7 @@ import { signTypedData } from "./smartAccount/signTypedData.js" export { deployContract, + type DeployContractParametersWithPaymaster, prepareUserOperationRequest, type PrepareUserOperationRequestParameters, type PrepareUserOperationRequestReturnType, diff --git a/src/actions/smartAccount/deployContract.ts b/src/actions/smartAccount/deployContract.ts index ce13555a..48bab17f 100644 --- a/src/actions/smartAccount/deployContract.ts +++ b/src/actions/smartAccount/deployContract.ts @@ -11,8 +11,16 @@ import { getAction } from "../../utils/getAction.js" import { parseAccount } from "../../utils/index.js" import { AccountOrClientNotFoundError } from "../../utils/signUserOperationHashWithECDSA.js" import { waitForUserOperationReceipt } from "../bundler/waitForUserOperationReceipt.js" +import { type SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" import { sendUserOperation } from "./sendUserOperation.js" -import { SponsorUserOperationMiddleware } from "./prepareUserOperationRequest.js" + +export type DeployContractParametersWithPaymaster< + TAbi extends Abi | readonly unknown[] = Abi | readonly unknown[], + TChain extends Chain | undefined = Chain | undefined, + TAccount extends SmartAccount | undefined = SmartAccount | undefined, + TChainOverride extends Chain | undefined = Chain | undefined +> = DeployContractParameters & + SponsorUserOperationMiddleware /** * Deploys a contract to the network, given bytecode and constructor arguments. @@ -55,8 +63,7 @@ export async function deployContract< bytecode, sponsorUserOperation, ...request - }: DeployContractParameters & - SponsorUserOperationMiddleware + }: DeployContractParametersWithPaymaster ): Promise { const { account: account_ = client.account } = request diff --git a/src/clients/decorators/smartAccount.ts b/src/clients/decorators/smartAccount.ts index ed9decfc..0399a098 100644 --- a/src/clients/decorators/smartAccount.ts +++ b/src/clients/decorators/smartAccount.ts @@ -2,13 +2,17 @@ import type { Abi, Chain, Client, + DeployContractParameters, SendTransactionParameters, Transport, TypedData, WriteContractParameters } from "viem" import type { SmartAccount } from "../../accounts/types.js" -import { deployContract } from "../../actions/smartAccount/deployContract.js" +import { + type DeployContractParametersWithPaymaster, + deployContract +} from "../../actions/smartAccount/deployContract.js" import { type PrepareUserOperationRequestReturnType, type SponsorUserOperationMiddleware, @@ -264,9 +268,12 @@ export type SmartAccountActions< const TAbi extends Abi | readonly unknown[], TChainOverride extends Chain | undefined = undefined >( - args: Parameters< - typeof deployContract - >[1] + args: DeployContractParameters< + TAbi, + TChain, + TSmartAccount, + TChainOverride + > ) => ReturnType< typeof deployContract > @@ -363,7 +370,11 @@ export const smartAccountActions = ): SmartAccountActions => ({ prepareUserOperationRequest: (args) => prepareUserOperationRequest(client, args), - deployContract: (args) => deployContract(client, args), + deployContract: (args) => + deployContract(client, { + ...args, + sponsorUserOperation + } as DeployContractParametersWithPaymaster), sendTransaction: (args) => sendTransaction(client, { ...args, diff --git a/src/package.json b/src/package.json index 5abbb12d..77b2b62e 100644 --- a/src/package.json +++ b/src/package.json @@ -65,6 +65,6 @@ } }, "peerDependencies": { - "viem": "0.0.0-jxom-fix-extract-chain-params.20231106T094653" + "viem": "^1.14.0" } } diff --git a/test/package.json b/test/package.json index 20f19196..df7cd408 100644 --- a/test/package.json +++ b/test/package.json @@ -7,6 +7,6 @@ }, "dependencies": { "dotenv": "^16.3.1", - "viem": "0.0.0-jxom-fix-extract-chain-params.20231106T094653" + "viem": "^1.14.0" } } From 68acaa505ac304db53413a70aedd59b992d45aa8 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Mon, 13 Nov 2023 17:12:25 +0000 Subject: [PATCH 23/26] chore: format --- bun.lockb | Bin 143506 -> 143365 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 5b6a819cd0e59b6ba05f5e3ccd55a4c632c1dd09..8cbf439df51bf9cb8a2ac2dafa7269e1aa8e5508 100755 GIT binary patch delta 3746 zcmbRAkfZeh#{@m4j?N3;GaePBN|%TSb?gnxSUvOSMA7dNe0wd+H@!Jry1Z0>V?Y77 z=8`N11|9~6hK1P-4BSAv8_4Gc(lemqbx?lB=9kDYGY(zU=W_XP*WSEh-pLCXRc2?Kv}m z_F00(6gA8_*8q9uVBSLobJoW|p7rEdYim}y9H1&ND;&tOo_trL*%&K)9e?7R#ooVvAbwbP1wEAvjxoDy5R z;gkKd!_!W0QC!~YT`)}{9yA>@G#nd0+nL3`6XDVoA3i2i)xrG z*p@3$iAfT?V7`g~qXwEnl6(hB0-(?d5n>dW9v}n^ukYdlV38$4IGn&C46zdCgek%p z3Q2MT#Q{d;2*u_em|u@b;r1&`O^-CA%;Xo@8raOCh1etsk?AV(L?;75aO`eT0D6S{ z+#n1#WQr2*&>+>PlO#C7W{RjXicFuN#;8NO0Rr3CXED|?k*aZ2J#`Dj(eNA%&(Sn6 zng&MGz-Ss60%<@D)M|Li##+F{z!0^4ZXx4kewGwqS7>`lBcq?KGzVjxp`L*ru!ED5 zUy_zMl;matDKIqAGhoP=&bXgZlBq6hy3Bq?38wC>=^qXN z=^0tm13>&GS<|!jGnVm)FxuIg0*y3bxSKWo4M^s7)^xT5j4q6^)4dNcDuOiT0X3Sz zx^>sBv~=pu*v(*KtOMEvw1@%Lv0J-0{%x)6{H0(SBajSG?;fCD860|nJvoT8G`Ixd u?w+o6kntL0?)IMt8RN31%WE@93zcQ2=IZO}gD72~otAnA+XZ!*8kqs_=w=B3 delta 7176 zcmdT}ZA@EL7(VAxkgmlx!$?Olg$4{0T5kEONPx-4wuHINuVnZE7O2oKTG|d73p>SV z{9()nH|&@>WiwffVP+v1`@;`lQ~!w3WK+xx(W#5WOtT-N(Leg0doOPMaDjVEuim8h zz2~{-?eo0vdCxiRz0ZG}J$5hL!(g{vJr-t` zM<`OEL+I*(g|jEJvyhqL(;wx%vL9pNl+B$<{Rp+w`%O64?1xJS5E!?2JHIdbZjUgV zXW<$G!lg)~{#hWagnL-<6u9x*LR){6ei^Vj5i2Tn>+3+KTWnn}bZ17ORWCeQU!OS% zS9Rjm4%q%m~0jUrVW?6m+C$-u6W{kmS;Ij$jb^7|8{ut|_3?pis zQ{YQd7Kt)3M12aC5zD+p#n`DLmU&643Q3`u+vEzS=Zv_SUN_=9v_F}9g5gd!p{~FT zgZY`Dya`)qwF$)C>SU&$vwjy@zm=1j7MgK6@y0q2_=4T&p*(55WZ#}fjA%tH^TI4k zbHu9yCK{xltm(SplFA0#N*<5tB9{4Tuv`z)%E?JdF{{#4<0D$3s$+elctkXff_9Ss>b`zXjrF z(?P2&aJ0yV%ShB43kL_hX#c}EmZLOcL@Q#MSNOT~D5@~j^ZbIFr-y!-!fHs|1Dm}y?h%6trp z>ha{O{rG}O_ZJ+=_z2$HU@gV%E+_AFBh(#^d;10uIyCjf7&-LCFLt{40p4P;V>i6* zse=7(nc^9|g);ag#z-Y#e4K?tU)yN;9*VvuG0L_}k zJMd4mbru&f$3AeZU3p!#aNj-Uhvyq#=I~*c6U4xFCcm(H>-~ Date: Tue, 14 Nov 2023 08:44:08 +0530 Subject: [PATCH 24/26] Add entrypoint support --- .../smartAccount/prepareUserOperationRequest.ts | 12 +++++++++--- test/simpleAccount.test.ts | 7 ++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/actions/smartAccount/prepareUserOperationRequest.ts b/src/actions/smartAccount/prepareUserOperationRequest.ts index ece3a637..3f49a0e1 100644 --- a/src/actions/smartAccount/prepareUserOperationRequest.ts +++ b/src/actions/smartAccount/prepareUserOperationRequest.ts @@ -1,4 +1,4 @@ -import type { Chain, Client, Hex, Transport } from "viem" +import type { Address, Chain, Client, Hex, Transport } from "viem" import { estimateFeesPerGas } from "viem/actions" import type { SmartAccount } from "../../accounts/types.js" import type { @@ -14,7 +14,10 @@ import { import { estimateUserOperationGas } from "../bundler/estimateUserOperationGas.js" export type SponsorUserOperationMiddleware = { - sponsorUserOperation?: (userOperation: UserOperation) => Promise<{ + sponsorUserOperation?: (args: { + userOperation: UserOperation + entryPoint: Address + }) => Promise<{ paymasterAndData: Hex preVerificationGas: bigint verificationGasLimit: bigint @@ -99,7 +102,10 @@ export async function prepareUserOperationRequest< verificationGasLimit, preVerificationGas, paymasterAndData - } = await sponsorUserOperation(userOperation) + } = await sponsorUserOperation({ + userOperation, + entryPoint: account.entryPoint + }) userOperation.paymasterAndData = paymasterAndData userOperation.callGasLimit = userOperation.callGasLimit || callGasLimit userOperation.verificationGasLimit = diff --git a/test/simpleAccount.test.ts b/test/simpleAccount.test.ts index c3bc854f..d6561502 100644 --- a/test/simpleAccount.test.ts +++ b/test/simpleAccount.test.ts @@ -164,9 +164,10 @@ describe("Simple Account", () => { const bundlerClient = getBundlerClient() const smartAccountClient = await getSmartAccountClient({ - sponsorUserOperation: async ( - userOperation: UserOperation - ): Promise<{ + sponsorUserOperation: async ({ + entryPoint: _entryPoint, + userOperation + }): Promise<{ paymasterAndData: Hex preVerificationGas: bigint verificationGasLimit: bigint From 1a3ee807b1ce447f53dbfad72183479b4decb367 Mon Sep 17 00:00:00 2001 From: plusminushalf Date: Thu, 16 Nov 2023 08:49:07 +0530 Subject: [PATCH 25/26] Fix type conversion --- src/actions/smartAccount/writeContract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/smartAccount/writeContract.ts b/src/actions/smartAccount/writeContract.ts index d4d0e239..f8e1cd90 100644 --- a/src/actions/smartAccount/writeContract.ts +++ b/src/actions/smartAccount/writeContract.ts @@ -117,7 +117,7 @@ export async function writeContract< data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, to: address, ...request - } as SendTransactionWithPaymasterParameters< + } as unknown as SendTransactionWithPaymasterParameters< TChain, TAccount, TChainOverride From aabe479f59812ce48930b5be077327887887e7f1 Mon Sep 17 00:00:00 2001 From: Kristof Gazso Date: Thu, 16 Nov 2023 02:18:10 -0800 Subject: [PATCH 26/26] Create fluffy-dolls-mate.md --- .changeset/fluffy-dolls-mate.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-dolls-mate.md diff --git a/.changeset/fluffy-dolls-mate.md b/.changeset/fluffy-dolls-mate.md new file mode 100644 index 00000000..4fcc9ff5 --- /dev/null +++ b/.changeset/fluffy-dolls-mate.md @@ -0,0 +1,5 @@ +--- +"permissionless": patch +--- + +Added support for SimpleAccount management