ChainSafe / ChainSafe/canton-middleware
Fee module: bundled CC fee for no-loss traffic cost recovery
- Dominant language
- Go
- Stars
- 1
- Forks
- 1
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
**Type: Epic**
Add a reusable fee layer to the middleware that recovers the Canton node's traffic cost from the users who cause it, by bundling a **Canton Coin (CC)** fee leg into the same Daml transaction as the user's transfer — one transaction, one signature, atomic, and **no-loss** for the operator.
Design doc: [`docs/fee-module-design.md`](../blob/main/docs/fee-module-design.md).
## Problem
Today the middleware charges no network fee, but the Canton node still burns CC to pay the synchronizer for **Layer A traffic** on every submission — so the operator absorbs 100% of that cost. The middleware's own tokens (DEMO, PROMPT, USDCx) are **not** Canton Coin, so their transfers only trigger Layer A traffic (a node cost), never an automatic user charge. Making the user pay is a middleware construct we have to build.
## Two Canton fee layers (background)
| Charge | Layer | Paid by | When |
|---|---|---|---|
| Extra traffic (per byte sequenced, burned in CC) | A | **Node operator** | every tx over the free base rate |
| Base-rate traffic | A | — | free burst allowance |
| Create / transfer / holding fees | B | CC sender/holder | only on CC transfers |
## Core idea — no-loss guarantee
Bundle a CC fee leg (user → operator fee party) into the user's transfer transaction:
```
fee = trafficCC + amuletFeeOnFee + buffer
```
- **trafficCC** — the node's real burn: `bytes × traffic price ÷ CC price`
- **amuletFeeOnFee** — the Layer-B fee the CC leg itself incurs, so the operator nets the full `trafficCC`
- **buffer** — small over-collect, always rounded up ⇒ operator never underwater
A reconciler compares real burn vs. collected CC and exposes `fee_coverage_ratio` (below 1.0 ⇒ raise the buffer).
## Architecture — one engine, two front-ends
The fee **engine** (`pkg/cantonsdk/fee`) is transport-free and reusable. Two things use it:
- **Phase 1 — in-process library:** the middleware imports the engine and wraps its ledger client so the CC fee leg is injected on submit. No token/bridge/relayer logic changes.
- **Phase 2 — deployable proxy (`pkg/feegate`):** the same engine fronted by a gRPC service speaking the Canton Ledger API. Write submissions get a fee leg; everything else passes through. Any service in any language points its endpoint at the proxy.
**Boundary rule:** the engine depends only on Canton API types + stdlib — never HTTP, DB, config, or a server runtime. Anything with external I/O lives in the service. **Invariant:** the engine never learns *how* forwarding happens (`Gate.Process(cmds) → augmented cmds + quote`) — which is exactly why Phase 2 reuses it unchanged.
## Prerequisites (operator-provided)
- A fee party holding CC with a live `TransferPreapproval` (enables single-signature CC receipt).
- A Scan API URL for live CC prices.
- Validator auto-topup enabled.
## Out of scope
- Topology traffic (party allocation / package vetting) — operator-driven, operator pays.
- Users with no CC — the transaction fails fast rather than being subsidized. (This is *why* CC balance visibility is the first deliverable.)
## Deliverables
- [ ] **#367** — Canton Coin (CC) read-only balance support (foundation — the engine must read CC balances; users must see CC to fund fees)
- [ ] **#368** — Phase 1: in-process fee engine (`pkg/cantonsdk/fee`) + gated ledger wiring (Observe → Quote-only → Collect)
- [ ] **#369** — Phase 2: fee gate gRPC proxy (`pkg/feegate`) fronting the same engine
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with docs/fee-module-design.md, then review the prerequisite and child issues #367, #368, and #369. The epic is done when CC balance support, the staged in-process fee engine, and the reusable gRPC fee gate are implemented with the described atomic fee collection and coverage reconciliation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, grpc
- Domain
- api, backend, payments
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100