---
eip: 7867
title: Flow Control Wallet Call Capability
description: An EIP-5792 capability providing atomicity and flow control configuration.
author: Sam Wilson (@SamWilsn) <sam@binarycake.ca>
discussions-to: https://ethereum-magicians.org/t/wallet-sendcalls-capability-flow-control/22624
status: Stagnant
type: Standards Track
category: Interface
created: 2025-01-17
requires: 5792
---

## Abstract

This proposal extends [EIP-5792](./eip-5792.md) 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

```json
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "optional": {
        "type": "boolean"
      },
      "atomicity": {
        "enum": ["strict", "loose", "none"]
      }
    }
}
```

###### Example Request

```js
[
  {
    "version": "1.0",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "chainId": "0x01",
    "calls": [],
    "capabilities": {
      "flowControl": {
        "atomicity": "loose"
      }
    }
  }
]

```


##### Call-scope

###### Schema

```json
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "optional": {
        "type": "boolean"
      },
      "onFailure": {
        "enum": ["rollback", "halt", "continue"]
      }
    }
}
```

###### Example Request

```js
[
  {
    "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

```json
{
    "$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

```js
{
    "0x1": {
        "flowControl": {
            "loose": ["halt", "continue"],
            "strict": ["continue"]
        }
    }
}
```

### Concepts

#### Call Failure

<!-- TODO: is a "call failure" the same as a "revert"? -->

#### 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:

 * the batch-scope `flowControl` capability **is not** present; and
 * a call-scope `flowControl` capability **is** present.

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:
>
> * MUST NOT await for any calls to be finalized to complete the batch
> * MUST submit multiple calls as an atomic unit in a single transaction
> * MAY revert all calls if any call fails
> * MUST NOT execute any further calls after a failed call
> * MAY reject the request if one or more calls in the batch is expected to
>   fail, when simulated sequentially

##### Added Requirements

The wallet:

###### Batch Atomicity

* MAY break the batch over multiple transactions.
* MUST treat a missing batch-scope `atomicity` level as equivalent to `strict`.
* MUST provide strict guarantees (as defined above) when the batch-scope
  `atomicity` is `strict`.
* MUST provide _at least_ loose guarantees (as defined above) when the
  batch-scope `atomicity` is `loose`.
* MUST provide _at least_ the in-order call inclusion guarantee (as defined
  above) when the batch-scope `atomicity` is `none`.
* MAY provide loose guarantees (as defined above) when the batch-scope
  `atomicity` is `none`.
* MAY provide strict guarantees (as defined above) when the batch-scope
  `atomicity` is `loose` or `none`.
* MUST rollback the batch if one or more critical calls (as defined above) fail.
* MUST NOT rollback the batch if zero critical calls (as defined above) fail.
  * In other words, if the only failures are non-critical, the successful calls
    have to appear on chain.
* MUST NOT execute a call (or ever allow a call to be executed) more than once.

###### Flow Control

* MUST treat a missing call-scope `flowControl` capability as equivalent to
  setting `onFailure` to `rollback`.
* MUST treat a missing call-scope `onFailure` mode as equivalent to `rollback`.
* MUST NOT execute any calls following a failed call with `onFailure` set to
  `halt`.
* MUST continue to execute calls as normal following a failed call with
  `onFailure` set to `continue`.

###### Errors

* MUST reject (with error code `REJECTED_LEVEL`) batches containing at least one
  critical call if the batch requests an atomicity level that the wallet can
  provide but the user rejected (such as might happen with an
  [EIP-7702](./eip-7702.md) set code transaction.)
  * Note that this only applies to user rejections specifically because of
    atomicity. It does not change the behaviour for batches rejected for other
    reasons. This error code MUST NOT be used for other rejection reasons.
* MUST reject (with error code `UNSUPPORTED_LEVEL`) batches containing at least
  one critical call if the batch requests an atomicity level the wallet cannot
  provide for any reason other than user rejection.
  * Wallets supporting `strict` but not `loose` SHOULD NOT reject `loose`
    batches and SHOULD instead upgrade the request to strict atomicity.
  * Note that a batch with exactly one call _always_ satisfies the requirements
    of strict atomicity.
* MUST reject (with error code `UNSUPPORTED_ON_FAIL`) batches containing
  unsupported `onFailure` modes.
* MUST reject (with error code `UNSUPPORTED_FLOW`) batches containing
  unsupported combinations/orderings of call-scope `onFailure` modes.
    * Wallets MUST reject `rollback` when used in a `none` batch, even if the
      batch is upgraded to `loose` or `strict` atomicity. This also applies to
      calls that do not specify an explicit `onFailure` mode.
* MAY reject (with error code `ROLLBACK_EXPECTED`) the request if the batch is
  expected to be rolled back.
* SHOULD inform the user before executing any calls if any call in the batch is
  expected to fail.


#### `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:

* There are no changes when called with a batch without this capability enabled;
  and
* Even if the behaviour of the batch is not changed from the default (eg.
  setting `atomicity` to `strict` and omitting the `flowControl` capability for
  all calls), the following changes still apply.

##### Removed Requirements

These requirements defined in EIP-5792 are removed:

> * If a wallet executes multiple calls **atomically** in a single transaction,
>   `wallet_getCallsStatus` MUST return an object with a `receipts` field that
>   contains a single transaction receipt, corresponding to the transaction
>   in which the calls were included.
> * If a wallet executes multiple calls **non-atomically** through some
>   `capability` defined elsewhere, `wallet_getCallsStatus` MUST return an
>   object with a `receipts` field that contains **an array of receipts** for
>   all transactions containing batch calls that were included onchain. This
>   includes the batch calls that were included on-chain but eventually
>   reverted.


##### Added Requirements

###### Capabilities

The returned capabilities object:

* MUST contain a `flowControl` key set to exactly the boolean `true`.
    * It may be tempting to include additional detail about the status of
      individual calls here, but don't. Instead use a multi-status capability
      defined elsewhere.

###### Receipts

The returned `receipts` array:

* MUST NOT contain more than one receipt for the same transaction.
* SHOULD NOT contain receipts for transactions without a call from the requested
  batch.
* MUST contain exactly one receipt capturing each successful call.
    * Multiple calls MAY be captured in one receipt, but the successful
      execution of one call MUST NOT be captured by multiple receipts.
    * Given two calls (_A_ and _B_) in a batch, the following are non-exhaustive
      example combinations of calls-per-receipt. Each `(...)` is a receipt from
      a single transaction.
        * Valid Examples:
            * `[(successful A, successful B)]`
            * `[(successful A), (successful B)]`
            * `[(successful A, unsuccessful B), (successful B)]`
            * `[(unsuccessful A), (successful A), (successful B)]`
        * Invalid Examples:
            * `[(successful A, unsuccessful B), (successful A, successful B)]`
            * `[(successful A, successful A), (successful B)]`
* MAY contain one or more receipts capturing each failed call.
    * For example, the wallet may retry a transaction with a higher gas limit.
      Both the failed and successful transaction receipts can be included,
      though only the successful receipt must be.
* SHOULD be stable over multiple `wallet_getCallsStatus` requests, with only new
  receipts being appended.
    * For example:
        * `[(unsuccessful A)]` followed by `[(unsuccessful A), (successful A)]`
        is valid; but
        * `[(unsuccessful A)]` followed by `[(successful A)]` should be avoided.


##### 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:

 * At least one call in the batch has been included on chain; and
 * The batch is not complete.

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:

* At least one call in the batch has been included and succeeded;
* At least one call in the batch with an `onFailure` mode of `continue`
   has been included and failed;
* No calls with an `onFailure` mode of `rollback` have been included and failed;
* No calls with an `onFailure` mode of `halt` have been included and failed;
  and
* The batch is complete.

###### `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:

* At least one call in the batch has been included and succeeded;
* At least one call in the batch with an `onFailure` mode of `halt` has been
  included and failed;
* No calls with an `onFailure` mode of `rollback` have been included and failed;
  and
* The batch is complete.

#### `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:

* MAY respond with one, two, or three `atomicity` levels.
* MAY respond with one, two, or three `onFailure` modes in each `atomicity`
  level. The levels do not need to support the same modes.
* MUST include the particular atomicity / onFailure combination if it is
  supported _at all_. For example, if particular orderings are impossible—say
  `rollback` before `halt` is fine, but `halt` before `rollback` is not—then
  both `rollback` and `halt` have to be included in the array.

##### 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.

```js
{
    "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`.

```js
{
    "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`.

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

### Error Codes

| Name                  | Value         |
| --------------------- | ------------- |
| `INVALID_SCHEMA`      | <!-- TODO --> |
| `MISSING_CAP`         | <!-- TODO --> |
| `REJECTED_LEVEL`      | <!-- TODO --> |
| `UNSUPPORTED_LEVEL`   | <!-- TODO --> |
| `UNSUPPORTED_ON_FAIL` | <!-- TODO --> |
| `UNSUPPORTED_FLOW`    | <!-- TODO --> |
| `ROLLBACK_EXPECTED`   | <!-- TODO --> |

## Rationale
<!--
  The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages.

  The current placeholder is acceptable for a draft.

  TODO: Remove this comment before submitting
-->

TBD

## Backwards Compatibility

<!--

  This section is optional.

  All EIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The EIP must explain how the author proposes to deal with these incompatibilities. EIP submissions without a sufficient backwards compatibility treatise may be rejected outright.

  The current placeholder is acceptable for a draft.

  TODO: Remove this comment before submitting
-->

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](../LICENSE.md).
