@zerodev/sdk
Version:
A utility library for working with ERC-4337
277 lines (248 loc) • 8.48 kB
text/typescript
// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/public/getSenderAddress.ts
import {
type Address,
BaseError,
type Client,
type ContractFunctionExecutionErrorType,
ContractFunctionRevertedError,
type Hex,
InvalidInputRpcError,
type OneOf,
type Prettify,
RawContractError,
RpcRequestError,
UnknownRpcError,
concat,
decodeErrorResult
} from "viem"
import { simulateContract } from "viem/actions"
import { getAction } from "viem/utils"
export type GetSenderAddressParams = OneOf<
| {
initCode: Hex
entryPointAddress: Address
factory?: never
factoryData?: never
}
| {
entryPointAddress: Address
factory: Address
factoryData: Hex
initCode?: never
}
>
export class InvalidEntryPointError extends BaseError {
override name = "InvalidEntryPointError"
constructor({
cause,
entryPointAddress
}: { cause?: BaseError; entryPointAddress?: Address } = {}) {
super(
`The entry point address (\`entryPoint\`${
entryPointAddress ? ` = ${entryPointAddress}` : ""
}) is not a valid entry point. getSenderAddress did not revert with a SenderAddressResult error.`,
{
cause
}
)
}
}
/**
* Returns the address of the account that will be deployed with the given init code.
*
* - Docs: https://docs.pimlico.io/permissionless/reference/public-actions/getSenderAddress
*
* @param client {@link Client} that you created using viem's createPublicClient.
* @param args {@link GetSenderAddressParams} initCode & entryPoint
* @returns Sender's Address
*
* @example
* import { createPublicClient } from "viem"
* import { getSenderAddress } from "permissionless/actions"
*
* const publicClient = createPublicClient({
* chain: goerli,
* transport: http("https://goerli.infura.io/v3/your-infura-key")
* })
*
* const senderAddress = await getSenderAddress(publicClient, {
* initCode,
* entryPoint
* })
*
* // Return '0x7a88a206ba40b37a8c07a2b5688cf8b287318b63'
*/
export const getSenderAddress = async (
client: Client,
args: Prettify<GetSenderAddressParams>
): Promise<Address> => {
const { initCode, entryPointAddress, factory, factoryData } = args
if (!initCode && !factory && !factoryData) {
throw new Error(
"Either `initCode` or `factory` and `factoryData` must be provided"
)
}
try {
await getAction(
client,
simulateContract,
"simulateContract"
)({
address: entryPointAddress,
abi: [
{
inputs: [
{
internalType: "address",
name: "sender",
type: "address"
}
],
name: "SenderAddressResult",
type: "error"
},
{
inputs: [
{
internalType: "bytes",
name: "initCode",
type: "bytes"
}
],
name: "getSenderAddress",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}
],
functionName: "getSenderAddress",
args: [initCode || concat([factory as Hex, factoryData as Hex])]
})
} catch (e) {
const revertError = (e as ContractFunctionExecutionErrorType).walk(
(err) =>
err instanceof ContractFunctionRevertedError ||
err instanceof RpcRequestError ||
err instanceof InvalidInputRpcError ||
err instanceof UnknownRpcError
)
if (!revertError) {
// biome-ignore lint/suspicious/noExplicitAny:
const cause = (e as ContractFunctionExecutionErrorType).cause as any
const errorName = cause?.data?.errorName ?? ""
if (
errorName === "SenderAddressResult" &&
cause?.data?.args &&
cause?.data?.args[0]
) {
return cause.data?.args[0] as Address
}
}
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName ?? ""
if (
errorName === "SenderAddressResult" &&
revertError.data?.args &&
revertError.data?.args[0]
) {
return revertError.data?.args[0] as Address
}
}
if (revertError instanceof RpcRequestError) {
const hexStringRegex = /0x[a-fA-F0-9]+/
// biome-ignore lint/suspicious/noExplicitAny:
const match = (revertError as unknown as any).cause.data.match(
hexStringRegex
)
if (!match) {
throw new Error(
"Failed to parse revert bytes from RPC response"
)
}
const data: Hex = match[0]
const error = decodeErrorResult({
abi: [
{
inputs: [
{
internalType: "address",
name: "sender",
type: "address"
}
],
name: "SenderAddressResult",
type: "error"
}
],
data
})
return error.args[0] as Address
}
if (revertError instanceof InvalidInputRpcError) {
const { data: data_ } = (
e instanceof RawContractError
? e
: e instanceof BaseError
? e.walk((err) => "data" in (err as Error)) || e.walk()
: {}
) as RawContractError
const data = typeof data_ === "string" ? data_ : data_?.data
if (data === undefined) {
throw new Error(
"Failed to parse revert bytes from RPC response"
)
}
const error = decodeErrorResult({
abi: [
{
inputs: [
{
internalType: "address",
name: "sender",
type: "address"
}
],
name: "SenderAddressResult",
type: "error"
}
],
data
})
return error.args[0] as Address
}
if (revertError instanceof UnknownRpcError) {
const parsedBody = JSON.parse(
// biome-ignore lint/suspicious/noExplicitAny:
(revertError as unknown as any).cause.body
)
const revertData = parsedBody.error.data
const hexStringRegex = /0x[a-fA-F0-9]+/
const match = revertData.match(hexStringRegex)
if (!match) {
throw new Error(
"Failed to parse revert bytes from RPC response"
)
}
const data: Hex = match[0]
const error = decodeErrorResult({
abi: [
{
inputs: [
{
internalType: "address",
name: "sender",
type: "address"
}
],
name: "SenderAddressResult",
type: "error"
}
],
data
})
return error.args[0] as Address
}
throw e
}
throw new InvalidEntryPointError({ entryPointAddress })
}