Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions ERCS/erc-7730.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,233 @@ To handle this, the `amountPath` and/or `spenderPath` parameters can be used to
}
```

#### Command-Based Protocols

The `calldata` format handles standard ABI-encoded bytes where the parameter is valid calldata. However, some protocols like Uniswap's Universal Router use a different pattern where a command byte array determines which ABI to apply to each element in a separate inputs array:

```solidity
function execute(bytes commands, bytes[] inputs, uint256 deadline)
```

Where:
- `commands`: Each byte is a command ID (e.g., `0x00` = V3_SWAP_EXACT_IN, `0x0b` = WRAP_ETH)
- `inputs`: Array of ABI-encoded parameters for each corresponding command

For such protocols, use command registries that map command bytes to their formatting specifications. The `inputs` array elements are standard ABI-encoded, but the wallet needs to know which ABI definition to use for each element based on the corresponding command byte.

**Command Registry Definition**

The `commandRegistries` key in metadata defines mappings from command bytes to their display specifications:

```json
{
"metadata": {
"commandRegistries": {
"universalRouterCommands": {
"0x00": {
"name": "V3_SWAP_EXACT_IN",
"intent": "Swap {amountIn} for minimum {amountOutMin}",
"inputs": [
{"name": "recipient", "type": "address"},
{"name": "amountIn", "type": "uint256"},
{"name": "amountOutMin", "type": "uint256"},
{"name": "path", "type": "bytes"},
{"name": "payerIsUser", "type": "bool"}
]
},
"0x0b": {
"name": "WRAP_ETH",
"intent": "Wrap {amountMin} ETH to WETH",
"inputs": [
{"name": "recipient", "type": "address"},
{"name": "amountMin", "type": "uint256"}
]
},
"0x0c": {
"name": "UNWRAP_WETH",
"intent": "Unwrap {amountMin} WETH to ETH",
"inputs": [
{"name": "recipient", "type": "address"},
{"name": "amountMin", "type": "uint256"}
]
}
}
}
}
}
```

**Nested Encoding Field Specification**

To parse command-based parameters, use the `nestedEncoding` field in a field format specification:

```json
{
"display": {
"formats": {
"execute(bytes commands,bytes[] inputs,uint256 deadline)": {
"intent": {
"type": "composite",
"source": "commands",
"separator": " + "
},
"fields": [
{
"path": "commands",
"label": "Commands",
"nestedEncoding": {
"type": "commandArray",
"registry": "$.metadata.commandRegistries.universalRouterCommands",
"decodeWith": "inputs"
}
},
{
"path": "deadline",
"label": "Deadline",
"format": "date",
"params": { "encoding": "timestamp" }
}
]
}
}
}
}
```

**Nested Encoding Types**

| Type | Description |
|------|-------------|
| `commandArray` | Each byte in the field is a command ID, decoded using the specified `registry` and corresponding `decodeWith` parameter |
| `abiEncoded` | Field contains ABI-encoded data with types specified in `abiTypes` |
| `packedEncoded` | Field contains packed encoding (e.g., Uniswap V3 path encoding) |

**Composite Intent**

For functions executing multiple sub-operations, use a composite intent object:

```json
{
"intent": {
"type": "composite",
"source": "commands",
"separator": " + ",
"maxDisplay": 3,
"overflow": "and {count} more"
}
}
```

| Field | Description |
|-------|-------------|
| `type` | Must be `"composite"` |
| `source` | The parameter containing sub-operations |
| `separator` | How to join multiple intents (e.g., `" + "`, `", "`) |
| `maxDisplay` | Optional: Maximum number of operations to show |
| `overflow` | Optional: Text when exceeding maxDisplay |

*Expected output example:*

Given a Universal Router transaction with commands `0x0b` (WRAP_ETH) + `0x00` (V3_SWAP_EXACT_IN):
```
"Wrap 1.5 ETH to WETH + Swap 1.5 WETH for minimum 3000 USDC"
```

**Uniswap V3 Path Decoding**

For path parameters with packed token/fee encoding, use `nestedEncoding` with type `packedEncoded`:

```json
{
"path": "path",
"nestedEncoding": {
"type": "packedEncoded",
"pattern": [
{"name": "token", "type": "address", "size": 20},
{"name": "fee", "type": "uint24", "size": 3}
],
"repeating": true
}
}
```

The Uniswap V3 path encoding follows this pattern:
```
[token0 (20 bytes)][fee (3 bytes)][token1 (20 bytes)][fee (3 bytes)][token2 (20 bytes)]...
```

Decoded path display: `"WETH → USDC (0.3%)"`

#### Packed Bytes Multicall

The previous sections handle cases where parameters are either standard ABI-encoded calldata or standard ABI arrays with command-byte mapping. However, some protocols like Safe's `multiSend(bytes transactions)` use completely custom packed formats where transactions are concatenated with a specific byte layout rather than standard ABI encoding.

For these cases, use the `parsing` section to define the byte structure and `multicallBatch` format. This metadata can be written once and applied to all contracts using the same function selector and packed format:

```json
{
"context": {
"contract": {
"abi": [
{
"inputs": [{ "name": "transactions", "type": "bytes" }],
"name": "multiSend",
"outputs": [],
"stateMutability": "payable",
"type": "function",
"selector": "0x8d80ff0a"
}
]
}
},
"parsing": {
"multicallStructure": {
"operation": { "type": "uint8", "size": 1 },
"to": { "type": "address", "size": 20 },
"value": { "type": "uint256", "size": 32 },
"dataLength": { "type": "uint256", "size": 32 },
"data": { "type": "bytes", "dynamic": true }
}
},
"display": {
"formats": {
"multiSend(bytes transactions)": {
"intent": "Multi-Send Batch Transaction",
"fields": [
{
"path": "transactions",
"label": "Batch Transactions",
"format": "multicallBatch",
"params": {
"parseNestedCalls": true
}
}
]
}
}
}
}
```

**`parsing` Section**

The `parsing` key is a top-level section that defines custom byte layouts for non-standard encoded parameters.

**`parsing.multicallStructure`**

Defines the packed byte layout for multicall batch transactions. Each field specifies:
- `type`: The Solidity type
- `size`: Fixed size in bytes
- `dynamic`: Set to `true` for variable-length fields whose size is determined by a preceding field

**`multicallBatch` Format**

| **`multicallBatch`** | |
|----------------------|--------------------------------------------------------------------------------------------|
| *Description* | Iterates through packed transactions using the structure defined in `parsing.multicallStructure` |
| *Parameters* | --- |
| `parseNestedCalls` | Boolean. When `true`, wallets recursively decode each transaction's `data` field using available ERC-7730 descriptors for the target contract (`to` field) |

### Reference

### Container structure values
Expand Down
Loading