import { getTypesForEIP712Domain, hexToBigInt, hexToNumber, isHex, numberToHex, parseSignature, serializeSignature, toHex, } from "viem"; import { yParityToV } from "ox/Signature"; import { errorMessage, object, union, withMetadata, type SchemaOptions, z, } from "./zod.js"; import { SemiModularAccount7702Version } from "./sma7702.js"; const HEX_REGEX = /^0x.*$/; const ADDRESS_REGEX = /^0x.*$/; const MAX_UINT32 = 0xffffffff; const MAX_SAFE_CHAIN_ID = Number.MAX_SAFE_INTEGER; const literalWithMetadata = ( value: T, options?: SchemaOptions, ) => withMetadata(z.literal(value), options); // Status codes for transaction/call status tracking export const StatusCode = union(undefined, [ literalWithMetadata(100, { title: "Pending", description: "Batch has been received by the wallet but has not completed execution onchain", }), literalWithMetadata(110, { title: "Preconfirmed", description: "Batch has been preconfirmed onchain", }), literalWithMetadata(115, { title: "Preconfirmed Onchain Failure", description: "Batch reverted *completely* in a preconfirmation and only changes related to gas charge may have been included onchain", }), literalWithMetadata(116, { title: "Preconfirmed Partial Onchain Failure", description: "Batch reverted *partially* in a preconfirmation and some changes related to batch calls may have been included onchain", }), literalWithMetadata(120, { title: "Cross-Chain In Progress", description: "Origin confirmed, destination pending", }), literalWithMetadata(200, { title: "Confirmed", description: "Batch has been included onchain without reverts, receipts array contains info of all calls", }), literalWithMetadata(400, { title: "Offchain Failure", description: "Batch has not been included onchain and wallet will not retry", }), literalWithMetadata(410, { title: "Cross-Chain Refunded", description: "Cross-chain operation failed and was refunded", }), literalWithMetadata(500, { title: "Onchain Failure", description: "Batch reverted *completely* and only changes related to gas charge may have been included onchain", }), literalWithMetadata(600, { title: "Partial Onchain Failure", description: "Batch reverted *partially* and some changes related to batch calls may have been included onchain", }), ]); export type StatusCode = z.input; export const k1SignerOwnedAccountTypes = [ "sma-b", "la-v2", "la-v2-multi-owner", "la-v1.0.1", "la-v1.0.2", "la-v1.1.0", "ma-v1-multi-owner", ] as const; export type K1SignerOwnedAccountType = (typeof k1SignerOwnedAccountTypes)[number]; export function isK1SignerOwnedAccountType( accountType: string, ): accountType is K1SignerOwnedAccountType { return k1SignerOwnedAccountTypes.includes( accountType as K1SignerOwnedAccountType, ); } export const signerOwnedAccountTypes = [...k1SignerOwnedAccountTypes] as const; export type SignerOwnedAccountType = (typeof signerOwnedAccountTypes)[number]; export const accountTypes = [...signerOwnedAccountTypes, "7702"] as const; export type AccountType = (typeof accountTypes)[number]; export const Hex = (options?: SchemaOptions) => { const message = errorMessage(options) ?? "Must be a valid hex string starting with '0x'"; return withMetadata( z.string({ error: message }).regex(HEX_REGEX, { error: message }), { ...options, errorMessage: message }, // Safe: HEX_REGEX guarantees the string starts with "0x"; Zod can't narrow a regex match to a template literal. ) as z.ZodType<`0x${string}`, `0x${string}`>; }; export type Hex = z.input>; export const Address = (options?: SchemaOptions) => { const message = errorMessage(options) ?? "Must be a valid Ethereum address starting with '0x' (e.g., '0xa363219d7C0b8673df17529D469Db9eFF0f35D2A')"; return withMetadata( z.string({ error: message }).regex(ADDRESS_REGEX, { error: message }), { ...options, errorMessage: message }, // Safe: ADDRESS_REGEX guarantees the string starts with "0x"; Zod can't narrow a regex match to a template literal. ) as z.ZodType<`0x${string}`, `0x${string}`>; }; export type Address = z.input>; export const Uuid = z.uuidv4({ error: "Must be a valid UUID v4 string (e.g., '3061cc5f-1f96-48a9-ab45-41faad2dd23b')", }); export type Uuid = z.input; export const Base64UrlEncodedString = z.base64url(); export type Base64UrlEncodedString = z.input; /** * SDK format: bigint (255n) * Wire format: "0xff" * * Used by SDK clients to encode bigint inputs into JSON-RPC hex strings. */ export const HexBigInt = (options?: SchemaOptions) => z.codec(Hex(options), z.bigint({ error: "Expected bigint" }).nonnegative(), { decode: (value, ctx) => { try { return hexToBigInt(value); } catch (err) { ctx.issues.push({ code: "custom", input: value, message: err instanceof Error ? `Invalid HexBigInt hex: ${err.message}` : `Invalid HexBigInt hex: ${value}`, }); return z.NEVER; } }, encode: (value) => toHex(value), }); /** * Like HexBigInt, but the SDK side accepts both bigint and hex string. * Use when migrating a field from Hex to HexBigInt without breaking existing * SDK consumers who already pass a hex string. */ export const HexBigIntCompat = (options?: SchemaOptions) => z.codec( Hex(options), z.union([ z.bigint({ error: "Expected bigint or hex string" }).nonnegative(), Hex(options).refine((value) => isHex(value), { error: "Expected bigint or hex string", }), ]), { decode: (value, ctx) => { try { return hexToBigInt(value); } catch (err) { ctx.issues.push({ code: "custom", input: value, message: err instanceof Error ? `Invalid HexBigInt hex: ${err.message}` : `Invalid HexBigInt hex: ${value}`, }); return z.NEVER; } }, encode: (value) => (typeof value === "bigint" ? toHex(value) : value), }, ); /** * Builds a codec between a hex wire value and a bounded JS number. * * `label` is used in error messages; `max` is the inclusive upper bound of the * decoded number (also the largest value that can be encoded). */ const hexNumberCodec = ( label: string, max: number, rangeDescription: string, ) => { const rangeError = `${label} encode error: value is out of ${rangeDescription} [0, ${max}]`; return (options?: SchemaOptions) => z.codec( Hex(options), z .number({ error: "Expected number" }) .int() .min(0) .max(max, { error: rangeError }), { decode: (value, ctx) => { let parsed: number; try { parsed = hexToNumber(value); } catch (err) { ctx.issues.push({ code: "custom", input: value, message: err instanceof Error ? `Invalid ${label} hex: ${err.message}` : `Invalid ${label} hex: ${value}`, }); return z.NEVER; } if (!Number.isFinite(parsed) || parsed < 0 || parsed > max) { ctx.issues.push({ code: "custom", input: value, message: `${label} decode error: ${value} is out of ${rangeDescription} [0, ${max}]`, }); return z.NEVER; } return parsed; }, encode: (value) => numberToHex(value), }, ); }; /** * SDK format: number (255) * Wire format: "0xff" * * Used by SDK clients to encode number inputs into JSON-RPC hex strings. */ export const HexNumber = hexNumberCodec( "HexNumber", MAX_UINT32, "32-bit unsigned range", ); /** * SDK format: number (7091047534) * Wire format: "0x1a6a8cc6e" * * Same as {@link HexNumber}, but sized for chain IDs, which routinely exceed * the 32-bit range (EIP-155 allows up to 2^63-1). Bounded by * `Number.MAX_SAFE_INTEGER` because the decoded value stays a JS number. */ export const HexChainId = hexNumberCodec( "HexChainId", MAX_SAFE_CHAIN_ID, "safe chain ID range", ); /** * SDK format: "success" | "reverted" * Wire format: "0x1" | "0x0" */ export const HexStatus = z.codec( z.union([ literalWithMetadata("0x1", { description: "Success" }), literalWithMetadata("0x0", { description: "Reverted" }), ]), z.enum(["success", "reverted"]), { decode: (value) => (value === "0x1" ? "success" : "reverted"), encode: (value) => (value === "success" ? "0x1" : "0x0"), }, ); // Transaction receipt type for status responses export const TransactionReceipt = object({ logs: z.array( object({ address: Address(), data: Hex(), topics: z.array(Hex()), }), ), status: Hex({ description: "0x1 for success, 0x0 for failure", }), blockHash: Hex(), blockNumber: HexNumber(), gasUsed: HexBigInt(), transactionHash: Hex(), }); export type TransactionReceipt = z.input; export const UserOperationRequest_v6 = object({ sender: Address(), nonce: HexBigInt(), initCode: z.union([ Hex({ description: "Present" }), literalWithMetadata("0x", { description: "Absent" }), ]), callData: Hex(), callGasLimit: HexBigInt(), verificationGasLimit: HexBigInt(), preVerificationGas: HexBigInt(), maxFeePerGas: HexBigInt(), maxPriorityFeePerGas: HexBigInt(), paymasterAndData: z.union([ Hex({ description: "Present" }), literalWithMetadata("0x", { description: "Absent" }), ]), signature: Hex(), }); export type UserOperationRequest_v6 = z.input; export const UserOperationRequest_v7 = object({ sender: Address(), nonce: HexBigInt(), factory: Address().optional(), factoryData: Hex().optional(), callData: Hex(), callGasLimit: HexBigInt(), verificationGasLimit: HexBigInt(), preVerificationGas: HexBigInt(), maxFeePerGas: HexBigInt(), maxPriorityFeePerGas: HexBigInt(), paymaster: Address().optional(), paymasterData: Hex().optional(), paymasterVerificationGasLimit: HexBigInt().optional(), paymasterPostOpGasLimit: HexBigInt().optional(), signature: Hex(), }); export type UserOperationRequest_v7 = z.input; export const Eip7702UnsignedAuth = object({ chainId: HexChainId(), address: Address(), nonce: HexNumber(), }); export type Eip7702UnsignedAuth = z.input; export const Eip7702SignedAuth = z.intersection( Eip7702UnsignedAuth, object({ r: Hex(), s: Hex(), yParity: Hex(), }), ); export type Eip7702SignedAuth = z.input; export const WebAuthnPublicKey = (options?: SchemaOptions) => object( { x: HexBigInt(), y: HexBigInt(), }, options, ); export type WebAuthnPublicKey = z.input>; export const PersistedInitcode = object({ factoryAddress: Address(), initcode: union(undefined, [ object( { factoryType: literalWithMetadata("unknown", { description: "Unknown factory type", }), factoryData: Hex(), }, { description: "Unknown factory type", }, ), object( { factoryType: z.union([ z.literal("LightAccountV2.0.0"), z.literal("LightAccountV1.1.0"), z.literal("LightAccountV1.0.2"), z.literal("LightAccountV1.0.1"), ]), salt: HexBigInt(), initialOwner: Address(), }, { description: "Light Account", }, ), object( { factoryType: z.union([ z.literal("MAv1.0.0-MultiOwner"), z.literal("LightAccountV2.0.0-MultiOwner"), ]), salt: HexBigInt(), initialOwners: z.array(Address()), }, { description: "Multi-Owner Modular Account", }, ), object( { factoryType: z.literal("MAv1.0.0-MultiSig"), salt: HexBigInt(), initialOwners: z.array(Address()), threshold: HexBigInt(), }, { description: "Multi-Sig Modular Account", }, ), z.intersection( object({ factoryType: z.literal("MAv2.0.0"), salt: HexBigInt(), }), union(undefined, [ object( { mav2type: z.literal("ma-ssv"), initialOwner: Address(), entityId: HexNumber(), }, { description: "Modular Account V2 SSV", }, ), object( { mav2type: z.literal("sma-b"), initialOwner: Address(), }, { description: "Modular Account V2 SMA-B", }, ), ]), ), ]), }); export type PersistedInitcode = z.input; export const SerializedInitcode = object({ factoryType: union(undefined, [ literalWithMetadata("LightAccountV1.0.1", { description: "Light Account v1.0.1", }), literalWithMetadata("LightAccountV1.0.2", { description: "Light Account v1.0.2", }), literalWithMetadata("LightAccountV1.1.0", { description: "Light Account v1.1.0", }), literalWithMetadata("LightAccountV2.0.0", { description: "Light Account v2.0.0", }), literalWithMetadata("LightAccountV2.0.0-MultiOwner", { description: "Light Account v2.0.0 MultiOwner", }), literalWithMetadata("MAv1.0.0-MultiOwner", { description: "Modular Account v1.0.0 MultiOwner", }), literalWithMetadata("MAv1.0.0-MultiSig", { description: "Modular Account v1.0.0 MultiSig", }), literalWithMetadata("MAv2.0.0-sma-b", { description: "Modular Account v2.0.0 SMA-B", }), literalWithMetadata("MAv2.0.0-ma-ssv", { description: "Modular Account v2.0.0 SSV", }), literalWithMetadata("unknown", { description: "Unknown factory type" }), ]), factoryAddress: Address(), factoryData: Hex(), }); export type SerializedInitcode = z.input; export const CounterfactualInfo = z.intersection( object({ accountAddress: Address(), }), union(undefined, [ z .intersection( object({ type: z.literal("sca"), }), PersistedInitcode, ) .describe("Smart Contract Account"), object( { type: z.literal("7702"), implementationAddress: Address(), }, { description: "7702 Account", }, ), ]), ); export type CounterfactualInfo = z.input; const BaseCreationOptions = object({ createAdditional: literalWithMetadata(true, { description: "If true, will allow creating more than one smart wallet per signer.", errorMessage: "createAdditional must be true or omitted", }).optional(), salt: HexBigIntCompat().optional(), }); const accountTypeError = "accountType must be one of: 'sma-b', 'la-v2', 'la-v1.0.1', 'la-v1.0.2', 'la-v1.1.0', 'la-v2-multi-owner', or 'ma-v1-multi-owner'"; const Secp256k1OwnerAccountCreationOptions = z.intersection( BaseCreationOptions, union( { description: "Smart contract account types", errorMessage: accountTypeError, }, [ object( { accountType: union( { errorMessage: accountTypeError, }, [ literalWithMetadata("sma-b", { description: "Modular Account V2 SMA-B account type", }), literalWithMetadata("la-v2", { description: "Light Account V2 account type", }), literalWithMetadata("la-v1.0.1", { description: "(deprecated - backwards compatibility only - reach out to support@alchemy.com with questions/concerns) Light Account V1.0.1 account type", }), literalWithMetadata("la-v1.0.2", { description: "(deprecated - backwards compatibility only - reach out to support@alchemy.com with questions/concerns) Light Account V1.0.2 account type", }), literalWithMetadata("la-v1.1.0", { description: "(deprecated - backwards compatibility only - reach out to support@alchemy.com with questions/concerns) Light Account V1.1.0 account type", }), ], ).optional(), }, { description: "Single owner account types", }, ), object( { accountType: union( { errorMessage: accountTypeError, }, [ literalWithMetadata("la-v2-multi-owner", { description: "Light Account V2 Multi-Owner account type", }), literalWithMetadata("ma-v1-multi-owner", { description: "(deprecated - backwards compatibility only - reach out to support@alchemy.com with questions/concerns) Modular Account V1 Multi-Owner account type", }), ], ), initialOwners: z .array(Address()) .meta({ description: "The initial owners of the account. The signer address will be implicitly added if not specified in this list.", }) .optional(), }, { description: "Multi-owner account types" }, ), ], ), ); export type Secp256k1OwnerAccountCreationOptions = z.input< typeof Secp256k1OwnerAccountCreationOptions >; const CreationOptions7702 = object( { accountType: z.literal("7702", { error: "accountType must be '7702' for Smart EOA accounts", }), version: SemiModularAccount7702Version.optional().meta({ description: "The SemiModularAccount7702 version to record for this account, defaulting to the latest supported version. Applies only when this call creates the account; an account already recorded for this team keeps the version it has. Recorded without checking the wallet's current on-chain delegation, and wallet_prepareCalls then requests an authorization to it — omit this unless you want to pin a specific version.", }), }, { description: "Smart EOA account type (EIP-7702)", }, ); export const CreationOptionsBySignerAddress = z .union([Secp256k1OwnerAccountCreationOptions, CreationOptions7702]) .optional(); export type CreationOptionsBySignerAddress = z.input< typeof CreationOptionsBySignerAddress >; export type SignerIdentifier = { signerAddress: Address; }; export type AccountCreationParams = { signerAddress: Address; creationHint?: CreationOptionsBySignerAddress; }; export const KeySignerType = z.union([ literalWithMetadata("secp256k1", { description: "Secp256k1" }), literalWithMetadata("ecdsa", { description: "ECDSA (alias for secp256k1)" }), literalWithMetadata("contract", { description: "Contract" }), ]); export type KeySignerType = z.input; export const KeySigner = object({ type: KeySignerType, publicKey: Hex({ description: "Note that this should be the public key's 20-byte address, not the full 64-byte public key", }), }); export type KeySigner = z.input; const TypedDataDomainInput = object({ chainId: z.union([z.int().min(1), z.bigint().positive()]).optional(), name: z.string().optional(), salt: Hex().optional(), verifyingContract: Address().optional(), version: z.string().optional(), }); const TypedDataDomainWire = object({ chainId: z.int().min(1).optional(), name: z.string().optional(), salt: Hex().optional(), verifyingContract: Address().optional(), version: z.string().optional(), }); const TypedDataTypes = z .record( z.string(), z.array( object({ name: z.string(), type: z.string(), }), ), ) .meta({ description: "Mapping from type name to array of field definitions", }); const TypedDataMessage = z .record(z.string(), z.unknown()) .meta({ description: "Mapping from field name to value" }); const TypedDataDefinitionInput = object({ domain: TypedDataDomainInput.optional(), types: TypedDataTypes, primaryType: z.string(), message: TypedDataMessage, }); const TypedDataDefinitionWire = object({ domain: TypedDataDomainWire.optional(), types: TypedDataTypes, primaryType: z.string(), message: TypedDataMessage, }); export const TypedDataDefinition = z.codec( TypedDataDefinitionWire, TypedDataDefinitionInput, { decode: (value) => value, encode: (value) => { const { domain, types, primaryType, message } = value; return { domain: { ...domain, chainId: typeof domain?.chainId === "bigint" ? Number(domain.chainId) : domain?.chainId, }, types: Object.assign({}, types, { EIP712Domain: getTypesForEIP712Domain({ domain }), }), primaryType, message: JSON.parse( JSON.stringify(message, (_, v) => typeof v === "bigint" ? toHex(v) : v, ), ), }; }, }, ); export type TypedDataDefinition = z.input; export const SignableMessage = z.union([ withMetadata(z.string().min(1), { description: "Message" }), object( { raw: Hex(), }, { description: "Raw message" }, ), ]); export const PersonalSignSignatureRequest = object( { type: z.literal("personal_sign"), data: SignableMessage, rawPayload: Hex({ description: "Raw payload that can be signed if using a signer that supports signing raw bytes (such as an Alchemy Signer)", }), }, { description: "Personal sign" }, ); export type PersonalSignSignatureRequest = z.input< typeof PersonalSignSignatureRequest >; export const TypedDataSignatureRequest = object( { type: z.literal("eth_signTypedData_v4"), data: TypedDataDefinition, rawPayload: Hex({ description: "Raw payload that can be signed if using a signer that supports signing raw bytes (such as an Alchemy Signer)", }), }, { description: "Typed data", }, ); export type TypedDataSignatureRequest = z.input< typeof TypedDataSignatureRequest >; export const AuthorizationSignatureRequest = object({ type: z.literal("eip7702Auth"), rawPayload: Hex({ description: "Raw payload that can be signed if using a signer that supports signing raw bytes (such as an Alchemy Signer)", }), }); export type AuthorizationSignatureRequest = z.input< typeof AuthorizationSignatureRequest >; export const SignatureRequest = z.discriminatedUnion("type", [ PersonalSignSignatureRequest, TypedDataSignatureRequest, AuthorizationSignatureRequest, ]); export type SignatureRequest = z.input; export const FeePayment = object({ sponsored: z.boolean(), tokenAddress: Address(), maxAmount: HexBigInt(), }); export type FeePayment = z.input; export const PreparedCall_UserOpV060 = object( { type: literalWithMetadata("user-operation-v060", { description: "User Operation (Entrypoint v0.6.0)", }), data: withMetadata(UserOperationRequest_v6.omit({ signature: true }), { description: "Unsigned User Operation (Entrypoint v0.6.0)", }), chainId: HexChainId(), signatureRequest: z .discriminatedUnion("type", [ PersonalSignSignatureRequest, TypedDataSignatureRequest, ]) .optional(), feePayment: FeePayment, }, { description: "Prepared User Operation (Entrypoint v0.6.0). Returned for legacy Light Account v1.x accounts.", }, ); export type PreparedCall_UserOpV060 = z.input; export const PreparedCall_UserOpV070 = object( { type: literalWithMetadata("user-operation-v070", { description: "User Operation (Entrypoint v0.7.0)", }), data: withMetadata(UserOperationRequest_v7.omit({ signature: true }), { description: "Unsigned User Operation (Entrypoint v0.7.0)", }), chainId: HexChainId(), signatureRequest: z .discriminatedUnion("type", [ PersonalSignSignatureRequest, TypedDataSignatureRequest, ]) .optional(), feePayment: FeePayment, }, { description: "Prepared User Operation (Entrypoint v0.7.0). Returned for modern account types including Modular Account (sma-b), Light Account v2, and 7702 accounts.", }, ); export type PreparedCall_UserOpV070 = z.input; export const PreparedCall_Authorization = object( { type: literalWithMetadata("authorization", { description: "EIP-7702 Authorization Request", }), data: object({ address: Address(), nonce: HexNumber(), }), chainId: HexChainId(), signatureRequest: AuthorizationSignatureRequest, }, { description: "Prepared EIP-7702 Authorization. Returned as part of an array response for 7702 accounts requiring delegation, or at the top level for BSO-sponsored undelegation.", }, ); export type PreparedCall_Authorization = z.input< typeof PreparedCall_Authorization >; const SigWithYParitySchema = object( { r: Hex(), s: Hex(), yParity: Hex(), }, { description: "r, s, yParity" }, ); const SigWithVSchema = object( { r: Hex(), s: Hex(), v: Hex(), }, { description: "r, s, v", }, ); export const EcdsaSigData = z.union([ Hex({ description: "Hex-encoded signature", }), SigWithYParitySchema, SigWithVSchema, ]); export type EcdsaSigData = z.input; export function decodeEcdsaSigData(data: EcdsaSigData): Hex { if (typeof data === "string") { return data; } if ("yParity" in data) { return serializeSignature({ ...data, yParity: hexToNumber(data.yParity), }); } return serializeSignature({ ...data, v: hexToBigInt(data.v) }); } export function encodeEcdsaSigWithYParity( hex: Hex, ): z.output { const parsed = parseSignature(hex); return { r: parsed.r, s: parsed.s, yParity: numberToHex(parsed.yParity), }; } export function encodeEcdsaSigWithV(hex: Hex): z.output { const parsed = parseSignature(hex); return { r: parsed.r, s: parsed.s, v: numberToHex(yParityToV(parsed.yParity)), }; } export const Secp256k1SigData = object( { type: z.literal("secp256k1"), data: EcdsaSigData, }, { description: "Secp256k1 signature", }, ); export type Secp256k1SigData = z.input; export const EcdsaTypeSigData = object( { type: z.literal("ecdsa"), data: EcdsaSigData, }, { description: "ECDSA signature (alias for secp256k1)", }, ); export type EcdsaTypeSigData = z.input; export const EcdsaSigInner = z.discriminatedUnion("type", [ Secp256k1SigData, EcdsaTypeSigData, ]); export type EcdsaSigInner = z.input; export const EcdsaSig = object({ signature: EcdsaSigInner, }); export type EcdsaSig = z.input; export const UserOpSig = object({ signature: z.discriminatedUnion("type", [Secp256k1SigData, EcdsaTypeSigData]), }); export type UserOpSig = z.input; export const PreparedCall_UserOpV060_Signed = withMetadata( z.intersection( PreparedCall_UserOpV060.omit({ signatureRequest: true, feePayment: true, }), UserOpSig, ), { description: "Prepared User Operation (Entrypoint v0.6.0) with signature", }, ); export type PreparedCall_UserOpV060_Signed = z.input< typeof PreparedCall_UserOpV060_Signed >; export const PreparedCall_UserOpV070_Signed = withMetadata( z.intersection( PreparedCall_UserOpV070.omit({ signatureRequest: true, feePayment: true, }), UserOpSig, ), { description: "Prepared User Operation (Entrypoint v0.7.0) with signature", }, ); export type PreparedCall_UserOpV070_Signed = z.input< typeof PreparedCall_UserOpV070_Signed >; export const PreparedCall_Authorization_Signed = withMetadata( z.intersection( PreparedCall_Authorization.omit({ signatureRequest: true }), EcdsaSig, ), { description: "Prepared EIP-7702 Authorization with signature", }, ); export type PreparedCall_Authorization_Signed = z.input< typeof PreparedCall_Authorization_Signed >; export const CallArray = object( { type: z.literal("array"), data: z .array( z.union([ PreparedCall_UserOpV060_Signed, PreparedCall_UserOpV070_Signed, PreparedCall_Authorization_Signed, ]), ) .min(1, "data must contain at least one prepared call"), }, { description: "Array of prepared calls with signatures", }, ); export type CallArray = z.input;