EIP-5792 - Wallet Function Call API

My concern with the atomicity feature is that dapps will build things with this API that only work for the strict level of atomicity, which is bad for users of wallets that do not support it, i.e. the majority of Ethereum users today

And anything that is strict can probably be made into none by just architecting the contract differently–I can’t think of a counterexample to this point. So there’s no reason to actually require strict.

strict is also a bit funky when the combined calls require more than block gas limit and must be sent as multiple transactions. Maybe there should be an additional specification that the request should reject if the wallet cannot make all the calls in a single block, or maybe we want to support that use case of many calls that must be made in separate blocks

So those dapps will either struggle to get adoption from current Ethereum users, or build a fallback. I think it’s better to standardize these features so that dapps using them will automatically support wallets adding atomicity instead of relying on bespoke integrations.

Imagine I wrote a dapp today that needed strict atomicity, and I write code that supports Gnosis Safe using their API. I die and obviously am unable to continue updating my dapp. Some time passes, and we finally get EIP-3074. Now all wallets can implement support for strict atomicity, but my dapp is stuck only supporting Gnosis Safe.

For sure! I’m thinking of scenarios like complex DeFi arbitrage or rescuing NFTs from hacked accounts, where the transaction sender doesn’t have control over what they’re interacting with.

If a wallet can’t ensure the operations are performed atomically (because it’s an EOA, because of the gas limit, or for any other reason), the RPC call must fail.

In absence of the strict atomicity request parameter, wouldn’t you build your dapp and contracts to work for all wallets, i.e. using the default none behavior? In that case, all wallets would support it on day 0, and the UX for gnosis safe would be unchanged. The atomicity request parameter enables dapp developers to build dapps that are only supported by certain kinds of wallets, even though they could just build it for all wallets without sacrificing anything.

I think this is out of scope for this EIP, for a bunch of reasons in no particular order:

  • These use cases have more to do with using a private relay than bundling function calls
  • E.g. in cases where a rescue does require multiple function calls in a single transaction, and there is no authorization, you can do that in the constructor of a contract deployment (no bundling necessary)
  • This is a bespoke use case that is usually handled with scripting
  • The mempool and sequencing behavior is closely related to the underlying blockchain, whereas this API is meant to abstract those details away from the dapp developer as much as possible
  • The wallet can have a toggle to use a private relay without any change to the RPC interface

This does push a lot of complexity onto the dapp in the rare case that the set of function calls costs more than block gas limit. It’s nice for dapp developers to not have to worry about the total size of their batch. Wallets will also have different amounts of overhead for batch function calls, so dapps cannot predict the actual cost of sending the entire batch of function calls. But I wouldn’t be strongly opposed to adding a constraint that the RPC fails if it would be impossible to include all the function calls under the block gas limit.

It’s possible today to build a dapp that relies on strict atomicity, but the specifics of how to do that are unique to each smart contract wallet. I’d really like to see some standardization there.

There are zero dapps that could be built with strict atomicity that could not be built without it?

Agreed! For the "atomicity": "none" case, I think it’s fine to split the batch over multiple blocks.

I believe so. Even if a smart contract is not EOA-friendly, it can always be wrapped. For example, consider an ERC20 without an #approve function. To spend this token you would have to call #transfer and then atomically call another contract. You can work around this: deploy a contract to hold the tokens, transfer the tokens to that contract, and then execute calls from the holder contract.

For every example I can think of, there is a different pattern that does not require atomicity across the function calls. It might be possible to formally prove this point. Assuming the point is true, I don’t think there’s any reason for a dapp developer to build a feature that requires it, so the request parameter only acts as a footgun.

While it seems very likely that a contract that enforces strict atomicity can be constructed for every scenario, there’s room for dapps to use the strict flag, even if it does fail.

Dapp developers may want to optimistically enforce a strict bundle, and upon failure, ask the user to acknowledge the risk they will take by downgrading to ‘loose’ or no atomicity. If a Dapp presents a user with one button to click and sign multiple transactions, it seems like pretty poor UX if part of the call reverts. This is especially true for EOA users, whose current expectation is that each signature from their wallet results in one (atomic) transaction.

