diff --git a/ERCS/erc-0000.md b/ERCS/erc-0000.md new file mode 100644 index 00000000000..49fc55c7ba1 --- /dev/null +++ b/ERCS/erc-0000.md @@ -0,0 +1,637 @@ +--- +eip: 0000 +title: Custom Encoding Layout for ERC-7730 +description: Format to describes any non-standard byte encodings for ERC-7730 +author: Alex Forshtat (@forshtat) +discussions-to: https://github.com/ethereum/ERCs/pull/1925 +status: Draft +type: Standards Track +category: ERC +created: 2026-07-26 +requires: 7730 +--- + +## Abstract + +TBD. + +## Motivation + +ERC-7730 provides a rich language for decoding the calldata inputs of smart contracts on Ethereum. + +In practice, there are multiple contracts in active use on Ethereum that implement alternative encoding for their inputs, and it is not possible to deprecate all of them for the purpose of promoting Clear Signing. + +Instead, we can add some features to ERC-7730 that would allow us to cover the most common non-standard encodings of smart contract inputs. + +## Specification + + +### `customEncoding` + +The main mechanism for declaring any parameter whose contents cannot be expressed using Solidity-friendly ABI-encoded data structures. + +It can also be provided to values of other formats if these represent some nested data structures. + +```json +{ + "sendPacked(bytes data)": { + "fields": [ + { + "path": "data", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "to", + "schema": { "type": "address" } + }, + { + "name": "amount", + "schema": { "type": "uint", "bytes": 32 } + } + ] + } + } + ] + } +} +``` + +Every `customEncoding` node consumes a well-defined, computable number of bytes from its buffer. +The `switch`'s path-sourced form is an exception as it reads an already-resolved value instead of parsing bytes. + +### `sequence` + +The mechanism for declaring an iterative, array-like data structure not represented by an ABI-encoded `array` data layout. + +The elements count for `sequence` parameters is optional, and decoding may continue until the input bytes are exhausted. + +For example, for a byte array with each byte representing a different element: + +```json +{ + "runCommands(bytes commands)": { + "fields": [ + { + "path": "commands", + "customEncoding": { + "type": "sequence", + "element": { "type": "uint", "bytes": 1 } + } + } + ] + } +} +``` + +Alternatively, `count` MAY give a literal element count, or `countFrom` may name a sibling field decoded earlier in the same object, sizing the sequence from a previously-decoded value – as with a Wormhole VAA's guardian-signature array, sized by its own `numSignatures` byte, in the Test Case below. + +```json +{ + "name": "signatures", + "schema": { + "type": "sequence", + "countFrom": "numSignatures", + "element": { + "type": "object", + "fields": [ + { + "name": "guardianIndex", + "schema": { "type": "uint", "bytes": 1 } + }, + { + "name": "signature", + "schema": { "type": "bytes", "length": 65 } + } + ] + } + } +} +``` + +### `object` + +The mechanism for declaring an entry in a `sequence` data structure that is not represented by an ABI-encoded parameter. + +It can also be used as a stand-in for any other complex data structure in the formating process. + +```json +{ + "batchCalls(bytes transactions)": { + "fields": [ + { + "path": "transactions", + "customEncoding": { + "type": "sequence", + "element": { + "type": "object", + "fields": [ + { + "name": "operation", + "schema": { "type": "uint", "bytes": 1 } + }, + { + "name": "to", + "schema": { "type": "address" } + } + ] + } + } + } + ] + } +} +``` + +An `object`'s field entries may carry `format`,`params`, `label` and `schema` parameters. +This allows a packed field to declare how it should be displayed using relative paths to that object's own sibling members. + +```json +{ + "name": "callData", + "schema": { "type": "bytes" }, + "label": "Execution", + "format": "calldata", + "params": { "calleePath": "target", "amountPath": "value" } +} +``` + +### `bitfield` + +A fixed-width value whose individual bits or bit ranges each carry independent, named meaning – unlike `object`, whose fields are always byte-aligned and never overlap. + +```json +{ + "type": "bitfield", + "bytes": 20, + "fields": [ + { "name": "beforeSwap", "bit": 7 }, + { + "name": "poolId", + "bits": [19, 8] + } + ] +} +``` + +Each entry is either `{name, bit}` (a single flag, decoded as `bool`) or `{name, bits: [hi, lo]}` (an inclusive bit range, decoded as an unsigned integer). + +### `switch` + +The mechanism that allows the decoding to choose the format based on a certain parameter decoded previously. Represents a common pattern of carrying the decoding format flag separately form the data being decoded. + +```json +{ + "execute(uint8 kind,bytes data)": { + "fields": [ + { + "path": "data", + "switch": { + "expression": { "path": "kind" }, + "cases": { + "0x00": { + "(address to,uint256 amount)": { + "fields": [ + { "path": "to", "label": "To" }, + { "path": "amount", "label": "Amount" } + ] + } + }, + "0x01": { + "(address from,address to,uint256 amount,uint256 deadline)": { + "fields": [ + { "path": "from", "label": "From" }, + { "path": "to", "label": "To" }, + { "path": "amount", "label": "Amount" }, + { + "path": "deadline", + "label": "Deadline", + "format": "date", + "params": { "encoding": "timestamp" } + } + ] + } + } + } + } + } + ] + } +} +``` + +When a `switch` case's tuple resolves to an array (`(...)[]`), its own `fields` can address that array's elements with `.[]` in place of the missing array name, e.g. `.[].callData`. +`#.` inside a case's own `fields` still resolves against the absolute root of the structured data. + +`switch` can also appear as a `customEncoding` node instead of a field-level key: + +```json +{ + "exampleCall(uint256 outputReference)": { + "fields": [ + { + "path": "outputReference", + "label": "Save result as", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Set by an earlier step, not known yet", + "intent": "info" + }, + "$default": { "format": "raw" } + } + } + } + ] + } +} +``` + +`mask` is available on any `switch` expression and is applied to the raw value before matching `cases`. +It lets a dispatch tag share space with unrelated bits, as with `UniversalRouter`'s revert-allowed flag in the Test Case below. + +Inside a `customEncoding` tree, `switch`'s inline form may also use `payloadFrom` in place of `$index`, naming a sibling ABI-decoded array to read at the same index as the enclosing `sequence` element. + +### `operation` + +An optional parameter for the ERC-7730's `format: "calldata"` record. +Allow specifying the actual EVM operation used when executing the call. +Supported values are: + +1. CALL +2. DELEGATECALL +3. CREATE +4. CREATE2 +5. CALLCODE (legacy opcode) + +### `interaction` + +The mechanism to declare that some data represents an interaction with an external contract. + +This is an equivalent of `calldata` format from ERC-7730 for contracts that perform their own encoding of the calldata, or execute `delegatecall` and `staticcall` operations. + +```json +{ + "executeSendReward(address account,uint256 amount)": { + "fields": [ + { "path": "account", "label": "Account" }, + { "path": "amount", "label": "Amount" }, + { + "interaction": { + "to": "target", + "signature": "grantReward(address,uint256)", + "args": [ + { "path": "account" }, + { "path": "amount" } + ] + } + } + ] + } +} +``` + +A wallet MUST resolve the matched target's own `intent`/`interpolatedIntent`/`fields` using the bound `args` values in place of that target's own decoded parameters, applying the same unknown-selector fallback if `to`'s descriptor has no entry matching `signature`. + +A [structured data format specification](./erc-7730.md) MAY declare a top-level `switch` in place of `intent`/`fields`, redirecting the entire call to a different function via a combination of `$fallback` and `interaction` keywords based on one of its own decoded parameters. + +### `$fallback` + +Contracts may have additional or completely alternative dispatch logic that does not follow the 4-byte selector entry point at all. +Calls to such contracts reach a raw fallback function that inspects the calldata internally. +`display.formats` MAY use the reserved key `"$fallback"` in this case, containing a [structured data format specification](./erc-7730.md#structured-data-format-specification). + +The `"$fallback"` is selected whenever calldata does not correspond to any selector entry in the file. +Wallets MUST prefer a matching selector entry over `$fallback` when a selector and a fallback are both present. + +If the fallback itself dispatches the execution, i.e. based on a leading tag bytes, descriptors may use a top-level `switch` whose `expression` reads that tag straight off `data`. + +Additionally, we define a container-level value `@.data` returning complete, raw calldata bytes of the transaction for use in such scenarios. Descriptors may use the [path slice](./erc-7730.md#path-slices) syntax on `@.data` to extract the top-level switch logic expression. + +```json +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Fallback Dispatcher Contract" + }, + "display": { + "formats": { + "$fallback": { + "switch": { + "expression": { "path": "@.data.[0:3]" }, + "cases": { + "0x112233": { + "intent": "Do the thing", + "fields": [ + { + "path": "@.data", + "customEncoding": { + "type": "object", + "fields": [ + { "name": "tag", "schema": { "type": "uint", "bytes": 3 } }, + { "name": "amount", "schema": { "type": "uint", "bytes": 32 } } + ]}} + ] + }, + "$default": "reject" + } + } + } + } + } +} +``` + +### `$index` + +A mechanism for element in a `sequence` to reference their position for indexing into other `sequence` or array-like parameters. + +```json +{ + "execute(bytes commands,bytes[] inputs)": { + "fields": [ + { + "path": "commands", + "customEncoding": { + "type": "sequence", + "element": { "type": "uint", "bytes": 1 } + } + }, + { + "path": "inputs[]", + "switch": { + "expression": { "path": "commands[$index]" }, + "cases": { + "0x00": { + "(address to)": { + "fields": [ + { "path": "to", "label": "To" } + ] + } + } + } + } + } + ] + } +} +``` +## Test Cases + +### Safe{Wallet} - `MultiSend` Contract + +The `MultiSend` contract encodes the data in the following format: + +``` +/** +* @notice Sends multiple transactions and reverts all if one fails. +* @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of: +* 1. _operation_ as a {uint8}, 0 for a `CALL` or 1 for a `DELEGATECALL` (=> 1 byte), +* 2. _to_ as an {address} (=> 20 bytes), +* 3. _value_ as a {uint256} (=> 32 bytes), +* 4. _data_ length as a {uint256} (=> 32 bytes), +* 5. _data_ as {bytes}. +function multiSend(bytes memory transactions) public payable; +*/ +``` +#### What makes this encoding unusual +1. The `transactions` count is not provided at all - the code has to iteratively decode the entire data array until it is exhausted. +2. The `data` field has a dynamic size that is specified as a separate sibling parameter. + +Using ERC-0000, this input can easily be described for Clear Singing – see [MultiSend Example](../assets/erc-0000/example-safe-multisend.json). + +### Uniswap v4 - `UniversalRouter` Contract + +The `UniversalRouter` contract encodes the data in the following format: + +``` +/// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired. +/// @param commands A set of concatenated commands, each 1 byte in length +/// @param inputs An array of byte strings containing abi encoded inputs for each command +/// @param deadline The deadline by which the transaction must be executed +function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; +``` + +#### What makes this encoding unusual + +1. `commands` is not an ABI `bytes[]` or `uint8[]` – it is a `bytes` value packed with one `command id` byte, decoded as a `sequence` until the input is exhausted, exactly like the `runCommands` example above. +2. Each command byte also carries a flag in its high bit (`0x80`/`0b10000000`, "allow this command to revert") alongside the actual `command id` in its low 6 bits (`0x3f`/`0b00111111`), so the extracted value must be masked before it can be matched against a `cases` table. +3. `inputs` is a regular ABI `bytes[]`, but the ABI type of `inputs[i]` is only known by decoding `commands[i]` – the format of one array must be resolved by indexing into a **sibling array** at the same position, which is the `$index` mechanism described above. + +Using ERC-0000, this input can be described for Clear Signing – see [Universal Router Example](../assets/erc-0000/example-universal-router.json). + +### ERC-7579 `execute` function + +The [ERC-7579](./erc-7579.md) `execute` function, which encodes the data in the following format: + + +| CallType | ExecType | Unused | ModeSelector | ModePayload | +| -------- | -------- | ------- | ------------ | ----------- | +| 1 byte | 1 byte | 4 bytes | 4 bytes | 22 bytes | + +```solidity +function execute(bytes32 mode, bytes calldata executionCalldata) external payable; +``` + +The `mode` value determines how `executionCalldata` itself must be decoded: +- `CALLTYPE = 0x00` (single call): `executionCalldata` is `abi.encodePacked(target, value, callData)`. +- `CALLTYPE = 0x01` (batch call): `executionCalldata` is a regularly ABI-encoded `Execution(address target, uint256 value, bytes callData)[]`. +- `CALLTYPE = 0xff` (delegatecall): `executionCalldata` is `abi.encodePacked(target, callData)`, with **no `value` field**. + +#### What makes this encoding unusual + +1. `mode` is a single `bytes32` packed with five sub-fields of uneven, non-32-byte-aligned widths (1/1/4/4/22 bytes). +2. The format of the second parameter, `executionCalldata`, is dependent on the first byte of the first parameter, `mode` – similar to the pattern used by `UniversalRouter`, but more complicated since the indication is a single byte inside a fixed-size `bytes32` rather than a whole named parameter. +3. The three `CALLTYPE`s are not just different tuples of the same shape – `single` and `delegatecall` use packed encoding, with no `value` field for `delegatecall`, while `batch` uses standard padded ABI encoding of a struct array, so `executionCalldata` decoding changes shape entirely between cases. + +Using ERC-0000, this input can be described for Clear Signing – see [ERC-7579 Execute Example](../assets/erc-0000/example-erc7579-execute.json). + +### Balancer Relayer `joinPool`/`exitPool` + +The `BatchRelayerLibrary` contract reached via `BalancerRelayer.multicall` encodes pool-kind and per-action metadata as sibling ABI parameters, then encodes a *second*, independent tag inside `userData` itself: + +```solidity +enum PoolKind { WEIGHTED, LEGACY_STABLE, COMPOSABLE_STABLE, COMPOSABLE_STABLE_V2 } + +function joinPool(bytes32 poolId, PoolKind kind, address sender, address recipient, + IVault.JoinPoolRequest memory request, uint256 value, uint256 outputReference) external payable; + +function exitPool(bytes32 poolId, PoolKind kind, address sender, address payable recipient, + IVault.ExitPoolRequest memory request, OutputReference[] calldata outputReferences) external payable; +``` + +#### What makes this encoding unusual + +1. `BalancerRelayer.multicall(bytes[] data)` always `DELEGATECALL`s every batched element into one fixed library address – unlike `MultiSend`, there is no per-element operation or target choice, only the embedded call itself needs resolving. +2. `request.userData`'s meaning is tag-dispatched twice: once by `kind` (a sibling parameter of `joinPool`/`exitPool` itself), then again by a `JoinKind`/`ExitKind` enum read from `userData`'s own first 32 bytes – a `switch` nested inside a `switch`. +3. The same already-ABI-decoded `uint256` fields (`maxAmountsIn[i]`, `outputReference`, `bptAmountIn`) can be either a literal amount or a "chained reference" – a placeholder for a value only known once an earlier step in the same batched transaction has actually executed on-chain – distinguished purely by masking the value's own high bits, not by a separate tag field. + +Using ERC-0000, this input can be described for Clear Signing – see [Balancer Relayer Multicall Example](../assets/erc-0000/example-balancer-relayer-multicall.json) and [Balancer Relayer Library Example](../assets/erc-0000/example-balancer-relayer-library.json). + +### ERC-7683 `open` + +The `AcrossOriginSettler` contract encodes the data in the following format: + +```solidity +struct OnchainCrossChainOrder { + uint32 fillDeadline; + bytes32 orderDataType; + bytes orderData; +} + +function open(OnchainCrossChainOrder calldata order) external; +``` + +#### What makes this encoding unusual + +1. `orderData`'s ABI type is selected by `orderDataType`, a `bytes32` equal to the `keccak256` hash of the target tuple's own Solidity type string (`keccak256("AcrossOrderData(address inputToken,...)")`) rather than a small, contract-defined enum – an open-ended, hash-keyed dispatch. +2. Both the tag and the payload are already plain sibling ABI parameters of `open` itself, so no `customEncoding` node is needed – only `switch`. + +Using ERC-0000, this input can be described for Clear Signing – see [ERC-7683 Order Example](../assets/erc-0000/example-erc7683-order.json). + +### Uniswap v4 - `PoolManager.initialize` + +The `PoolManager` contract identifies a pool's hook contract, and which of its callbacks are active, entirely through the low bits of that hook contract's own address: + +```solidity +struct PoolKey { + Currency currency0; + Currency currency1; + uint24 fee; + int24 tickSpacing; + IHooks hooks; +} + +function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick); + +/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits +/// of the address that the hooks contract is deployed to. +/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400 +/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used. +library Hooks { + /// @notice Returns whether the flag is configured for the hook + function hasPermission(IHook self, uint160 flag) internal pure returns (bool) { + return uint160(address(self)) & flag != 0; + } +} +``` + +#### What makes this encoding unusual + +1. `key.hooks` is an ordinary ABI `address` parameter, but its lowest 14 bits are individually meaningful flags (`beforeSwap`, `afterSwap`, `beforeAddLiquidity`, etc.) chosen by **mining a vanity address at hook-deployment time** – the address *is* the bitfield, with no separate flags parameter anywhere in the call. +2. Unlike every other Test Case here, this needs no `sequence` or `switch` at all – just a `bitfield` `customEncoding` node attached directly to an already-ABI-decoded scalar, to tell a signer which of a hook's callbacks it is trusting to run on every swap/mint/burn against this pool. + +Using ERC-0000, this input can be described for Clear Signing – see [Uniswap v4 Initialize Example](../assets/erc-0000/example-uniswap-v4-initialize.json). + +### Compound III `Bulker.invoke` + +The `Bulker` contract batches several actions in one call, but unlike `MultiSend` or `UniversalRouter`, the per-action payload is never itself calldata for a callable function: + +```solidity +bytes32 constant ACTION_SUPPLY_ASSET = "ACTION_SUPPLY_ASSET"; +bytes32 constant ACTION_TRANSFER_ASSET = "ACTION_TRANSFER_ASSET"; +bytes32 constant ACTION_WITHDRAW_ASSET = "ACTION_WITHDRAW_ASSET"; +bytes32 constant ACTION_CLAIM_REWARD = "ACTION_CLAIM_REWARD"; + +function invoke(bytes32[] calldata actions, bytes[] calldata data) external payable; +``` + +#### What makes this encoding unusual + +1. Each `data[i]` is a bare `abi.encode` of a tuple – no function selector – that `Bulker` itself `abi.decode`s and forwards, under a **different function name and a different argument list**, to the `Comet` contract. + For example, `ACTION_SUPPLY_ASSET` is encoded as `abi.encode(comet, to, asset, amount)` but eventually becomes a call to `comet.supplyFrom(msg.sender, to, asset, amount)`. + This is a clear example of the "inner call with reassembled arguments" pattern common in routing contracts in DeFi. +2. `msg.sender` – the account that actually calls `Bulker.invoke` – is added into that reassembled call's arguments despite never appearing anywhere in `data[i]`. +3. The target contract itself can vary by either the action or by the tuple's first field. + +Using ERC-0000, this input can be described for Clear Signing – see [Compound III Bulker Example](../assets/erc-0000/example-compound-bulker.json). + +### Wormhole Token Bridge `completeTransfer` + +The `TokenBridge` contract accepts a signed Wormhole message called "VAA" as a single opaque packed `bytes` argument: + +```solidity +function completeTransfer(bytes memory encodedVm) public; +``` + +A VAA encoded as follows: + +``` +version(1) | guardianSetIndex(4) | numSignatures(1) | signatures[] | timestamp(4) | nonce(4) | emitterChainId(2) | emitterAddress(32) | sequence(8) | consistencyLevel(1) | payload +``` + +Where each of the `numSignatures` signature entries is: + +```guardianIndex(1) | r(32) | s(32) | v(1)``` + +And `payload`'s own first byte selects its shape: + +``` +PayloadID uint8 = 1 +Amount uint256 +TokenAddress bytes32 +TokenChain uint16 +To bytes32 +ToChain uint16 +Fee uint256 +--- +PayloadID uint8 = 2 +TokenAddress [32]uint8 +TokenChain uint16 +Decimals uint8 +Symbol [32]uint8 +Name [32]uint8 +--- +PayloadID uint8 = 3 +Amount uint256 +TokenAddress bytes32 +TokenChain uint16 +To bytes32 +ToChain uint16 +FromAddress bytes32 +Payload bytes +``` + +#### What makes this encoding unusual + +1. `signatures`'s element count is neither ABI-length-prefixed, nor fixed, nor is it an iterator – it is a plain `numSignatures` byte decoded a few bytes earlier in the very same buffer. +2. `payload` is dispatched by its own leading tag byte, nested inside a `switch` – the entire "VAA", both dispatch tags and all data, lives in one opaque `bytes` argument with no ABI structure anywhere around it. +3. `emitterAddress`/`tokenAddress`/`to` are 32-byte, chain-agnostic identifiers, decoded as raw bytes rather than `address` – they are only interpretable as EVM addresses once matched against their accompanying `chain id` field. + +Using ERC-0000, this input can be described for Clear Signing – see [Wormhole Token Bridge Example](../assets/erc-0000/example-wormhole-token-bridge.json). + +### `TieredExecutor` (artificial illustrative example) + +The `TieredExecutor` contract encodes the data in the following format: + +```solidity +enum Operation { None, GrantReward, CreditLegacy } + +function executeOperation(address target, Operation op, address account, uint256 amount) external; +``` + +#### What makes this encoding unusual + +1. `executeOperation` has no fixed meaning of its own at all – the entire call exists only to be redirected, and which target function it becomes requires a top-level `switch` instead of an ordinary `intent`/`fields` pair. +2. `account`/`amount` are generic-looking arguments with no inherent semantics – depending on `op`, they are bound to two unrelated target interfaces with a **different parameter order** via `interaction` rather than `format: "calldata"`. There is no contiguous calldata blob to slice out, only already-decoded values that need to be reassembled. +3. This contract is illustrative only, written for this ERC and not deployed anywhere. + +Using ERC-0000, this input can be described for Clear Signing – see [TieredExecutor Example](../assets/erc-0000/example-tiered-executor.json). + +## Rationale + +TBD. + +## Security Considerations + +TBD. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/erc-0000/TieredExecutor.sol b/assets/erc-0000/TieredExecutor.sol new file mode 100644 index 00000000000..87aaae9f9ec --- /dev/null +++ b/assets/erc-0000/TieredExecutor.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.20; + +// Illustrative contract written for the non-ABI-dispatch companion ERC. +// It is NOT deployed anywhere; it exists to demonstrate a real pattern +// (a small trusted relayer that accepts one numeric operation tag and +// re-dispatches the call to one of several unrelated target interfaces, +// each with its own function signature and its own argument order) using +// concrete, compilable Solidity rather than pseudocode. +// +// See example-tiered-executor.json in this same folder for the ERC-7730 +// descriptor that clear-signs calls to `executeOperation`, and +// example-reward-vault.json / example-legacy-token.json for the +// descriptors of the two target interfaces it dispatches into. + +interface IRewardVault { + function grantReward(address to, uint256 amount) external; +} + +interface ILegacyToken { + function creditAccount(uint256 amount, address to) external; +} + +contract TieredExecutor { + enum Operation { + None, // 0 - unused, always rejected + GrantReward, // 1 - IRewardVault.grantReward(address,uint256) + CreditLegacy // 2 - ILegacyToken.creditAccount(uint256,address) + } + + event Executed(Operation indexed op, address indexed target, address indexed account, uint256 amount); + + /// @param target The contract to route the call to. + /// @param op Which trusted operation to perform. + /// @param account The account argument for that operation. + /// @param amount The amount argument for that operation. + function executeOperation(address target, Operation op, address account, uint256 amount) external { + if (op == Operation.GrantReward) { + IRewardVault(target).grantReward(account, amount); + } else if (op == Operation.CreditLegacy) { + ILegacyToken(target).creditAccount(amount, account); + } else { + revert("TieredExecutor: unsupported operation"); + } + emit Executed(op, target, account, amount); + } +} diff --git a/assets/erc-0000/erc7730-non-abi-dispatch.schema.json b/assets/erc-0000/erc7730-non-abi-dispatch.schema.json new file mode 100644 index 00000000000..03e2a1c81d3 --- /dev/null +++ b/assets/erc-0000/erc7730-non-abi-dispatch.schema.json @@ -0,0 +1,1978 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "version": "2.0.0", + "type": "object", + "description": "Full schema for ERC-7730 descriptors, including the customEncoding/switch/interaction keys and the operation calldata param defined by the non-ABI-dispatch companion ERC (requires 7730). Based on erc7730-v2.schema.json; see that file for the unmodified base ERC-7730 schema, and the companion ERC's Specification section for the normative prose this schema encodes.", + "properties": { + "$schema": { + "title": "Schema", + "type": "string", + "format": "uri-reference", + "description": "The schema that the document should conform to. This should be the URL of a version of the clear signing JSON schemas available under https://github.com/LedgerHQ/clear-signing-erc7730-registry/tree/master/specs" + }, + "$comment": { + "title": "Schema", + "type": "string", + "description": "An optional comment string that can be used to document the purpose of the file." + }, + "includes": { + "title": "External includes", + "type": "string", + "format": "uri-reference", + "description": "An URL of another ERC 7730 file that should be merged into this one. Includes are merged into this file before analysis. This can be used to manage interfaces definitions without redundancy." + }, + "context": { + "$ref": "#/$context/main" + }, + "metadata": { + "$ref": "#/$metadata/main" + }, + "display": { + "$ref": "#/$display/main" + } + }, + "additionalProperties": false, + "$context": { + "main": { + "title": "Binding Context Section", + "type": "object", + "description": "The binding context is a set of constraints that are used to bind the ERC7730 file to a specific structured data being displayed. Currently, supported contexts include contract-specific constraints or EIP712 message specific constraints.", + "properties": { + "$id": { + "$ref": "#/$definitions/id" + } + }, + "oneOf": [ + { + "$ref": "#/$context/contract" + }, + { + "$ref": "#/$context/EIP712" + } + ], + "unevaluatedProperties": false + }, + "contract": { + "type": "object", + "properties": { + "contract": { + "title": "Contract Binding Context", + "type": "object", + "description": "The contract binding context is a set constraints that are used to bind the ERC7730 file to a specific smart contract.", + "properties": { + "abi": { + "description": "[Deprecated] ABI definition bound to this file. Continue providing it for backward compatibility only; new specs should rely on display formats." + }, + "deployments": { + "$ref": "#/$context/deployments" + }, + "factory": { + "title": "Factory constraint", + "type": "object", + "description": "A factory constraint is used to check whether the target contract is deployed by a specified factory.", + "properties": { + "deployments": { + "$ref": "#/$context/deployments" + }, + "deployEvent": { + "title": "Deploy Event signature", + "type": "string", + "description": "The event signature that is emitted by the factory when deploying a new contract." + } + }, + "required": [ + "deployments", + "deployEvent" + ], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": [ + "contract" + ] + }, + "EIP712": { + "type": "object", + "properties": { + "eip712": { + "title": "EIP 712 Binding", + "type": "object", + "description": "The EIP-712 binding context is a set of constraints that must be verified by the message being signed.", + "properties": { + "schemas": { + "description": "[Deprecated] Schema definition bound to this file. Continue providing it for backward compatibility only; new specs should rely on display formats." + }, + "domain": { + "title": "EIP 712 Domain Binding constraint", + "type": "object", + "description": "Each value of the domain constraint MUST match the corresponding eip 712 message domain value.", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "chainId": { + "type": "integer", + "format": "eip155" + }, + "verifyingContract": { + "type": "string", + "format": "eip55" + } + } + }, + "domainSeparator": { + "title": "Domain Separator constraint", + "type": "string", + "description": "The domain separator value that must be matched by the message. In hex string representation." + }, + "deployments": { + "description": "An array of deployments describing what the chainId and verifyingContract in the domain should match.", + "$ref": "#/$context/deployments" + } + }, + "additionalProperties": false + } + }, + "required": [ + "eip712" + ] + }, + "deployments": { + "title": "Deployments constraint", + "type": "array", + "description": "An array of deployments describing where the contract is deployed. The target contract (Tx to or factory) MUST match one of those deployments.", + "items": { + "properties": { + "chainId": { + "type": "integer", + "format": "eip155" + }, + "address": { + "type": "string", + "format": "eip55" + } + } + } + } + }, + "$metadata": { + "main": { + "title": "Metadata Section", + "type": "object", + "description": "The metadata section contains information about constant values relevant in the scope of the current contract / message (as matched by the `context` section)", + "properties": { + "owner": { + "title": "Owner display name", + "type": "string", + "description": "The display name of the owner or target of the contract / message to be clear signed." + }, + "contractName": { + "title": "Contract Name", + "type": "string", + "description": "The name of the contract targeted by the transaction or message." + }, + "info": { + "$ref": "#/$metadata/info" + }, + "token": { + "$ref": "#/$metadata/token" + }, + "constants": { + "$ref": "#/$metadata/constants" + }, + "enums": { + "$ref": "#/$metadata/enums" + } + } + }, + "info": { + "title": "Main contract's owner detailed information", + "type": "object", + "description": "The owner info section contains detailed information about the owner or target of the contract / message to be clear signed.", + "properties": { + "deploymentDate": { + "title": "Deployment date of the contract / message", + "type": "string", + "format": "date-time", + "description": "The date of deployment of the contract / message." + }, + "url": { + "title": "Owner URL", + "type": "string", + "format": "uri", + "description": "URL with more info on the entity the user interacts with." + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + "token": { + "title": "Token Description", + "type": "object", + "description": "A description of an ERC20 token exported by this format, that should be trusted. Not mandatory if the corresponding metadata can be fetched from the contract itself.", + "properties": { + "name": { + "title": "Token Name", + "type": "string" + }, + "ticker": { + "title": "Token Ticker", + "type": "string", + "description": "A short capitalized ticker for the token, that will be displayed in front of corresponding amounts." + }, + "decimals": { + "title": "Token Decimals", + "type": "integer", + "description": "The number of decimals of the token ticker, used to display amounts." + } + }, + "required": [ + "name", + "ticker", + "decimals" + ], + "additionalProperties": false + }, + "constants": { + "title": "Constant values", + "type": "object", + "description": "A set of values that can be used in format parameters. Can be referenced with a path expression like $.metadata.constants.CONSTANT_NAME", + "additionalProperties": { + "type": [ + "string", + "integer", + "number", + "boolean", + "null" + ] + } + }, + "enums": { + "title": "Enums", + "type": "object", + "description": "A set of enums that are used to format fields replacing values with human readable strings.", + "additionalProperties": { + "title": "Enumeration", + "type": "object", + "description": "A set of values that will be used to replace a field value with a human readable string. Enumeration keys are the field values and enumeration values are the displayable strings", + "additionalProperties": { + "type": "string" + } + } + }, + "maps": { + "title": "Maps", + "type": "object", + "description": "A set of maps that are used to manage context dependant constants. Maps can be used in place of constants, and the correpsonding constant is based on the map key resolved value. Each key is a map name that can be used as a reference.", + "additionalProperties": { + "type": "object", + "properties": { + "$keyType": { + "title": "Key Type", + "type": "string", + "description": "An informational representation of the expected key type." + }, + "values": { + "title": "Map Values", + "type": "object", + "description": "A set of values that can be used as constants based on the resolved key. Each key is a possible key value, and each value is the corresponding constant value.", + "additionalProperties": { + "type": [ + "string", + "integer", + "number", + "boolean", + "null" + ] + } + }, + "unresolvedProperties": false + } + } + } + }, + "$display": { + "main": { + "title": "Display Formatting Info Section", + "type": "object", + "description": "The display section contains all the information needed to format the data in a human readable way. It contains the constants and formatters used to display the data contained in the bound structure.", + "properties": { + "definitions": { + "type": "object", + "title": "Common Formatter Definitions", + "description": "A set of definitions that can be used to share formatting information between multiple messages / functions. The definitions can be referenced by the key name in an internal path.", + "additionalProperties": { + "$ref": "#/$format/field" + } + }, + "formats": { + "title": "List of field formats", + "description": "The list includes formatting info for each field of a structure. For contract bindings, entries are keyed by the full function signature with parameter names; for EIP712 bindings, entries are keyed by the string returned by EIP 712 encodeType on the primary type.", + "type": "object", + "propertyNames": { + "anyOf": [ + { + "pattern": "^\\s*[A-Za-z_][A-Za-z0-9_:]*\\s*\\(.*\\)$" + }, + { + "const": "$fallback" + } + ] + }, + "additionalProperties": { + "oneOf": [ + { + "title": "A structured data format specification", + "description": "A structured data format specification contains formatting information of fields in a single type of message.", + "type": "object", + "properties": { + "$id": { + "$ref": "#/$definitions/id" + }, + "intent": { + "$ref": "#/$display/intent" + }, + "interpolatedIntent": { + "$ref": "#/$display/interpolatedIntent" + }, + "fields": { + "$ref": "#/$display/fields" + } + }, + "additionalProperties": false, + "required": [ + "fields" + ] + }, + { + "title": "A structured data format specification, redirected via a top-level switch", + "type": "object", + "properties": { + "$id": { + "$ref": "#/$definitions/id" + }, + "switch": { + "$ref": "#/$customEncoding/topLevelSwitch" + } + }, + "required": [ + "switch" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "formats" + ], + "additionalProperties": false + }, + "intent": { + "oneOf": [ + { + "title": "Simple intent message", + "description": "A description of the intent of the structured data signing, that will be displayed to the user.", + "type": "string" + }, + { + "title": "Complex intent message", + "description": "A description of the intent of the structured data signing, that will be displayed to the user.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "interpolatedIntent": { + "title": "Interpolated intent message", + "description": "An optional intent string with embedded field values using {path} interpolation syntax. This provides a dynamic, contextual description by embedding actual transaction/message values directly in the intent string. Wallets should prefer displaying interpolatedIntent when available and fall back to intent if interpolation fails. See the specification for detailed formatting behavior and security considerations.", + "type": "string" + }, + "fields": { + "title": "Field Formats set", + "type": "array", + "description": "An array containing the ordered definitions of fields formats. See the specification for more details.", + "items": { + "oneOf": [ + { + "$ref": "#/$format/field" + }, + { + "$ref": "#/$display/fieldGroup" + }, + { + "$ref": "#/$display/reference" + } + ] + }, + "unevaluatedProperties": false + }, + "fieldGroup": { + "title": "A group of field formats, allowing recursivity in the schema and control over grouping and iteration.", + "description": "A set of field formats used to group whole definitions for structures for instance. This allows nesting definitions of formats, but note that support for deep nesting will be device dependent.", + "type": "object", + "properties": { + "$id": { + "$ref": "#/$definitions/id" + }, + "path": { + "$ref": "#/$format/path" + }, + "label": { + "title": "Group Label", + "description": "The group label of the field group, that will be displayed to the user in front of the formatted field values.", + "type": "string" + }, + "iteration": { + "title": "Group arrays iteration mode", + "description": "Specifies how iteration over arrays in the group should be handled. Sequential mode displays elements grouped by array, ie arr_0[0] ... arr_0[N] arr_1[0] ... arr_1[M]. Bundled mode displays elements of the arrays grouped by index, ie arr_0[0] arr_1[0] arr_0[1] arr_1[1] ... arr_0[N] arr_1[N]. In bundled mode, all arrays MUST be of the same length.", + "type": "string", + "enum": [ + "sequential", + "bundled" + ] + }, + "fields": { + "$ref": "#/$display/fields" + } + }, + "required": [ + "fields" + ], + "additionalProperties": false + }, + "reference": { + "title": "Reference", + "description": "A reference to a shared definition that should be used as the field formatting definition. The value is the key in the display definitions section, as a path expression $.display.definitions.DEFINITION_NAME. It is used to share definitions between multiple messages / functions.", + "properties": { + "path": { + "$ref": "#/$format/path" + }, + "value": { + "$ref": "#/$format/value" + }, + "label": { + "description": "This value overrides the label in the referenced definition if set.", + "type": "string" + }, + "$ref": { + "description": "An internal definition that should be used as the field formatting definition. The value is the key in the display definitions section, as a path expression $.display.definitions.DEFINITION_NAME.", + "type": "string" + }, + "params": { + "description": "Parameters override. These values takes precedence over the ones in the definition itself", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "separator": { + "description": "Separator override for the referenced definition.", + "type": "string" + }, + "visible": { + "description": "Visibility override for the referenced definition.", + "$ref": "#/$format/rules" + }, + "encryption": { + "description": "Encryption override for the referenced definition.", + "$ref": "#/$format/encryptionParameters" + } + }, + "required": [ + "$ref" + ], + "allOf": [ + { + "not": { + "required": [ + "path", + "value" + ] + } + } + ], + "additionalProperties": false + } + }, + "$format": { + "path": { + "title": "Path", + "type": "string", + "description": "A path to the field in the structured data. The path is a JSON path expression that can be used to extract the field value from the structured data." + }, + "value": { + "title": "Value", + "type": [ + "string", + "integer", + "number", + "boolean" + ], + "description": "A literal value on which the format should be applied instead of looking up a field in the structured data." + }, + "field": { + "title": "Field formatter", + "description": "A field formatter contains formatting information of a single field in a message.", + "type": "object", + "properties": { + "$id": { + "$ref": "#/$definitions/id" + }, + "path": { + "$ref": "#/$format/path" + }, + "value": { + "$ref": "#/$format/value" + }, + "visible": { + "$ref": "#/$format/rules" + }, + "label": { + "title": "Field Label", + "description": "The label of the field, that will be displayed to the user in front of the formatted field value.", + "type": "string" + }, + "format": { + "title": "Field Format", + "description": "The format of the field, that will be used to format the field value in a human readable way.", + "type": "string", + "$ref": "#/$format/names" + }, + "separator": { + "title": "Field Separator", + "description": "An optional separator string that will be used to separate multiple values when the field is an array. A separator use the interpolated string format with one specific parameter {index} replaced by the index of the element in the array.", + "type": "string" + }, + "encryption": { + "$ref": "#/$format/encryptionParameters", + "description": "If present, the field value is encrypted. The format specifies how to display the decrypted value." + }, + "customEncoding": { + "$ref": "#/$customEncoding/node" + }, + "switch": { + "$ref": "#/$customEncoding/fieldSwitch" + }, + "interaction": { + "$ref": "#/$customEncoding/interaction" + } + }, + "allOf": [ + { + "not": { + "required": [ + "path", + "value" + ] + } + }, + { + "if": { + "properties": { + "format": { + "const": "addressName" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/addressNameParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "interoperableAddressName" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/interoperableAddressNameParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "calldata" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/calldataParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "tokenAmount" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/tokenAmountParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "tokenTicker" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/tokenTickerParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "nftName" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/nftNameParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "date" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/dateParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "unit" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/unitParameters" + } + } + } + }, + { + "if": { + "properties": { + "format": { + "const": "enum" + } + } + }, + "then": { + "properties": { + "params": { + "$ref": "#/$format/enumParameters" + } + } + } + }, + { + "not": { + "required": [ + "customEncoding", + "format" + ] + }, + "$comment": "customEncoding MUST NOT combine with format (non-abi-dispatch companion ERC, ### customEncoding)." + }, + { + "not": { + "required": [ + "interaction", + "format" + ] + }, + "$comment": "interaction is mutually exclusive with format (non-abi-dispatch companion ERC, ### interaction)." + }, + { + "not": { + "required": [ + "interaction", + "customEncoding" + ] + }, + "$comment": "interaction is mutually exclusive with customEncoding (non-abi-dispatch companion ERC, ### interaction)." + } + ], + "unevaluatedProperties": false + }, + "rules": { + "title": "Display Rule", + "description": "Specifies when a field should be displayed based on its value or context. Defaults to 'always' if not specified.", + "oneOf": [ + { + "type": "string", + "enum": [ + "always", + "never", + "optional" + ], + "description": "Simple display rule: 'always' means always display, 'never' means always skip display, 'optional' means display only if wallet can." + }, + { + "type": "object", + "properties": { + "ifNotIn": { + "type": "array", + "description": "Display the field only if its value is NOT in this list.", + "items": { + "type": [ + "string", + "number", + "boolean", + "null" + ] + }, + "minItems": 1 + }, + "mustMatch": { + "type": "array", + "description": "Always skip display, but value MUST match one of these values.", + "items": { + "type": [ + "string", + "number", + "boolean", + "null" + ] + }, + "minItems": 1 + } + }, + "additionalProperties": false, + "allOf": [ + { + "not": { + "required": [ + "ifNotIn", + "mustMatch" + ] + } + } + ], + "description": "Conditional display rules. Either 'ifNotIn' or 'mustMatch' must be defined, but not both." + } + ] + }, + "mapReference": { + "title": "Map Reference", + "type": "object", + "properties": { + "map": { + "type": "string", + "description": "The path to the referenced map." + }, + "keyPath": { + "type": "string", + "description": "The path to the key used to resolve a value using the referenced map." + } + } + }, + "names": { + "anyOf": [ + { + "title": "Raw format", + "const": "raw", + "description": "The field should be displayed as the natural representation of the underlying structured data type." + }, + { + "title": "address format", + "const": "addressName", + "description": "The field should be displayed as a trusted name, or as a raw address if no names are found in trusted sources. List of trusted sources can be optionally specified in parameters." + }, + { + "title": "address format", + "const": "tokenTicker", + "description": "The field should be displayed as an ERC 20 token ticker, or as a raw address if no token definition are found." + }, + { + "title": "bytes format", + "const": "calldata", + "description": "The field is itself a calldata embedded in main call. Another ERC 7730 should be used to parse this field. If not available or not supported, the wallet MAY display a hash of the embedded calldata instead." + }, + { + "title": "integer format", + "const": "amount", + "description": "The field should be displayed as an amount in underlying currency, converted using the best magnitude / ticker available." + }, + { + "title": "integer format", + "const": "tokenAmount", + "description": "The field should be displayed as an amount, preceded by the ticker. The magnitude and ticker should be derived from the token or tokenPath parameter corresponding metadata." + }, + { + "title": "integer format", + "const": "nftName", + "description": "The field should be displayed as a single NFT names, or as a raw token Id if a specific name is not found. Collection is specified by the collection or collectionPath parameter." + }, + { + "title": "integer format", + "const": "date", + "description": "The field should be displayed as a date. Suggested RFC3339 representation. Parameter specifies the encoding of the date." + }, + { + "title": "integer format", + "const": "duration", + "description": "The field should be displayed as a duration in HH:MM:ss form. Value is interpreted as a number of seconds." + }, + { + "title": "integer format", + "const": "unit", + "description": "The field should be displayed as a percentage. Magnitude of the percentage encoding is specified as a parameter. Example: a value of 3000 with magnitude 4 is displayed as 0.3%." + }, + { + "title": "integer format", + "const": "enum", + "description": "The field should be displayed as a human readable string by converting the value using the enum referenced in parameters." + }, + { + "title": "integer format", + "const": "chainId", + "description": "The field should be displayed as a Blockchain explicit name, as defined in EIP-155, based on the chain id value." + }, + { + "title": "integer format", + "const": "interoperableAddressName", + "description": "The field should be displayed as a trusted name or as an EIP-7930 Interoperable Address human readable format. List of trusted sources can be optionally specified in parameters." + } + ] + }, + "addressNameParameters": { + "title": "Address Names Formatting Parameters", + "type": "object", + "properties": { + "types": { + "title": "Address Type", + "type": "array", + "description": "The types of address to display. Restrict allowable sources of names and MAY lead to additional checks from wallets.", + "items": { + "type": "string", + "enum": [ + "wallet", + "eoa", + "contract", + "token", + "collection" + ] + } + }, + "sources": { + "title": "Trusted Sources", + "description": "Trusted Sources for names, in order of preferences. Sources values are wallet manufacturer specific, example values are \"local\" or \"ens\". See specification for more details on sources values.", + "type": "array", + "items": { + "type": "string" + } + }, + "senderAddress": { + "title": "Sender Address", + "oneOf": [ + { + "type": "string", + "description": "An address equal to this value is interpreted as the sender referenced by `@.from`." + }, + { + "type": "array", + "description": "An array of addresses, any of which are interpreted as the sender referenced by `@.from`.", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "calldataParameters": { + "title": "Embedded Calldata Formatting Parameters", + "type": "object", + "properties": { + "callee": { + "title": "Callee Address", + "type": [ + "string", + "object" + ], + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The address of the contract being called by this embedded calldata." + }, + "calleePath": { + "title": "Callee Path", + "type": "string", + "description": "The path to the address of the contract being called by this embedded calldata." + }, + "selector": { + "title": "Called Selector (Optional)", + "type": [ + "string", + "object" + ], + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The selector being called, if not contained in the calldata. Hex string representation." + }, + "selectorPath": { + "title": "Called Selector Path (Optional)", + "type": "string", + "description": "The path to the selector being called, if not contained in the calldata." + }, + "amount": { + "title": "Amount (Optional)", + "type": [ + "integer", + "object" + ], + "anyOf": [ + { + "type": "integer" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The associated amount in native currency, if the calldata can be associated with a container value." + }, + "amountPath": { + "title": "Amoun Path (Optional)", + "type": "string", + "description": "The path to the associated amount in native currency, if the calldata can be associated with a container value." + }, + "spender": { + "title": "Spender Path (Optional)", + "type": [ + "string", + "object" + ], + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The associated spender, if the calldata can be associated with a container value." + }, + "spenderPath": { + "title": "Spender Path (Optional)", + "type": "string", + "description": "The path to the associated spender, if the calldata can be associated with a container value." + }, + "operation": { + "$ref": "#/$customEncoding/operationParam" + } + }, + "anyOf": [ + { + "required": [ + "callee" + ] + }, + { + "required": [ + "calleePath" + ] + } + ], + "allOf": [ + { + "not": { + "required": [ + "callee", + "calleePath" + ] + } + }, + { + "not": { + "required": [ + "selector", + "selectorPath" + ] + } + }, + { + "not": { + "required": [ + "amount", + "amountPath" + ] + } + }, + { + "not": { + "required": [ + "spender", + "spenderPath" + ] + } + } + ], + "additionalProperties": false + }, + "tokenAmountParameters": { + "title": "Token Amount Formatting Parameters", + "type": "object", + "properties": { + "token": { + "title": "Token", + "type": [ + "string", + "object" + ], + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The token address, or a path to a constant in the ERC 7730 file." + }, + "tokenPath": { + "title": "Token Path", + "type": "string", + "description": "The path to the token address in the structured data." + }, + "nativeCurrencyAddress": { + "title": "Native Currency Address", + "oneOf": [ + { + "type": "string", + "description": "An address equal to this value is interpreted as an amount in native currency rather than a token." + }, + { + "type": "array", + "description": "An array of addresses, any of which are interpreted as an amount in native currency rather than a token.", + "items": { + "type": "string" + } + } + ] + }, + "threshold": { + "title": "Unlimited Threshold", + "type": "string", + "description": "The threshold above which the amount should be displayed using the message parameter rather than the real amount." + }, + "message": { + "title": "Unlimited Message", + "type": "string", + "description": "The message to display when the amount is above the threshold." + }, + "chainId": { + "title": "Chain ID", + "type": [ + "integer", + "object" + ], + "anyOf": [ + { + "type": "integer" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "Optional. The chain on which the token is deployed (constant, or a map reference). When present, the wallet SHOULD resolve token metadata (ticker, decimals) for this chain. Useful for cross-chain swap clear signing where the same token address may refer to different chains." + }, + "chainIdPath": { + "title": "Chain ID Path", + "type": "string", + "description": "Optional. Path to the chain ID in the structured data. When present, the wallet SHOULD resolve token metadata for the chain at this path. Useful for cross-chain swap clear signing." + } + }, + "allOf": [ + { + "not": { + "required": [ + "token", + "tokenPath" + ] + } + }, + { + "not": { + "required": [ + "chainId", + "chainIdPath" + ] + } + } + ], + "additionalProperties": false + }, + "tokenTickerParameters": { + "title": "Token Ticker Formatting Parameters", + "type": "object", + "properties": { + "chainId": { + "title": "Chain ID", + "type": [ + "integer", + "object" + ], + "anyOf": [ + { + "type": "integer" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "Optional. The chain on which the token is deployed (constant, or a map reference). When present, the wallet SHOULD resolve the token ticker for this chain. Useful for cross-chain swap clear signing." + }, + "chainIdPath": { + "title": "Chain ID Path", + "type": "string", + "description": "Optional. Path to the chain ID in the structured data. When present, the wallet SHOULD resolve the token ticker for the chain at this path. Useful for cross-chain swap clear signing." + } + }, + "allOf": [ + { + "not": { + "required": [ + "chainId", + "chainIdPath" + ] + } + } + ], + "additionalProperties": false + }, + "nftNameParameters": { + "title": "NFT Names Formatting Parameters", + "type": "object", + "properties": { + "collection": { + "title": "Collection Address", + "type": [ + "string", + "object" + ], + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$format/mapReference" + } + ], + "description": "The collection address, or a path to a constant in the ERC 7730 file." + }, + "collectionPath": { + "title": "Collection Path", + "type": "string", + "description": "The path to the collection in the structured data." + } + }, + "anyOf": [ + { + "required": [ + "collection" + ] + }, + { + "required": [ + "collectionPath" + ] + } + ], + "not": { + "required": [ + "collection", + "collectionPath" + ] + }, + "additionalProperties": false + }, + "dateParameters": { + "title": "Date Formatting Parameters", + "type": "object", + "properties": { + "encoding": { + "title": "Date Encoding", + "type": "string", + "description": "The encoding of the date.", + "enum": [ + "blockheight", + "timestamp" + ] + } + }, + "required": [ + "encoding" + ], + "additionalProperties": false + }, + "unitParameters": { + "title": "Unit Formatting Parameters", + "type": "object", + "properties": { + "base": { + "title": "Unit base symbol", + "type": "string", + "description": "The base symbol of the unit, displayed after the converted value. It can be an SI unit symbol or acceptable dimensionless symbols like % or bps." + }, + "decimals": { + "title": "Decimals", + "type": "integer", + "description": "The number of decimals of the value, used to convert to a float." + }, + "prefix": { + "title": "Prefix", + "type": "boolean", + "description": "Whether the value should be converted to a prefixed unit, like k, M, G, etc." + } + }, + "required": [ + "base" + ], + "additionalProperties": false + }, + "enumParameters": { + "title": "Enum Formatting Parameters", + "type": "object", + "properties": { + "$ref": { + "title": "Enum reference", + "type": "string", + "description": "The internal path to the enum definition used to convert this value." + } + }, + "required": [ + "$ref" + ], + "additionalProperties": false + }, + "interoperableAddressNameParameters": { + "title": "Interoperable Address Names Formatting Parameters", + "type": "object", + "properties": { + "types": { + "title": "Address Type", + "type": "array", + "description": "The types of address to display. Restrict allowable sources of names and MAY lead to additional checks from wallets.", + "items": { + "type": "string", + "enum": [ + "wallet", + "eoa", + "contract", + "token", + "collection" + ] + } + }, + "sources": { + "title": "Trusted Sources", + "description": "Trusted Sources for names, in order of preferences. Sources values are wallet manufacturer specific, example values are \"local\" or \"ens\". See specification for more details on sources values.", + "type": "array", + "items": { + "type": "string" + } + }, + "senderAddress": { + "title": "Sender Address", + "oneOf": [ + { + "type": "string", + "description": "An address equal to this value is interpreted as the sender referenced by `@.from`." + }, + { + "type": "array", + "description": "An array of addresses, any of which are interpreted as the sender referenced by `@.from`.", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "encryptionParameters": { + "title": "Encrypted Value Parameters", + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "The encryption scheme used to produce the handle." + }, + "plaintextType": { + "type": "string", + "description": "Solidity type of the decrypted value (the handle does not encode this)." + }, + "fallbackLabel": { + "type": "string", + "description": "Optional label to display when decryption is not possible. Defaults to \"[Encrypted]\"." + } + }, + "required": [ + "scheme" + ], + "additionalProperties": false + } + }, + "$definitions": { + "id": { + "title": "ID", + "type": "string", + "description": "An internal identifier that can be used either for clarity specifying what the element is or as a reference in device specific sections." + } + }, + "$id": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "title": "ERC-7730 Non-ABI Dispatch Companion Schema", + "$customEncoding": { + "node": { + "title": "A customEncoding node", + "oneOf": [ + { + "$ref": "#/$customEncoding/uint" + }, + { + "$ref": "#/$customEncoding/bytes" + }, + { + "$ref": "#/$customEncoding/address" + }, + { + "$ref": "#/$customEncoding/bool" + }, + { + "$ref": "#/$customEncoding/bitfield" + }, + { + "$ref": "#/$customEncoding/object" + }, + { + "$ref": "#/$customEncoding/sequence" + }, + { + "$ref": "#/$customEncoding/switchNode" + } + ] + }, + "uint": { + "type": "object", + "properties": { + "type": { + "const": "uint" + }, + "bytes": { + "type": "integer", + "minimum": 1, + "maximum": 32 + }, + "endian": { + "enum": [ + "be", + "le" + ] + }, + "mask": { + "type": "string", + "pattern": "^0x[0-9a-fA-F]+$|^0b[01]+$" + } + }, + "required": [ + "type", + "bytes" + ], + "additionalProperties": false + }, + "bytes": { + "type": "object", + "properties": { + "type": { + "const": "bytes" + }, + "length": { + "type": "integer", + "minimum": 0 + }, + "lengthFrom": { + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "address": { + "type": "object", + "properties": { + "type": { + "const": "address" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "bool": { + "type": "object", + "properties": { + "type": { + "const": "bool" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "bitfield": { + "type": "object", + "properties": { + "type": { + "const": "bitfield" + }, + "bytes": { + "type": "integer", + "minimum": 1, + "maximum": 32 + }, + "endian": { + "enum": [ + "be", + "le" + ] + }, + "fields": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "bit": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "name", + "bit" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "bits": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "name", + "bits" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "type", + "bytes", + "fields" + ], + "additionalProperties": false + }, + "object": { + "type": "object", + "properties": { + "type": { + "const": "object" + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "schema": { + "$ref": "#/$customEncoding/node" + }, + "label": { + "type": "string" + }, + "format": { + "$ref": "#/$format/names" + }, + "params": { + "type": "object" + } + }, + "required": [ + "name", + "schema" + ], + "additionalProperties": false + } + } + }, + "required": [ + "type", + "fields" + ], + "additionalProperties": false + }, + "sequence": { + "type": "object", + "properties": { + "type": { + "const": "sequence" + }, + "element": { + "$ref": "#/$customEncoding/node" + }, + "count": { + "type": "integer", + "minimum": 0 + }, + "countFrom": { + "type": "string" + } + }, + "required": [ + "type", + "element" + ], + "additionalProperties": false + }, + "switchNode": { + "title": "switch used as a customEncoding node (inline, reads from the buffer at the cursor)", + "type": "object", + "properties": { + "type": { + "const": "switch" + }, + "expression": { + "$ref": "#/$customEncoding/node" + }, + "payloadFrom": { + "type": "string" + }, + "cases": { + "$ref": "#/$customEncoding/switchCases" + } + }, + "required": [ + "type", + "expression", + "cases" + ], + "additionalProperties": false + }, + "fieldSwitch": { + "title": "switch used as a field-format-specification key (sibling-path-sourced form)", + "type": "object", + "properties": { + "expression": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "mask": { + "type": "string", + "pattern": "^0x[0-9a-fA-F]+$|^0b[01]+$" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + { + "$ref": "#/$customEncoding/node" + } + ] + }, + "cases": { + "$ref": "#/$customEncoding/switchCases" + } + }, + "required": [ + "expression", + "cases" + ], + "additionalProperties": false + }, + "switchCases": { + "title": "A switch cases map, including the reserved $default key", + "type": "object", + "additionalProperties": { + "$ref": "#/$customEncoding/switchCaseValue" + } + }, + "switchCaseValue": { + "oneOf": [ + { + "const": "reject" + }, + { + "type": "object", + "properties": { + "customEncoding": { + "$ref": "#/$customEncoding/node" + } + }, + "required": [ + "customEncoding" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "switch": { + "$ref": "#/$customEncoding/fieldSwitch" + } + }, + "required": [ + "switch" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "format": { + "$ref": "#/$format/names" + }, + "params": { + "type": "object" + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "intent": { + "enum": [ + "info", + "warning" + ] + } + }, + "required": [ + "label" + ], + "additionalProperties": false + }, + { + "title": "Tuple-signature-key shorthand: decode as standard ABI using this Solidity tuple type, then apply intent/fields inline", + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "additionalProperties": { + "type": "object", + "properties": { + "intent": { + "$ref": "#/$display/intent" + }, + "fields": { + "$ref": "#/$display/fields" + } + }, + "required": [ + "fields" + ], + "additionalProperties": false + } + } + ] + }, + "topLevelSwitch": { + "title": "switch at the top of a structured data format specification", + "type": "object", + "properties": { + "expression": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + { + "$ref": "#/$customEncoding/node" + } + ] + }, + "cases": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "const": "reject" + }, + { + "type": "object", + "properties": { + "switch": { + "$ref": "#/$customEncoding/topLevelSwitch" + } + }, + "required": [ + "switch" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "interaction": { + "$ref": "#/$customEncoding/interaction" + } + }, + "required": [ + "interaction" + ], + "additionalProperties": false + }, + { + "title": "A case that directly decodes and displays its own data, same shape as an ordinary format entry", + "type": "object", + "properties": { + "intent": { + "$ref": "#/$display/intent" + }, + "interpolatedIntent": { + "$ref": "#/$display/interpolatedIntent" + }, + "fields": { + "$ref": "#/$display/fields" + } + }, + "required": [ + "fields" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "expression", + "cases" + ], + "additionalProperties": false + }, + "interaction": { + "title": "interaction: a call synthesized from already-decoded pieces", + "type": "object", + "properties": { + "to": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "value": {} + }, + "required": [ + "value" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "to", + "signature", + "args" + ], + "additionalProperties": false + }, + "operationParam": { + "title": "operation: the EVM operation used to execute an embedded call, for format:calldata", + "oneOf": [ + { + "enum": [ + "CALL", + "DELEGATECALL", + "CREATE", + "CREATE2", + "CALLCODE" + ] + }, + { + "type": "object", + "properties": { + "expression": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + }, + "cases": { + "type": "object", + "additionalProperties": { + "enum": [ + "CALL", + "DELEGATECALL", + "CREATE", + "CREATE2", + "CALLCODE" + ] + } + } + }, + "required": [ + "expression", + "cases" + ], + "additionalProperties": false + } + ] + } + }, + "$comment": "This is a full schema, not a thin extension of erc7730-v2.schema.json: composing new properties onto a schema closed with additionalProperties:false (calldataParameters) or reached only via $ref (field, whose own unevaluatedProperties:false does not see sibling allOf properties across a $ref boundary - verified empirically, not just by reading the spec) does not validate the way a thin allOf+$ref extension would suggest. Everything here is spliced directly into the same base schema objects instead. One side effect worth knowing: because field is modified in place rather than duplicated, customEncoding/switch/interaction are also available inside $display/fieldGroup's and $display/reference's own nested fields, not just top-level display.formats.*.fields[], even though no current example exercises that." +} \ No newline at end of file diff --git a/assets/erc-0000/example-balancer-relayer-library.json b/assets/erc-0000/example-balancer-relayer-library.json new file mode 100644 index 00000000000..2112a7d135b --- /dev/null +++ b/assets/erc-0000/example-balancer-relayer-library.json @@ -0,0 +1,463 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Balancer Batch Relayer Library", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0xeA66501dF1A00261E3bB79D1E90444fc6A186B62" + } + ] + } + }, + "metadata": { + "owner": "Balancer", + "contractName": "BatchRelayerLibrary", + "info": { + "url": "https://docs-v2.balancer.fi/concepts/advanced/relayers.html" + } + }, + "display": { + "formats": { + "setRelayerApproval(address relayer,bool approved,bytes authorization)": { + "$id": "Balancer Relayer Approval", + "intent": "Allow relayer to act on your behalf", + "fields": [ + { + "path": "relayer", + "label": "Relayer", + "format": "addressName" + }, + { + "path": "approved", + "label": "Approved", + "format": "raw" + }, + { + "path": "authorization", + "label": "Signed authorization", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "deadline", + "schema": { + "type": "uint", + "bytes": 32 + } + }, + { + "name": "v", + "schema": { + "type": "uint", + "bytes": 32 + } + }, + { + "name": "r", + "schema": { + "type": "bytes", + "length": 32 + } + }, + { + "name": "s", + "schema": { + "type": "bytes", + "length": 32 + } + } + ] + } + }, + { + "path": "authorization.deadline", + "label": "Valid until", + "format": "date", + "params": { + "encoding": "timestamp" + } + } + ] + }, + "joinPool(bytes32 poolId,uint8 kind,address sender,address recipient,(address[],uint256[],bytes,bool) request,uint256 value,uint256 outputReference)": { + "$id": "Balancer Relayer Join Pool", + "intent": "Add liquidity", + "fields": [ + { + "path": "poolId", + "label": "Pool", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "poolAddress", + "schema": { + "type": "address" + } + }, + { + "name": "specialization", + "schema": { + "type": "uint", + "bytes": 2 + } + }, + { + "name": "nonce", + "schema": { + "type": "uint", + "bytes": 10 + } + } + ] + } + }, + { + "path": "poolId.poolAddress", + "label": "Pool", + "format": "addressName" + }, + { + "path": "sender", + "label": "From", + "format": "addressName" + }, + { + "path": "recipient", + "label": "Recipient", + "format": "addressName" + }, + { + "path": "request.maxAmountsIn[]", + "label": "Maximum amount in", + "format": "tokenAmount", + "params": { + "tokenPath": "request.assets[]" + } + }, + { + "path": "request.userData", + "label": "Join details", + "switch": { + "expression": { + "path": "kind" + }, + "cases": { + "0x00": { + "switch": { + "expression": { + "type": "uint", + "bytes": 32 + }, + "cases": { + "0x01": { + "(uint8 joinKind,uint256[] amountsIn,uint256 minBptAmountOut)": { + "intent": "Add liquidity for exact tokens", + "fields": [ + { + "path": "amountsIn[]", + "label": "Amount in", + "format": "tokenAmount", + "params": { + "tokenPath": "#.request.assets[]" + } + }, + { + "path": "minBptAmountOut", + "label": "Minimum pool tokens out", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Dynamic value \u2014 set by an earlier step in this transaction, not known yet", + "intent": "warning" + }, + "$default": { + "format": "tokenAmount", + "params": { + "tokenPath": "#.poolId.poolAddress" + } + } + } + } + } + ] + } + }, + "$default": "reject" + } + } + }, + "$default": "reject" + } + } + }, + { + "path": "request.fromInternalBalance", + "label": "Pay from Vault internal balance", + "format": "raw" + }, + { + "path": "value", + "label": "ETH sent", + "format": "amount" + }, + { + "path": "outputReference", + "label": "Save result as", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Stored for use by a later step in this transaction", + "intent": "info" + }, + "$default": { + "format": "raw" + } + } + } + } + ] + }, + "exitPool(bytes32 poolId,uint8 kind,address sender,address recipient,(address[],uint256[],bytes,bool) request,(uint256,uint256)[] outputReferences)": { + "$id": "Balancer Relayer Exit Pool", + "intent": "Remove liquidity", + "fields": [ + { + "path": "poolId", + "label": "Pool", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "poolAddress", + "schema": { + "type": "address" + } + }, + { + "name": "specialization", + "schema": { + "type": "uint", + "bytes": 2 + } + }, + { + "name": "nonce", + "schema": { + "type": "uint", + "bytes": 10 + } + } + ] + } + }, + { + "path": "poolId.poolAddress", + "label": "Pool", + "format": "addressName" + }, + { + "path": "sender", + "label": "From", + "format": "addressName" + }, + { + "path": "recipient", + "label": "Recipient", + "format": "addressName" + }, + { + "path": "request.minAmountsOut[]", + "label": "Minimum amount out", + "format": "tokenAmount", + "params": { + "tokenPath": "request.assets[]" + } + }, + { + "path": "request.userData", + "label": "Exit details", + "switch": { + "expression": { + "type": "uint", + "bytes": 32 + }, + "cases": { + "0xff": { + "(uint8 exitKind,uint256 bptAmountIn)": { + "intent": "Exit in Recovery Mode", + "fields": [ + { + "path": "bptAmountIn", + "label": "Pool tokens in", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Dynamic value \u2014 set by an earlier step in this transaction, not known yet", + "intent": "warning" + }, + "$default": { + "format": "tokenAmount", + "params": { + "tokenPath": "#.poolId.poolAddress" + } + } + } + } + } + ] + } + }, + "$default": { + "switch": { + "expression": { + "path": "kind" + }, + "cases": { + "0x00": { + "switch": { + "expression": { + "type": "uint", + "bytes": 32 + }, + "cases": { + "0x01": { + "(uint8 exitKind,uint256 bptAmountIn)": { + "intent": "Remove liquidity proportionally", + "fields": [ + { + "path": "bptAmountIn", + "label": "Pool tokens in", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Dynamic value \u2014 set by an earlier step in this transaction, not known yet", + "intent": "warning" + }, + "$default": { + "format": "tokenAmount", + "params": { + "tokenPath": "#.poolId.poolAddress" + } + } + } + } + } + ] + } + }, + "$default": "reject" + } + } + }, + "0x03": { + "switch": { + "expression": { + "type": "uint", + "bytes": 32 + }, + "cases": { + "0x02": { + "(uint8 exitKind,uint256 bptAmountIn)": { + "intent": "Remove liquidity proportionally", + "fields": [ + { + "path": "bptAmountIn", + "label": "Pool tokens in", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Dynamic value \u2014 set by an earlier step in this transaction, not known yet", + "intent": "warning" + }, + "$default": { + "format": "tokenAmount", + "params": { + "tokenPath": "#.poolId.poolAddress" + } + } + } + } + } + ] + } + }, + "$default": "reject" + } + } + }, + "0x01": "reject", + "0x02": "reject", + "$default": "reject" + } + } + } + } + } + }, + { + "path": "request.toInternalBalance", + "label": "Receive to Vault internal balance", + "format": "raw" + }, + { + "path": "outputReferences[].index", + "label": "Token", + "format": "raw" + }, + { + "path": "outputReferences[].key", + "label": "Save result as", + "customEncoding": { + "type": "switch", + "expression": { + "type": "uint", + "bytes": 32, + "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" + }, + "cases": { + "0xba10000000000000000000000000000000000000000000000000000000000000": { + "label": "Stored for use by a later step in this transaction", + "intent": "info" + }, + "$default": "reject" + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-balancer-relayer-multicall.json b/assets/erc-0000/example-balancer-relayer-multicall.json new file mode 100644 index 00000000000..0a100d4580a --- /dev/null +++ b/assets/erc-0000/example-balancer-relayer-multicall.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Balancer Relayer Multicall", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0x35Cea9e57A393ac66Aaa7E25C391D52C74B5648f" + } + ] + } + }, + "metadata": { + "owner": "Balancer", + "contractName": "BalancerRelayer", + "info": { + "url": "https://docs-v2.balancer.fi/concepts/advanced/relayers.html" + } + }, + "display": { + "formats": { + "multicall(bytes[] data)": { + "$id": "Balancer Relayer Multicall", + "intent": "Execute batch", + "fields": [ + { + "path": "data[]", + "label": "Batched relayer action", + "format": "calldata", + "params": { + "callee": "0xeA66501dF1A00261E3bB79D1E90444fc6A186B62", + "operation": "DELEGATECALL" + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-compound-bulker.json b/assets/erc-0000/example-compound-bulker.json new file mode 100644 index 00000000000..0ce9dae8485 --- /dev/null +++ b/assets/erc-0000/example-compound-bulker.json @@ -0,0 +1,129 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Compound III Bulker", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0xa397a8C2086C554B531c02E29f3291c9704B00c7" + } + ] + } + }, + "metadata": { + "owner": "Compound", + "contractName": "Bulker", + "info": { + "url": "https://github.com/compound-finance/comet" + } + }, + "display": { + "formats": { + "invoke(bytes32[] actions,bytes[] data)": { + "$id": "Compound III Bulk Actions", + "intent": "Execute batch", + "fields": [ + { + "path": "data[]", + "label": "Batched action", + "switch": { + "expression": { + "path": "actions[$index]" + }, + "cases": { + "0x414354494f4e5f535550504c595f415353455400000000000000000000000000": { + "(address comet,address to,address asset,uint256 amount)": { + "intent": "Supply collateral", + "fields": [ + { "path": "to", "label": "On behalf of", "format": "addressName" }, + { "path": "asset", "label": "Asset", "format": "addressName" }, + { "path": "amount", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "asset" } }, + { + "interaction": { + "to": "comet", + "signature": "supplyFrom(address,address,address,uint256)", + "args": [ + { "path": "@.from" }, + { "path": "to" }, + { "path": "asset" }, + { "path": "amount" } + ] + } + } + ] + } + }, + "0x414354494f4e5f5452414e534645525f41535345540000000000000000000000": { + "(address comet,address to,address asset,uint256 amount)": { + "intent": "Transfer within Compound", + "fields": [ + { "path": "to", "label": "Recipient", "format": "addressName" }, + { "path": "asset", "label": "Asset", "format": "addressName" }, + { "path": "amount", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "asset" } }, + { + "interaction": { + "to": "comet", + "signature": "transferAssetFrom(address,address,address,uint256)", + "args": [ + { "path": "@.from" }, + { "path": "to" }, + { "path": "asset" }, + { "path": "amount" } + ] + } + } + ] + } + }, + "0x414354494f4e5f57495448445241575f41535345540000000000000000000000": { + "(address comet,address to,address asset,uint256 amount)": { + "intent": "Withdraw or borrow", + "fields": [ + { "path": "to", "label": "Recipient", "format": "addressName" }, + { "path": "asset", "label": "Asset", "format": "addressName" }, + { "path": "amount", "label": "Amount", "format": "tokenAmount", "params": { "tokenPath": "asset" } }, + { + "interaction": { + "to": "comet", + "signature": "withdrawFrom(address,address,address,uint256)", + "args": [ + { "path": "@.from" }, + { "path": "to" }, + { "path": "asset" }, + { "path": "amount" } + ] + } + } + ] + } + }, + "0x414354494f4e5f434c41494d5f52455741524400000000000000000000000000": { + "(address comet,address rewards,address src,bool shouldAccrue)": { + "intent": "Claim rewards", + "fields": [ + { "path": "src", "label": "Claim for", "format": "addressName" }, + { "path": "shouldAccrue", "label": "Accrue first", "format": "raw" }, + { + "interaction": { + "to": "rewards", + "signature": "claim(address,address,bool)", + "args": [ + { "path": "comet" }, + { "path": "src" }, + { "path": "shouldAccrue" } + ] + } + } + ] + } + }, + "$default": "reject" + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-erc7579-execute.json b/assets/erc-0000/example-erc7579-execute.json new file mode 100644 index 00000000000..e3707f3c2f3 --- /dev/null +++ b/assets/erc-0000/example-erc7579-execute.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "ERC-7579 Account Execute", + "contract": { + "deployments": [ + { + "chainId": 8453, + "address": "0x510a1274373c8120DE634dB372723edcBE899994" + }, + { + "chainId": 8453, + "address": "0xE8cCb14989F0dB3f51A71876195D5867F7fa943d" + } + ] + } + }, + "metadata": { + "owner": "Biconomy", + "contractName": "Nexus", + "info": { + "url": "https://github.com/bcnmy/nexus" + } + }, + "display": { + "formats": { + "execute(bytes32 mode,bytes executionCalldata)": { + "$id": "ERC-7579 Execute", + "intent": "Execute", + "fields": [ + { + "path": "mode", + "label": "Mode", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "callType", + "schema": { + "type": "uint", + "bytes": 1 + } + }, + { + "name": "execType", + "schema": { + "type": "uint", + "bytes": 1 + } + }, + { + "name": "unused", + "schema": { + "type": "bytes", + "length": 4 + } + }, + { + "name": "modeSelector", + "schema": { + "type": "bytes", + "length": 4 + } + }, + { + "name": "modePayload", + "schema": { + "type": "bytes", + "length": 22 + } + } + ] + } + }, + { + "path": "executionCalldata", + "label": "Execution", + "switch": { + "expression": { + "path": "mode.callType" + }, + "cases": { + "0x00": { + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "target", + "schema": { + "type": "address" + } + }, + { + "name": "value", + "schema": { + "type": "uint", + "bytes": 32 + } + }, + { + "name": "callData", + "schema": { + "type": "bytes" + }, + "label": "Execution", + "format": "calldata", + "params": { + "calleePath": "target", + "amountPath": "value" + } + } + ] + } + }, + "0x01": { + "(address target,uint256 value,bytes callData)[]": { + "intent": "Execute batch", + "fields": [ + { + "path": ".[].callData", + "label": "Batched call data", + "format": "calldata", + "params": { + "calleePath": ".[].target", + "amountPath": ".[].value" + } + } + ] + } + }, + "0xff": { + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "target", + "schema": { + "type": "address" + } + }, + { + "name": "callData", + "schema": { + "type": "bytes" + }, + "label": "Execution", + "format": "calldata", + "params": { + "calleePath": "target", + "operation": "DELEGATECALL" + } + } + ] + } + }, + "$default": "reject" + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-erc7683-order.json b/assets/erc-0000/example-erc7683-order.json new file mode 100644 index 00000000000..f91b6553102 --- /dev/null +++ b/assets/erc-0000/example-erc7683-order.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Across Origin Settler", + "contract": { + "deployments": [ + { + "chainId": 8453, + "address": "0x4afb570AC68BfFc26Bb02FdA3D801728B0f93C9E" + } + ] + } + }, + "metadata": { + "owner": "Across Protocol", + "contractName": "AcrossOriginSettler", + "info": { + "url": "https://docs.across.to/guides/concepts/erc-7683" + } + }, + "display": { + "formats": { + "open((uint32 fillDeadline,bytes32 orderDataType,bytes orderData) order)": { + "$id": "ERC-7683 Open Order", + "intent": "Open cross-chain order", + "fields": [ + { + "path": "order.fillDeadline", + "label": "Fill deadline", + "format": "date", + "params": { + "encoding": "timestamp" + } + }, + { + "path": "order.orderData", + "label": "Order data", + "switch": { + "expression": { + "path": "order.orderDataType" + }, + "cases": { + "0x9df4b782e7bbc178b3b93bfe8aafb909e84e39484d7f3c59f400f1b4691f85e2": { + "(address inputToken,uint256 inputAmount,address outputToken,uint256 outputAmount,uint256 destinationChainId,bytes32 recipient,address exclusiveRelayer,uint256 depositNonce,uint32 exclusivityPeriod,bytes message)": { + "intent": "Across V3 relay order", + "fields": [ + { "path": "inputToken", "label": "Input token", "format": "addressName" }, + { "path": "inputAmount", "label": "Input amount", "format": "tokenAmount", "params": { "tokenPath": "inputToken" } }, + { "path": "outputToken", "label": "Output token", "format": "addressName" }, + { "path": "outputAmount", "label": "Output amount", "format": "tokenAmount", "params": { "tokenPath": "outputToken" } }, + { "path": "destinationChainId", "label": "Destination chain", "format": "chainId" }, + { "path": "recipient", "label": "Recipient" }, + { "path": "exclusiveRelayer", "label": "Exclusive relayer", "format": "addressName" } + ] + } + }, + "$default": "reject" + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-legacy-token.json b/assets/erc-0000/example-legacy-token.json new file mode 100644 index 00000000000..82d833f399a --- /dev/null +++ b/assets/erc-0000/example-legacy-token.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Legacy Token", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0xYourLegacyTokenAddress" + } + ] + } + }, + "metadata": { + "owner": "Example", + "contractName": "Legacy Token" + }, + "display": { + "formats": { + "creditAccount(uint256 amount,address to)": { + "intent": "Credit legacy balance", + "interpolatedIntent": "Credit {to} with {amount}", + "fields": [ + { + "path": "amount", + "label": "Amount", + "format": "amount" + }, + { + "path": "to", + "label": "Recipient", + "format": "addressName" + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-reward-vault.json b/assets/erc-0000/example-reward-vault.json new file mode 100644 index 00000000000..4a771aa24f1 --- /dev/null +++ b/assets/erc-0000/example-reward-vault.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Reward Vault", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0xYourRewardVaultAddress" + } + ] + } + }, + "metadata": { + "owner": "Example", + "contractName": "Reward Vault" + }, + "display": { + "formats": { + "grantReward(address to,uint256 amount)": { + "intent": "Grant reward", + "interpolatedIntent": "Grant {amount} reward to {to}", + "fields": [ + { + "path": "to", + "label": "Recipient", + "format": "addressName" + }, + { + "path": "amount", + "label": "Amount", + "format": "amount" + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-safe-multisend.json b/assets/erc-0000/example-safe-multisend.json new file mode 100644 index 00000000000..2e50dd1bb31 --- /dev/null +++ b/assets/erc-0000/example-safe-multisend.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Safe MultiSendCallOnly 1.3.0", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D" + } + ] + } + }, + "metadata": { + "owner": "Safe", + "contractName": "MultiSendCallOnly", + "info": { + "url": "https://github.com/safe-global/safe-smart-account" + } + }, + "display": { + "formats": { + "multiSend(bytes transactions)": { + "$id": "MultiSend Batch", + "intent": "Execute batch", + "fields": [ + { + "path": "transactions", + "label": "Batched calls", + "customEncoding": { + "type": "sequence", + "element": { + "type": "object", + "fields": [ + { + "name": "operation", + "schema": { + "type": "uint", + "bytes": 1 + } + }, + { + "name": "to", + "schema": { + "type": "address" + } + }, + { + "name": "value", + "schema": { + "type": "uint", + "bytes": 32 + } + }, + { + "name": "dataLength", + "schema": { + "type": "uint", + "bytes": 32 + } + }, + { + "name": "data", + "schema": { + "type": "bytes", + "lengthFrom": "dataLength" + }, + "label": "Batched call data", + "format": "calldata", + "params": { + "calleePath": "to", + "amountPath": "value", + "operation": { + "expression": "operation", + "cases": { + "0x01": "DELEGATECALL", + "$default": "CALL" + } + } + } + } + ] + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-tiered-executor.json b/assets/erc-0000/example-tiered-executor.json new file mode 100644 index 00000000000..c0b83f73344 --- /dev/null +++ b/assets/erc-0000/example-tiered-executor.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "TieredExecutor Example", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0xYourTieredExecutorAddress" + } + ] + } + }, + "metadata": { + "owner": "Example", + "contractName": "TieredExecutor", + "enums": { + "operationKind": { + "1": "Grant reward", + "2": "Credit legacy balance" + } + } + }, + "display": { + "formats": { + "executeOperation(address target,uint8 op,address account,uint256 amount)": { + "$id": "Execute Operation", + "switch": { + "expression": { + "path": "op" + }, + "cases": { + "1": { + "interaction": { + "to": "target", + "signature": "grantReward(address,uint256)", + "args": [ + { + "path": "account" + }, + { + "path": "amount" + } + ] + } + }, + "2": { + "interaction": { + "to": "target", + "signature": "creditAccount(uint256,address)", + "args": [ + { + "path": "amount" + }, + { + "path": "account" + } + ] + } + }, + "$default": "reject" + } + } + } + } + } +} diff --git a/assets/erc-0000/example-uniswap-v4-initialize.json b/assets/erc-0000/example-uniswap-v4-initialize.json new file mode 100644 index 00000000000..900e6744b3e --- /dev/null +++ b/assets/erc-0000/example-uniswap-v4-initialize.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Uniswap v4 PoolManager", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0x000000000004444c5dc75cB358380D2e3dE08A90" + } + ] + } + }, + "metadata": { + "owner": "Uniswap Labs", + "contractName": "PoolManager", + "info": { + "url": "https://github.com/Uniswap/v4-core" + } + }, + "display": { + "formats": { + "initialize((address currency0,address currency1,uint24 fee,int24 tickSpacing,address hooks) key,uint160 sqrtPriceX96)": { + "$id": "Initialize Pool", + "intent": "Create pool", + "fields": [ + { + "path": "key.currency0", + "label": "Token 0", + "format": "addressName" + }, + { + "path": "key.currency1", + "label": "Token 1", + "format": "addressName" + }, + { + "path": "key.fee", + "label": "Fee tier", + "format": "raw" + }, + { + "path": "key.tickSpacing", + "label": "Tick spacing", + "format": "raw" + }, + { + "path": "key.hooks", + "label": "Hooks contract", + "format": "addressName" + }, + { + "path": "key.hooks", + "label": "Hook permissions", + "customEncoding": { + "type": "bitfield", + "bytes": 20, + "fields": [ + { "name": "beforeInitialize", "bit": 13 }, + { "name": "afterInitialize", "bit": 12 }, + { "name": "beforeAddLiquidity", "bit": 11 }, + { "name": "afterAddLiquidity", "bit": 10 }, + { "name": "beforeRemoveLiquidity", "bit": 9 }, + { "name": "afterRemoveLiquidity", "bit": 8 }, + { "name": "beforeSwap", "bit": 7 }, + { "name": "afterSwap", "bit": 6 }, + { "name": "beforeDonate", "bit": 5 }, + { "name": "afterDonate", "bit": 4 }, + { "name": "beforeSwapReturnDelta", "bit": 3 }, + { "name": "afterSwapReturnDelta", "bit": 2 }, + { "name": "afterAddLiquidityReturnDelta", "bit": 1 }, + { "name": "afterRemoveLiquidityReturnDelta", "bit": 0 } + ] + } + }, + { + "path": "sqrtPriceX96", + "label": "Initial price (sqrtPriceX96)", + "format": "raw" + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-universal-router.json b/assets/erc-0000/example-universal-router.json new file mode 100644 index 00000000000..cf56fca3f11 --- /dev/null +++ b/assets/erc-0000/example-universal-router.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Uniswap Universal Router", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af" + } + ] + } + }, + "metadata": { + "owner": "Uniswap", + "contractName": "Universal Router", + "info": { + "url": "https://docs.uniswap.org/contracts/universal-router/overview" + } + }, + "display": { + "formats": { + "execute(bytes commands,bytes[] inputs,uint256 deadline)": { + "$id": "Universal Router Execute", + "intent": "Execute swap", + "fields": [ + { + "path": "deadline", + "label": "Valid until", + "format": "date", + "params": { + "encoding": "timestamp" + } + }, + { + "path": "commands", + "label": "Commands", + "customEncoding": { + "type": "sequence", + "element": { + "type": "uint", + "bytes": 1 + } + } + }, + { + "path": "inputs[]", + "label": "Command input", + "switch": { + "expression": { + "path": "commands[$index]", + "mask": "0x3f" + }, + "cases": { + "0x00": { + "(address recipient,uint256 amountIn,uint256 amountOutMinimum,bytes path,bool payerIsUser)": { + "intent": "Execute swap with exact input and minimum output", + "fields": [ + { + "path": "recipient", + "label": "Recipient", + "format": "addressName" + }, + { + "path": "amountIn", + "label": "Amount in", + "format": "raw" + }, + { + "path": "amountOutMinimum", + "label": "Minimum amount out", + "format": "raw" + }, + { + "path": "path", + "label": "Swap path", + "format": "raw" + }, + { + "path": "payerIsUser", + "label": "Pay from wallet", + "format": "raw" + } + ] + } + }, + "0x04": { + "(address token,address recipient,uint256 amountMinimum)": { + "intent": "Sweep remaining balance", + "fields": [ + { + "path": "token", + "label": "Token", + "format": "addressName" + }, + { + "path": "recipient", + "label": "Recipient", + "format": "addressName" + }, + { + "path": "amountMinimum", + "label": "Minimum amount", + "format": "tokenAmount", + "params": { + "tokenPath": "token" + } + } + ] + } + }, + "$default": "reject" + } + } + } + ] + } + } + } +} diff --git a/assets/erc-0000/example-wormhole-token-bridge.json b/assets/erc-0000/example-wormhole-token-bridge.json new file mode 100644 index 00000000000..17b197d719c --- /dev/null +++ b/assets/erc-0000/example-wormhole-token-bridge.json @@ -0,0 +1,147 @@ +{ + "$schema": "https://eips.ethereum.org/assets/eip-7730/erc7730-non-abi-dispatch.schema.json", + "context": { + "$id": "Wormhole Token Bridge", + "contract": { + "deployments": [ + { + "chainId": 1, + "address": "0x3ee18B2214AFF97000D974cf647E7C347E8fa585" + } + ] + } + }, + "metadata": { + "owner": "Wormhole", + "contractName": "TokenBridge", + "info": { + "url": "https://github.com/wormhole-foundation/wormhole" + } + }, + "display": { + "formats": { + "completeTransfer(bytes encodedVm)": { + "$id": "Complete Token Transfer", + "intent": "Complete cross-chain transfer", + "fields": [ + { + "path": "encodedVm", + "label": "Signed message (VAA)", + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "version", + "schema": { "type": "uint", "bytes": 1 } + }, + { + "name": "guardianSetIndex", + "schema": { "type": "uint", "bytes": 4 } + }, + { + "name": "numSignatures", + "schema": { "type": "uint", "bytes": 1 } + }, + { + "name": "signatures", + "schema": { + "type": "sequence", + "countFrom": "numSignatures", + "element": { + "type": "object", + "fields": [ + { "name": "guardianIndex", "schema": { "type": "uint", "bytes": 1 } }, + { "name": "r", "schema": { "type": "bytes", "length": 32 } }, + { "name": "s", "schema": { "type": "bytes", "length": 32 } }, + { "name": "v", "schema": { "type": "uint", "bytes": 1 } } + ] + } + } + }, + { + "name": "timestamp", + "schema": { "type": "uint", "bytes": 4 }, + "label": "Signed at", + "format": "date", + "params": { "encoding": "timestamp" } + }, + { + "name": "nonce", + "schema": { "type": "uint", "bytes": 4 } + }, + { + "name": "emitterChainId", + "schema": { "type": "uint", "bytes": 2 }, + "label": "Origin chain (Wormhole ID)" + }, + { + "name": "emitterAddress", + "schema": { "type": "bytes", "length": 32 }, + "label": "Origin emitter" + }, + { + "name": "sequenceNumber", + "schema": { "type": "uint", "bytes": 8 } + }, + { + "name": "consistencyLevel", + "schema": { "type": "uint", "bytes": 1 } + }, + { + "name": "payload", + "label": "Transfer details", + "schema": { + "type": "switch", + "expression": { "type": "uint", "bytes": 1 }, + "cases": { + "0x01": { + "customEncoding": { + "type": "object", + "fields": [ + { + "name": "amount", + "schema": { "type": "uint", "bytes": 32 }, + "label": "Amount", + "format": "raw" + }, + { + "name": "tokenAddress", + "schema": { "type": "bytes", "length": 32 }, + "label": "Token (origin chain address)" + }, + { + "name": "tokenChain", + "schema": { "type": "uint", "bytes": 2 }, + "label": "Token's origin chain (Wormhole ID)" + }, + { + "name": "to", + "schema": { "type": "bytes", "length": 32 }, + "label": "Recipient (destination chain address)" + }, + { + "name": "toChain", + "schema": { "type": "uint", "bytes": 2 }, + "label": "Destination chain (Wormhole ID)" + }, + { + "name": "fee", + "schema": { "type": "uint", "bytes": 32 }, + "label": "Relayer fee", + "format": "raw" + } + ] + } + }, + "$default": "reject" + } + } + } + ] + } + } + ] + } + } + } +}