diff --git a/CHIPs/chip-0056.md b/CHIPs/chip-0056.md new file mode 100644 index 00000000..00d2fb56 --- /dev/null +++ b/CHIPs/chip-0056.md @@ -0,0 +1,323 @@ +CHIP Number | 0056 +:-------------|:---- +Title | Fee CATs +Description | A standard for a Chia Asset Token (CAT) with an issuer-defined percentage-based transfer fee +Author | [Cameron Cooper](https://github.com/cameroncooper) +Editor | [Dan Perry](https://github.com/danieljperry) +Comments-URI | [CHIPs repo, PR #194](https://github.com/Chia-Network/chips/pull/194) +Status | Draft +Category | Standards Track +Sub-Category | Primitive +Created | 2026-02-19 +Requires | [CAT2](https://chialisp.com/cats/), [CHIP-0038](https://github.com/Chia-Network/chips/blob/main/CHIPs/chip-0038.md) + +## Abstract + +This CHIP introduces a new puzzle layer for Chia Asset Tokens (CATs) that enforces a percentage-based transfer fee payable to the token issuer on every trade. The fee is enforced on-chain via the Chia offer protocol's settlement payment mechanism. Fee parameters -- including the percentage, a minimum fee floor, and whether free transfers are allowed -- are fixed at issuance time. Fee CATs are fully composable with revocable CATs (CHIP-0038), enabling issuers to create tokens that are both revocable and fee-bearing. + +## Motivation + +Token issuers on Chia currently have no on-chain mechanism to collect a fee when their tokens change hands through trades. In traditional finance, transfer fees and transaction levies on securities are common revenue and regulatory tools. On other blockchains, "tax tokens" that deduct a percentage on each transfer have become a widespread pattern for funding project treasuries, providing liquidity, or complying with regulatory requirements. + +By standardizing a fee layer for CATs, this CHIP enables: + +1. **Issuer revenue** -- Token issuers can earn a percentage of the trade value every time their tokens are bought or sold via Chia offers, providing a sustainable revenue model for tokenized assets. +2. **Real-world asset compatibility** -- Many jurisdictions impose transfer taxes or levies on securities transactions. An on-chain fee mechanism allows tokenized securities to comply with these requirements programmatically. +3. **Composability** -- The fee layer is designed to work alongside the existing CAT2 and revocable CAT (CHIP-0038) standards without modifying either. Issuers can choose any combination of fee and revocation properties. + +The fee only applies to *trades* conducted through Chia's offer protocol. Minting, melting, and issuer-initiated revocations are exempt from fees by design, since the issuer should not pay fees to themselves for supply management operations. + +## Backwards Compatibility + +This proposal introduces a new puzzle layer that wraps the inner puzzle of a CAT. It does not modify the CAT2 outer layer, any TAIL programs, the revocation layer, the settlement payments puzzle, or Chia's consensus rules. + +Existing CATs (both standard and revocable) are unaffected. Wallets that do not support fee CATs will see them as CATs with an unrecognized inner puzzle structure; they will not be able to spend them but will not be harmed by their existence. + +## Rationale + +### Layer ordering + +The fee layer sits between the CAT outer layer and the optional revocation layer: + +``` +CAT (cat_v2) -> Fee Layer -> Revocation Layer (optional) -> p2 (owner puzzle) +``` + +This ordering was chosen so that: + +* The fee layer can observe the inner puzzle's conditions (including `CREATE_COIN`) and wrap new outputs with the fee layer, ensuring the fee layer persists across transfers. +* When an issuer revokes a token via the hidden puzzle path, the fee layer detects this and can skip fee enforcement. After revocation, the issuer receives the coin and can strip the fee layer if desired, since they control the inner puzzle. +* TAIL spends (minting/melting) pass through the fee layer, which detects the `(51 () -113 ...)` TAIL marker condition and skips fee enforcement. This ensures the issuer is not charged fees for supply management. + +### Fee enforcement via settlement puzzle announcements + +Rather than deducting fees from the transferred amount directly (which would change the CAT's accounting model), fee CATs require that a separate payment be made to the issuer as part of the offer settlement. The fee layer enforces this by asserting a puzzle announcement from the settlement payments puzzle. + +This approach was chosen because: + +1. It preserves the existing CAT accounting model (inputs must equal outputs within the CAT ring). +2. It leverages the existing offer/settlement infrastructure without requiring new puzzle types. +3. The fee payment is visible on-chain as a separate coin creation, making it auditable. + +### Settlement puzzle hash reconstruction + +For trades quoted in XCH, the fee layer asserts an announcement from the well-known `SETTLEMENT_PAYMENT_HASH`. For trades quoted in CATs, the settlement puzzle is wrapped in additional layers (CAT layer, and potentially revocation and/or fee layers of the *quote* asset). The fee layer reconstructs the expected settlement puzzle hash on-chain from a trade price descriptor provided in the synthetic trade-context condition. + +This on-chain reconstruction ensures the announcement can only originate from a legitimate settlement puzzle with the correct layer configuration, preventing an attacker from spoofing the announcement with a crafted puzzle. + +### Immutable fee parameters + +All fee parameters (`fee_basis_points`, `min_fee`, `issuer_fee_puzzle_hash`, `allow_zero_price`, `allow_revoke_fee_bypass`) are curried into the puzzle at issuance time and cannot be changed. This guarantees to holders that the fee terms will never change after they acquire the token. If an issuer wants to change fee parameters, they must issue a new token. + +### The `allow_zero_price` flag + +When `allow_zero_price` is `false`, every fee-enforced spend must include at least one trade price descriptor with a non-zero amount. This prevents holders from transferring the token for free (avoiding fees). This includes ordinary transfers such as self-transfers and coin split/combine operations when they are executed on the fee-enforced path. + +Exempt paths are defined explicitly in this CHIP: settlement unlock bypass, hidden revocation bypass, and TAIL spends. + +When `allow_zero_price` is `true`, holders may transfer without providing trade prices. + +### The `allow_revoke_fee_bypass` flag + +When a fee CAT is also revocable (CHIP-0038), the issuer may want revocation spends to bypass fee enforcement. The `allow_revoke_fee_bypass` flag, combined with `has_hidden_revoke_layer`, controls this behavior. Both must be `true`, and the inner solution must indicate a hidden puzzle spend, for the bypass to activate. This flag is curried at issuance time, so it cannot be changed or exploited after the fact. + +## Specification + +### Puzzle structure + +``` +├ CAT outer layer (cat_v2.clsp) +├── TAIL (any compatible TAIL, e.g. everything_with_signature, everything_with_singleton) +├── Fee Layer (fee_layer_v1.rue) +├──── Revocation Layer (optional, revocation_layer.clsp, per CHIP-0038) +├────── Inner puzzle (the p2 puzzle, e.g. p2_delegated_puzzle_or_hidden_puzzle) +``` + +All four supported permutations are valid: + +| Permutation | Layer stack | +|---|---| +| Fee CAT | `cat -> fee_layer -> p2` | +| Fee + Revocable CAT | `cat -> fee_layer -> revocation_layer -> p2` | +| Plain CAT (existing) | `cat -> p2` | +| Revocable CAT (CHIP-0038) | `cat -> revocation_layer -> p2` | + +This CHIP defines composition rules for CAT2 and optional CHIP-0038 revocation only. Additional wrapper families (for example, any future CR-CAT-style wrappers) are out of scope unless explicitly specified with compatible fee-layer composition and quote-stack reconstruction rules. + +### Curried parameters + +The fee layer puzzle accepts the following curried arguments: + +| Parameter | Type | Description | +|---|---|---| +| `mod_hash` | `Bytes32` | Tree hash of the fee layer puzzle itself (for self-wrapping child outputs) | +| `issuer_fee_puzzle_hash` | `Bytes32` | Puzzle hash where fee payments are sent | +| `fee_basis_points` | `Int` | Fee percentage in basis points (e.g. 500 = 5%) | +| `min_fee` | `Int` | Minimum fee amount (floor), in mojos of the quote asset (`0` disables the floor) | +| `allow_zero_price` | `Bool` | Whether transfers with no trade prices (free transfers) are permitted | +| `allow_revoke_fee_bypass` | `Bool` | Whether hidden revocation spends bypass fee enforcement | +| `has_hidden_revoke_layer` | `Bool` | Whether the inner puzzle is wrapped in a revocation layer | +| `inner_puzzle` | `Program` | The inner puzzle (revocation layer or p2 directly) | + +### Solution structure + +The fee layer solution is a single-field list: + +| Field | Type | Description | +|---|---|---| +| `inner_solution` | `Any` | Solution passed through to the inner puzzle | + +Trade context is carried by an inner synthetic condition: + +| Field | Type | Description | +|---|---|---| +| `opcode` | `Int` | `-26` (`SetCatTradeContext`) | +| `trade_nonce` | `Bytes32` | Unique nonce tying fee assertions to a specific offer | +| `trade_prices` | `List` | List of trade price descriptors (one per quote asset in the offer) | + +This condition must appear exactly once on fee-CAT lock-leg spends. The fee layer consumes it and strips it from forwarded conditions. + +### Trade price descriptor + +Each trade price descriptor contains: + +| Field | Type | Description | +|---|---|---| +| `amount` | `Int` | Trade amount denominated in the quote asset | +| `asset_id` | `Bytes` | `nil` for XCH, or 32-byte TAIL hash for a CAT | +| `quote_hidden_puzzle_hash` | `Bytes` | `nil` if the quote asset is not revocable, or 32-byte hash | +| `quote_fee_policy` | `Any` | `nil` if the quote asset has no fee layer, or a `QuoteFeePolicy` struct | + +The `QuoteFeePolicy` struct, when present: + +| Field | Type | Description | +|---|---|---| +| `issuer_fee_puzzle_hash` | `Bytes32` | Fee destination of the quote asset's fee layer | +| `fee_basis_points` | `Int` | Quote asset's fee percentage | +| `min_fee` | `Int` | Quote asset's minimum fee (`0` disables the floor) | +| `allow_zero_price` | `Bool` | Quote asset's zero-price flag | +| `allow_revoke_fee_bypass` | `Bool` | Quote asset's revocation bypass flag | + +These fields are only needed for CAT-quoted trades so the fee layer can reconstruct the quote asset's settlement puzzle hash on-chain. + +### Spend paths + +The fee layer has four spend paths: + +#### 1. Normal transfer (fee enforcement active) + +1. The inner puzzle is evaluated, producing a list of conditions. +2. Exactly one `SetCatTradeContext` condition is required. Missing or duplicate context causes the spend to fail. +3. Every `CREATE_COIN` condition is morphed: its puzzle hash is wrapped with the fee layer's curried parameters, ensuring the fee layer persists in child coins. `(51 () -113 ...)` TAIL marker conditions are passed through unwrapped. +4. For each trade price in the context condition, the fee is calculated and an `ASSERT_PUZZLE_ANNOUNCEMENT` condition is emitted, requiring the settlement payments puzzle to have created a coin paying the fee to `issuer_fee_puzzle_hash`. +5. If `allow_zero_price` is `false` and the context contains no trade prices, the spend is rejected. + +#### 2. Settlement unlock bypass (lock-leg-only charging) + +If the spend is detected as a settlement unlock leg (a coin being released from the settlement puzzle), fee assertions are skipped. This enforces transfer fees on the lock leg only (owner -> settlement), not on the unlock leg (settlement -> recipient). + +#### 3. TAIL spend (mint/melt) + +If the inner puzzle's conditions contain a `(51 () -113 ...)` TAIL marker, the fee layer skips all fee assertions. The `CREATE_COIN` morphing still occurs (so newly minted coins retain the fee layer), but no fee payment is required. + +#### 4. Hidden revocation bypass + +If `allow_revoke_fee_bypass` is `true`, `has_hidden_revoke_layer` is `true`, and the inner solution indicates a hidden puzzle spend (first element is truthy), then fee assertions are skipped entirely. This allows the issuer to revoke tokens without paying fees. + +### Fee calculation + +The fee for a given trade price is calculated as: + +``` +if amount == 0: + fee = 0 +else: + proportional_fee = amount * fee_basis_points / 10000 + fee = max(proportional_fee, min_fee) +``` + +### Fee assertion mechanism + +For each trade price with a non-zero computed fee, the fee layer emits: + +``` +ASSERT_PUZZLE_ANNOUNCEMENT(sha256(settlement_puzzle_hash, message)) +``` + +Where: + +* **`settlement_puzzle_hash`**: For XCH trades, this is the well-known `SETTLEMENT_PAYMENT_HASH`. For CAT trades, this is reconstructed on-chain as `curry_tree_hash(CAT_MOD_HASH, [CAT_MOD_HASH, asset_id, inner_settlement_hash])`, where `inner_settlement_hash` accounts for any revocation and/or fee layers on the quote asset. + +* **`message`**: A tree hash encoding the expected settlement payment: + * For XCH trades: `tree_hash([trade_nonce, [issuer_fee_puzzle_hash, fee_amount, nil]])` + * For CAT trades: `tree_hash([trade_nonce, [issuer_fee_puzzle_hash, fee_amount, [issuer_fee_puzzle_hash]]])` (the memo hint enables wallet discovery of the fee payment) + +This announcement is naturally emitted by the settlement payments puzzle when it creates a coin with the matching puzzle hash, amount, and memos. The `trade_nonce` ensures the assertion is unique to a specific offer and cannot be replayed. + +### Settlement puzzle hash reconstruction (CAT-quoted trades) + +When a fee CAT is traded for another CAT, the fee layer must know the full puzzle hash of the settlement payments puzzle as wrapped by the quote asset's layers. The reconstruction follows this order (innermost to outermost): + +1. Start with `SETTLEMENT_PAYMENT_HASH` +2. If `quote_hidden_puzzle_hash` is non-nil, wrap with the revocation layer: `curry_tree_hash(REVOCATION_LAYER_HASH, [REVOCATION_LAYER_HASH, quote_hidden_puzzle_hash, settlement_hash])` +3. If `quote_fee_policy` is non-nil, wrap with the fee layer: `curry_tree_hash(FEE_LAYER_MOD_HASH, [mod_hash, issuer_fee_puzzle_hash, fee_basis_points, min_fee, allow_zero_price, allow_revoke_fee_bypass, has_hidden_revoke_layer, inner_hash])` +4. Wrap with the CAT layer: `curry_tree_hash(CAT_MOD_HASH, [CAT_MOD_HASH, asset_id, inner_hash])` + +This enables fee CATs to trade for any combination of plain, revocable, fee-bearing, and revocable-fee-bearing CATs. + +### Offer integration + +When constructing an offer involving a fee CAT: + +1. The offer maker's wallet calculates the fee amounts for each trade price. +2. Additional settlement payments are included in the offer for each fee (one per fee CAT involved in the trade). +3. The wallet injects `SetCatTradeContext(trade_nonce, trade_prices)` into each fee-CAT lock-leg spend's delegated conditions. +4. The offer taker's wallet must include the fee settlement payments when completing the offer. +5. If an offer includes both NFT royalty obligations and fee-CAT transfer-fee obligations (for example, NFT <-> Fee CAT trades), settlement must satisfy both sets of required payments/assertions in the same aggregate offer spend. + +### Wallet display recommendations + +Wallets that support fee CATs should: + +1. Display the fee parameters (percentage, minimum fee, fee destination) wherever the token is listed. +2. Show the expected fee amount before a user accepts an offer involving fee CATs. +3. If a CAT has both fee and revocation layers, display both properties prominently. +4. Follow the same display recommendations from CHIP-0038 regarding revocability confusion. + +## Test Cases + +* [End-to-end fee CAT flow tests](e2e-tests/tests/fee_cat_flows.rs) -- 24 tests covering all permutations of XCH-quoted and CAT-quoted trades, revocable and non-revocable fee CATs, issuance, melting, revocation bypass, descriptor tampering, self-announcement bypass probes, and the full offer matrix. +* [Grinding attack tests](e2e-tests/tests/fee_layer_grinding_attacks.rs) -- Adversarial tests for trade price amount mutation (negative, zero) and inner solution manipulation. +* [Rue compiler consistency tests](e2e-tests/tests/rue_fee_layer_consistency.rs) -- Verifies that the checked-in Rue source compiles successfully and that debug/release builds differ. + +## Reference Implementation + +The fee layer is implemented as a single Rue puzzle with Rust driver support in the Chia wallet SDK: + +* **On-chain puzzle**: [fee_layer_v1.rue](chia-wallet-sdk/crates/chia-sdk-puzzles/puzzles/fee_layer_v1.rue) -- written in [Rue](https://github.com/Rigidity/rue), compiled to CLVM and mirrored into checked-in puzzle bytes. +* **SDK puzzle crate**: [chia-sdk-puzzles](chia-wallet-sdk/crates/chia-sdk-puzzles/) -- owns the Rue source plus embedded bytes/tree hash, with CI parity tests to detect drift. +* **Rust types**: [fee_layer.rs (types)](chia-wallet-sdk/crates/chia-sdk-types/src/puzzles/fee_layer.rs) -- `FeeLayerArgs`, `FeeLayerSolution`, `FeeTradePrice`, `FeeTradePriceFeePolicy`. +* **Driver layer**: [fee_layer.rs (driver)](chia-wallet-sdk/crates/chia-sdk-driver/src/layers/fee_layer.rs) -- `FeeLayer` implements the `Layer` trait for parsing and constructing fee layer puzzles and solutions. +* **Fee calculation**: [transfer_fee.rs](chia-wallet-sdk/crates/chia-sdk-driver/src/offers/transfer_fee.rs) -- `calculate_transfer_fee`, `calculate_transfer_fee_payments`, fee payment generation for offers. +* **CAT integration**: [cat_info.rs](chia-wallet-sdk/crates/chia-sdk-driver/src/primitives/cat/cat_info.rs) -- `CatInfo` and `FeePolicy` types; puzzle hash calculation and puzzle construction incorporating the fee layer. + +## Security + +### Fee layer removal + +Like the revocation layer (see CHIP-0038), the fee layer could theoretically be stripped by combining a fee CAT coin with a non-fee CAT coin of the same TAIL in a single spend, producing a non-fee output whose value is the sum of the inputs. The CAT accounting allows this because both coins share the same `asset_id`. + +**Mitigation**: Issuers who want fees to be permanent should include the fee layer on *every* minting of the token. If all coins of a given TAIL have the fee layer, the stripping technique cannot be employed because there are no non-fee coins to combine with. + +### Self-announcement bypass + +An attacker might attempt to have a fee CAT satisfy its own fee assertion by emitting a puzzle announcement that matches the expected settlement payment announcement. This is prevented because the fee layer reconstructs the full settlement puzzle hash on-chain and asserts an announcement from that specific puzzle hash. Since the fee CAT's own puzzle hash is different from the settlement puzzle hash, a self-emitted announcement will not match. + +This defense was validated with dedicated adversarial tests for both XCH-quoted and CAT-quoted trade prices. + +### Trade price descriptor tampering + +The fee layer validates all fields of the trade price descriptor: + +* `asset_id` must be `nil` (XCH) or exactly 32 bytes (CAT TAIL hash). +* `quote_hidden_puzzle_hash` must be `nil` or exactly 32 bytes. +* `quote_fee_policy` must be `nil` or a valid `QuoteFeePolicy` struct with non-negative `fee_basis_points` and `min_fee`. +* XCH trade prices must not carry `quote_hidden_puzzle_hash` or `quote_fee_policy`. + +These validations prevent an attacker from providing a malformed descriptor that would cause the settlement puzzle hash reconstruction to produce an incorrect hash, potentially matching a puzzle the attacker controls. + +### Grinding attacks + +The fee layer validates that trade price amounts are non-negative. When `allow_zero_price` is `false`, zero amounts are also rejected. This prevents an attacker from grinding a trade price amount to zero (avoiding the fee) or to a negative value (which could cause unexpected behavior). + +Inner solution mutation is also tested: an attacker cannot craft a malformed inner solution that tricks the fee layer into taking the revocation bypass path when no revocation layer is present. + +### Revocation bypass safety + +The `allow_revoke_fee_bypass` flag is curried into the puzzle at issuance time and cannot be changed. An attacker cannot enable it after the fact. Furthermore, bypass only activates when *all three* conditions are met: `allow_revoke_fee_bypass` is `true`, `has_hidden_revoke_layer` is `true`, and the inner solution's first element is truthy (indicating a hidden puzzle spend). If any condition fails, normal fee enforcement applies. + +### Offer reliability + +The same offer reliability considerations from CHIP-0038 apply to fee CATs that also have a revocation layer. Additionally, because fee enforcement relies on puzzle announcements from settlement payments, a fee CAT trade will fail if the settlement payment for the fee is not included in the offer. This is by design: the fee payment is mandatory on the lock leg, and both the maker's and taker's wallets must cooperate to include it. + +### Declared trade price vs economic consideration + +Fee assertions authenticate that the canonical settlement puzzle produced the required fee payment for the declared `trade_prices` and `trade_nonce`. They do not prove that the declared on-chain trade price equals total economic consideration exchanged between counterparties. + +As with NFT royalty-style mechanisms, parties can still under-declare on-chain price if value is moved through external agreements or side payments. This is a market-level limitation, not a puzzle-authenticity failure. + +### CLVM cost overhead + +The fee layer adds CLVM cost to every CAT spend due to condition morphing and (for trades) fee assertion generation. The overhead is modest: approximately one `curry_tree_hash` computation per `CREATE_COIN` output for morphing, plus one `sha256` and one `ASSERT_PUZZLE_ANNOUNCEMENT` per trade price for fee enforcement. For CAT-quoted trades, the settlement puzzle hash reconstruction adds additional `curry_tree_hash` computations proportional to the number of layers on the quote asset. + +### Mempool considerations + +Fee CAT spends are slightly larger than standard CAT spends due to additional curried parameters and the synthetic trade-context condition carried in delegated conditions. However, they remain well within standard transaction size limits. The additional settlement payment coins in fee-bearing offers increase the total transaction cost, which may require slightly higher blockchain fees during periods of high mempool congestion. + +## Additional Assets + +None + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/README.md b/README.md index 2ea2c0b7..5b3d2dd8 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The rest of this document is a summary of all notable CHIPs, organized by status * [53 - Secure the Bag for distributed payouts](https://github.com/Chia-Network/chips/pull/183) * [54 - XCHandles](https://github.com/Chia-Network/chips/pull/192) * [55 - CATalog](https://github.com/Chia-Network/chips/pull/192) +* [56 - Fee CATs](https://github.com/Chia-Network/chips/pull/194) ### Review * [50 - Action Layer and Slots](https://github.com/Chia-Network/chips/pull/165)