Instead of building an on-chain abstraction over every contract in the Dapp, developers can leverage account abstraction to create one-signature contract compositions for users (including for contracts that the dapp developer was unaware of, provided the user can import an ABI). Optimistically, the developer can request the bundle using "atomicity": "strict", then fall back to "loose" or "none" along with a warning and explanation that the atomic composition implied by the single signature for the queued transactions will be degraded.

Can you share an example of when you’d be ok with loose or no atomicity even though you wanted strict?

This case sounds like it’s not known by the dapp whether atomicity is needed or not, but that’s a really advanced user and the user should specify the atomicity requirement in this case… but wouldn’t they just not send these transactions separately if their wallet doesn’t support it or do some other workaround? Seems like a slightly convoluted use case AFAICT

The most concrete use case that came to mind was a block explorer with a transaction queue. If it provides users visiting with a way to visit a few contracts and build a set of transactions to execute, a naive user would probably expect the batch to execute together, especially with only one wallet signature for the full queue, and so the default behavior should be strict atomicity.

I agree that the ‘loose’ atomicity is a rather weak construction, as it encourages users to route batches to private mempools, and feels like an abstraction leak that may change with underlying things like AUTH/AUTHCALL or single-slot finality. I’d be happy to hear more arguments in it’s favor, but having a bollean flag for enforcing all-or-nothing execution of a bundle seems like a good abstraction of underlying wallet mechanics for developers of tools like block explorers or general purpose contract interfaces.

I’m operating under the assumption that as the EVM gets used by more people, competent early adopters will continue using EOAs, but new/less advanced users will onboard to accounts with features like atomic batching. I think we should make it easy for dapps to cater to them, and warn EOA users that “the bundle may only succeed in part or be split up, would you like to send the transactions anyway”.

Essentially I’d like the line in the EIP

MAY revert all calls if any call fails

to instead be

MUST revert all calls if any call fails when "atomic": "true"

1 Like

For something like complex transaction building (e.g. furucombo) you usually want to involve a smart contract to introduce logic between the calls (e.g. all or nothing, these 2 calls are optional, take output of this call and pass as input to another call). Furucombo still greatly benefits from this EIP because the spending approvals can be batched with the function calls you want to make. The one drawback is the calls do not come from the user’s wallet. But I’m not sure if support for that is really required for any meaningful transaction building use case.

I’m on the fence. I want this interface to be as narrow and abstract as possible so as to prevent dapps from building branching logic based on wallet capabilities, which either adds work for the dapp developer or excludes wallets. The “atomic” flag definitely leaks wallet implementation details to the dapp. But if people are strongly in favor of this change I’ll add it–I’m definitely more ok with atomic flag than 3 different levels of atomicity which feels even leakier.

My two cents on this is I’d think early adopters will want account abstracted wallets for better security/recovery, or other features they offer. I myself am a user of a smart contract wallet, and many of my friends use gnosis safe for self custody. I’m not sure if that changes the calculus.

Yup, makes sense, totally get what’s being asked for here.

1 Like

Hey everyone, wanted to add that we have added support for EIP-5792 for CANDIDE mobile Wallet. We are supporting the three methods:

  • wallet_sendFunctionCallBundle
  • wallet_getBundleStatus
  • wallet_showBundleStatus

Here’s our implementation for other wallet developers to reference:

Documentation for dapp developers wishing to support bundle calls in their app, along with a codesanbox to start experimenting with. We wrote examples for Web3Modal and WalletConnect Standalone:

1 Like

In the absence of this method, does it make sense to add one to the EIP just for checking if this particular set of methods is supported? Reviewing the guide posted by @marc and thinking about this again, I can’t imagine implementing this in a mature dapp and falling back to multiple eth_sendTransaction calls as I originally proposed. There will need to be a transitional period where a dapp needs to support both existing wallet APIs well and the function call bundle API.

Such a method could look like: wallet_isFunctionCallBundleSupported with no parameters, returns true if supported and errors or returns false if not supported.