diff --git a/src/pages/docs/protocol/tips/tip-1034.mdx b/src/pages/docs/protocol/tips/tip-1034.mdx new file mode 100644 index 00000000..25d65985 --- /dev/null +++ b/src/pages/docs/protocol/tips/tip-1034.mdx @@ -0,0 +1,154 @@ +--- +id: TIP-1034 +title: Enshrined TIP-20 Reserve Channel +description: Introduces the TIP20ChannelReserve precompile at 0x4D50…0000 (ASCII MPP), replacing the app-level reserve contract with up to 72% gas savings and no approval round-trip. +authors: Tempo Team +status: Implemented +related: TIP-1035, TIP-1045 +protocolVersion: T5 +--- + +# TIP-1034: Enshrined TIP-20 Reserve Channel + +## Abstract + +Introduces the `TIP20ChannelReserve` precompile at [`0x4D50500000000000000000000000000000000000`](https://explore.tempo.xyz/address/0x4D50500000000000000000000000000000000000) (ASCII `MPP`). It provides a protocol-native payment-channel reserve for session-based payments, replacing the application-level MPP reserve contract for new integrations. Because the precompile is on the Implicit Approvals List ([TIP-1035](https://tips.sh/1035)), callers no longer need a separate `approve` transaction before opening a channel. + +## Motivation + +The application-level MPP reserve contract required two separate on-chain transactions to open a new channel: an `approve` (to grant the reserve contract an allowance) and the `open` (to deposit tokens). Each transaction carries its own storage write, gas overhead, and wallet confirmation. For first-time users the combined cost exceeded 1.3 M gas. + +Making the reserve a protocol precompile solves both problems at once: + +- **Implicit approval** — the precompile is on the Implicit Approvals List, so it pulls TIP-20 tokens via `system_transfer_from` without ever touching the allowance slot. The approve round-trip and allowance storage write disappear entirely. +- **Optimised storage layout** — precompile storage is keyed directly from channel parameters rather than going through Solidity mapping slots, shrinking both the write cost and the slot surface that subsequent operations must warm. + +--- + +# Specification + +## Address + +The `TIP20ChannelReserve` precompile is deployed at the deterministic address: + +``` +0x4D50500000000000000000000000000000000000 +``` + +The leading bytes are the ASCII encoding of `MPP` (`0x4D5050`), making the address human-readable in explorers. + +## Activation + +The precompile activates at the T5 hardfork. Before T5, any call to the address reverts. + +## Channel lifecycle + +A **channel** is an on-chain reserve of TIP-20 tokens that a client locks for a given recipient. Off-chain, the client signs incrementing vouchers that the recipient can settle at any time. The on-chain reserve is only touched for lifecycle transitions: open, top-up, settle, and close. + +### Operations + +| Function | Description | +|---|---| +| `open(recipient, token, amount)` | Create a new channel; pull `amount` of `token` from `msg.sender` into the precompile. | +| `topUp(channelId, amount)` | Add `amount` to an existing channel's reserve. | +| `close(channelId, voucher)` | Settle the outstanding voucher with the recipient and refund the remaining balance to the client. Either party may call. | +| `requestClose(channelId)` | Signal intent to close (starts a dispute window). The counterparty may still settle during the window. | +| `topUpAndCancelClose(channelId, amount)` | Top up the reserve and withdraw a pending close request in a single call. | + +## Implicit approval + +The `TIP20ChannelReserve` precompile is a member of the **Implicit Approvals List** defined in [TIP-1035](https://tips.sh/1035). This means: + +- Callers do **not** need a prior `approve` or `permit` call before opening a channel. +- The precompile calls `system_transfer_from(client, precompile, amount)` internally, bypassing the public `transferFrom` path. +- All TIP-403 transfer policies and AccountKeychain spending limits are still enforced. +- A standard TIP-20 `Transfer` event is emitted from the token contract with the precompile address as the recipient. + +## Events + +Events are emitted from the precompile address (`0x4D50500000000000000000000000000000000000`): + +```solidity +/// @notice Emitted when a new channel is opened. +event ChannelOpened( + bytes32 indexed channelId, + address indexed client, + address indexed recipient, + address token, + uint256 amount +); + +/// @notice Emitted when a channel reserve is increased. +event ChannelToppedUp( + bytes32 indexed channelId, + uint256 addedAmount, + uint256 newBalance +); + +/// @notice Emitted when a settlement is applied and the channel is closed. +event ChannelClosed( + bytes32 indexed channelId, + uint256 settledAmount, + uint256 refundedAmount +); + +/// @notice Emitted when a close request is registered. +event CloseRequested(bytes32 indexed channelId, uint256 deadline); + +/// @notice Emitted when a pending close request is cancelled. +event CloseRequestCancelled(bytes32 indexed channelId); +``` + +TIP-20 `Transfer` events are emitted from the token contract itself (not the precompile) as the underlying token moves. + +## Channel ID derivation + +Channel IDs are derived deterministically so clients can reconstruct them off-chain without reading state: + +``` +channelId = keccak256(abi.encode(client, recipient, token, openNonce)) +``` + +`openNonce` is the client's channel-open count for the `(recipient, token)` pair, stored in the precompile and incremented on each successful `open`. + +## Payment-lane eligibility + +`open`, `topUp`, `requestClose`, `close`, and `topUpAndCancelClose` calls to this precompile are **payment transactions** under the T5 payment-lane classification ([TIP-1045](https://tips.sh/1045)). They benefit from dedicated payment blockspace and are not throttled by `general_gas_limit` during congestion. + +--- + +# Gas comparison + +Measured gas against the legacy application-level MPP reserve contract (T5 mainnet): + +| Operation | Legacy contract | Precompile | Gas reduction | +|---|---:|---:|---:| +| Open channel, existing reserve balance | 1,055,229 | 294,425 | **72%** | +| Open channel, first reserve balance | 1,302,429 | 791,625 | **39%** | +| Close existing channel | 85,118 | 62,913 | **26%** | +| Top up existing channel | 53,724 | 46,805 | **13%** | +| Top up and cancel close request | 58,785 | 48,680 | **17%** | + +"Existing reserve balance" means the client has previously interacted with the precompile for this token (warm storage). "First reserve balance" means a cold storage write on the client's first `open` for that token. + +The legacy path additionally required a separate `approve` transaction before the first `open`. That transaction and the allowance storage write are not reflected in the table above but add further cost and an extra wallet confirmation for first-time users. + +--- + +# Invariants + +1. **No approve required** — any call to the precompile for a token the caller holds succeeds without a prior `approve`. The Implicit Approvals List path does not touch the allowance slot. + +2. **Balance conservation** — the total TIP-20 balance held by the precompile equals the sum of all open channel reserves at any point in time. + +3. **Channel isolation** — settling or closing one channel never affects the reserve of another channel. + +4. **Refund correctness** — on close, `settledAmount + refundedAmount == channelBalance` at the time of closure. + +5. **Event completeness** — every state-changing call emits exactly one precompile-level event and, where tokens move, corresponding TIP-20 `Transfer` events from the token contract. + +6. **Pre-T5 behaviour** — before the T5 activation timestamp, calls to the precompile address revert and no state is written. + +## Compatibility + +The legacy application-level MPP reserve contract continues to operate without changes. Existing integrations are unaffected. New integrations and new SDK versions (from `mppx@0.7.0`) should prefer `TIP20ChannelReserve`.