-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Changes from 34 commits
40d79c7
a97f3a2
d14ce17
0ecea7b
b364b92
84c6d4d
2c3d75e
b769acc
c837526
b8e75d5
b337a12
e10da9a
0a705ca
41b1024
4253f62
cfe2529
c6f2700
ad36c64
746cc8c
15e27af
00b6242
2ef7d48
baeeb02
20ccae2
f88f6ed
b89b616
6bfea5b
b9c213a
2995ac0
7516f52
80fbeb4
5ff15e7
54a6243
0de3f96
8532ab0
e457851
04d4525
0b506c0
9d6d4e9
534c81b
77f32cc
a3d61d3
7701a64
1be7d7d
a0ff4d7
30c907f
08b645c
9a88ef6
5f5d026
379a2f3
97175a3
3f37aaa
ffe443c
10f6d85
ca8dcc2
2dcee4d
03b1d03
ddf53b1
e8e1400
8fa2c54
29d4589
06a9997
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
eip: 5792 | ||
title: Wallet Call API | ||
description: Adds JSON-RPC methods for sending multiple calls from the user's wallet, and checking their status | ||
author: Moody Salem (@moodysalem), Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack), Dror Tirosh (@drortirosh), Jake Moxey (@jxom), Derek Rein (@arein) | ||
author: Moody Salem (@moodysalem), Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack), Dror Tirosh (@drortirosh), Jake Moxey (@jxom), Derek Rein (@arein), Alex Forshtat (@forshtat) | ||
discussions-to: https://ethereum-magicians.org/t/eip-5792-wallet-abstract-transaction-send-api/11374 | ||
status: Last Call | ||
last-call-deadline: 2024-09-05 | ||
|
@@ -20,7 +20,7 @@ Applications can specify that these onchain calls be executed taking advantage o | |
|
||
The current methods used to send transactions from the user wallet and check their status are `eth_sendTransaction` and `eth_getTransactionReceipt`. | ||
|
||
The current methods used to send transactions from the user wallet and check their status do not meet modern developer demands and cannot accommodate new transaction formats. Even the name–- `eth_sendTransaction`-– is an artifact of a time when nodes served as wallets. | ||
The current methods used to send transactions from the user wallet and check their status do not meet modern developer demands and cannot accommodate new transaction formats. Even the name–- `eth_sendTransaction`-– is an artifact of a time when nodes served as wallets. | ||
|
||
Today, developers want to send multiple calls batched together in a single RPC call, which many smart accounts can, in turn, execute atomically in a single transaction. Developers also want to make use of features afforded by new transaction formats, like paymasters in [ERC-4337](./eip-4337.md) transactions. `eth_sendTransaction` offers no way to do these things. | ||
|
||
|
@@ -31,42 +31,55 @@ In updating to a new set of `wallet_` RPCs, our main goals are to enforce a clea | |
Four new JSON-RPC methods are added: three are for handling batches of onchain calls, and one is for querying support for wallet capabilities, such as to make better use of the three batching methods. | ||
Apps may begin using these first three methods immediately, falling back to `eth_sendTransaction` and `eth_getTransactionReceipt` when they are not available. | ||
|
||
We also define one capability expression for use with the fourth method, which further enables a wallet to promise atomicity of the execution of calls passed and managed by the first three methods. | ||
|
||
### `wallet_sendCalls` | ||
|
||
Requests that a wallet submits a batch of calls. `from` and `chainId` are top-level properties rather than per-call properties because all calls should be sent from the same sender and on the same chain, identified by [EIP-155](./eip-155.md) integers expressed in hexadecimal notation. The items in the `calls` field are only those that are shared by all transaction types. Any other fields that a wallet may need to submit a transaction should be handled by the wallet. | ||
Requests that a wallet submits a batch of calls. `from` and `chainId` are identified by [EIP-155](./eip-155.md) integers expressed in hexadecimal notation. | ||
The items in the `calls` field are simple `{to, data, value}` tuples. | ||
|
||
The capabilities field is how an app can communicate with a wallet about capabilities a wallet supports. For example, this is where an app can specify a paymaster service URL from which an [ERC-4337](./eip-4337.md) wallet can request a `paymasterAndData` input for a user operation. | ||
|
||
Each capability defined in the "capabilities" member can define global or call specific fields. | ||
These fields are set inside this capability's entry in the `capabilities` object. | ||
Each entity in the `calls` field may contain an optional `capabilities` object. | ||
This object allows the applications to attach a capability-specific metadata to individual calls. | ||
|
||
Unless these requirements are explicitly overriden by a certain `capability`, the wallet must adhere to the following rules. | ||
Note that such a `capability` is not in the scope of this EIP and should be defined in its own ERC. | ||
|
||
The wallet: | ||
|
||
* MUST send the calls in the order specified in the request | ||
* MUST send the calls on the same chain identified by the call's `chainId` property | ||
* MUST send the calls on the same chain identified by the request's `chainId` property | ||
* 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 | ||
* 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 | ||
* MUST not execute any further calls after a failed call | ||
* 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` (either top-level or call-level) that is not supported by the wallet and the `capability` is not explicitly marked as optional. | ||
* Applications may mark a capability as optional by setting `optional` to `true`. See the RPC Specification section below for details. | ||
|
||
#### `wallet_sendCalls` RPC Specification | ||
|
||
```typescript | ||
type SendCallsParams = { | ||
version: string; | ||
from: `0x${string}`; | ||
chainId: `0x${string}` | undefined; // Hex chain id | ||
calls: { | ||
to?: `0x${string}` | undefined; | ||
data?: `0x${string}` | undefined; | ||
value?: `0x${string}` | undefined; // Hex value | ||
chainId?: `0x${string}` | undefined; // Hex chain id | ||
capabilities?: Record<string, any> | undefined; | ||
}[]; | ||
capabilities?: Record<string, any> | undefined; | ||
}; | ||
|
||
type SendCallsResult = string; | ||
type SendCallsResult = { | ||
id: string; | ||
capabilities?: Record<string, any> | undefined; | ||
}; | ||
``` | ||
|
||
##### `wallet_sendCalls` Example Parameters | ||
|
@@ -76,43 +89,54 @@ type SendCallsResult = string; | |
{ | ||
"version": "1.0", | ||
"from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", | ||
"chainId": "0x01", | ||
"calls": [ | ||
{ | ||
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", | ||
"value": "0x9184e72a", | ||
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675", | ||
"chainId": "0x01", | ||
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" | ||
}, | ||
{ | ||
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", | ||
"value": "0x182183", | ||
"data": "0xfbadbaf01", | ||
"chainId": "0x01", | ||
"data": "0xfbadbaf01" | ||
} | ||
], | ||
"capabilities": { | ||
// Illustrative | ||
"paymasterService": { | ||
"url": "https://..." | ||
"url": "https://...", | ||
"optional": true | ||
} | ||
} | ||
} | ||
] | ||
``` | ||
|
||
Note that since the `paymasterService` `capability` is marked as optional, wallets that do not support it will still process and handle the request as if the capability had not been present. If this `optional` field were set to `false` or absent from the request, wallets that do not support the `capability` MUST reject the request. | ||
|
||
##### `wallet_sendCalls` Example Return Value | ||
|
||
The identifier can be any string. The only requirement is that for a given session, users should be able to call `wallet_getCallsStatus` with this value and expect a call-batch status to be returned. | ||
The identifier MUST be unique 64 bytes represented as a hex encoded string. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is ambiguous. Unique how? Unique throughout Ethereum? Unique to the address making the calls? Unique to the session? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a 64 bit identifier can be assumed globally unique for the purpose of this EIP, but we can add "for the current session". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also make it explicitly more limited, something like "The identifier MUST be unique for a given RPC provider within a 24 hour window. It is 64 bytes long and is represented as a hex encoded string." What do you think? The 24 hour window comes from this commit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the rationale behind a strict 64-byte identifier? 🤔 A lot of folks could be using 32-byte hashes for these identifiers? Some Wallets could also just return a transaction hash if they support 7702. |
||
While within the same session as the corresponding `wallet_sendCalls`, wallets SHOULD return a call-batch status when `wallet_getCallsStatus` is called with this value. | ||
|
||
The `capabilities` object allows the wallets to attach a capability-specific metadata to the response. | ||
|
||
```json | ||
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" | ||
{ | ||
"id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", | ||
} | ||
``` | ||
|
||
### `wallet_getCallsStatus` | ||
|
||
Returns the status of a call batch that was sent via `wallet_sendCalls`. The identifier of the transaction is the value returned from the `wallet_sendCalls` RPC. Note that this method only returns a subset of the fields that `eth_getTransactionReceipt` returns, excluding any fields that may differ across wallet implementations. | ||
Returns the status of a call batch that was sent via `wallet_sendCalls`. | ||
The identifier of the batch is the value returned from the `wallet_sendCalls` RPC. | ||
Note that the `receipts` objects of this method's response is a strict subset of the object returned by `eth_getTransactionReceipt`. | ||
|
||
* If a wallet does not execute multiple calls atomically (i.e. in multiple transactions), the receipts in the `receipts` field MUST be in order of the calls sent. | ||
The `capabilities` object allows the wallets to attach a capability-specific metadata to the response. | ||
|
||
* The receipts in the `receipts` field MUST be in the order that they are included onchain. | ||
* The `receipts` field MUST contain receipts for all calls in a batch that were included onchain, including reverted ones. | ||
* If a wallet executes multiple calls atomically (i.e. in a single transaction), `wallet_getCallsStatus` MUST return a single receipt, corresponding to the transaction in which the calls were included. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two lines confuse me. Does a receipt include multiple calls? If so, I'd clarify the wording in the document. I'd also argue that there could be a case for multiple transactions implementing a single atomic batch. A contrived example might be a multisig wallet, where each signature is recorded in a transaction, then applied atomically once the quorum is reached. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rephrased a little: |
||
* The `logs` in the receipt objects MUST only include logs relevant to the calls submitted using `wallet_sendCalls`. For example, in the case of a transaction submitted onchain by an [ERC-4337](./eip-4337.md) bundler, the logs must only include those relevant to the user operation constructed using the calls submitted via `wallet_sendCalls`. I.e. the logs should not include those from other unrelated user operations submitted in the same bundle. | ||
|
||
|
@@ -122,38 +146,63 @@ Returns the status of a call batch that was sent via `wallet_sendCalls`. The ide | |
type GetCallsParams = string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could make sense to add a 'version' parameter here similar to sendCalls: This would make updating to the new proposed schema much easier to handle for folks who have already implemented the previously-proposed version There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Added here: |
||
|
||
type GetCallsResult = { | ||
status: 'PENDING' | 'CONFIRMED'; | ||
id: `0x${string}`; | ||
chainId?: `0x${string}`; | ||
status: number; // See "Status Codes" | ||
receipts?: { | ||
logs: { | ||
address: `0x${string}`; | ||
data: `0x${string}`; | ||
topics: `0x${string}`[]; | ||
}[]; | ||
status: `0x${string}`; // Hex 1 or 0 for success or failure, respectively | ||
chainId: `0x${string}`; | ||
blockHash: `0x${string}`; | ||
blockNumber: `0x${string}`; | ||
gasUsed: `0x${string}`; | ||
transactionHash: `0x${string}`; | ||
}[]; | ||
capabilities?: Record<string, any> | undefined; | ||
}; | ||
``` | ||
|
||
##### Status Codes for `status` field | ||
|
||
The purpose of the `status` 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`. | ||
|
||
| Code | Description | | ||
|------|-------------| | ||
| 100 | Batch has been received by the wallet but has not completed execution onchain (pending) | | ||
| 200 | Batch has been included onchain without reverts, receipts array contains info of all calls (confirmed) | | ||
| 400 | Batch has not been included onchain and wallet will not retry (offchain failure) | | ||
| 500 | Batch reverted completely and only changes related to gas charge may have been included onchain (chain rules failure) | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What code covers the case of some calls being included and some reverting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a 600 status code, what do you think? We can also make it, say, |
||
|
||
Status codes follow these categories: | ||
|
||
* 1xx: Pending states | ||
* 2xx: Confirmed states | ||
* 4xx: Offchain failures | ||
* 5xx: Chain rules failures | ||
SamWilsn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
More specific status codes within these categories should be proposed and agreed upon in separate ERCs. | ||
|
||
##### `wallet_getCallsStatus` Example Parameters | ||
|
||
As with the return value of `wallet_sendCalls`, the batch identifier may be any string. | ||
The `id` batch identifier is a unique 64 bytes represented as a hex encoded string returned from `wallet_sendCalls`. | ||
|
||
```json | ||
[ | ||
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" | ||
"0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" | ||
] | ||
``` | ||
|
||
##### `wallet_getCallsStatus` Example Return Value | ||
|
||
```json | ||
{ | ||
"status": "CONFIRMED", | ||
"chainId": "0x01", | ||
"id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", | ||
"status": 200, | ||
"receipts": [ | ||
{ | ||
"logs": [ | ||
|
@@ -166,7 +215,6 @@ As with the return value of `wallet_sendCalls`, the batch identifier may be any | |
} | ||
], | ||
"status": "0x1", | ||
"chainId": "0x01", | ||
"blockHash": "0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a", | ||
"blockNumber": "0xabcd", | ||
"gasUsed": "0xdef", | ||
|
@@ -178,7 +226,7 @@ As with the return value of `wallet_sendCalls`, the batch identifier may be any | |
|
||
### `wallet_showCallsStatus` | ||
|
||
Requests that a wallet shows information about a given call bundle that was sent with `wallet_sendCalls`. Note that this method does not return anything. | ||
Requests that a wallet shows information about a given call bundle that was sent with `wallet_sendCalls`. Note that this method does not return anything for a known `id` batch identifier. If the identifier is not known, or in case of any other failure to execute `wallet_showCallsStatus` returns an RPC call error. | ||
|
||
#### `wallet_showCallsStatus` RPC Specification | ||
|
||
|
@@ -191,14 +239,12 @@ type ShowCallsParams = string; // Call bundle identifier returned by wallet_send | |
This method accepts a call bundle identifier returned by a `wallet_sendCalls` call. | ||
|
||
```json | ||
[ | ||
"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" | ||
] | ||
["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"] | ||
``` | ||
|
||
### `wallet_getCapabilities` | ||
|
||
This RPC allows an application to request capabilities from a wallet (e.g. batch transactions, paymaster communication), without distinct discovery and permission requests. For more on the difference between requesting capabilities and discoverying features, see the ["Privacy Considerations" section](#privacy-considerations). | ||
This RPC allows an application to request capabilities from a wallet (e.g. batch transactions, paymaster communication), without distinct discovery and permission requests. For more on the difference between requesting capabilities and discovering features, see the ["Privacy Considerations" section](#privacy-considerations). | ||
|
||
This method SHOULD return an error if the user has not already authorized a connection between the application and the requested address. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no definition of specific errors or error codes for any of the APIs, we could either define all of them explicitly or leave it to the wallets. Should we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for this one specifically i would suggest 4100 unauthorized. i dont think any others need to be defined ? as i believe they are covered by the other 1193 errors + getCallsStatus errors There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
|
@@ -214,15 +260,15 @@ Capabilities are returned in key/value pairs, with the key naming a capability a | |
Capabilities are nested in per-chain objects because wallets may support different capabilities across multiple chains authorized in a given session. | ||
|
||
```typescript | ||
type GetCapabilitiesParams = [`0x${string}`]; // Wallet address | ||
type GetCapabilitiesParams = [`0x${string}`, [`0x${string}`]]; // Wallet address, array of queried chain ids (optional) | ||
|
||
type GetCapabilitiesResult = Record<`0x${string}`, <Record<string, any>>; // Hex chain id | ||
``` | ||
|
||
##### `wallet_getCapabilities` Example Parameters | ||
|
||
```json | ||
["0xd46e8dd67c5d32be8058bb8eb970870f07244567"] | ||
["0xd46e8dd67c5d32be8058bb8eb970870f07244567", ["0x2105", "0x14A34"]] | ||
``` | ||
|
||
##### `wallet_getCapabilities` Example Return Value | ||
|
@@ -247,43 +293,6 @@ The capabilities below are for illustrative purposes. | |
} | ||
``` | ||
|
||
### `atomicBatch` Capability | ||
|
||
Like the illustrative examples given above and other capabilities to be defined in future EIPs, the capability to execute calls delivered via the above-defined methods in a single transaction can be attested by the wallet in boolean form. | ||
|
||
This capability is expressed separately on each chain and should be interpreted as a guarantee only for batches of transactions on that chain; batches including calls to multiple chains is out of scope of this capability and this specification. | ||
|
||
If a wallet has affirmatively expressed this `atomicBatch` capability to a calling application, it MUST submit calls submitted with `wallet_sendCalls` as part of a single transaction. | ||
|
||
#### `atomicBatch` Capability Specification | ||
|
||
```typescript | ||
type AtomicBatchCapability = { | ||
supported: true; | ||
}; | ||
``` | ||
|
||
The only valid JSON-RPC values for this capability's only member, `supported`, are `true` or `false`; if a returned value for `supported` is typed as a string or number, it SHOULD be considered malformed. | ||
|
||
For each chain on which a wallet can submit multiple calls atomically, the wallet SHOULD include an `atomicBatch` capability with a `supported` field equal to `true`. | ||
|
||
#### `wallet_getCapabilities` Example Return Value including `atomicBatch` | ||
|
||
```json | ||
{ | ||
"0x2105": { | ||
"atomicBatch": { | ||
"supported": true | ||
}, | ||
}, | ||
"0x14A34": { | ||
"atomicBatch": { | ||
"supported": true | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Rationale | ||
|
||
### On Naming | ||
|
@@ -294,9 +303,7 @@ We also debated whether the methods should be called `wallet_sendTransaction`, ` | |
|
||
### Call Execution Atomicity | ||
|
||
The `wallet_sendCalls` method accepts an array of `calls`. However, this proposal does not require that these calls be executed as part of a single transaction. It enables EOA wallets to accept multiple calls as well over the same interface. That said, we expect that in some cases app developers might want to submit batched calls if and only if they will be executed atomically. This would reduce the number of error cases an app developer would need to handle, while still contributing to the unification over time of interfaces across wallets types. | ||
|
||
We initially proposed that multiple calls must be executed atomically, but after some debate we ultimately decided this was too opinionated. Instead, we are including a specification for an `atomicBatch` capability. This allows for EOA wallets to accept multiple calls and still gives developers the option to only submit batched calls if they are executed atomically. | ||
The `wallet_sendCalls` method accepts an array of `calls` and by default requires that these calls be executed as part of a single transaction. That said, we expect that in some cases app developers might want to submit batched calls if and only if they can be executed non-atomically. This would enable EOA wallets to accept multiple calls as well over the same interface. A definition of this behaviour may be proposed as a separate capability ERC. | ||
|
||
### Call Gas Limit | ||
|
||
|
@@ -308,7 +315,7 @@ Wallets that do not support the methods defined here SHOULD return error respons | |
|
||
## Security Considerations | ||
|
||
App developers MUST treat each call in a batch as if the call were an independent transaction. In other words, there may be additional untrusted transactions between any of the calls in a batch. The calls in the batch MAY also 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 MUST encode deadlines and timeout behaviors in the smart contract calls, just as they do today for transactions, including ones otherwise bundled. Unless a wallet has indicated affirmatively to the application that it can submit batched calls atomically via capability declarations, app developers MUST NOT assume that all calls will be sent in a single transaction. | ||
App developers MUST treat each call in a batch as if the call were an independent transaction. In other words, there may be additional untrusted transactions between any of the calls in a batch. The calls in the batch MAY also 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 MUST encode deadlines and timeout behaviors in the smart contract calls, just as they do today for transactions, including ones otherwise bundled. App developers MUST NOT assume that all calls will be sent in a single transaction. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This can be removed, but I'll steal it for flow-control |
||
|
||
### Privacy Considerations | ||
|
||
|
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'd recommend defining a
Capability
type. Maybe something like: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.
534c81b