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.
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 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_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.
[(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.
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.
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.