EIP-7867 - Flow Control Wallet Call Capability

Created 2025-01-17
Status Draft
Category Interface
Type Standards Track
Authors
Requires

Abstract

This proposal extends EIP-5792 to allow dapps to downgrade their required atomicity guarantees and control the behaviour after a failed/reverted call. It introduces the batch-scope concept of strict vs. loose atomicity, where a strict batch remains atomic in the face of chain reorgs and a loose batch does not; and the per-call ability to continue after a failed/reverted call (continue) or stop processing (halt).

Motivation

While the base EIP-5792 specification works extremely well for smart contract wallets, it does not allow the expression of the full range of flow control options that wallets can implement. For example, a dapp may only be submitting a batch for gas savings and not care about whether all calls are reverted on failure. A wallet may only be able to offer a limited form of atomicity through block builder backchannels, but that may be sufficient for a trading platform.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

RPC Interface

The following subsections are modifications to the API endpoints from EIP-5792.

If a request does not match the schema defined below, the wallet MUST reject the request with an error code of INVALID_SCHEMA.

wallet_sendCalls

The following JSON Schema SHALL be inserted, in the request object, as values of either the batch-scope or call-scope capabilities objects (as appropriate) with a key of flowControl.

Batch-scope
Schema
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "optional": {
        "type": "boolean"
      },
      "atomicity": {
        "enum": ["strict", "loose", "none"]
      }
    }
}
Example Request
[
  {
    "version": "1.0",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "chainId": "0x01",
    "calls": [],
    "capabilities": {
      "flowControl": {
        "atomicity": "loose"
      }
    }
  }
]
Call-scope
Schema
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "optional": {
        "type": "boolean"
      },
      "onFailure": {
        "enum": ["rollback", "halt", "continue"]
      }
    }
}
Example Request
[
  {
    "version": "1.0",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "chainId": "0x01",
    "calls": [
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x182183",
        "data": "0xfbadbaf01",
        "capabilities": {
            "flowControl": {
                "onFailure": "continue"
            }
        }
      }
    ]
  }
]

wallet_getCapabilities

The following JSON Schema is inserted into the per-chain object returned from wallet_getCapabilities with a key of flowControl.

Schema
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "minProperties": 1,
    "properties": {
        "none": { "$ref": "#/$defs/onFailure" },
        "loose": { "$ref": "#/$defs/onFailure" },
        "strict": { "$ref": "#/$defs/onFailure" }
    },
    "$defs": {
        "onFailure": {
            "type": "array",
            "uniqueItems": true,
            "minItems": 1,
            "items": {
                "enum": ["rollback", "halt", "continue"]
            }
        }
    }
}
Example Response
{
    "0x1": {
        "flowControl": {
            "loose": ["halt", "continue"],
            "strict": ["continue"]
        }
    }
}

Concepts

Call Failure

Rollback

A rollback is informally defined as "causing no meaningful changes on chain." A rolled back batch only makes gas accounting and bookkeeping (eg. nonce) changes. In other words, a rollback is the default behaviour of EIP-5792 when a call fails.

Critical Calls

A critical call is a call that causes the entire batch to rollback on failure, and correspondingly a non-critical call does not. Specifically, a critical call has a call-scope onFailure of rollback (or no onFailure present), while non-critical calls have either halt or continue.

Atomicity Levels

This proposal introduces three atomicity levels: strict, loose, and none; enabled by setting batch-scope atomicity to strict, loose, or none respectively. Strict may also be enabled by omitting atomicity entirely.

Strict atomicity is simply naming the default behaviour of EIP-5792: calls within a single batch MUST be contiguous and applied atomically (or the batch rolled back.)

Loose atomicity, on the other hand, is a weaker guarantee. In the event of a block reorg, any number of calls from the batch MAY appear on chain (possibly interspersed with other transactions). If there are no block reorgs, loose atomicity MUST provide the same guarantees as strict.

The none level of atomicity only provides the guarantee that the calls appear on chain in the order they are in the batch. Any number of calls from the batch MAY appear on chain (possibly interspersed with other transactions).

Behaviour

wallet_sendCalls

The wallet MUST reject wallet_sendCalls requests with error code MISSING_CAP where both:

Note that the above requirement still applies if the call-scope flowControl capability is marked as optional.

When flowControl is present in the batch-scope capabilities, the following changes override the behaviour specified in EIP-5792.

