forked from DecentralizedClimateFoundation/DCIPs
392 lines
15 KiB
Markdown
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).
|