-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from coinbase/conner/update-docs
Update docs
- Loading branch information
Showing
7 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,110 @@ | ||
# Spend Permissions | ||
|
||
> :information_source: These contracts are unaudited. Please use at your own risk. | ||
**Spend Permissions enable apps to spend native and ERC-20 tokens on behalf of users.** | ||
|
||
## Design Overview | ||
|
||
### 1. Periphery addition to Coinbase Smart Wallet V1 | ||
|
||
While implementing this feature as a new V2 wallet implementation was tempting, we decided to leverage the modular owner system from [Smart Wallet V1](https://github.com/coinbase/smart-wallet) and avoid a hard upgrade. | ||
|
||
### 2. Only Native and ERC-20 token support | ||
|
||
Spend Permissions only supports spending Native (e.g. ETH) and ERC-20 (e.g. USDC) tokens on a recurring period. This enables use cases like subscriptions out of the box (e.g 10 USDC per month) and also can support apps that want to avoid asking users for spend permissions every session. | ||
|
||
This approach does **not** enable apps to make arbitrary external calls from user accounts, improving security by having a tighter and fully-known scope of account control. | ||
|
||
### 3. Spender-originated calls | ||
|
||
Spend Permissions allow users to delegate token spending to a `spender` address, presumably controlled by the app. When an app wants to spend user tokens, it calls into `SpendPermissionManager` from this `spender` address. `SpendPermissionManager` will then validate the spend is within the approved permission's allowance and calls into the user's account to transfer tokens. | ||
|
||
This approach does **not** use the ERC-4337 EntryPoint to prompt external calls from user accounts, improving security by avoiding the possibility of ERC-4337 Paymasters spending users' tokens on gas fees. | ||
|
||
## End-to-end Journey | ||
|
||
### 1. App requests and user signs permissions (offchain) | ||
|
||
Apps request spend permissions for users to sign by sending an `eth_signTypedData` RPC request containing the permission details. | ||
|
||
Read more details [here](./docs/diagrams/signSpendPermission.md). | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant A as App | ||
participant WF as Wallet Frontend | ||
participant U as User | ||
participant WB as Wallet Backend | ||
A->>WF: eth_signTypedData | ||
WF->>U: approve permission | ||
U-->>WF: signature | ||
WF->>WB: get account status | ||
WB-->>WF: deploy status, initCode, current + pending owners | ||
alt account not deployed && manager in initCode | ||
Note right of WF: wrap signature in ERC-6492 | ||
else manager not in initCode && manager not owner | ||
WF->>U: add manager | ||
U-->>WF: signature | ||
end | ||
WF-->>A: signature | ||
``` | ||
|
||
### 2. App approves and spends (onchain) | ||
|
||
Spenders (apps) spend tokens by calling `SpendPermissionManager.spend` with their spend permission values, a recipient, and an amount of tokens to spend. | ||
|
||
Spenders may want to batch this call with an additionally prepended call to [approve their permission via user signature](./approveWithSignature.md) or the convenience function `SpendPermissionManager.spendWithSignature`. | ||
|
||
Read more details [here](./docs/diagrams/spend.md). | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant S as Spender | ||
participant PM as Permission Manager | ||
participant A as Account | ||
participant ERC20 | ||
opt | ||
S->>PM: approveWithSignature | ||
Note over PM: validate signature and store approval | ||
end | ||
S->>PM: spend | ||
Note over PM: validate permission approved <br> and spend value within allowance | ||
PM->>A: execute | ||
Note over PM,A: transfer tokens | ||
alt token is ERC-7528 address | ||
A->>S: call{value}() | ||
Note over A,S: transfer native token to spender | ||
else else is ERC-20 contract | ||
A->>ERC20: transfer(spender, value) | ||
Note over A,ERC20: transfer ERC-20 to spender | ||
end | ||
``` | ||
|
||
### 3. User revokes permission (onchain) | ||
|
||
Users can revoke permissions at any time by calling `SpendPermissionManager.revoke`, which can also be batched via `CoinbaseSmartWallet.executeBatch`. | ||
|
||
Read more details [here](./docs/diagrams/revoke.md). | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant E as Entrypoint | ||
participant A as Account | ||
participant PM as Permission Manager | ||
Note over E: Validation phase | ||
E->>A: validateUserOp | ||
A-->>E: validation data | ||
Note over E: Execution phase | ||
E->>A: executeBatch | ||
loop | ||
A->>PM: revoke | ||
Note over A,PM: SpendPermission data | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Spend Permission Accounting | ||
|
||
## Onchain Accounting | ||
|
||
Tracking asset expenditure accurately is important for enforcing user-approved limits. Given the fragility and complications with doing this accounting offchain and the severity of inaccuracies, we designed for fully onchain accounting. Onchain accounting enables us to have higher confidence in its reliability and keep our system trust-minimized. | ||
The accounting for each spend permission is stored in a single slot, via a mapping keyed by the hash of its struct values. | ||
A spend permission contains 3 entity values: | ||
|
||
1. `account`: user whose tokens will be spent | ||
1. `spender`: app who is able to spend tokens | ||
1. `token`: ERC-20 contract or ERC-7528 native token | ||
|
||
A spend permission contains 4 accounting values: | ||
|
||
1. `start`: time this permission is valid starting at | ||
1. `end`: time this permission is valid until | ||
1. `period`: duration of a recurring interval that resets the `spender`'s allowance | ||
1. `allowance`: amount of tokens spendable per period | ||
|
||
### Recurring Accounting | ||
|
||
Spend permissions allow an app to request to spend user assets on a recurring basis (e.g. 10 USDC / month). As apps spend user assets, the recurring logic automatically increments and enforces the allowance for the current period. Once enough time passes to enter the next period, the allowance usage is reset to zero and the app can keep spending up to the same allowance. | ||
|
||
This design allows users and apps to have reduced friction in approving asset use, while still giving the user control to manage risk and keep asset allowance small upfront. This design is also intuitive for users and can easily support recurring models like subscriptions, automated trading strategies, and payroll. | ||
|
||
The start time and period period set a deterministic schedule infinitely into the future for when allowances reset to zero for the next period. The end time enforces when the permission can no longer be used and does not have to correlate with a clean period boundary. | ||
Consider this example configuration: | ||
|
||
``` | ||
start = 0 | ||
end = 1000 | ||
period = 100 | ||
allowance = 100 | ||
``` | ||
|
||
This configuration would produce the following period-size ranges each with their own allowance: | ||
|
||
``` | ||
[0, 99], [100, 199], [200, 299], ... | ||
``` | ||
|
||
When a new spend is attempted, the contract first determines what the current period range is. If the current time falls within the period of last stored use, we simply check if this new usage will exceed the allowance. | ||
|
||
``` | ||
new spend=25 @ t=0 | ||
period = [0, 99] | ||
allowance = 0 + 25 = 25, | ||
overspend = 25 > 100 = false | ||
new spend=25 @ t=50 | ||
period = [0, 99] | ||
allowance = 25 + 25 = 50 | ||
overspend = 50 > 100 = false | ||
``` | ||
|
||
If the current time exceeds the period of last stored use, that means we are in a new period and should reset the allowance to zero and then add our new attempted spend. | ||
|
||
``` | ||
new spend=25 @ t=0 | ||
period = [0, 99] | ||
allowance = 0 + 25 = 25, | ||
overspend = 25 > 100 = false | ||
new spend=25 @ t=150 | ||
period = [100, 199] | ||
allowance = 0 + 25 = 25 | ||
overspend = 25 > 100 = false | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Approve Permissions | ||
|
||
While the default experience is for apps to request the user [sign spend permissions](signSpendPermission.md) and [approve with signatures](./approveWithSignature.md), it can also be valuable to approve permissions via direct calls to `SpendPermissionManager.approve`. For example, paying now to start a subscription and approving a permission to pay the same amount every month. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant E as Entrypoint | ||
participant A as Account | ||
participant PM as Permission Manager | ||
participant EC as External Contract | ||
Note over E: Validation phase | ||
E->>A: validateUserOp | ||
A-->>E: validation data | ||
Note over E: Execution phase | ||
E->>A: executeBatch | ||
opt | ||
A->>EC: call | ||
end | ||
A->>PM: approve | ||
Note over A,PM: SpendPermission data | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Approve Permission With Signature | ||
|
||
Apps approve their permission by calling `SpendPermissionManager.approveWithSignature` using the signature returned from the wallet when [signing spend permissions](signSpendPermission.md). | ||
|
||
If the signature is [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) formatted, `SpendPermissionManager` will automatically detect this and deploy the account on behalf of the app. Afterwards, it will call `isValidSignature` to verify the account signed the permission. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant S as Spender | ||
participant PM as Permission Manager | ||
participant A as Account | ||
participant F as Factory | ||
S->>PM: approveWithSignature | ||
Note over PM: validate signature | ||
opt if 6492 initCode | ||
PM->>F: createAccount | ||
F->>A: create2 | ||
end | ||
PM->>A: isValidSignature | ||
A-->>PM: EIP-1271 magic value | ||
Note over PM: revert or store approval | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Revoke Permissions | ||
|
||
Users can revoke permissions at any time by calling `SpendPermissionManager.revoke`, which can also be batched via `CoinbaseSmartWallet.executeBatch`. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant E as Entrypoint | ||
participant A as Account | ||
participant PM as Permission Manager | ||
Note over E: Validation phase | ||
E->>A: validateUserOp | ||
A-->>E: validation data | ||
Note over E: Execution phase | ||
E->>A: executeBatch | ||
loop | ||
A->>PM: revoke | ||
Note over A,PM: SpendPermission data | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Sign Spend Permission | ||
|
||
Apps request spend permissions from users by sending an `eth_signTypedData` request containing the permission details. | ||
|
||
Users are guided to sign the permission hash and add the `SpendPermissionManager` contract as an owner if it is not already. Signing to approve enables users to not spend gas on this action, offsetting this cost to the app. | ||
|
||
If a users account is not yet deployed, but has the `SpendPermissionManager` as an initial owner in its `initCode`, the returned signature is formatted according to [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) with the `CoinbaseSmartWalletFactory` address and this `initCode`. | ||
|
||
This entire process takes place offchain and requires no transactions or network fees. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant A as App | ||
participant WF as Wallet Frontend | ||
participant U as User | ||
participant WB as Wallet Backend | ||
A->>WF: eth_signTypedData | ||
WF->>U: approve permission | ||
U-->>WF: signature | ||
WF->>WB: get account status | ||
WB-->>WF: deploy status, initCode, current + pending owners | ||
alt account not deployed && manager in initCode | ||
Note right of WF: wrap signature in ERC-6492 | ||
else manager not in initCode && manager not owner | ||
WF->>U: add manager | ||
U-->>WF: signature | ||
end | ||
WF-->>A: signature | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Spend Tokens | ||
|
||
Spenders (apps) spend tokens by calling `SpendPermissionManager.spend` with their spend permission values, a recipient, and an amount of tokens to spend. | ||
|
||
Spenders may want to batch this call with an additionally prepended call to [approve their permission via user signature](./approveWithSignature.md) or the convenience function `SpendPermissionManager.spendWithSignature`. After a permission is approved, it is cheaper to avoid re-validating the signature and approval by just calling `SpendPermissionManager.spend` for repeated use. | ||
|
||
When executing a spend, `SpendPermissionManager` calls `CoinbaseSmartWallet.execute` with either a call to transfer native or ERC-20 tokens to the recipient. Only these two kinds of calls are allowed by the `SpendPermissionManager` on a `CoinbaseSmartWallet` for reduced risk. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant S as Spender | ||
participant PM as Permission Manager | ||
participant A as Account | ||
participant ERC20 | ||
opt | ||
S->>PM: approveWithSignature | ||
Note over PM: validate signature and store approval | ||
end | ||
S->>PM: spend | ||
Note over PM: validate permission approved <br> and spend value within allowance | ||
PM->>A: execute | ||
Note over PM,A: transfer tokens | ||
alt token is ERC-7528 address | ||
A->>S: call{value}() | ||
Note over A,S: transfer native token to spender | ||
else else is ERC-20 contract | ||
A->>ERC20: transfer(spender, value) | ||
Note over A,ERC20: transfer ERC-20 to spender | ||
end | ||
``` |