UNPKG

viem

Version:

TypeScript Interface for Ethereum

798 lines (774 loc) 16.6 kB
import type { Abi, Address, TypedData } from 'abitype' import { parseAccount } from '../../../accounts/utils/parseAccount.js' import { readContract } from '../../../actions/public/readContract.js' import { signMessage as signMessage_ } from '../../../actions/wallet/signMessage.js' import type { Client } from '../../../clients/createClient.js' import { entryPoint07Address } from '../../../constants/address.js' import { BaseError } from '../../../errors/base.js' import { signMessage } from '../../../experimental/erc7739/actions/signMessage.js' import { signTypedData } from '../../../experimental/erc7739/actions/signTypedData.js' import type { Account } from '../../../types/account.js' import type { Hex } from '../../../types/misc.js' import type { TypedDataDefinition } from '../../../types/typedData.js' import type { Prettify } from '../../../types/utils.js' import { decodeFunctionData } from '../../../utils/abi/decodeFunctionData.js' import { encodeFunctionData } from '../../../utils/abi/encodeFunctionData.js' import { pad } from '../../../utils/data/pad.js' import { getAction } from '../../../utils/getAction.js' import { entryPoint07Abi } from '../../constants/abis.js' import type { EntryPointVersion } from '../../types/entryPointVersion.js' import { getUserOperationHash } from '../../utils/userOperation/getUserOperationHash.js' import { toSmartAccount } from '../toSmartAccount.js' import type { SmartAccount, SmartAccountImplementation } from '../types.js' export type ToSoladySmartAccountParameters< entryPointAbi extends Abi = Abi, entryPointVersion extends EntryPointVersion = EntryPointVersion, > = { address?: Address | undefined client: Client entryPoint?: | { abi: entryPointAbi address: Address version: entryPointVersion | EntryPointVersion } | undefined factoryAddress?: Address | undefined getNonce?: SmartAccountImplementation['getNonce'] | undefined owner: Address | Account salt?: Hex | undefined } export type ToSoladySmartAccountReturnType< entryPointAbi extends Abi = Abi, entryPointVersion extends EntryPointVersion = EntryPointVersion, > = Prettify< SmartAccount< SoladySmartAccountImplementation<entryPointAbi, entryPointVersion> > > export type SoladySmartAccountImplementation< entryPointAbi extends Abi = Abi, entryPointVersion extends EntryPointVersion = EntryPointVersion, > = SmartAccountImplementation< entryPointAbi, entryPointVersion, { abi: typeof abi; factory: { abi: typeof factoryAbi; address: Address } } > /** * @description Create a Solady Smart Account – based off [Solady's `ERC4337.sol`](https://github.com/Vectorized/solady/blob/main/src/accounts/ERC4337.sol). * * @param parameters - {@link ToSoladySmartAccountParameters} * @returns Solady Smart Account. {@link ToSoladySmartAccountReturnType} * * @example * import { toSoladySmartAccount } from 'viem/account-abstraction' * import { client } from './client.js' * * const implementation = toSoladySmartAccount({ * client, * owner: '0x...', * }) */ export async function toSoladySmartAccount< entryPointAbi extends Abi = typeof entryPoint07Abi, entryPointVersion extends EntryPointVersion = '0.7', >( parameters: ToSoladySmartAccountParameters<entryPointAbi, entryPointVersion>, ): Promise<ToSoladySmartAccountReturnType<entryPointAbi, entryPointVersion>> { const { address, client, entryPoint: entryPoint_ = { abi: entryPoint07Abi, address: entryPoint07Address, version: '0.7', }, factoryAddress = '0x5d82735936c6Cd5DE57cC3c1A799f6B2E6F933Df', getNonce, salt = '0x0', } = parameters const entryPoint = { abi: entryPoint_.abi as entryPointAbi, address: entryPoint_.address, version: entryPoint_.version as entryPointVersion, } as const const factory = { abi: factoryAbi, address: factoryAddress, } as const const owner = parseAccount(parameters.owner) return toSmartAccount({ client, entryPoint, getNonce, extend: { abi, factory }, async decodeCalls(data) { const result = decodeFunctionData({ abi, data, }) if (result.functionName === 'execute') return [ { to: result.args[0], value: result.args[1], data: result.args[2] }, ] if (result.functionName === 'executeBatch') return result.args[0].map((arg) => ({ to: arg.target, value: arg.value, data: arg.data, })) throw new BaseError(`unable to decode calls for "${result.functionName}"`) }, async encodeCalls(calls) { if (calls.length === 1) return encodeFunctionData({ abi, functionName: 'execute', args: [calls[0].to, calls[0].value ?? 0n, calls[0].data ?? '0x'], }) return encodeFunctionData({ abi, functionName: 'executeBatch', args: [ calls.map((call) => ({ data: call.data ?? '0x', target: call.to, value: call.value ?? 0n, })), ], }) }, async getAddress() { if (address) return address return await readContract(client, { ...factory, functionName: 'getAddress', args: [pad(salt)], }) }, async getFactoryArgs() { const factoryData = encodeFunctionData({ abi: factory.abi, functionName: 'createAccount', args: [owner.address, pad(salt)], }) return { factory: factory.address, factoryData } }, async getStubSignature() { return '0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c' }, async signMessage(parameters) { const { message } = parameters const [address, { factory, factoryData }] = await Promise.all([ this.getAddress(), this.getFactoryArgs(), ]) return await signMessage(client, { account: owner, factory, factoryData, message, verifier: address, }) }, async signTypedData(parameters) { const { domain, types, primaryType, message } = parameters as TypedDataDefinition<TypedData, string> const [address, { factory, factoryData }] = await Promise.all([ this.getAddress(), this.getFactoryArgs(), ]) return await signTypedData(client, { account: owner, domain, message, factory, factoryData, primaryType, types, verifier: address, }) }, async signUserOperation(parameters) { const { chainId = client.chain!.id, ...userOperation } = parameters const address = await this.getAddress() const userOpHash = getUserOperationHash({ chainId, entryPointAddress: entryPoint.address, entryPointVersion: entryPoint.version, userOperation: { ...(userOperation as any), sender: address, }, }) const signature = await getAction( client, signMessage_, 'signMessage', )({ account: owner, message: { raw: userOpHash, }, }) return signature }, }) } ///////////////////////////////////////////////////////////////////////////////////////////// // Constants const abi = [ { type: 'fallback', stateMutability: 'payable', }, { type: 'receive', stateMutability: 'payable', }, { type: 'function', name: 'addDeposit', inputs: [], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'cancelOwnershipHandover', inputs: [], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'completeOwnershipHandover', inputs: [ { name: 'pendingOwner', type: 'address', }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'delegateExecute', inputs: [ { name: 'delegate', type: 'address', }, { name: 'data', type: 'bytes', }, ], outputs: [ { name: 'result', type: 'bytes', }, ], stateMutability: 'payable', }, { type: 'function', name: 'eip712Domain', inputs: [], outputs: [ { name: 'name', type: 'string', }, { name: 'version', type: 'string', }, { name: 'chainId', type: 'uint256', }, { name: 'verifyingContract', type: 'address', }, { name: 'salt', type: 'bytes32', }, ], stateMutability: 'view', }, { type: 'function', name: 'entryPoint', inputs: [], outputs: [ { name: '', type: 'address', }, ], stateMutability: 'view', }, { type: 'function', name: 'execute', inputs: [ { name: 'target', type: 'address', }, { name: 'value', type: 'uint256', }, { name: 'data', type: 'bytes', }, ], outputs: [ { name: 'result', type: 'bytes', }, ], stateMutability: 'payable', }, { type: 'function', name: 'executeBatch', inputs: [ { name: 'calls', type: 'tuple[]', components: [ { name: 'target', type: 'address', }, { name: 'value', type: 'uint256', }, { name: 'data', type: 'bytes', }, ], }, ], outputs: [ { name: 'results', type: 'bytes[]', }, ], stateMutability: 'payable', }, { type: 'function', name: 'getDeposit', inputs: [], outputs: [ { name: 'result', type: 'uint256', }, ], stateMutability: 'view', }, { type: 'function', name: 'initialize', inputs: [ { name: 'newOwner', type: 'address', }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'isValidSignature', inputs: [ { name: 'hash', type: 'bytes32', }, { name: 'signature', type: 'bytes', }, ], outputs: [ { name: 'result', type: 'bytes4', }, ], stateMutability: 'view', }, { type: 'function', name: 'owner', inputs: [], outputs: [ { name: 'result', type: 'address', }, ], stateMutability: 'view', }, { type: 'function', name: 'ownershipHandoverExpiresAt', inputs: [ { name: 'pendingOwner', type: 'address', }, ], outputs: [ { name: 'result', type: 'uint256', }, ], stateMutability: 'view', }, { type: 'function', name: 'proxiableUUID', inputs: [], outputs: [ { name: '', type: 'bytes32', }, ], stateMutability: 'view', }, { type: 'function', name: 'renounceOwnership', inputs: [], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'requestOwnershipHandover', inputs: [], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'storageLoad', inputs: [ { name: 'storageSlot', type: 'bytes32', }, ], outputs: [ { name: 'result', type: 'bytes32', }, ], stateMutability: 'view', }, { type: 'function', name: 'storageStore', inputs: [ { name: 'storageSlot', type: 'bytes32', }, { name: 'storageValue', type: 'bytes32', }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'transferOwnership', inputs: [ { name: 'newOwner', type: 'address', }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'upgradeToAndCall', inputs: [ { name: 'newImplementation', type: 'address', }, { name: 'data', type: 'bytes', }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'validateUserOp', inputs: [ { name: 'userOp', type: 'tuple', components: [ { name: 'sender', type: 'address', }, { name: 'nonce', type: 'uint256', }, { name: 'initCode', type: 'bytes', }, { name: 'callData', type: 'bytes', }, { name: 'accountGasLimits', type: 'bytes32', }, { name: 'preVerificationGas', type: 'uint256', }, { name: 'gasFees', type: 'bytes32', }, { name: 'paymasterAndData', type: 'bytes', }, { name: 'signature', type: 'bytes', }, ], }, { name: 'userOpHash', type: 'bytes32', }, { name: 'missingAccountFunds', type: 'uint256', }, ], outputs: [ { name: 'validationData', type: 'uint256', }, ], stateMutability: 'payable', }, { type: 'function', name: 'withdrawDepositTo', inputs: [ { name: 'to', type: 'address', }, { name: 'amount', type: 'uint256', }, ], outputs: [], stateMutability: 'payable', }, { type: 'event', name: 'OwnershipHandoverCanceled', inputs: [ { name: 'pendingOwner', type: 'address', indexed: true, }, ], anonymous: false, }, { type: 'event', name: 'OwnershipHandoverRequested', inputs: [ { name: 'pendingOwner', type: 'address', indexed: true, }, ], anonymous: false, }, { type: 'event', name: 'OwnershipTransferred', inputs: [ { name: 'oldOwner', type: 'address', indexed: true, }, { name: 'newOwner', type: 'address', indexed: true, }, ], anonymous: false, }, { type: 'event', name: 'Upgraded', inputs: [ { name: 'implementation', type: 'address', indexed: true, }, ], anonymous: false, }, { type: 'error', name: 'AlreadyInitialized', inputs: [], }, { type: 'error', name: 'FnSelectorNotRecognized', inputs: [], }, { type: 'error', name: 'NewOwnerIsZeroAddress', inputs: [], }, { type: 'error', name: 'NoHandoverRequest', inputs: [], }, { type: 'error', name: 'Unauthorized', inputs: [], }, { type: 'error', name: 'UnauthorizedCallContext', inputs: [], }, { type: 'error', name: 'UpgradeFailed', inputs: [], }, ] as const const factoryAbi = [ { type: 'constructor', inputs: [ { name: 'erc4337', type: 'address', }, ], stateMutability: 'nonpayable', }, { type: 'function', name: 'createAccount', inputs: [ { name: 'owner', type: 'address', }, { name: 'salt', type: 'bytes32', }, ], outputs: [ { name: '', type: 'address', }, ], stateMutability: 'payable', }, { type: 'function', name: 'getAddress', inputs: [ { name: 'salt', type: 'bytes32', }, ], outputs: [ { name: '', type: 'address', }, ], stateMutability: 'view', }, { type: 'function', name: 'implementation', inputs: [], outputs: [ { name: '', type: 'address', }, ], stateMutability: 'view', }, { type: 'function', name: 'initCodeHash', inputs: [], outputs: [ { name: '', type: 'bytes32', }, ], stateMutability: 'view', }, ] as const