--- eip: 8410 title: Portable Execution Plan Artifact description: Verifiable references for passing execution plans from agent tools to wallets without relaying calldata author: Moody Salem (@moodysalem) discussions-to: https://ethereum-magicians.org/t/erc-8410-portable-execution-plan-artifact/29587 status: Draft type: Standards Track category: ERC created: 2026-09-04 requires: 155, 712 --- ## Abstract This ERC defines a format for passing prepared execution plans from tools to wallets through an AI agent. A producer stores a plan and returns an *artifact reference* containing its location, byte length, and integrity digest. The agent passes the reference to the wallet, which retrieves and validates the plan directly. The plan body need not enter the agent's context or tool-call arguments. An *execution plan* contains an ordered sequence of calls from one account on one chain and any required execution capabilities. The same reference format also supports requests to sign an [EIP-712](./eip-712.md) message. The wallet checks the retrieved bytes and their source and independently authorizes signing or execution. The format is independent of the agent framework and tool protocol. ## Motivation An agent arranging a swap may call one tool to prepare it and another to execute it. If the preparation tool returns calldata, the model consumes that data as input tokens and reproduces it as output tokens in the wallet tool call. Large or multi-step transactions increase context usage, latency, and cost. Copying the payload through the model also introduces opportunities for omission, truncation, or modification, even though the agent does not need to interpret the bytes to select the operation. A reference allows the producer and wallet to exchange the payload directly: ```text Producer -> Agent: summary and artifact reference Agent -> Wallet: artifact reference Wallet -> Producer: retrieve artifact Wallet: verify source and integrity, validate, simulate, authorize, execute ``` This provides five benefits: - **Lower token usage.** The agent reads and passes a small reference instead of the full payload. Calldata size does not determine the size of the agent's tool response or subsequent tool-call arguments. - **Faster tool handoffs.** The model does not have to process the full payload or generate it again before invoking the signing tool. The wallet can fetch the bytes directly once it receives the reference, reducing model processing and generation latency for large payloads. - **Exact payload transfer.** The wallet checks the retrieved byte length and digest before parsing. The model does not need to copy or reconstruct calldata. - **Verifiable source.** When retrieving over HTTPS, the wallet verifies the serving origin itself and can apply policy to that origin, rather than relying on the agent's claim about which tool produced the calls. - **Independent validation.** The wallet receives the complete call sequence, including approvals, execution, cleanup, and atomicity requirements. It can validate and simulate that sequence and present it for authorization without relying on the agent's description. The producer can return a concise summary for the agent to compare alternatives or explain an operation. The wallet evaluates the retrieved artifact, and the summary does not authorize it. HTTPS establishes the serving origin, not that its content is trustworthy; integrity establishes agreement with the reference, not the user's consent. These benefits apply when the agent passes a reference to an externally stored artifact. An inline document or a `data:` URI still carries the payload through the agent if it appears in the model's context. This ERC specifies the reference, document formats, and validation rules. Producer APIs, agent orchestration, and wallet authorization policies are outside its scope. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. Machine-readable schemas are provided for [the execution plan](../assets/eip-8410/execution-plan.schema.json), [the typed-data signature request](../assets/eip-8410/typed-data-signature-request.schema.json), and [the artifact reference](../assets/eip-8410/artifact-reference.schema.json). A *producer* prepares a document. A *consumer* is the wallet that validates it and decides whether to sign or execute it. A *relay* passes a document or reference from the producer to the consumer. ### Quantity encoding A *quantity* is a JSON string holding a canonical unsigned decimal integer: the single character `0`, or an ASCII digit string with no leading zero. A quantity MUST fit in a `uint256`. A *byte string* is a JSON string of the form `0x` followed by an even number of hexadecimal digits. An *address* is a byte string of exactly 20 bytes. Producers MUST emit both in lowercase. Consumers MUST normalize both to lowercase before computing the digest, and MAY additionally reject input that is not already lowercase. ### Execution plan | Field | Type | Required | Description | | --- | --- | --- | --- | | `schema_version` | string | yes | MUST be `"1"` for this revision | | `chain_id` | quantity | yes | [EIP-155](./eip-155.md) chain identifier the plan executes on | | `caip2_chain_id` | string | yes | MUST equal `eip155:` concatenated with `chain_id` | | `sender` | address | yes | Account expected to send every step | | `ordered_steps` | array | yes | 1 to 4096 steps, in execution order | | `required_capabilities` | array of string | no | Behaviors the consumer must implement | | `simulation_failure_policy` | object | no | What the producer advises on failure | | `extensions` | object | no | Producer-defined data the consumer ignores | A document containing a member not listed here MUST be rejected. Consumers MUST reject unsupported `schema_version` values and inconsistent chain identifiers. ### Steps Each entry of `ordered_steps` is an object: | Field | Type | Required | Description | | --- | --- | --- | --- | | `step` | integer | yes | 1-indexed position; entry *n* MUST have `step` equal to *n* | | `kind` | string | yes | One of the step kinds below | | `transaction` | object | yes | The call to make | | `revert_decode` | object | no | ABI for rendering a revert from this step | `transaction` has the following members: | Field | Type | Required | Description | | --- | --- | --- | --- | | `chain_id` | quantity | yes | MUST equal the plan's `chain_id` | | `from` | address | yes | MUST equal the plan's `sender` | | `to` | address | yes | Call destination | | `data` | byte string | yes | Calldata | | `value` | quantity | yes | Native token value, in wei | | `gas` | quantity | no | Suggested gas limit | Unknown step and transaction members MUST be rejected. A transaction without `to` cannot be represented; the format does not directly support contract creation transactions. Consumers MUST reject a plan containing inconsistent chain or sender values rather than execute it partially. `gas` is advisory. A consumer MUST NOT broadcast a producer-supplied gas limit without its own estimation. `kind` explains why a step is in the plan, and MUST be one of: - `execution` — a call the user asked for. - `approval` — grants a spending allowance a later step needs. - `allowance_cleanup` — revokes an allowance an earlier step granted. - `signature_dependent_execution` — spends a signature approved earlier. - `other` — none of the above. `kind` is a producer-supplied label for display. Consumers MUST NOT derive authority from it or assume that the calldata implements the labeled operation. `revert_decode`, when present, is an object with `kind` equal to `error_result`, an `abi` array holding a JSON ABI fragment declaring at least one error, and an OPTIONAL boolean `required` defaulting to false. `required` indicates whether the producer considers decoding necessary to explain the failure; consumers MAY ignore it. Consumers MUST treat the decoded result as display text only and MUST bound the fragment they accept. The schema permits at most 128 ABI entries; the reference implementation also limits the fragment to 65536 bytes. ### Required capabilities `required_capabilities` lists behaviors required to execute the plan. A consumer MUST reject a plan naming any capability it does not implement. This revision defines one capability: - `atomic_batch` — every step executes in a single transaction that reverts as a whole if any step fails. Consumers can satisfy `atomic_batch` through an account-abstraction batch executor or an [EIP-7702](./eip-7702.md) delegation to a batching implementation. A multi-step plan that does not list `atomic_batch` MAY be executed as separate transactions. A consumer doing so MUST execute steps in order, MUST NOT begin a step until the previous one has been mined successfully, and MUST stop at the first failure. A consumer that can execute atomically MAY do so regardless of whether the plan asks for it. Capability names MUST contain 1 to 64 ASCII characters in the range `0x21` to `0x7e`. A plan MUST NOT list more than 32 capabilities. Additional capabilities may be defined by later proposals. ### Simulation failure policy When present, `simulation_failure_policy` MUST contain all three of `rpc_error`, `execution_reverted`, and `simulation_setup_error`. Each is an object with an `action` and a human-readable `instruction` of 1 to 2000 characters. `action` MUST be one of: - `retry_same_plan` — the failure is transient; the identical document may be retried. - `reprepare_plan` — the document is stale; ask the producer for a new one. - `user_review` — do not retry automatically; surface it to a human. `execution_reverted` and `simulation_setup_error` MUST NOT specify `retry_same_plan`. The policy is advisory. Consumers MAY ignore it and MUST NOT treat it as authorization to broadcast. ### Extensions `extensions` is an object reserved for producer-defined data. Consumers MUST ignore its contents and MUST bound its serialized size; 65536 bytes is RECOMMENDED. It is excluded from the digest, so a producer MUST NOT place in it anything that changes what executes or what a reviewer needs to see. ### Size limits A consumer MUST enforce limits on any plan it accepts and MUST reject rather than truncate. Plans MUST contain at most 4096 steps. Additional RECOMMENDED limits are 8 MiB of `data` summed across steps and 16 MiB of serialized document. ### Plan digest The *plan digest* is the keccak256 hash of the UTF-8 serialization of a canonical JSON object built from the plan: ```json { "schema_version": "1", "chain_id": "1", "sender": "0x...", "ordered_steps": [ { "step": 1, "kind": "approval", "transaction": { "chain_id": "1", "from": "0x...", "to": "0x...", "data": "0x...", "value": "0" } } ] } ``` The serialization MUST place members in exactly the order shown, MUST contain no whitespace between tokens, and MUST encode addresses and `data` as lowercase byte strings. `step` MUST be serialized as a JSON number and every other scalar as a JSON string. Strings MUST be emitted without optional JSON escapes; `step` MUST use decimal integer notation without a fractional or exponent part. This member order is normative and is **not** lexicographic. A generic canonicalizer such as RFC 8785 sorts members by name and would order the outer object `chain_id`, `ordered_steps`, `schema_version`, `sender`, producing different bytes and a different digest. Implementations MUST emit the order given here rather than delegating to a general-purpose canonicalization routine. The digest covers `schema_version`, `chain_id`, `sender`, and each step's `step`, `kind`, and transaction `chain_id`, `from`, `to`, `data`, and `value`. It excludes `caip2_chain_id` (derivable from `chain_id`), `gas`, `revert_decode`, `simulation_failure_policy`, `required_capabilities`, and `extensions`. The plan digest identifies this projection, not the complete document or a signed transaction. Plans with different gas hints or capability requirements can have the same digest. The artifact reference's integrity digest, defined below, covers the complete serialized document instead. Consumers SHOULD display the digest wherever they display a plan for approval, SHOULD record it against the resulting approval, and SHOULD record it against the broadcast transaction hash. The digest does not authenticate the producer or establish authorization. ### Typed-data signature requests A `typed_data_signature_request` is a separate document carrying one concrete EIP-712 message and the account expected to sign it. The [typed-data signature request specification](../assets/eip-8410/typed-data-signature-requests.md) is normative for this document type. It defines the request and result shapes, the request digest, and optional delivery of the signature to the producer. Supporting signature requests is OPTIONAL. A consumer that supports execution plans only MUST reject signature requests. A signature request MUST NOT be encoded as an execution-plan step or placed in its ignored `extensions` field. Producers MAY return several signature requests, receive their results, and then return another concrete signature request or an execution plan. Each new document requires its own validation and authorization. No execution plan is required when the workflow ends with an offchain signature and producer relay. ### Delivery by reference A plan or typed-data signature request MAY be supplied inline or by an *artifact reference*. The reference identifies a document for the consumer to retrieve and verify. | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | string | yes | MUST be `"artifact_reference"` | | `artifact_type` | string | yes | `"execution_plan"` or `"typed_data_signature_request"` | | `url` | string | yes | `https` URL, `data:` URI, or `file` URI locating the bytes | | `integrity` | object | conditional | Digest over the exact stored bytes | | `bytes` | integer | conditional | Nonnegative exact byte length of the stored bytes | | `instruction` | string | no | Human-readable note from the producer | `integrity` and `bytes` are REQUIRED when `url` is an `https` or `file` URL, and OPTIONAL when it is a `data:` URI. When either is supplied for a `data:` URI it MUST still be checked. Consumers MUST ignore unrecognized top-level members of the envelope. `integrity` is an object with `algorithm`, which MUST be `"keccak256"` for this revision, and `value`, `0x` followed by 64 hexadecimal digits. A consumer MUST reject an `integrity` object containing any other member. `artifact_type` MUST be exactly `execution_plan` or `typed_data_signature_request`. No other artifact types are defined or accepted by this revision. A consumer MUST reject a type it does not implement, and MUST reject a reference whose `artifact_type` differs from the one expected at the point of use. In the retrieval rules below, the maximum document size and validation rules are those of the selected type. Fetching a signature request MUST NOT use the larger execution-plan size limit merely because the envelope is shared. `instruction` is producer-supplied display text and MUST NOT influence any authorization decision. #### Retrieval Given an envelope, a consumer MUST, in this order: 1. Reject the envelope if its serialized length exceeds the consumer's limit, before parsing the URL. 2. Reject the reference if `bytes` is present and exceeds the document type's size limit, before opening any connection or reading any file. 3. Retrieve the bytes according to the transport rules below. A consumer that does not implement the `file` transport MUST reject a `file` envelope. 4. Reject the result if `bytes` is present and the retrieved length differs. 5. Reject the result if `integrity` is present and the digest of the retrieved bytes differs. 6. Parse and validate the bytes exactly as it would a document of that type supplied inline. Where the transport applies a content encoding, length and integrity checks apply to the decoded bytes. Successful verification establishes that the bytes match the reference, not that the document is authorized. #### File transport A `file` URI identifies a document on the consumer's local filesystem. Support for this transport is OPTIONAL. A consumer fetching a `file` URL MUST enforce all of the following and MUST reject the reference if any condition cannot be met: - The URI has an empty host or `localhost`, and names an absolute path. - `integrity` and `bytes` are present; a `file` reference without them is rejected before any read. - The bytes are read once, bounded by the document type's size limit during reading, and the length-then-digest checks above are applied before parsing. - Missing, oversized, mismatched, and unreadable files fail indistinguishably, and error messages MUST NOT include any part of the file contents. - No network access is performed for the read. Consumers SHOULD additionally restrict `file` reads to one or more user-configured directories and treat anything outside them as failure. #### HTTPS transport A consumer fetching an `https` URL MUST enforce all of the following and MUST reject the reference if any condition cannot be met: - The scheme is `https` on the default port. - The URL carries no userinfo component and no fragment. - The host resolves, and every address it resolves to is a public unicast address. Loopback, link-local, unique-local, and other private ranges MUST be refused. - Redirects are not followed. - No credentials, cookies, or ambient authorization are attached. - The response body is bounded by the document type's size limit, enforced during reading, and the bound applies to the decoded stream where a content encoding is in use. - Connection and total timeouts are enforced, and the number of concurrent retrievals is bounded. Error messages MUST NOT include any part of the response body. #### Data URI transport A `data:` URI MUST have media type `application/json`, MAY use the `;base64` parameter, and MUST decode to at most the document type's size limit. Consumers MUST bound the encoded length before decoding and MUST NOT perform network access for a `data:` reference. For a decoded limit of *N* bytes, padded base64 requires at most `4 × ceil(N / 3)` characters before percent-encoding. Percent-encoding can use three characters per encoded byte. The encoded-length bound must account for the URI prefix and the encoding used; the decoded-size limit still applies. #### Provenance A consumer reporting provenance MUST distinguish the HTTPS host from which it retrieved a document from a `data:` URI, which provides no producer provenance. A caller-supplied `file` path MUST NOT be presented as provenance beyond the local machine. Authorization policies MAY use the observed HTTPS host, but TLS does not establish the trustworthiness of documents hosted there. ### Relationship to `wallet_sendCalls` A consumer or producer supporting [EIP-5792](./eip-5792.md) MAY convert a plan's calls to `wallet_sendCalls` parameters: | Plan | `wallet_sendCalls` | | --- | --- | | `chain_id` (decimal) | `chainId` (hexadecimal) | | `sender` | `from` | | `ordered_steps[n].transaction.to` / `data` | `calls[n].to` / `data` | | `ordered_steps[n].transaction.value` (decimal) | `calls[n].value` (hexadecimal) | | `atomic_batch` in `required_capabilities` | `atomicRequired: true` | | `kind`, `revert_decode`, `simulation_failure_policy`, `extensions` | no equivalent; dropped | When converting to a plan, `caip2_chain_id` is derived from `chainId`, the resolved `from` account becomes `sender`, steps are numbered consecutively, and every step's `kind` is `execution`. Transaction quantities are converted to decimal strings, omitted `data` and `value` become `"0x"` and `"0"`, and `atomicRequired: true` becomes `atomic_batch`. The resulting plan is subject to the same validation as any other plan. Calls without a destination cannot be represented. This mapping does not preserve all metadata. In particular, changing a step's `kind` changes the plan digest. A round trip preserves the digest only if all digest-covered fields are preserved. Capabilities without an equivalent in the destination format cannot be preserved by this mapping. The mapping alone does not establish execution compatibility. EIP-5792 does not require an atomic batch to use a single transaction, whereas `atomic_batch` does. Non-atomic execution of a plan also requires waiting for each preceding step to be mined successfully and stopping on failure. Consumers executing a converted plan remain subject to these requirements. ## Rationale ### Document format EIP-5792 specifies a wallet interface for submitting calls. This ERC defines a document the wallet can retrieve independently of the agent's tool calls. The agent selects a prepared operation by its reference; the producer supplies the call sequence and the wallet performs validation and authorization. The same format can also be used by non-agent applications. Passing a reference keeps bulk data transfer outside model inference. This reduces both token usage and the time spent processing and generating tool payloads. The bytes still need to reach the wallet, but they travel through ordinary retrieval rather than model output. Restricting each plan to one sender and one chain allows one wallet account to validate and execute the complete sequence. Cross-chain and multi-account workflows can coordinate several plans outside this format. ### Quantity encoding Decimal strings avoid the precision limits of JSON numbers in implementations using IEEE-754 doubles. They also allow quantities to be read without converting from hexadecimal. Addresses and calldata remain hexadecimal byte strings. The redundant `caip2_chain_id` supports configuration that uses namespaced chain identifiers; requiring agreement with `chain_id` prevents conflicting values. ### Digest scope Gas is excluded because consumers estimate it against current state. This allows gas estimation to change without changing the identity of the calls. The wallet remains responsible for gas limits and fee policy. The plan digest and reference integrity digest serve different purposes. The former identifies the ordered calls and labels; the latter detects any change to the serialized document, including fields excluded from the plan digest. ### Required capabilities Ignoring an unsupported capability could change execution semantics. For example, executing an approval, swap, and cleanup as separate transactions can leave an allowance active if a later step fails. Requiring consumers to reject unsupported capabilities prevents this implicit fallback. ### Reference envelope A separate envelope provides a URL, document type, byte length, and integrity digest in a transport-independent format. Keccak256 is used consistently for plan and reference digests. Subresource Integrity alone does not define the document types or retrieval rules needed here. Unknown envelope members are ignored to allow additional metadata. Unknown integrity members are rejected because they may express verification requirements the consumer does not implement. Redirects are prohibited to keep retrieval and provenance checks limited to the URL supplied in the reference. ## Backwards Compatibility This proposal introduces a new document format and no changes to the chain, to existing interfaces, or to deployed contracts. Producers and consumers that do not implement it are unaffected. Consumers may support execution plans without supporting typed-data signature requests. Existing transaction signing permissions MUST NOT implicitly authorize typed-data signatures. EIP-5792 conversion is described in the Specification. EIP-7702 is one way to provide atomic execution and is not required by this format. ## Test Cases [Execution-plan test vectors](../assets/eip-8410/digest-test-vectors.json) cover canonical serialization and digest computation. Each case gives a plan, the exact canonical byte string its digest is taken over, and the resulting digest. | Case | Digest | | --- | --- | | A one-step plan with empty calldata | `0x93aeec006e55dfe0f54041d53c94387e08c504d4f3b3826cd3426dbc7da38ea5` | | A two-step plan, whose step 2 carries a gas hint the canonical form omits | `0x13c4b058741ee7bdfe5a51825c71bd205859b89436505a15adf60bd3f2281deb` | | The same two-step plan with different `gas`, `extensions`, `required_capabilities`, and a different failure instruction | `0x13c4b058741ee7bdfe5a51825c71bd205859b89436505a15adf60bd3f2281deb` | | The same two-step plan with the final byte of step 2 calldata changed | `0x5bf27f490fa44998b6386dfe1b0fae6df0c1b2a9604b69d83bbbbd7a6d285549` | The third case changes only digest-excluded fields. The fourth changes calldata and therefore has a different digest. [Typed-data signature request vectors](../assets/eip-8410/typed-data-test-vectors.json) cover the request digest, EIP-712 domain binding, delivery constraints, and structural validation. The [fixture runner](../assets/eip-8410/typed-data-tests/README.md) checks both document types and compares EIP-712 hashes from ethers and viem. ## Reference Implementation Ekubo Wallet (`github.com/EkuboProtocol/wallet`) implements plan validation and digest computation in `crates/ekubo-wallet-core/src/core/execution_plan.rs` and reference retrieval in `crates/ekubo-wallet-core/src/plan_fetch.rs`. It records the plan digest with approvals and transaction hashes and uses EIP-7702 batches for atomic multi-step execution. The Ekubo protocol's producer at `mcp.ekubo.org` returns execution plans and artifact references for transfers, swaps, liquidity, and yield operations. ## Security Considerations ### Authorization Plans and references are untrusted input. Consumers MUST validate the document and evaluate their own authorization rules against its calls. They MUST NOT use `kind`, `instruction`, `extensions`, or `revert_decode` output as authorization inputs. Producer descriptions can misrepresent calldata, and a relay may modify or replace a request. Neither digest authenticates the producer or proves approval. Reference verification detects changes relative to the supplied envelope; a relay able to replace both the document and its reference can supply a different valid pair. The plan digest excludes `required_capabilities`. Equal digests therefore do not imply equal execution requirements or interchangeable approvals. Consumers must evaluate those requirements independently of the digest. Gas hints are also excluded and MUST be independently estimated before use. ### Simulation and execution State changes between simulation and inclusion may cause a call to revert or execute differently. Consumers SHOULD re-simulate immediately before signing and MUST NOT treat a stored simulation result as authorization to broadcast. An EIP-7702 delegation persists beyond the transaction that installs it. A consumer using delegation for batching needs to account for the delegated implementation's continuing authority. ### Resource limits Unbounded documents, calldata, and ABI fragments can exhaust memory or stall parsing. Consumers MUST apply limits before parsing where the transport allows and MUST reject oversized input rather than truncate it. Retrieval limits apply to the retrieval implementation, including concurrent requests and timeouts. A consumer decoding a content encoding MUST bound the decoded stream as it decodes. ### Revert decoding Both the ABI fragment and the revert data may be attacker-controlled. Consumers MUST bound the fragment, MUST isolate decoder failures, and MUST escape decoded output before displaying it as untrusted text. ### Local file access A `file` reference causes the consumer to read with its own filesystem privileges. Directory restrictions limit which files a relay can request. Missing, oversized, mismatched, and unparsable input must produce indistinguishable failures to avoid exposing file contents through errors. Even with uniform failures, a guessed path and digest can confirm file contents if the consumer accepts the document. Special files can block or have side effects when read. Consumers SHOULD apply read timeouts and treat stalled reads as failures. Consumers that do not support local file access MUST reject `file` references. ### Server-side request forgery Fetching caller-supplied URLs can expose services on the consumer's network. Consumers MUST check resolved addresses rather than hostnames alone and SHOULD connect to an address they checked. Otherwise, DNS rebinding can change a public address to a private address between validation and connection. The transport rules prohibit redirects, credentials, and non-default ports, and prevent response bodies from being disclosed in errors. ### Reference integrity Consumers MUST reject a digest mismatch and MUST NOT fall back to using the retrieved bytes. Producers SHOULD serve immutable bodies at each URL. Integrity verification and provenance are separate: a `data:` URI has no producer provenance, and a `file` URI establishes only a local source. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).