Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/pages/docs/protocol/tips/tip-1034.mdx
Original file line number Diff line number Diff line change
@@ -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`.