--- eip: 8265 title: Prepared Transaction Envelope description: Off-chain envelope for prepared-but-not-yet-signed transactions, batches, and signature requests. author: Mike Diamond (@mike-diamond) discussions-to: https://ethereum-magicians.org/t/erc-8265-prepared-transaction-envelope/28557 status: Draft type: Standards Track category: ERC created: 2026-05-11 requires: 155, 712, 4337, 4361, 5792, 6492 --- ## Abstract A Prepared Transaction Envelope is an off-chain shape that carries pre-execution intents from producers (parties that prepare a transaction or signature) to consumers (wallets, signers, and policy engines). The envelope encodes a single EVM transaction, an [ERC-5792](./eip-5792.md) batch, or an [EIP-712](./eip-712.md) signature request, together with semantic metadata, producer provenance, origin verification, and an optional risk slot. It composes with existing typed-data, batch-call, and counterfactual-signature standards without redefining them. By standardizing the handoff one level above wallet RPC, the envelope lets wallets present consistent previews, enforce allowlists, and apply policy decisions across diverse upstream producers, including dApps, CLIs, hardware-wallet companions, multisig coordinators, automated payment terminals, and AI agents. ## Motivation By May 2026, production stacks for transaction-issuing software ship at scale. Alchemy's CLI grants developers scoped, time-bound sessions to bridge, swap, and deposit through partner-custodied wallets. Coinbase AgentKit integrates AI assistants with action-provider stacks including Alchemy. Crossmint's GOAT SDK ships with LangChain. Stripe's ACP and MPP, Google's AP2, and Visa, Mastercard, and Amex each released agent-commerce extensions in recent months. None of these stacks defines a wallet-facing envelope for the producer-to-signer handoff. Each builds its own proprietary session, payment, and execution shape. The same window saw production losses traceable to gaps in that handoff. Multi-signature operators approved transactions whose UI representation diverged from their on-chain effect. Dormant signed transactions executed months after the user expected them. Permit and Permit2 signatures were extracted via blind-signing flows where wallets received only opaque typed-data with no semantic context. Authorization tuples introduced under [EIP-7702](./eip-7702.md) enabled drainers against users whose wallets could not display the resulting code installation. Address poisoning placed lookalike recipients into wallet history without provenance. Each incident shares one structural cause: the upstream context that would let a wallet refuse never reached the wallet. A Prepared Transaction Envelope is the off-chain composition surface for this handoff. It composes with EIP-712 typed-data signatures, ERC-5792 batch calls, and [ERC-6492](./eip-6492.md) counterfactual signatures, all Final. It integrates non-normatively with [ERC-8004](./eip-8004.md) identity registries, [ERC-7730](./eip-7730.md) clear-signing metadata, [ERC-7715](./eip-7715.md) permission contexts, and emerging commerce-lifecycle drafts (all currently at Draft status, hence non-normative). It does not duplicate, replace, or normalize any of them. The envelope is the connective layer above wallet RPC where these standards meet, and where wallets can apply uniform preview, policy, and audit logic regardless of which producer prepared the call. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### 1. Envelope A Prepared Transaction Envelope is a JSON object per RFC 8259 that wraps a `kind`-tagged content payload with handoff metadata. Producers (parties that prepare an envelope) serialize envelopes as JSON. Consumers (wallets, signers, and policy engines) parse envelopes and decide whether to surface, sign, or submit them. The envelope is generic over a `kind` discriminator `K` and a kind-specific content type `C`: ```typescript interface BaseEnvelope { $schema: string kind: K id?: string issuedAt: string expiresAt?: string nonce?: `0x${string}` producer?: Producer origin?: Origin content: C risk?: RiskAssessment capabilities?: Capabilities meta?: Record } ``` #### 1.1 Required and recommended fields | Field | Status | Notes | |---|---|---| | `$schema` | REQUIRED | URL identifying this specification | | `kind` | REQUIRED | See §2 | | `issuedAt` | REQUIRED | RFC 3339 UTC date-time | | `content` | REQUIRED | Conforms to the schema implied by `kind` | | `producer` | RECOMMENDED | Absent producer triggers an "unsigned producer" warning | | `origin` | RECOMMENDED | Absent origin triggers an "origin unverified" warning. MUST be absent when `producer.signature.coverage === 'content'` (see §3.2) | | `id` | OPTIONAL | Idempotency, at most 4096 characters | | `expiresAt` | OPTIONAL | For tx kinds, MUST equal `content.validity.notAfter` | | `nonce` | OPTIONAL | Envelope-level replay protection | | `risk` | OPTIONAL | Consumer-injected; MUST NOT be treated as producer-attested | | `capabilities` | OPTIONAL | Open-ended extension | | `meta` | OPTIONAL | Consumer display hints; MUST NOT affect on-chain effect | #### 1.2 Field semantics `$schema` is a URL identifying the schema version of this specification. The URL is the version contract; envelopes MUST NOT carry a separate `version` field. `kind` is one of the implemented values defined in §2. `id`, when present, is a string of at most 4096 characters (mirroring the ERC-5792 `wallet_sendCalls` identifier limit). Consumers MAY assign an `id` if the producer omits it. Where a consumer reports call status through `wallet_getCallsStatus`, the `id` is REQUIRED. `issuedAt` is an RFC 3339 date-time in UTC, identifying when the producer prepared the envelope. `expiresAt`, when present, is an RFC 3339 date-time in UTC. For envelopes whose `kind` is `evm-tx` or `evm-batch`, `expiresAt` MUST equal `content.validity.notAfter` reformatted as RFC 3339. `nonce`, when present, is a `0x`-prefixed hex string. It provides envelope-level replay protection independent of on-chain nonces. `producer`, when present, conforms to §3. `origin`, when present, conforms to §4. `content` conforms to the schema implied by `kind`. The shapes for `evm-tx` and `evm-batch` content are defined in §5; the shape for `signature` content is defined in §6. `risk`, when present, MUST NOT be treated as producer-attested. Consumers inject risk verdicts after reception; the structure is defined in §7. `capabilities`, when present, is an open-ended record. Capability entries MUST NOT influence security-critical UI decisions unless the consumer explicitly recognizes the entry. Vendor-specific entries MUST use an `x-` prefix. The structure is defined in §8. `meta`, when present, is a record of consumer-only display hints. `meta` MUST NOT affect on-chain effect, signing decisions, or policy outcomes. Consumers SHOULD ignore unknown top-level fields to preserve forward compatibility with later versions of this specification. ### 2. Kind Discriminator `kind` is a closed string discriminator that selects the content schema. Three values are normative in this specification: | `kind` | Content schema | Use | |---|---|---| | `evm-tx` | `EvmTxContent` (§5) with `calls.length === 1` | Single EVM transaction | | `evm-batch` | `EvmTxContent` (§5) with `calls.length >= 2` | ERC-5792 batch | | `signature` | `SignatureContent` (§6) | EIP-712 typed data, personal-sign, or SIWE | Nine additional values are reserved (see Appendix A in §10): `evm-userop`, `evm-frame`, `evm-7702`, `mandate`, `intent`, `psbt`, `svm-tx`, `move-tx`, `cosmos-tx`. Validators MUST: - Accept envelopes whose `kind` is one of `evm-tx`, `evm-batch`, or `signature`, subject to the content validation in §5 and §6 - Reject envelopes whose `kind` matches a reserved value (see §10) with an error indicating "reserved, not yet specified" - Reject envelopes whose `kind` is neither implemented nor reserved with an error indicating "unknown kind" Consumers MUST NOT silently accept reserved-kind envelopes by mapping them to alternative content schemas. Silent remapping defeats the discriminator's role as a security-critical type guard. ### 3. Producer The producer is the party that prepared the envelope. Producer identity SHOULD be conveyed in every envelope. ```typescript interface Producer { id: string name?: string signature?: ProducerSignature } interface ProducerSignature { scheme: string publicKey?: string signature: string coverage: 'envelope' | 'content' } ``` #### 3.1 Producer identity `producer.id` is a stable identifier of one of the following forms: - A W3C Decentralized Identifier (DID), for example `did:web:agent.example.com` - A CAIP-10 account identifier, for example `eip155:1:0x1234...` - An ERC-8004 Identity Registry record reference - An absolute URL identifying the producer service `producer.name`, when present, is a human-readable display label. It is presentational and MUST NOT influence policy decisions. #### 3.2 Producer signature When `producer.signature` is present, the producer attests to the bytes of the envelope. `scheme` identifies the signature algorithm. Implementations SHOULD support `secp256k1`, `ed25519`, and `p256`. Other schemes MAY be used; the field is an open string to permit future algorithms (including post-quantum schemes) without revising this specification. Consumers that do not implement a given scheme MUST treat the signature as unverified and SHOULD raise a warning rather than reject the envelope outright. `publicKey` SHOULD be present unless `producer.id` is a DID that resolves to a verification key through a method recognized by the consumer. `signature` MUST verify against a canonical serialization of either the entire envelope (when `coverage === 'envelope'`) or just the `content` field (when `coverage === 'content'`). The canonical serialization is deterministic; implementations SHOULD use the RFC 8785 JSON Canonicalization Scheme (JCS). Other deterministic canonicalizations MAY be used by agreement between producer and consumer. `coverage` is exactly `'envelope'` or `'content'`. Consumers MUST evaluate signature verification against the bytes implied by `coverage`. When `coverage === 'content'`, the envelope MUST NOT include `origin`, and consumers MUST NOT treat any envelope-level field other than `content` as producer-attested. A content-only signature attests to `content` bytes alone, so an attacker who mutates `origin`, `expiresAt`, `nonce`, or `capabilities` leaves the signature valid; without this restriction a consumer could read a tampered `origin` or extended `expiresAt` as producer-attested. To bind `origin`/`expiresAt` to the producer, the producer MUST use `coverage === 'envelope'`. #### 3.2.1 Canonicalization scope The bytes signed depend on `coverage`. When `coverage === 'envelope'`, the canonical serialization is the JCS encoding of the envelope with both the `producer.signature` and `risk` fields removed; the signature attests to every other envelope field. When `coverage === 'content'`, the canonical serialization is the JCS encoding of `content` alone; envelope-level fields outside `content` are not attested. Verifiers MUST exclude `risk` from the pre-image irrespective of whether the producer populated the field. This rule preserves a single canonicalization across producer-attested and consumer-injected risk and avoids a three-state ambiguity (producer-populated, consumer-injected, or post-signature tampered) that cannot be resolved from envelope bytes alone. Producer attestation of risk views, where desired, lives within `content` (for example via a content-level field or a vendor capability) rather than the envelope-level `risk` field. Implementations that adopt a canonicalization scheme other than JCS MUST document the field inclusion and exclusion rules so verifiers can deterministically reproduce the exact pre-image. #### 3.3 Verification semantics A consumer that receives an envelope with a `producer.signature` MUST: - Verify the signature before treating any off-chain field (`description`, `metadata`, `origin`, `meta`, `decoderRef`, `clearSigning`) as producer-attested - On signature failure, surface a "tamper detected" warning and SHOULD block signing flows that depend on off-chain fields - On unknown `scheme`, surface an "unverified producer" warning and SHOULD continue evaluating raw `calls[]` or signature content independently of off-chain fields A consumer that receives an envelope without `producer.signature` MUST treat off-chain fields as advisory only and SHOULD raise an "unsigned producer" warning. For an unsigned producer, the only verifiable signals are the raw on-chain `calls[]` and the consumer's own risk assessment; a consumer cannot distinguish a lazily-unsigned envelope from a maliciously-unsigned one, and no wallet RPC enforces producer signatures today. `description` and `metadata` MUST NOT be trusted for policy under the unsigned-producer model. Consumers that require producer auditability SHOULD reject unsigned producers or require explicit per-use approval. ### 4. Origin The origin field binds an envelope to the URL of the entity that produced it. ```typescript interface Origin { url: string verifyStatus: 'VERIFIED' | 'UNVERIFIED' | 'MISMATCH' attestation?: string } ``` `url` is an absolute URL identifying the producer's origin. For web producers, this is typically the dApp URL. For non-web producers (CLI, hardware, MCP servers), `url` MAY use a non-HTTP scheme as long as it is absolute. `verifyStatus` is exactly one of `VERIFIED`, `UNVERIFIED`, or `MISMATCH`: - `VERIFIED` indicates that the consumer or an upstream relay has confirmed `url` against an attestation source (such as the WalletConnect Verify API or an equivalent) - `UNVERIFIED` indicates that no attestation source confirmed `url`; consumers SHOULD render an unverified-origin warning - `MISMATCH` indicates that an attestation source returned a different value than `url`; consumers SHOULD render a hard warning and SHOULD block flows that depend on origin trust `attestation`, when present, is an opaque token (such as an attestation hash) that a consumer MAY independently re-verify. Consumers MUST NOT promote `UNVERIFIED` to `VERIFIED` based on producer claims alone. A consumer that performs its own attestation MAY overwrite the field in its internal state; the envelope as received remains authoritative for audit. ### 5. EVM Transaction Content The `evm-tx` and `evm-batch` kinds share content shape: ```typescript interface EvmTxContent { chain: `eip155:${string}` chainId?: number from?: `0x${string}` calls: EvmCall[] validity: Validity description: Description metadata: Metadata decoderRef?: string clearSigning?: Record } ``` #### 5.1 Kind and call-count discrimination - For `kind === 'evm-tx'`, `calls.length` MUST equal `1` - For `kind === 'evm-batch'`, `calls.length` MUST be `2` or greater - `calls.length === 0` MUST be rejected for both kinds #### 5.2 Chain identifier `chain` is a CAIP-2 identifier in the form `eip155:`, where `` is the [EIP-155](./eip-155.md) chain identifier as a decimal string. `chainId`, when present, is a legacy numeric alias for `chain`. When both `chain` and `chainId` are present, `chain` is authoritative; consumers MUST raise a warning if the two disagree. `chainId` is deprecated and SHOULD NOT be set in new envelopes. #### 5.3 Calls ```typescript interface EvmCall { to: `0x${string}` value?: `0x${string}` data?: `0x${string}` operation: 'call' | 'delegatecall' capabilities?: Record } ``` `to` is a `0x`-prefixed 20-byte hex address. `value` defaults to `"0x0"` when absent; when present, it is a `0x`-prefixed hex bigint (matching the ERC-5792 call encoding). `data` defaults to `"0x"` when absent; when present, it is a `0x`-prefixed hex byte string. `operation` MUST be present and is exactly `'call'` or `'delegatecall'`. It carries no default: a producer cannot omit it to let a delegate call inherit a low-risk `'call'` policy. Making `operation` a first-class required field allows consumers to apply distinct risk policies to delegate-call flows. `capabilities`, when present, carries per-call capability hints in the same open-record shape as envelope-level capabilities (§8). Vendor-specific per-call entries MUST use an `x-` prefix. #### 5.4 Validity ```typescript interface Validity { notBefore?: number notAfter: number nonceKind?: 'sequential' | 'durable' | 'bitmap' blockhashRecency?: { maxAge: number } } ``` `notBefore`, when present, is a Unix timestamp in seconds. Consumers MUST NOT submit a transaction before `notBefore`. `notAfter` is REQUIRED. It is a Unix timestamp in seconds and represents the latest moment at which the producer's preparation remains valid. Producers MUST set `notAfter` to a finite future time. Consumers MUST NOT submit a transaction after `notAfter`. Consumers SHOULD surface a warning when offered an envelope whose `notAfter` is further in the future than a consumer-configured "long validity" threshold. A producer preparing a transaction whose effect depends on chain state that can change between preparation and signing (a swap subject to price movement, a liquidation, any oracle-sensitive call) SHOULD set a short `notAfter` window. A consumer offered such an envelope close to its `notAfter` SHOULD re-prepare it rather than sign a preparation that may no longer reflect current state. When the envelope's `calls[]` includes a call that carries an independent on-chain expiry (such as a Permit2-style `deadline` or an [ERC-4337](./eip-4337.md) UserOperation `validUntil`), `validity.notAfter` MUST be set below the earliest such on-chain expiry by a safety buffer, and consumers MUST reject envelopes where `notAfter > (earliest on-chain expiry - buffer)`. The buffer SHOULD be at least 60 seconds as a recommended minimum to account for typical mempool delay, but consumers MUST provide configuration to adjust it for chain conditions, transaction cost, and network load, and producers MAY use a shorter buffer with documented justification. Where a consumer cannot statically decode a call to recover its on-chain expiry, it SHOULD fall back to pre-execution simulation to recover the effective expiry and enforce the same constraint against the simulated value. Static on-chain-expiry decode is a consumer-runtime obligation performed at the point of use; the stateless schema validator does not perform it, so a schema-conformant envelope MAY still be rejected by a consumer that decodes the buffer constraint. `nonceKind`, when present, identifies the on-chain nonce scheme the producer is targeting: `'sequential'` for the default EOA nonce, `'durable'` for non-sequential schemes such as the ERC-4337 two-dimensional nonce, or `'bitmap'` for unordered schemes such as the Permit2 bitmap. `blockhashRecency`, when present, asserts that the producer constructed the envelope against a blockhash no older than `maxAge` seconds. #### 5.5 Description ```typescript interface Description { short: string long?: string action: ActionType } type ActionType = | 'transfer' | 'approve' | 'permit' | 'revoke-approval' | 'swap' | 'stake' | 'unstake' | 'claim' | 'restake' | 'mint' | 'burn' | 'deposit' | 'withdraw' | 'delegate' | 'bridge' | 'admin-op' | 'other' ``` `short` is a bounded human-readable summary suitable for a single-line confirmation prompt. Consumers SHOULD render `short` as part of the transaction preview. `long`, when present, contains multi-line elaboration suitable for an expanded preview. `action` is one of the closed enumeration values. New action types are added by amendments to this specification. Producers MUST emit `'other'` for actions not represented and SHOULD set `long` to describe the action when `'other'` is used. Consumers SHOULD treat `'other'` as triggering stronger confirmation friction because the semantic category is unspecified. Producers SHOULD NOT emit `'other'` as a standing fallback for action classes that are common in production. When a class emerges at scale, propose an ERC amendment that adds it to the enumeration; routine `'other'` emission signals an amendment is due. Description fields are presentational. Policy engines, allowlists, spend limits, and risk evaluations MUST NOT be based on `description.short` or `description.action` alone; they MUST be evaluated against the raw `calls[]` (decoded selector and arguments) or the equivalent signature content. Presentational means not policy-binding without consumer verification, not unverifiable (non-normative). `producer.signature` with `coverage: 'envelope'` binds these fields to producer identity for tamper-detection; it does not substitute for consumer-side calldata decoding before granting policy trust. #### 5.6 Metadata ```typescript interface Metadata { protocol: string tokenMovements: TokenMovement[] counterparties: Counterparty[] estimatedGas?: string } interface TokenMovement { token: `0x${string}` | 'native' standard: 'erc20' | 'erc721' | 'erc1155' | 'native' symbol: string decimals: number amount: string tokenId?: string kind: 'transfer' | 'approve' | 'permit' | 'mint' | 'burn' | 'revoke' isUnlimited?: boolean from: `0x${string}` to: `0x${string}` usdValue?: number } interface Counterparty { address: `0x${string}` role: 'recipient' | 'spender' | 'swap-venue' | 'pool' | 'bridge' | 'admin' | 'unknown' label?: string labelSource?: 'contact_book' | 'protocol_directory' | 'recent_interaction' | 'untrusted' similarityWarning?: { similarTo: `0x${string}` distance: number } } ``` `protocol` identifies the protocol the envelope targets in a stable string form (such as `'aave-v3'` or `'uniswap-v4'`). Naming conventions are non-normative in this version; producers SHOULD adopt the conventions of an established ecosystem directory when one is selected (such as an ERC-8004 Identity Registry entry where the protocol's operator is itself a registered party, or an equivalent directory). `tokenMovements` enumerates every token movement implied by the envelope. Each entry describes a single movement: - `token` is the token contract address or the literal `'native'` for chain-native assets - `standard` identifies the token standard - `symbol` and `decimals` are present for display; consumers SHOULD verify them against the on-chain token contract when feasible - `amount` is a stringified decimal in the token's own decimals - `tokenId` is present when `standard === 'erc721'` and SHOULD be present when `standard === 'erc1155'` - `kind` identifies the movement category - `isUnlimited` SHOULD be `true` when the movement represents an unbounded allowance grant (such as a `MAX_UINT256` approval) - `from` and `to` are present on every movement and identify the on-chain source and destination of value flow as decoded from `calls[].data` or implied by the call type `counterparties` enumerates every entity touched by the envelope. Each entry describes a single counterparty: - `address` is the on-chain address - `role` identifies the relationship - `label`, when present, is a human-readable name - `labelSource` indicates the provenance of `label` when `label` is present: `'contact_book'` (user-curated), `'protocol_directory'` (registry-attested), `'recent_interaction'` (consumer-observed history), or `'untrusted'` (producer-supplied without external verification) - `similarityWarning` SHOULD be present when `address` is visually similar to another address observed in the consumer's recent history; `distance` is a consumer-defined similarity metric below a consumer-configured threshold #### 5.6.1 Label resolution order When more than one source can supply a label for the same `Counterparty.address`, consumers SHOULD evaluate sources in the following order, from highest trust to lowest: `contact_book` (user-curated), `protocol_directory` (registry-attested), `recent_interaction` (consumer-observed), and `untrusted` (producer-supplied). The highest-trust available source supplies the rendered label; lower-trust sources MAY appear as secondary annotations but MUST NOT replace a higher-trust label. When `labelSource: 'contact_book'`, consumers MUST verify the address exists in the consumer's own contact book before applying contact-book priority. If it does not, consumers MUST treat the label as `untrusted` and resolve from `protocol_directory` downward. `similarityWarning` MUST NOT be suppressed on a producer-claimed `contact_book` source - only on a consumer-verified one. A worked example: a producer prepares an envelope with `label: "Uniswap V4 Router"` and `labelSource: 'protocol_directory'` for an address that the consumer's contact book separately tags as `"My swap test"` with `labelSource: 'contact_book'`. The consumer renders `"My swap test"` as the primary label and the producer-supplied `"Uniswap V4 Router"` as a contextual annotation (for example, in a tooltip or expanded view). Hiding the contact-book label would mislead a user who has explicitly tagged the address themselves. When a `Counterparty.address` matches a `similarityWarning.similarTo` value from any source, the similarity warning SHOULD take precedence over any label other than one drawn from `contact_book`. The contact-book exception preserves user-curated familiarity for addresses the user has explicitly trusted; otherwise, the similarity warning surfaces the lookalike vector ahead of producer-supplied or registry-supplied labels. `estimatedGas`, when present, is a stringified decimal indication of expected gas consumption. Consumers MAY surface fee breakdown and output-amount estimates through `meta` or vendor-specific `capabilities` entries; structured fields for these are deferred to a follow-up specification. #### 5.7 Decoder references `decoderRef`, when present, is a stable identifier of an external ERC-7730 descriptor, typically a URI that resolves to a descriptor document in an ERC-7730 registry. Consumers that resolve `decoderRef` MUST verify the resolved descriptor's binding to the same `calls[].to`, `chain`, and selector before rendering its output. `clearSigning`, when present, is an inline ERC-7730-conforming descriptor fragment that consumers MAY render directly without registry resolution. Producers that target offline consumer flows (such as hardware wallets that cannot resolve registry URIs over network) SHOULD include `clearSigning` inline; consumers operating offline MUST NOT rely on `decoderRef` alone. ### 6. Signature Content The `signature` kind carries an off-chain signature request that produces no on-chain transaction. It covers EIP-712 typed data, the `personal_sign` byte-string convention, and SIWE login messages, and accommodates counterfactual smart-account signatures per ERC-6492. ```typescript interface SignatureContent { chain: `eip155:${string}` from?: `0x${string}` scheme: 'eip-712' | 'personal-sign' | 'siwe' domain?: TypedDataDomain types?: Record> primaryType?: string message?: Record messageText?: string description: Description metadata?: Metadata validity?: Validity erc6492?: boolean } interface TypedDataDomain { name?: string version?: string chainId?: number verifyingContract?: `0x${string}` salt?: `0x${string}` } ``` #### 6.1 Chain identifier and signer `chain` is a CAIP-2 identifier in the form `eip155:`, matching §5.2. `from`, when present, is the `0x`-prefixed 20-byte hex address that the producer expects to sign the message. #### 6.2 Scheme discrimination `scheme` selects the signature format. For each of its values, the envelope carries exactly the fields enumerated in the table below; fields not listed for the scheme MUST NOT be present. | `scheme` | Required fields | |---|---| | [eip-712](./eip-712.md) | `domain`, `types`, `primaryType`, `message`, together encoding a valid EIP-712 message | | `personal-sign` | `messageText` as a UTF-8 string; consumers sign it under the `personal_sign` byte-string convention with the `\x19Ethereum Signed Message:\n` prefix | | `siwe` | `messageText` as a SIWE message conforming to the [ERC-4361](./eip-4361.md) grammar | #### 6.3 EIP-712 fields `domain` is the EIP-712 typed-data domain separator. Its sub-fields follow the EIP-712 definitions verbatim and are reproduced here for convenience only; the authoritative encoding rules are those of EIP-712. `types` encodes the type definitions used by `message`. The `EIP712Domain` type MAY be omitted from `types` when its inclusion is implied by the presence of `domain`. `primaryType` is a key of `types`. `message` conforms to the type identified by `primaryType`. #### 6.3.1 Worked example: Permit2 single transfer The following fragment illustrates a `signature`-kind envelope authorizing a single Permit2 transfer: ```json { "$schema": "https://txkit.dev/schemas/v0.1/envelope.json", "issuedAt": "2026-05-12T08:00:00Z", "kind": "signature", "content": { "chain": "eip155:1", "scheme": "eip-712", "domain": { "name": "Permit2", "chainId": 1, "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3" }, "types": { "PermitTransferFrom": [ { "name": "permitted", "type": "TokenPermissions" }, { "name": "spender", "type": "address" }, { "name": "nonce", "type": "uint256" }, { "name": "deadline", "type": "uint256" } ], "TokenPermissions": [ { "name": "token", "type": "address" }, { "name": "amount", "type": "uint256" } ] }, "primaryType": "PermitTransferFrom", "message": { "permitted": { "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "1000000000" }, "spender": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "nonce": "0", "deadline": "1734567890" }, "description": { "short": "Permit Uniswap V3 router to spend 1,000 USDC", "action": "permit" }, "metadata": { "protocol": "permit2", "tokenMovements": [ { "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "standard": "erc20", "symbol": "USDC", "decimals": 6, "amount": "1000000000", "kind": "permit", "isUnlimited": false, "from": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "to": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" } ], "counterparties": [ { "address": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", "role": "spender", "label": "Uniswap V3 Router", "labelSource": "protocol_directory" } ] } } } ``` The envelope describes what the resulting signature authorizes: a bounded transfer of 1,000 USDC from the signer to the Uniswap V3 Router, gated by `deadline` and `nonce`. The on-chain transaction triggered by this signature is a separate `evm-tx`-kind envelope whose interaction with the Permit2 contract consumes the signature. #### 6.4 Description, metadata, and validity `description` MUST be present and conforms to the shape defined in §5.5. The same anti-spoof rule applies: policy engines, allowlists, and risk evaluations MUST NOT be based on `description.short` or `description.action` alone; they MUST be evaluated against the raw signature payload (`message` for EIP-712, or `messageText` for personal-sign and SIWE). `metadata`, when present, conforms to §5.6 with the same provenance rules. For Permit and Permit2 messages, `metadata.tokenMovements` SHOULD enumerate the approvals or token transfers the resulting signature can authorize, including `isUnlimited` for unbounded allowances. `validity`, when present, MUST follow §5.4. Producers SHOULD include `validity.notAfter` on signature envelopes that delegate spending authority. Consumers SHOULD reject signature envelopes whose declared validity extends past a consumer-configured "long validity" threshold for permit-class flows. A producer that prepares an off-chain signature request MUST emit a `signature`-kind envelope. A producer MUST NOT emit an `evm-tx`-kind envelope that claims `description.action === 'permit'` in place of a signature envelope; conflating signature requests with transaction envelopes defeats the per-kind UI and policy semantics defined in this specification. #### 6.5 Counterfactual signatures `erc6492`, when `true`, declares that the signature produced from this envelope will be wrapped in the ERC-6492 deployment-preamble format because the signer account has not been deployed at the time of signing. Consumers that produce signatures for counterfactual accounts MUST emit ERC-6492-wrapped output when `erc6492` is `true`, and MUST emit raw signatures when `erc6492` is `false` or absent. ### 7. Risk The optional `risk` field carries a risk verdict that a consumer (or an upstream relay acting on the consumer's behalf) has computed for the envelope. ```typescript interface RiskAssessment { action: 'ALLOW' | 'WARN' | 'BLOCK' score?: number warnings: RiskWarning[] scanners?: ScannerVerdict[] } interface RiskWarning { code: string severity: 'low' | 'medium' | 'high' | 'critical' message: string } interface ScannerVerdict { provider: string verdict: 'ALLOW' | 'WARN' | 'BLOCK' url?: string } ``` `action` is exactly one of `'ALLOW'`, `'WARN'`, or `'BLOCK'`. Consumers SHOULD block signing flows when `action === 'BLOCK'` and SHOULD present a confirmation step when `action === 'WARN'`. `score`, when present, is a number between `0` and `100` inclusive. The mapping between `score` and `action` is consumer-defined; producers MUST NOT assume that a numeric score will be re-interpreted by the consumer. `warnings` is an array, possibly empty. Each `RiskWarning` carries a machine-readable `code` (such as `'delegatecall-to-unknown'` or `'unbounded-approval'`), a `severity` value drawn from the closed enumeration, and a human-readable `message`. `scanners`, when present, enumerates third-party scanner verdicts that contributed to the overall `action`. Each entry identifies the `provider` and the scanner's verdict; `url` MAY link to a verdict page. A producer MAY include `risk` as a self-report (for example, a protocol-owned tool flagging its own pre-image as low-risk). Consumers MUST NOT treat producer-supplied `risk` as authoritative even when `producer.signature` covers the envelope. The `risk` field is consumer-injectable by design: consumer-side scanners and policy engines overwrite the field, and a consumer-side canonical hash MAY be recomputed at that point. ### 8. Capabilities `capabilities` is an open-ended record of capability hints. The structure intentionally mirrors the capability extensibility model of ERC-5792. ```typescript interface Capabilities { atomicRequired?: boolean paymasterService?: PaymasterService permissions?: PermissionContext requiresAccountType?: 'eoa' | 'delegated-eoa' | 'erc-4337' [key: string]: unknown } interface PaymasterService { url: string sponsor?: `0x${string}` } interface PermissionContext { context: `0x${string}` type: string } ``` #### 8.1 Known capabilities `atomicRequired`, when `true`, MUST be interpreted as defined by ERC-5792: the consumer MUST execute every call atomically or fail the batch. This capability applies only to `evm-batch` envelopes. `paymasterService`, when present, identifies a paymaster endpoint compatible with ERC-4337. The `url` field is the paymaster service endpoint. `sponsor`, when present, identifies the sponsor address. Consumers that do not implement ERC-4337 paymaster flows MUST ignore this field. `permissions`, when present, MUST be treated as an opaque grant whose structure ERC-7715 (Draft) defines. The `context` field carries the bytes returned by `wallet_grantPermissions`; the `type` field identifies the permission category. Consumers without ERC-7715 support MUST treat `context` as opaque bytes and forward it unchanged. Consumers MAY support ERC-7715-aware parsing where available. `requiresAccountType`, when present, is one of the listed enumeration values and declares that the envelope is only valid against an account of that type. #### 8.2 Vendor-specific entries Capability keys not listed in §8.1 are reserved for vendor extension. Vendor-specific entries MUST use an `x-` prefix (for example, `x-alchemy.gasPolicy`). Standardized capability extensions MAY be added by amendments to this specification. #### 8.3 Trust model Capability entries MUST NOT influence security-critical UI decisions unless the consumer explicitly recognizes the entry and has a defined policy for it. An unrecognized `x-`-prefixed entry MUST be ignored for policy purposes; the consumer MAY surface it as an informational signal. #### 8.4 Projection to wallet_sendCalls When a consumer forwards an `evm-batch` envelope to a wallet via the ERC-5792 `wallet_sendCalls` RPC, the projection follows a one-way mapping. The envelope is the producer-to-consumer shape; the ERC-5792 request is the consumer-to-wallet shape: ```json { "method": "wallet_sendCalls", "params": [{ "version": "", "id": "", "from": "", "chainId": "", "atomicRequired": "", "calls": [ { "to": "", "data": "", "value": "", "capabilities": "" } ], "capabilities": { "paymasterService": "" } }] } ``` Envelope-level `capabilities.atomicRequired` projects to the top-level `atomicRequired` flag in the ERC-5792 request. Envelope-level `capabilities.paymasterService` projects to `capabilities.paymasterService` at the request root, where the wallet propagates it per the ERC-5792 capability model. Vendor-specific `x-`-prefixed entries project as-is. Consumers MAY drop envelope-level capabilities they do not recognize when constructing the ERC-5792 request; they MUST NOT remap unrecognized entries to known capability keys. The `version` field in the projected request carries the `wallet_sendCalls` protocol version that the consumer negotiated with the target wallet via `wallet_getCapabilities`; this specification does not pin a particular version. Consumers MUST convert the envelope's `eip155:` chain identifier to the `0x`-prefixed hex chain-id form required by ERC-5792 before projection. When `envelope.content.from` is absent, the projection omits the `from` field, and the wallet derives the signer per ERC-5792. Note: this projected `version` is the ERC-5792 `wallet_sendCalls` RPC version negotiated via `wallet_getCapabilities`. It is unrelated to the envelope version contract (`$schema`, §1.2) and MUST NOT appear in the envelope. ### 9. Call Status Taxonomy Consumers that report post-submission call status SHOULD use the status codes defined in ERC-5792 §5. The authoritative definitions live in ERC-5792; this section describes the codes informationally so consumer-side tooling can cross-reference the envelope without departing the document. The defined codes are 100 (pending submission, no terminal state reached), 200 (confirmed on-chain, receipt final), 400 (off-chain failure, the consumer never submitted the call), 500 (fully reverted on-chain), and 600 (partially reverted, where at least one call in a batch reverted while others succeeded). A consumer that supports the `wallet_getCallsStatus` RPC SHOULD report status under these codes. The envelope `id`, when present, MAY be used as the identifier passed to `wallet_getCallsStatus`. Consumers that do not implement ERC-5792 status reporting MAY use these codes informationally without exposing them through the ERC-5792 RPC surface. ### 10. Appendix A: Reserved Kinds (Non-normative) This appendix is non-normative. It catalogs `kind` values that this specification reserves for future use. The §2 rejection rule applies normatively to all values listed below; this section describes intended future scope only. Future versions of this specification, or follow-up specifications, may define normative semantics for these reservations. | Reserved `kind` | Future scope | Anchor standard or specification | |---|---|---| | `evm-userop` | ERC-4337 UserOperation, including v0.7 and v0.8 variants | ERC-4337 | | `evm-frame` | Frame Transaction format | [EIP-8141](./eip-8141.md) | | `evm-7702` | SET_CODE transaction type with authorization tuples | EIP-7702 | | `mandate` | Agentic commerce mandate (cart, intent, payment) | AP2, Visa TAP, Mastercard Verifiable Intent | | `intent` | Cross-chain and RFQ-style intents | [ERC-7683](./eip-7683.md) | | `psbt` | Bitcoin partially signed transactions | BIP-174 | | `svm-tx` | Solana versioned transactions | Solana core protocol documentation | | `move-tx` | Move-based ledger transactions (Aptos, Sui) | Move specification | | `cosmos-tx` | Cosmos SDK Any-wrapped messages | Cosmos SDK | The reservations exist to keep the discriminator namespace stable across the lifecycle of this specification. A producer that targets one of these reserved kinds before a follow-up specification defines it can use an `x-`-prefixed kind (such as `x-vendor.userop`) until the reserved kind becomes normative. The reject rule and the no-silent-remapping rule for these values live in §2. A reserved `kind` value MAY never be specified. If the target ecosystem does not adopt the envelope model, the slot remains reserved. Vendors that wish to use a namespace before or instead of future standardization use `x-vendor.` per §8.2. ### 11. Appendix B: Outcome Reporting (Non-normative) This appendix is non-normative. It reserves design space; it defines no fields and places no requirements on implementations. The envelope specified in §1 through §9 is one-directional: it carries a prepared payload from a producer to a consumer. The reverse direction, a consumer reporting the outcome of an envelope back to the producer that prepared it, is out of scope for this version and is reserved here so that a future version of this specification, or a companion specification, can define it without colliding with the envelope namespace. The reverse path matters when the producer runs an automated loop that must decide what to do once the consumer has acted: a producer that receives no structured outcome cannot distinguish an explicit rejection from a silent expiry, and so cannot tell whether to re-prepare the payload or to stop. A future outcome message is expected to carry, at minimum, a terminal status (the envelope was acted on, was rejected, or expired before being acted on); a producer-facing disposition that separates an outcome the producer should treat as final from one it may re-prepare against; an optional human-readable reason; and, where the envelope produced a transaction, that transaction's identifier. Of these, the producer-facing disposition is the field an automated producer loop branches on: a terminal status records what happened to the envelope, but only the disposition tells the producer what to do next - an envelope that expired without being acted on and one a consumer explicitly rejected are both terminal, yet the first invites re-preparation and the second does not. This specification reserves that shape without fixing field names or semantics; both are settled if and when the reverse path is specified normatively. ## Rationale The envelope/content discriminated union, the `$schema`-as-version-contract decision, the presentational treatment of off-chain fields, and the open capability slot together form the load-bearing design choices in this specification. Each was selected after explicit consideration of competing approaches. The most consequential alternatives are recorded here. ### A single flat schema with optional fields An earlier private draft used a single object whose fields covered every possible payload type, with the active set selected by context. This shape proved fragile: validators could not produce a useful "unknown kind" error, consumers had to inspect multiple fields to decide which code path to run, and future kinds (UserOperation, intents, PSBT) would have required either further field overloads or migration to a discriminator anyway. The current envelope wraps a `kind`-tagged `content` payload, so validators emit one error class per failure mode (unknown kind, reserved kind, content mismatch) and consumers branch on `kind` once. ### An explicit `version` field on the envelope The first private draft included `version: '0.1'` as a literal field on the envelope. Two issues led to its removal. First, the specification itself is the version contract, and embedding a version inside the envelope duplicates that role. Second, two consumers reading the same envelope could disagree about whether `version: '0.1'` denoted "specification version 0.1" or "this producer's envelope-template version 0.1"; the field had no canonical meaning without external resolution. The `$schema` URL, in contrast, identifies a specific schema document by path and admits exact-match comparison. This specification therefore standardizes `$schema` as the version contract and forbids a separate `version` field. ### Rich JSX-style display tree A display tree that producers could embed directly in the envelope (similar in spirit to a generic rich-content rendering format) was considered as a way to give producers fine-grained control over rendering. The approach was rejected because it tightly couples the envelope to a particular rendering model. Consumers that are not graphical wallets (policy engines, hardware-wallet companions, audit pipelines) would have to ignore or re-render the tree, and the structure would have to evolve every time a new layout primitive was needed. The current shape carries semantic facts in `description.action`, `metadata.tokenMovements`, and `metadata.counterparties`; consumers compose their own preview from those facts. ERC-7730 provides the standardized display layer when a consumer wants registry-resolved rendering. ### A boolean `safe` field A single `safe: boolean` field would have produced a compact API. It was rejected for two reasons. First, "safe" is not a property a producer can attest to credibly; the producer either does not know the consumer's policy or has an incentive to over-report safety. Second, a boolean elides the most useful information: which threats apply, at what severity, and which scanner reported them. The `risk` field with a graded `action` enum, severity-tagged `warnings`, and per-scanner verdicts captures the threat surface in a form that consumers can compose with their own policy. ### Trusting `description.short` as the policy surface Some early prototypes used `description.short` directly as a policy lookup key ("allow when `short` matches a known protocol name"). The approach is unsafe: a malicious or compromised producer can prepare a calldata payload that drains funds while supplying a benign `description.short`. This specification therefore declares all off-chain description and metadata fields as presentational only, and requires policy engines to evaluate against the raw `calls[]` data or signature content. The shift moves the policy decision from the producer-controlled surface to the canonically authoritative one. ### Per-kind RPC methods instead of a unified envelope A model in which each `kind` corresponded to its own producer-to-wallet RPC method was considered. The approach was rejected because it pushes the discriminator into the transport layer and forces every transport (HTTP, MCP, postMessage, WalletConnect session) to enumerate every kind. The envelope's `kind` discriminator allows a single transport surface to carry every payload type; new transports adopt the envelope without coordinating with every consumer. ### Composition with adjacent standards rather than duplication ERC-7730 (clear-signing descriptors), ERC-7715 (scoped permissions), ERC-8004 (agent identity), and ERC-5792 (batch calls) each occupy specific layers of the producer-to-consumer pipeline. An alternative would have been to absorb their schemas into this envelope. That approach was rejected on two grounds. First, those specifications evolve on independent timelines; embedding their schemas would force this specification to track every change. Second, the same producer or consumer may compose those specifications outside of an envelope context (a non-envelope clear-signing descriptor served from a static registry, a non-envelope permission grant returned by `wallet_grantPermissions`). The envelope therefore references each adjacent standard by pointer (`decoderRef`, `capabilities.permissions.context`, `producer.id`, `calls[]` shape) rather than by inclusion. A parallel class of proposals hardens the producer side of this pipeline: on-chain input trust boundaries, trust-scope declarations, and execution attestation for autonomous agents constrain what a well-behaved producer is permitted to prepare. This specification operates at the opposite end. It does not assume a well-behaved producer; the consumer re-verifies every envelope against the raw `calls[]` or signature content regardless of how the producer was constrained. The two layers compose without overlap: a producer bounded by an agent-hardening proposal still emits an envelope, and the consumer still applies the verification this specification defines. Producer-side hardening and consumer-side verification are complementary, and neither subsumes the other. ### Re-preparation at consumption time Several choices in this specification follow one principle: a consumer treats an envelope as a claim to re-verify at the moment of use, not as producer state to trust on faith. `validity.notAfter`, present on every transaction envelope, bounds how long a preparation may be acted on, so a consumer offered a stale envelope re-prepares rather than signs it. The presentational treatment of `description` and `metadata` forces policy evaluation against the raw `calls[]` or signature content. The consumer-injected `risk` field places the risk verdict at consumption time rather than preparation time. Stating the principle explicitly clarifies why the envelope composes with any producer rather than only well-behaved ones: a consumer that re-verifies at consumption need not trust how, or how carefully, the envelope was prepared. ### Relationship to existing standards No directly duplicative standard exists, but several adjacent standards warrant explicit positioning so a reviewer can see where the envelope sits relative to each. ERC-5792 and this specification sit at two different stages of the same pipeline. ERC-5792 standardizes the consumer-to-wallet RPC surface (`wallet_sendCalls`); this specification standardizes the producer-to-consumer envelope that precedes any wallet involvement. The flow is two-stage: a producer (dApp, CLI, hardware-wallet companion, multisig coordinator, or AI agent) prepares a payload and attests who prepared it, from what origin, with what intent and risk verdicts, and only then does a consumer, after applying policy, optionally forward the result via `wallet_sendCalls`. Many consumers never reach the second stage at all: a policy engine, an air-gapped signer, or a hardware-wallet companion consumes the envelope and never calls `wallet_sendCalls`. `wallet_sendCalls` also has no field for producer identity, origin attestation, or a producer signature over the prepared payload. The envelope closes the handoff gap; ERC-5792 closes the invocation gap. They compose (§8.4) rather than compete. ERC-7683 (cross-chain intents) coordinates RFQ-style cross-chain orders with solvers; this specification carries a producer-to-consumer handoff for prepared single-chain payloads. The two are complementary: an ERC-7683 order MAY be nested as envelope `content` (under a future `intent` kind, reserved in Appendix A) without either standard absorbing the other. EIP-7702 (SET_CODE authorization tuples) is a future-mitigation concern rather than an overlap. The reserved `evm-7702` kind and the "Authorization-tuple code installation" entry in Security Considerations stake out the threat surface; when `evm-7702` is specified normatively it is expected to mandate delegate-call surfacing, consumer-side delegate decoding, clear-signing address binding, and an allowlist cross-check. [EIP-2718](./eip-2718.md) (Typed Transaction Envelope) is the closest precedent and the clearest contrast. EIP-2718 introduced an on-chain typed-transaction envelope, but it is pure transport: a one-byte type discriminator in front of opaque payload bytes, with no handoff semantics, no producer attestation, and no policy composition. This specification is the off-chain analog that adds exactly the layer EIP-2718 omits - producer attestation and consumer-side policy composition above the wallet boundary. It is a new surface, not a re-skin of 2718 off-chain. The remaining adjacent standards each occupy one narrow role: ERC-7715 supplies the opaque permission `context` an envelope forwards unparsed; ERC-6492 supplies the counterfactual-signature wrapping signaled by the `erc6492` boolean mode-flag; ERC-4337 supplies the downstream UserOperation invocation target (paymaster endpoint, two-dimensional nonce, reserved `evm-userop` kind); and [ERC-8183](./eip-8183.md) occupies a parallel track this specification neither depends on nor blocks. ## Backwards Compatibility This specification defines an off-chain shape. It does not modify any on-chain encoding, transaction-pool rule, or wallet RPC surface defined elsewhere. Consumers that do not recognize the envelope simply ignore it and operate on the underlying transaction, batch, or signature request as before. Producers that adopt the envelope retain the ability to also issue raw `eth_sendTransaction`, `wallet_sendCalls`, or `eth_signTypedData_v4` requests for consumers that have not adopted it. No prior version of this specification was published, so no migration is required. ## Test Cases The reference implementation ships four example envelopes that cover the three normative kinds and exercise key invariants of this specification: | Example | Kind | Invariants exercised | |---|---|---| | `stakewise-deposit.ts` | `evm-tx` | Single-call discrimination (§5.1), CAIP-2 chain encoding (§5.2), `validity.notAfter` requirement (§5.4), `tokenMovements` enumeration with `from` and `to` (§5.6) | | `uniswap-permit2-swap.ts` | `signature` + `evm-tx` (two envelopes) | Permit signature carried as a `signature` kind (§6), swap as `evm-tx` (§5), counterparties with `role` and `labelSource` (§5.6) | | `safe-delegatecall-warning.ts` | `evm-tx` | First-class `operation: 'delegatecall'` (§5.3), risk-slot annotation (§7) | | `multicall-batch.ts` | `evm-batch` | Batch-call discrimination (§5.1), `atomicRequired` capability per ERC-5792 (§8.1), multi-call `tokenMovements` enumeration covering approve + transfer in the same envelope (§5.6) | Examples are non-normative. They illustrate canonical use of the specification's fields without constraining how implementations choose to produce or consume envelopes. Each example is a runnable TypeScript file in the reference implementation; reviewers can execute `pnpm exec tsx packages/tx-protocol/examples/.ts` to obtain the canonical envelope JSON. ## Reference Implementation A reference implementation of the envelope shape ships as `@txkit/tx-protocol` on npm (Apache-2.0). Source lives under `packages/tx-protocol/` in the repository at `https://github.com/txkit/mono`. The package exports TypeScript types matching every field defined in §1 through §10, runtime validators built with `zod`, and the four runnable examples enumerated in the Test Cases section. Implementers can install the package via `pnpm add @txkit/tx-protocol` and import `createEvmTx`, `createEvmBatch`, `createSignature`, and `validateEnvelope` directly. ## Security Considerations This specification defines an off-chain composition surface. It does not introduce new cryptographic primitives, modify any on-chain encoding, or change the rules under which a wallet signs a payload. The relevant threat surface is the off-chain handoff: how a consumer obtains an envelope, what fields it trusts, and how it bridges the off-chain description of an action to the on-chain effect that action will have. The following classes of threat are inherent to this surface; each is paired with the structural defense the specification provides and the residual risk that consumers must address. ### Off-chain description divergence from on-chain effect A malicious or compromised producer can prepare a calldata payload whose on-chain effect does not match the off-chain description, metadata, or token-movement enumeration. A substantial class of recent multi-signature, swap-routing, and approval-extraction losses arises from this gap. The specification structurally mitigates the threat in three ways. The envelope's `description` and `metadata` fields are declared as presentational, and the Specification requires that policy engines, allowlists, and risk evaluations evaluate the raw `calls[].data` or the equivalent signature content rather than the description. Per-call `operation` is a first-class typed field, so consumers can distinguish ordinary calls from delegate calls without parsing calldata. The `producer.signature` field, when present, binds the off-chain fields to the producer identity so that in-transit tampering is detectable. Two residual-risk classes remain. The first is consumer-side decoder absence: a consumer that displays `description.short` and `metadata.tokenMovements` without independently decoding `calls[].data` can be deceived by a producer that signs an envelope correctly while claiming false effects. The second is state-dependent calldata where static decoding cannot reconstruct on-chain effects, including dynamic dispatcher patterns (routers that forward an inner calldata blob via `call` or `delegatecall`) and contracts whose behaviour depends on storage state at execution time. Static decoding silently fails on these. For the first class, consumers should run a local calldata decoder and compare its synthesized token movements and counterparties against the envelope's claims. For the second class, no decoder closes the gap; consumers should fall back on pre-execution simulation followed by post-execution receipt comparison. The envelope cannot close this residual at the description layer; it can only constrain the description-vs-call binding for cases where static decoding is decidable. ### Counterparty label spoofing and address poisoning An attacker who can plant an address into a consumer's recent-interaction history (for example, through a low-value transfer using a lookalike address) can leverage the convention of labeling addresses by recent use. A subsequent envelope claiming the lookalike address as a familiar recipient becomes harder to distinguish from a legitimate one. The specification's structural mitigation is the `labelSource` field on every counterparty entry that carries a `label`. The producer is required to declare the provenance of the label: a user-curated contact-book entry, an attested protocol-directory record, an observed recent interaction, or an untrusted producer-supplied string. A label is no longer separable from the question of where it came from. The specification also defines `similarityWarning` so producers can flag addresses that closely resemble a previously-seen address, exposing the lookalike pattern at the envelope layer rather than requiring every consumer to recompute similarity independently. Two residual-risk classes remain. The first is upstream pollution of the trust source: if a contact book or protocol directory has itself been compromised, a `labelSource` value of `'contact_book'` does not imply safety. The second is that producer-attested `labelSource` values are unverifiable at the consumer when the source state is consumer-owned (such as `'contact_book'` or `'recent_interaction'`); a producer claiming `labelSource: 'contact_book'` cannot prove membership of the user's private state. Consumers MUST verify a producer-claimed `'contact_book'` source against their own contact book before applying contact-book priority and MUST NOT suppress `similarityWarning` on an unverified claim (§5.6.1); producer-supplied label values are otherwise informational only. ### Delegate-call concealment Delegate calls have been responsible for a sustained class of multi-signature compromises in which the off-chain description presented an ordinary call while the underlying transaction executed `delegatecall` to attacker-controlled code, granting that code the full storage and authority of the signing account. The class is structural to the EVM: `delegatecall` is indistinguishable from a regular call at the calldata layer alone, and historical wallet UIs treated both as a single "transaction" without surfacing the operation distinction. The specification's structural mitigation is making `operation` a first-class typed field on every call (§5.3). Consumers can identify delegate calls without parsing calldata and can apply distinct policies to them. Three policy patterns compose with this field. Under an allowlist policy, a consumer blocks any delegate call whose `to` address is not on an explicit list of trusted libraries (such as audited multisig delegate-call targets or canonical proxy implementations). Under a deny-by-default policy, a consumer requires explicit user confirmation per delegate call, separate from any "approve all transactions" workflow. Under a simulation-required policy, a consumer mandates pre-execution simulation of every delegate call and surfaces the resulting state delta to the signer before requesting a signature, subject to the residual that simulation may diverge from execution in MEV-sensitive flows. The specification does not mandate a particular policy; it provides the typed surface on which any of these policies can be expressed uniformly. The residual risk is allowlist maintenance. Trusted address sets exist (such as audited multisig module addresses and canonical multicall implementations), but the maintenance burden remains on the consumer or on a registry the consumer trusts. A consumer that allowlists a delegate-call target without verifying the target's source code against a known audit accepts the residual risk that the target may itself be vulnerable. The envelope cannot certify that a delegate-call target is safe; it can only surface that the call is a delegate call so consumer-side policy can decide. ### Dormant transaction execution A consumer that accepts an envelope prepared months earlier can submit a stale transaction whose preparation context (asset balances, governance state, mempool conditions) no longer applies. The specification structurally addresses this threat by making `content.validity.notAfter` a required field on every transaction envelope (§5.4). Producers MUST declare a finite future expiry, and consumers MUST NOT submit after that time. Where a call carries an independent on-chain expiry, `validity.notAfter` MUST sit below the earliest such expiry by a safety buffer (a recommended minimum of 60 seconds, consumer-configurable), and consumers MUST reject envelopes where `notAfter > (earliest on-chain expiry - buffer)` (§5.4). Long-validity envelopes SHOULD additionally surface a warning when `notAfter` is further out than a consumer-configured threshold for the envelope's action class. Consumers are advised to treat `notAfter` as a deadline for inclusion, not just submission, by applying a safety buffer that accounts for mempool delay; this specification does not normatively define that buffer. [ERC-8255](./eip-8255.md) (Expiring Token Approvals) provides an on-chain analog for approval-class expiry at the contract layer; the envelope-level `validity.notAfter` precedes any such on-chain primitive and binds the entire prepared payload, not approvals alone. ### Blind signature requests Permit, Permit2, and similar typed-data flows account for a substantial fraction of off-chain authorization losses, because wallets historically received opaque typed-data with no semantic context. The `signature` kind requires `description` on every signature envelope and prohibits `evm-tx` envelopes claiming `action: 'permit'` (§6.4). ### Cross-chain signature replay An EIP-712 typed-data message whose `domain` omits `chainId` is replayable across every chain on which the verifying contract exists. The envelope's `chain` field is a CAIP-2 identifier at the off-chain composition layer; it provides the surface for consumers to cross-check against `domain.chainId`. The residual is that some Permit-family contracts deliberately omit `domain.chainId` to enable single-deployment reuse, leaving only the envelope's `chain` field as the chain binding. Consumers SHOULD refuse to sign EIP-712 messages whose `domain` omits `chainId` unless they have independently attested that the verifying contract's deployment is single-chain. ### Counterfactual signature wrapping A signature produced under `erc6492: true` is wrapped in a deployment preamble that executes factory code inside the verifier's `eth_call`. Two residuals follow. Simulation accuracy may diverge because a simulator stripping the preamble sees different state than one executing it; the consumer's pre-sign preview can differ from the verifier's post-sign view. Account-derivation mismatch is also possible: a producer can claim `erc6492: true` while supplying ERC-6492 factory call data that, under an auto-deploying verifier, places code at a different address than asserted. The explicit `erc6492` boolean prevents implicit-mode confusion. Consumers should run the preamble in simulation only when the signer-side `from` derivation matches the verifier-side account address that would result from executing the ERC-6492 factory call. ### Slot-injection trust boundaries The `producer.signature`, `origin`, `capabilities`, and `risk` slots each carry data that a consumer evaluates after reception. An envelope without `producer.signature` carries no integrity guarantee for off-chain fields; consumers MUST treat such envelopes as advisory (§3.3). A signature whose `scheme` the consumer does not implement MUST be treated as unverified rather than rejected (§3.2); a consumer that surfaces a softer warning for unknown-scheme than for missing-signature creates a downgrade path, so the two cases SHOULD produce equivalent advisory treatment. When `producer.signature.coverage === 'content'`, the envelope MUST NOT include `origin` and consumers MUST NOT treat any envelope-level field other than `content` as producer-attested (§3.2). The `origin.verifyStatus` field is consumer-evaluated; a consumer MUST NOT promote `UNVERIFIED` to `VERIFIED` on producer claims alone (§4). Open `capabilities` entries that the consumer does not recognize MUST NOT influence security-critical UI (§8.3). Producer-supplied `risk` is not authoritative even when producer-signed (§7), and `scanners[]` entries carry no freshness guarantee, so consumers are advised to re-query scanners locally when policy correctness depends on the verdict's currency. ### Authorization-tuple code installation Motivation names authorization tuples introduced under EIP-7702 as a motivating loss: a `SET_CODE` transaction installs code at an EOA whose wallet could not display the resulting installation, and drainers exploited exactly that blind spot. The envelope does not yet model this transaction type. The `evm-7702` kind is reserved (Appendix A) pending a normative specification, and until that specification exists the envelope provides no structural surface for `SET_CODE` effects. Two obligations follow in the interim. Consumers MUST treat any `SET_CODE` effect that reaches them outside the envelope as out of band: an envelope cannot represent an authorization-tuple installation, so a consumer that encounters one alongside an envelope MUST NOT assume the envelope describes it. Producers MUST NOT represent an authorization-tuple installation in `description` or `metadata` without an `evm-7702` kind; smuggling a code installation under an `evm-tx` or `evm-batch` envelope would let a benign `description` mask a delegation that grants attacker code the account's authority. This subsection is superseded when `evm-7702` is specified. That specification is expected to mandate delegate-call surfacing, consumer-side delegate decoding, clear-signing address binding, and an allowlist cross-check for the installed delegate before the envelope can carry a `SET_CODE` effect. ### Consumer-only fields `meta` (§1.2) is consumer-only and MUST NOT affect signing, on-chain effect, or policy. A consumer that feeds `meta` into a policy decision can be steered by a malicious producer, so consumers SHOULD treat `meta` as untrusted display advice and MUST keep it out of any signing, settlement, or allowlist path. ### Out of scope This specification does not address: scanner-source vetting (which scanners a consumer chooses to invoke), key management on the consumer side, on-chain replay protection, transport authentication (TLS, attestation channels, and the like), or the trust model of any external registry referenced by `decoderRef`, `producer.id`, or `origin.attestation`. Resolution semantics for `producer.id` DIDs whose document mutates between envelope issuance and signature verification (including key rotation, revocation, and deletion) follow the DID method specification, not this one. Those concerns are the responsibility of the consumer, the wallet integrator, and the surrounding transport and registry layers. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).