DCIPs/EIPS/eip-5792.md

392 lines
15 KiB
Markdown

---
eip: 5792
title: Wallet Function Call API
description: Adds JSON-RPC methods for sending multiple function calls from the user's wallet, and checking their status
author: Moody Salem (@moodysalem)
discussions-to: https://ethereum-magicians.org/t/eip-5792-wallet-abstract-transaction-send-api/11374
status: Draft
type: Standards Track
category: Interface
created: 2022-10-17
---
## Abstract
Defines new JSON-RPC methods for dapps to send batches of function calls from a user's wallet, as well as check
on the status of those calls. The new methods are more abstract in regard to the underlying transactions than the
existing transaction sending APIs to allow for differences between wallet implementations, e.g.
smart contract wallets utilizing [EIP-4337](./eip-4337.md) or EOA wallets that support bundled transactions
via [EIP-3074](./eip-3074.md). Dapps may use this more abstract interface to support different kinds of wallets without
additional effort, as well as provide a better UX for sending bundles of function calls (
e.g. [EIP-20](./eip-20.md) `#approve` followed by a contract call).
## Motivation
The current methods used to send transactions from the user wallet and check their status are `eth_sendTransaction`
and `eth_getTransactionReceipt`.
These methods are keyed on the hash of the on-chain transaction, i.e. `eth_sendTransaction` returns an transaction hash
computed from the transaction parameters, and `eth_getTransactionReceipt` takes as one argument the transaction hash.
When the transaction hash changes, for example when a user speeds up the transaction in their wallet, the transaction
hash that the dapp is aware of becomes irrelevant. There is no communication delivered to the dapp of the change in
transaction hash, and no way to connect the old transaction hash to the new one, except by the user account and
transaction nonce. It is not trivial for the dapp to find all signed transactions for a given nonce and account,
especially for smart contract accounts which usually store the nonce in a contract storage slot. This happens more
frequently with smart contract wallets, which usually use a third party relaying service and automatically re-broadcast
transactions with higher gas prices.
These methods also do not support sending multiple function calls related to a single action. For example, when swapping
on Uniswap, the user must often call [EIP-20](./eip-20.md) `#approve` before calling the Uniswap router contract to
swap. The dapp has to manage a complex multi-step asynchronous workflow to guide the user through sending a single swap.
The ideal UX would be to bundle the approve call with the swap call, and abstract the underlying approve function call
from the user.
The current interface also does not work well for account abstracted wallets (e.g. [EIP-4337](./eip-4337.md)
or [EIP-3074](./eip-3074.md)), which often involve a relaying service to sign and broadcast the transaction that
triggers the function calls from the user's wallet. In these cases the actual transaction hash may not be known at the
time of user signing, but must still be returned by `eth_sendTransaction`. The transaction hash returned
by `eth_sendTransaction` in these cases is unlikely to be relevant to the transaction hash of the included transaction.
The existing interface also provides no way to delay the resolution of the transaction hash, since it is used as the key
of the transaction tracked by the dapp. Dapps often link to the block explorer for the returned transaction hash, but in
these cases the transaction hash is wrong and the link will not work.
Dapps need a better interface for sending batches of function calls from the user's wallet so they can interact with
wallets without considering the differences between wallet implementations. These methods are backwards compatible with
existing EOA wallets. EOA wallets may send bundles of function calls as individual transactions. The goal of the new
interface is to be a more stable and flexible interface that enables a better user experience, while wallet
implementations evolve over time.
## Specification
Three new JSON-RPC methods are added. Dapps may begin using these methods immediately, falling back
to `eth_sendTransaction` and `eth_getTransactionReceipt` when they are not available.
### `wallet_sendFunctionCallBundle`
Requests that the wallet deliver a group of function calls on-chain from the user's wallet.
The wallet:
- MUST send these calls in the order specified in the request.
- MUST send the calls on the request chain ID.
- MUST stop executing the calls if any call fails
- MUST NOT send any calls from the request if the user rejects the request
- MAY revert all calls if any call fails
- MAY send all the function calls as part of one transaction or multiple transactions, depending on wallet capability.
- MAY reject the request if the request chain ID does not match the currently selected chain ID.
- MAY reject the request if the `from` address does not match the enabled account.
- MAY reject the request if one or more calls in the bundle is expected to fail, when simulated sequentially
#### `wallet_sendFunctionCallBundle` OpenRPC Specification
```yaml
- name: wallet_sendFunctionCallBundle
summary: Sends a bundle of function calls from the user wallet
params:
- name: Function calls
required: true
schema:
type: object
title: Send function call bundle request
required:
- chainId
- from
- calls
properties:
chainId:
title: chainId
description: Chain ID that these calls should be sent on
$ref: '#/components/schemas/uint'
from:
title: from address
description: The address from which the function calls should be sent
$ref: '#/components/schemas/address'
calls:
title: calls to make
description: The calls that the wallet should make from the user's address
type: array
minItems: 1
items:
title: function call
description: A single function call
type: object
required:
- gas
- data
to:
title: to address
description: The address that is being called
$ref: '#/components/schemas/address'
gas:
title: gas limit
description: The gas limit for this particular call
$ref: '#/components/schemas/uint'
value:
title: value
description: How much value to send with the call
$ref: '#/components/schemas/uint'
data:
title: data
description: The data to send with the function call
$ref: '#/components/schemas/bytes'
result:
name: Bundle identifier
schema:
type: string
maxLength: 66
```
##### `wallet_sendFunctionCallBundle` Example Parameters
```json
[
{
"chainId": 1,
"from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"calls": [
{
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"value": "0x9184e72a",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
},
{
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0xdefa",
"value": "0x182183",
"data": "0xfbadbaf01"
}
]
}
]
```
##### `wallet_sendFunctionCallBundle` Example Return Value
The identifier may be the hash of the call bundle, e.g.:
```json
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
```
The identifier may be a numeric identifier represented as a hex string, e.g.:
```json
"0x01"
```
The identifier may be a base64 encoded string:
```json
"aGVsbG8gd29ybGQ="
```
### `wallet_getBundleStatus`
Returns the status of a bundle that was sent via `wallet_sendFunctionCallBundle`. The identifier of the bundle is the
value returned from the `wallet_sendFunctionCallBundle` RPC. Note this method only returns a subset of fields
that `eth_getTransactionReceipt` returns, excluding any fields that may differ across wallet implementations.
#### `wallet_getBundleStatus` OpenRPC Specification
```yaml
- name: wallet_getBundleStatus
summary: Sends a bundle of function calls from the user wallet
params:
- name: Bundle identifier
required: true
schema:
type: string
title: Bundle identifier
result:
name: Call status
schema:
type: object
properties:
calls:
type: array
items:
title: call status
description: Status of the call at the given index
type: object
status:
title: The current status of the call
enum:
- CONFIRMED
- PENDING
receipt:
type: object
required:
- success
- blockHash
- blockNumber
- blockTimestamp
- gasUsed
- transactionHash
properties:
logs:
type: array
items:
title: Log object
type: object
properties:
address:
$ref: '#/components/schemas/address'
data:
title: data
$ref: '#/components/schemas/bytes'
topics:
title: topics
type: array
items:
$ref: '#/components/schemas/bytes32'
success:
type: boolean
title: Whether the call succeeded
blockHash:
title: The hash of the block in which the call was included
$ref: '#/components/schemas/bytes32'
blockNumber:
title: The number of the block in which the call was included
$ref: '#/components/schemas/uint'
blockTimestamp:
title: The timestamp of the block in which the call was included
$ref: '#/components/schemas/uint'
gasUsed:
title: How much gas the call actually used
$ref: '#/components/schemas/uint'
transactionHash:
title: The hash of the transaction in which the call was made
$ref: '#/components/schemas/bytes32'
```
##### `wallet_getBundleStatus` Example Parameters
As with the return value of `wallet_sendFunctionCallBundle`, the bundle identifier may be any string of max length `66`.
It may be the hash of the call bundle:
```json
[
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
]
```
It may contain a numeric identifier as a hex string:
```json
[
"0x01"
]
```
It may be a base64 encoded string:
```json
[
"aGVsbG8gd29ybGQ="
]
```
##### `wallet_getBundleStatus` Example Return Value
```json
{
"calls": [
{
"status": "CONFIRMED",
"receipt": {
"logs": [
{
"address": "0xa922b54716264130634d6ff183747a8ead91a40b",
"topics": [
"0x5a2a90727cc9d000dd060b1132a5c977c9702bb3a52afe360c9c22f0e9451a68"
],
"data": "0xabcd"
}
],
"success": true,
"blockHash": "0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a",
"blockNumber": "0xabcd",
"blockTimestamp": "0xabcdef",
"gasUsed": "0xdef",
"transactionHash": "0x9b7bb827c2e5e3c1a0a44dc53e573aa0b3af3bd1f9f5ed03071b100bb039eaff"
}
},
{
"status": "PENDING"
}
]
}
```
### `wallet_showBundleStatus`
Requests that the wallet present UI showing the status of the given bundle. This allows dapps to delegate the
display of the function call status to the wallet, which can most accurately render the current status of the bundle.
This RPC is intended to replace the typical user experience of a dapp linking to a block explorer for a given
transaction hash.
- The wallet MAY ignore the request, for example if the wallet is busy with other user actions.
- The wallet MAY direct the user to a third party block explorer for more information.
#### `wallet_showBundleStatus` OpenRPC Specification
```yaml
- name: wallet_showBundleStatus
summary: Requests that the wallet show the status of the bundle with the given identifier
params:
- name: Bundle identifier
required: true
schema:
type: string
maxLength: 66
result:
name: Empty
schema:
type: "null"
```
##### `wallet_showBundleStatus` Example Parameters
```json
[
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
]
```
##### `wallet_showBundleStatus` Example Return Value
```json
null
```
## Rationale
Account abstracted wallets, either via [EIP-3074](./eip-3074.md) or [EIP-4337](./eip-4337.md) or other specifications,
have more capabilities than regular EOA accounts.
The `eth_sendTransaction` and `eth_getTransactionReceipt` methods limit the quality of in-dapp transaction
construction and status tracking. It's possible for dapps to stop tracking transactions altogether and
leave that UX to the wallet, but it is a better UX when dapps provide updates on transactions constructed within the
dapp, because dapps will always have more context than the wallets on the action that a set of calls is
meant to perform. For example, an approve and swap might both be related to a trade that a user is attempting to make.
Without these APIs, it's necessary for a dapp to represent those actions as individual transactions.
## Backwards Compatibility
Wallets that do not support the following methods should return error responses to the new JSON-RPC methods.
Dapps MAY attempt to send the same bundle of calls via `eth_sendTransaction` when they receive a not implemented
call, or otherwise indicate to the user that their wallet is not supported.
## Security Considerations
Dapp developers MUST treat each call in a bundle as if the call was an independent transaction.
In other words, there may be additional untrusted transactions between any of the calls in a bundle.
The calls in the bundle may also be included in separate, non-contiguous blocks. There is no constraint over how long
it will take a bundle to be included. Dapps must encode deadlines in the smart contract calls as they do today.
Dapp developers MUST NOT assume that all calls are sent in a single transaction.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).