Skip to content

Protocol Fee Harvester #165

Description

@zahorodnyi

Describe the feature

Protocol Fee Harvester

Summary

Automated service that collects protocol fees from per-offer protocol_fee vaults and consolidates them into a single Fee Pool UTXO under a new Simplicity covenant. Fees from every offer land in that one pool. Withdrawal from the pool is restricted to a dedicated pubkey.

Scope

  • Indexer API for claimable protocol_fee vaults (flat list, sorted, harvest-ready)
  • New Fee Pool Simplicity contract (one pool for all harvested fees)
  • New workspace crate / service fee-harvester
  • Config-driven schedule (default: every 24h)
  • Greedy harvest batches: vault withdraw → Fee Pool deposit
  • Admin withdraw from Fee Pool (part of the same service)

How it works

Protocol fee is a share of the lender fee, paid in the offer’s principal asset. After repayment it sits in a per-offer protocol_fee vault (asset_auth_vault). There are many such vaults (one per offer that earned protocol fee); there is one Fee Pool that absorbs them all.

The protocol uses a single protocol_fee_keeper_asset_id for all offers. The same keeper auth withdraws from vaults and authorizes Fee Pool create/deposit.

The harvester:

  1. Pulls claimable vaults from the indexer (flat list, amount DESC)
  2. On each run, builds economically sensible batches (greedy)
  3. Withdraws from vaults and deposits into the Fee Pool (outpoint from config)
  4. Leaves spending out of the pool to a separate withdraw path (pubkey)
Indexer (flat list, amount DESC)
        │
        ▼
Fee Harvester
  • schedule from config (default 24h)
  • one protocol-fee keeper for vault withdraw + pool deposit
  • greedy batch over claimable vaults
  • pool outpoint from config; skip if a harvest is already pending
        │
        ▼
Fee Pool UTXO (one pool)
        │
        └── admin withdraw (pubkey) → destination address

On-chain: Fee Pool

New Simplicity covenant. Accumulates harvested protocol fees from all offers into one pool UTXO.

Path Authorization Behavior
Create Protocol fee keeper auth asset Creates the initial Fee Pool output (no prior pool input)
Deposit Protocol fee keeper auth asset Spends the current pool + deposits more; may only increase pool balance
Withdraw Signature of a fixed withdraw pubkey May send funds out of the pool

Deposit cannot drain the pool. Withdraw does not require the keeper auth asset.

Bootstrap

Separate CLI command creates the Fee Pool once; then it need to be putted into into harvester config. The scheduled harvest path never creates a pool — it only deposits into the configured outpoint. After each confirmed deposit the harvester updates that stored outpoint to the new pool UTXO (same place it reads from).


Indexer

Endpoint(s) return data shaped for a single harvester pass — no extra client-side reshaping.

The indexer does not track the Fee Pool UTXO or harvest history. It only serves claimable per-offer protocol_fee vaults. The current pool outpoint lives in harvester config.

Response shape (concept):

  • Top-level: count, total_amount
  • vaults[] sorted by amount descending
  • Each vault: only fields needed to build txs (e.g. offer_id, outpoint, amount, …)

Filters: unspent, finalized protocol_fee vaults matching the requested principal asset (query param from harvester config). Dust filtering is left to the harvester (min_vault_amount).


Harvester service

Triggers

  • Default: run every 24 hours
  • Config overrides the interval / schedule when set
  • Manual / on-demand run is allowed

Each run: if a harvest is already pending → skip; else fetch vaults → greedy batch → broadcast deposit → mark pending → on confirm clear pending and update the stored pool outpoint.

Pending harvests

Track in-flight harvest txs (submitted, not yet confirmed) so a manual or overlapping run does not rebuild a conflicting batch on the same vaults / pool input. Drop the pending record once the tx confirms or is abandoned.

Greedy batching

Uses the indexer list as returned (already amount DESC):

  • Add vaults while under max_vaults_per_tx (tx / witness size cap) and above min_vault_amount
  • If batch_total < min_batch_total → skip the run (wait for the next schedule)
  • Otherwise broadcast; network fee uses configured fee_rate

Dust / leftovers below thresholds wait for a later run.

Config (examples)

  • Indexer client (IndexerClient base URL)
  • Esplora URL + network (broadcast / fees)
  • Protocol fee keeper asset + wallet material (one keeper for the protocol)
  • Fee Pool parameters (principal asset, withdraw pubkey, …)
  • Current Fee Pool outpoint (set after create / each confirmed deposit)
  • Withdraw key material and default destination address
  • Schedule (default 24h)
  • min_vault_amount, min_batch_total, max_vaults_per_tx, fee_rate

Harvest (hot) keys stay separate from Fee Pool withdraw keys when that separation is required.

Admin withdraw

The harvester exposes a way (CLI command or similar) to spend from the Fee Pool to a destination address using the withdraw pubkey.


Building blocks (order)

  1. Indexer — flat, sorted claimable protocol_fee vaults (+ totals); no Fee Pool tracking
  2. Fee Pool contract — fee_pool.simf (+ Rust wrappers / tests): keeper-gated create + deposit, pubkey-gated withdraw
  3. Harvester — create-pool command, config (incl. pool outpoint), schedule, greedy deposit, pending tracking
  4. Admin withdraw — spend-from-pool command in the harvester crate

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions