-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update EIP-5792: Propose changes to EIP-5792 #8826
base: master
Are you sure you want to change the base?
Conversation
File
|
}; | ||
``` | ||
|
||
##### Status Codes for `batchStatus` field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should define what a batch is first? it's the first time we use it
Co-authored-by: shahafn <[email protected]>
calls: { | ||
to?: `0x${string}` | undefined; | ||
data?: `0x${string}` | undefined; | ||
value?: `0x${string}` | undefined; // Hex value | ||
chainId?: `0x${string}` | undefined; // Hex chain id | ||
capabilitiesData?: Record<string, any> | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I don't love how this kind of muddles the simplicity of the calls struct. Is the complaint that capabilities makes it hard to specify info on a per call basis. Could we get a better example of when this would be needed 🤔
One example could be an "optional flag" capability that would allow a Smart Contract Wallet to proceed with the batch in case of a revert
This should be possible with a general capability? Or you're saying one call in particular can revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to implement it, if our wallet doesn't have any per-call capability.
Also, an app doesn't have to know about it, unless it needs that capability.
The idea that if someone ever want to add per-call capability, it doesn't mess directly with the "call" structure, or with such params of another capability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the complaint that capabilities makes it hard to specify info on a per call basis?
Yes, that pretty much sums it up. Of course, the capability
could provide an array of details, which would have to match the calls
array's length exactly and contain null
values for calls that do not require extra parameters, which is functionally equivalent but seems like a much more quirky API to me.
Or you're saying one call in particular can revert?
In my example, yes, I was talking about an "atomic" batch with a single "optional" call that should not revert the batch;
It is hard to reason about potential future additional examples, so these are a little hypothetical, but in my opinion, worth considering:
If a "per-call gas limit capability" is defined, it can be used to provide a per-call gas limit;
In case a "multi-chain capability" is defined, the 'capabilitiesData' field can be used to specify a per-call 'chainId' and L2-specific parameters, something like gas_per_pubdata_limit
;
In case a "conditional call capability" is defined, it can provide some description of a condition to include or exclude a call from the batch;
etc.
Does this make sense? Or should dapps never need to go to the trouble of defining per-call parameters for a sendCalls
request?
The purpose of the `batchStatus` field is to provide a short summary of the current status of the batch. | ||
It provides some off-chain context to the array of inner transaction `receipts`. | ||
|
||
| Name | Description | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, thoughts @jxom @lukasrosario ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i think this makes sense if we're talking about single chain. would be tough if multichain.
also not clear to me where DISCARDED
would come up. @forshtat can you share more on how you were thinking this might come up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it sounds similar to "FAILURE" but the difference I had in mind was that a "FAILURE" means the action failed due to on-chain reverts, and "DISCARDED" means the wallet gave up on trying to send the batch to the network.
Maybe the names do not make the distinction clear enough, or do you think the distinction is not worth the extra status code?
One example could be a batch that specifies a paymasterService
capability, and given the wallet need to go through these steps before sending anything on-chain:
The wallet cannot block the wallet_sendCalls()
for all this time, so returns some identifier
and proceeds with preparing the UserOperation.
During the preparation phase the wallet_getCallsStatus
returns PENDING as nothing really happened.
However, if the "Paymaster Service" fails with an error at some stage, wallet could keep retrying to reach it for some time, and the transaction has not technically "failed", but after 2 minutes of trying to reach the "Paymaster Service" it gives up. I thought that this action's status should be marked as "DISCARDED" instead of "FAILURE".
Another example is a UserOperation that has some condition in its validation function. Canonical ERC-4337 limits this use-case a lot, but does not completely prevent it. A wallet may want to accept a batch that is not yet valid, check if it has become valid during the next 2 hours, and mark the entire operation as "DISCARDED" after that.
Does this makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yet another example of "discarded": assume you put a maxFeePerGas that is too low, and after sometime , because the network gas raised, the maxFee becomes too low below the baseFee, and the transaction is completely dropped. If we are sending a "sequence", it can happen to any transaction along the sequence.
Now this transaction didn't technically "fail", since it didn't get anything on-chain - but definitely there's no reason to continue and wait for it.
* MUST NOT await for any calls to be finalized to complete the batch | ||
* 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 calls as part of one or more transactions, depending on wallet capability | ||
* SHOULD stop executing the calls if any call fails | ||
* 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 batch is expected to fail, when simulated sequentially | ||
* MUST reject the request if it contains a `capability` that is not supported by the wallet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I wonder if this is best 🤔 . E.g. why not allow specifying a paymaster which the app would like to be used but is not a necessity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How a wallet can tell if the cap (e.g. paymaster) is required by this app or optional?
The app can always query the wallet caps first, to decide how to continue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capability specs should specify whether they're required or not / how optional ones are handled. paymaster, for example, notes that the app must not assume the provided paymaster service is the one that ends up being used.
so if an app wants the capability to be required, the capability spec should include a required
field, and that is how a wallet can tell if the capability is required by the app. i dont think we should blanket all capabilities to be required by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two issues raised here:
- App providing a "capability" that is not supported by the wallet:
There is a wallet_getCapabilities
method that provides the application with a list of supported "capabilities".
Shouldn't the app that ignores that list and submits a wallet_sendCalls
with a "capability" that is not supported be considered to behave incorrectly? I think it should reject the call, which would prevent dapps from yoloing their sendCalls
requests.
- Wallet discretion to use or ignore a supported "capability" requested by the dapp:
As the API defined here deals with real value and assets, making capabilities required
by default makes a lot of sense to me. If using a paymaster for a transaction is indeed optional, it is trivial to specify optional: true
, but forgetting to set required: true
for a security-oriented capability may be catastrophic.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So @forshtat is the conclusion here that each capability could have an optional:
flag? But passing a capability the wallet doesn't have would be hard failure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So @forshtat is the conclusion here that each capability could have an
optional:
flag? But passing a capability the wallet doesn't have would be hard failure?
In my personal opinion, yes, this seems pretty reasonable.
EIPS/eip-5792.md
Outdated
blockHash: `0x${string}`; | ||
blockNumber: `0x${string}`; | ||
gasUsed: `0x${string}`; | ||
transactionHash: `0x${string}`; | ||
}[]; | ||
capabilitiesData?: Record<string, any> | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it kinda confusing that this is the same phrase as on the request, but would likely be different data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it is per-capability result data, in the results structure. Should it be named capabilitiesResultsData
?
I think it is clear enough, simply because it is in the response .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully support renaming this parameter. capabilitiesResultsData
is a bit long, how about just capabilitiesResults
or capabilitiesStatus
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer clarity even if verbose so I think I like capabilitiesResultsData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer clarity even if verbose so I think I like
capabilitiesResultsData
Done.
@forshtat I'm ok with constraining it to hex, but 32 bytes makes things difficult. For example, in our case the calls id packs the chain id and user op hash together, which we wouldn't be able to do with 32 bytes. Especially with conversations around allowing calls across multiple chains, it would be inevitable that wallets would need to have some more complex state to store + report statuses, which they would not be too happy about. how would you feel about hex of any length? |
Hmm. I assumed the Anyways, maybe instead of "any length" we should just pick a length that makes most sense and just left-pad it with zeroes? |
It does. Because in our case, and in the case of some other wallet teams we've been chatting with, getCallsStatus will route to a server so an app can get a status without opening an extension / popup / app. So either:
Maybe it's inevitable, but we (and presumably other wallets) would like to avoid (2) if possible, as it would require introducing some storage into these servers. 64 bytes would be sufficient if:
While user op hashes hash over the chain id, we would still need to pack the chain id and user op hash together in the identifier so we know which bundler to call. This fails about if we start to talk about making the RPC multichain though, as we could never guarantee all the info we need would fit into whatever max length we specify. It also does not work well for EOAs that would need to send multiple transactions for multiple calls and keep track of multiple transaction hashes. |
Doesn't it raise a concern that the |
Yeah, that's why I'm starting to think it's inevitable that wallets will need to have some type of storage in their backends. Could alternatively tag on extra calldata with relevant info (unfortunately at the cost of the user). For now though assuming we stay with one chain as you have here can we say 64 bytes? This would be sufficient for wallets that can do atomic batching. |
Done. |
EIPS/eip-5792.md
Outdated
@@ -122,38 +136,55 @@ Returns the status of a call batch that was sent via `wallet_sendCalls`. The ide | |||
type GetCallsParams = string; | |||
|
|||
type GetCallsResult = { | |||
status: 'PENDING' | 'CONFIRMED'; | |||
batchId: `0x${string}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
callsId
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should keep the name the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
| Name | Description | | ||
|-----------|----------------------------------------------------------------------------------------------------------------------| | ||
| PENDING | Batch has been received by the wallet but has not completed execution on-chain | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you feel about possibly expanding PENDING
to capture more of the offchain context around the pending state? How can a caller know if it needs to retry a batch with a higher gas price, or if there is some transient infra issue with getting batches included?
For example
PENDING
: Batch has been received by the wallet but has not completed execution on-chainUNDERPRICED
orPENDING_RETRYABLE
: Batch has been received by the wallet but the gas prices are too low for the current chain conditionsSTALLED
: Batch has been received by the wallet but is stuck for non-pricing related reasons; i.e. ERC-4337 bundler is down or the chain has halted. The definition of 'stuck' is left up to the wallet, i.e. batch is priced appropriately but has not been included in >60s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@forshtat would be good to get your thoughts here. imo UNDERPRICED
specifically seems like a reasonable addition, as it could be retried.
@ajhodges i'm not a huge fan of the ambiguity for STALLED
though. to me this is effectively still PENDING
, and each wallet should have their own interpretations of PENDING
taking "too long". if we and others feel strongly about it being its own status, i would prefer to be more opinionated in the spec and define when a PENDING
batch becomes STALLED
, instead of leaving it open-ended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we just left it as PENDING
and CONFIRMED
, and had a sibling statusCode
or detail
property that could be extensible enough to accommodate for what ever addition?
This would make it a lot easier to coerce on frontend apps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we just left it as
PENDING
andCONFIRMED
, and had a siblingstatusCode
ordetail
property that could be extensible enough to accommodate for what ever addition? This would make it a lot easier to coerce on frontend apps.
Should the detail
property be a human-readable string in this case, to provide the error from the wallet directly to the app?
This can be done as a last resort but should not be the default way of providing a data to the application in my opinion. Wallets may have very technical error messages and apps don't want to show them to users, and will try to parse the detail
strings.
If detail
is not supposed to be human-readable, it is still basically a second "status code" parameter and maybe we can merge it back to the statusCode
field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UNDERPRICED
specifically seems like a reasonable addition, as it could be retried.
I don't have any strong feelings against UNDERPRICED
or any other status code we can clearly define.
About UNDERPRICED
specifically, I guess its difference with the regular PENDING
state is that the user needs to confirm a modification to the original request? And if the wallet could just increase the price internally and retry, it would not be different from PENDING
, would it?
If so, maybe a better broader category would be ACTION_REQUIRED
or something like that, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I guess statusCode
might be more realistic than detail
as an application can choose which message they want to display to an end-user (if anything).
Perhaps this could be a similar pattern to HTTP status codes where 2xx
≈ success, 4xx
≈ client error, 5xx
≈ server error, etc. For this case, maybe 1xx
represents PENDING
sub-statuses, 2xx
represents CONFIRMED
sub-statuses, and so on. For example, a status
of PENDING
and a statusCode
of 101
could mean “stalled”, and an application could either choose to show a generic loading indicator, or a loading indicator with “this tx is taking a while, pls wait”.
I guess the motivation of a sub-status field like statusCode
is that it would make the branching logic within an application so much easier — they can rely on the simplicity of status
and choose to show extra information by utilizing statusCode
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the motivation of a sub-status field like statusCode is that it would make the branching logic within an application so much easier — they can rely on the simplicity of status and choose to show extra information by utilizing statusCode.
Yep, exactly. As an app, I want to know if things are healthy. Is my transaction stuck? Is there anything I can do about it if it's stuck? (i.e. underpriced vs stalled). I think the display logic/messaging to the user should be left up to the app, but the wallet can oftentimes provide additional context about what happened to make the UX a little better.
I agree UNDERPRICED
is maybe a bit vague. But the wallet should be the expert on the current chain conditions and the bundle pricing so it's best suited to detect if the calls should be retried (and we shouldn't prescribe any conditions in this EIP). So maybe RETRYABLE
is a better sub-status than UNDERPRICED
, signifying that the calls might go through quicker if you retry. I think I like this better than ACTION_REQUIRED
because technically an underpriced tx can still go through if chain conditions change
List of changes proposed in this PR and their motivations:
calls
field are only those that are shared by all transaction types" and "method only returns a subset of the fields thateth_getTransactionReceipt
returns, excluding any fields that may differ across wallet implementations".There is a rapidly growing list of transaction types, and the definition of the "shared by all" can become confusing.
Specifically, most transaction fields are removed in RIP-7560 transaction type which is part of the Account Abstraction roadmap.
It seems likely that more advanced capabilities will need to provide some data on a per-call basis. One example could be an "optional flag" capability that would allow a Smart Contract Wallet to proceed with the batch in case of a revert, which would otherwise default to requiring success from all calls. Whether this particular capability is needed or not, it serves as an example of why such an option might be needed in the future.
I did not see it in the text anywhere, and ignoring data provided in the capabilities field is obviously extremely dangerous.
For now, it is defined as optional, but probably shouldn't be. This has been debated lately and I don't know what the conclusion is.
One reason is that "any string" seems to be too vague of a definition for an ERC. What about "", "ለከበሩጓደኛመርህመንገድይሆናል", or "😎🤏🕶😳", are these strings okay?
Also, in other programming languages it could be more convenient to use the same identifier type as for transactions, blocks and all other things in Ethereum. If there is a motivation for leaving it as "any string", please at least specify it in the ERC.
It seems very plausible that 'capabilities' will need to provide some additional information to the response. This was previously impossible, which could lead to wallets creating non-standard ways of querying that information.
These rules were probably implied anyway, but now they are explicit.
There is a 'status' field in each 'receipt', which is a hex string. Having another 'status' field, that is not of the same type, appears to be confusing.
It seems like the previous two values did not cover the entire set of possible 'sendCalls' outcomes. The new status values are: PENDING, SUCCESS, PARTIAL, FAILURE and DISCARDED.
It seems like a wallet that supports a very long list of chains would be forced to return an unusually large JSON object as a response. However, as most dapps know which chains they may run on, it makes sense for them to limit the scope of the request.
It is not likely that all 'capabilities' will reach their final version from day one.
As there was no built-in mechanism for versioning, and having no centralized "capabilities authority", it would be pretty likely to see capabilities like 'paymasterServiceV2' and 'atomicBatch_3' proliferate, creating a lot of confusion.