Removed Requirements

These requirements defined in EIP-5792 are removed:

The wallet:

Added Requirements

The wallet:

Batch Atomicity
Flow Control
Errors

wallet_getCallsStatus

When wallet_getCallsStatus is called with a batch identifier corresponding to a batch submitted with the batch-scope flowControl capability enabled, the following changes override the behaviour defined in EIP-5792. Note that:

Removed Requirements

These requirements defined in EIP-5792 are removed:

Added Requirements
Capabilities

The returned capabilities object:

Receipts

The returned receipts array:

Status Codes

This proposal modifies some of the status codes for use with EIP-5792's GetCallsResult.status field, and introduces the following new codes:

Code Description
102 Partially Executed
207 Partial Success

An "included" call, in this section, is defined as having either been successfully or unsuccessfully executed. A call that has been recorded on chain, but has not yet been executed, does not qualify as included. Executed calls contained in batches that may still be rolled back also do not qualify as included.

A batch is "complete" when all of the calls in the batch (up to and including a failed call with an onFailure mode of halt should one be present) have been included and the wallet will not resubmit failed calls.

100 Pending

Status 100 MUST NOT be returned if any calls in the batch have been included on chain.

102 Partially Executed

Status 102 SHALL be returned only when all of the following are true:

Responses with status 102 MUST contain at least one receipt, and SHOULD contain receipts for all transactions with calls that have been included.

Note that a receipt capturing a failed call does not mean the call will ultimately fail. Wallets can resubmit calls (eg. with a higher gas limit), and the call may be executed successfully eventually.

200 Confirmed

Status 200 MUST NOT be returned if any calls in the batch failed (including batch rollback, and the onFailure modes halt/continue).

207 Partial Success

Status 207 SHALL be returned only when all of the following are true:

500 Chain Rules Failure

To clarify, status 500 is the correct code when the batch has rolled back or when all calls are non-critical and have all failed.

If any calls are included and succeeded, one of 200, 207, or 600 should be returned instead.

600 Partial Chain Rules Failure

Status 600 SHALL be returned only when all of the following are true:

wallet_getCapabilities

The response to wallet_getCapabilities indicates what call-scope onFailure modes are supported for each supported batch-scope atomicity level for batches with two or more calls. Support, here, means "natively supports." A wallet that offers strict atomicity but not loose MUST NOT advertise support for loose (even if the wallet will upgrade loose to strict without an error.)

The wallet:

Examples
Plain Externally Owned Account (EOA)

A plain EOA might offer halt functionality by submitting one transaction per block, and continue by submitting all calls at once.

{
    "0x1": {
        "flowControl": {
            "none": [ "halt", "continue" ]
        }
    }
}
Shielded Mempool Externally Owned Account (EOA)

Unlike a plain EOA, a shielded mempool can provide additional guarantees about transaction atomicity. In this example, the wallet only offers the onFailure mode of continue when using none atomicity, but offers all three levels when using loose.

{
    "0x1": {
        "flowControl": {
            "none": [ "continue" ]
            "loose": [ "rollback", "halt", "continue" ]
        }
    }
}
Smart Contract Wallet

In this example, the wallet will service batches specifying none and loose as if they requested strict. Even though the batches will work, the wallet_getCapabilities response does not list none or loose.

{
    "0x1": {
        "flowControl": {
            "strict": [ "rollback" ]
        }
    }
}

Error Codes

Name Value
INVALID_SCHEMA
MISSING_CAP
REJECTED_LEVEL
UNSUPPORTED_LEVEL
UNSUPPORTED_ON_FAIL
UNSUPPORTED_FLOW
ROLLBACK_EXPECTED

Rationale

TBD

Backwards Compatibility

No backward compatibility issues found.

Security Considerations

App developers cannot treat each call in a batch as an independent transaction unless the atomicity level is strict. In other words, there may be additional untrusted transactions between any of the calls in a batch. Calls that failed may eventually flip to succeeding, and vice versa. Even strictly atomic batches can flip between succeeding/failing in the face of a block reorg. The calls in loosely atomic batches can be included in separate, non-contiguous blocks. There is no constraint over how long it will take all the calls in a batch to be included. Apps should encode deadlines and timeout behaviors in the smart contract calls, just as they do today for transactions, including ones otherwise bundled.

Copyright

Copyright and related rights waived via CC0.