ERC-8265: Prepared Transaction Envelope - current working draft (Last-Call-ready). Canonical: ethereum/ERCs#1753 · Discussion: https://ethereum-magicians.org/t/erc-8265-prepared-transaction-envelope/28557 This gist mirrors the live draft; the PR is authoritative.
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) mike@txkit.dev 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
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 batch, or an EIP-712 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.
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 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 counterfactual signatures, all Final. It integrates non-normatively with ERC-8004 identity registries, ERC-7730 clear-signing metadata, ERC-7715 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.
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.
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:
interface BaseEnvelope<K extends string, C> {
$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<string, unknown>
}| 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 |
$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.
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
kindis one ofevm-tx,evm-batch, orsignature, subject to the content validation in §5 and §6 - Reject envelopes whose
kindmatches a reserved value (see §10) with an error indicating "reserved, not yet specified" - Reject envelopes whose
kindis 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.
The producer is the party that prepared the envelope. Producer identity SHOULD be conveyed in every envelope.
interface Producer {
id: string
name?: string
signature?: ProducerSignature
}
interface ProducerSignature {
scheme: string
publicKey?: string
signature: string
coverage: 'envelope' | 'content'
}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.
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'.
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.
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 rawcalls[]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.
The origin field binds an envelope to the URL of the entity that produced it.
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:
VERIFIEDindicates that the consumer or an upstream relay has confirmedurlagainst an attestation source (such as the WalletConnect Verify API or an equivalent)UNVERIFIEDindicates that no attestation source confirmedurl; consumers SHOULD render an unverified-origin warningMISMATCHindicates that an attestation source returned a different value thanurl; 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.
The evm-tx and evm-batch kinds share content shape:
interface EvmTxContent {
chain: `eip155:${string}`
chainId?: number
from?: `0x${string}`
calls: EvmCall[]
validity: Validity
description: Description
metadata: Metadata
decoderRef?: string
clearSigning?: Record<string, unknown>
}- For
kind === 'evm-tx',calls.lengthMUST equal1 - For
kind === 'evm-batch',calls.lengthMUST be2or greater calls.length === 0MUST be rejected for both kinds
chain is a CAIP-2 identifier in the form eip155:<chain-id-decimal>, where <chain-id-decimal> is the EIP-155 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.
interface EvmCall {
to: `0x${string}`
value?: `0x${string}`
data?: `0x${string}`
operation: 'call' | 'delegatecall'
capabilities?: Record<string, unknown>
}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.
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 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.
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.
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:
tokenis the token contract address or the literal'native'for chain-native assetsstandardidentifies the token standardsymbolanddecimalsare present for display; consumers SHOULD verify them against the on-chain token contract when feasibleamountis a stringified decimal in the token's own decimalstokenIdis present whenstandard === 'erc721'and SHOULD be present whenstandard === 'erc1155'kindidentifies the movement categoryisUnlimitedSHOULD betruewhen the movement represents an unbounded allowance grant (such as aMAX_UINT256approval)fromandtoare present on every movement and identify the on-chain source and destination of value flow as decoded fromcalls[].dataor implied by the call type
counterparties enumerates every entity touched by the envelope. Each entry describes a single counterparty:
addressis the on-chain addressroleidentifies the relationshiplabel, when present, is a human-readable namelabelSourceindicates the provenance oflabelwhenlabelis present:'contact_book'(user-curated),'protocol_directory'(registry-attested),'recent_interaction'(consumer-observed history), or'untrusted'(producer-supplied without external verification)similarityWarningSHOULD be present whenaddressis visually similar to another address observed in the consumer's recent history;distanceis a consumer-defined similarity metric below a consumer-configured threshold
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.
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.
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.
interface SignatureContent {
chain: `eip155:${string}`
from?: `0x${string}`
scheme: 'eip-712' | 'personal-sign' | 'siwe'
domain?: TypedDataDomain
types?: Record<string, Array<{ name: string; type: string }>>
primaryType?: string
message?: Record<string, unknown>
messageText?: string
description: Description
metadata?: Metadata
validity?: Validity
erc6492?: boolean
}
interface TypedDataDomain {
name?: string
version?: string
chainId?: number
verifyingContract?: `0x${string}`
salt?: `0x${string}`
}chain is a CAIP-2 identifier in the form eip155:<chain-id-decimal>, matching §5.2.
from, when present, is the 0x-prefixed 20-byte hex address that the producer expects to sign the message.
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 | 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<len> prefix |
siwe |
messageText as a SIWE message conforming to the ERC-4361 grammar |
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.
The following fragment illustrates a signature-kind envelope authorizing a single Permit2 transfer:
{
"$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.
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.
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.
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.
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.
capabilities is an open-ended record of capability hints. The structure intentionally mirrors the capability extensibility model of ERC-5792.
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
}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.
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.
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.
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:
{
"method": "wallet_sendCalls",
"params": [{
"version": "<wallet_sendCalls version supported by target wallet>",
"id": "<envelope.id>",
"from": "<envelope.content.from>",
"chainId": "<eip155 chain-id-hex from envelope.content.chain>",
"atomicRequired": "<envelope.capabilities.atomicRequired>",
"calls": [
{
"to": "<envelope.content.calls[i].to>",
"data": "<envelope.content.calls[i].data>",
"value": "<envelope.content.calls[i].value>",
"capabilities": "<envelope.content.calls[i].capabilities>"
}
],
"capabilities": {
"paymasterService": "<envelope.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-id-decimal> 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.
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.
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 |
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 |
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.<kind> per §8.2.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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 (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 occupies a parallel track this specification neither depends on nor blocks.
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.
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/<name>.ts to obtain the canonical envelope JSON.
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.
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.
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.
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 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.
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 (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.
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).
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.
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.
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.
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.
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.
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 and related rights waived via CC0.