diff --git a/README.md b/README.md index 945e9db..834a0ff 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ This repository contains the design specifications and implementation plans for - [m010 — Reputation Signal (v0 advisory)](mechanisms/m010-reputation-signal/) +- [m013 — Value-Based Fee Routing (v0)](mechanisms/m013-value-based-fee-routing/) diff --git a/docs/MECHANISM_CONSUMERS.md b/docs/MECHANISM_CONSUMERS.md index abd7745..49e2be5 100644 --- a/docs/MECHANISM_CONSUMERS.md +++ b/docs/MECHANISM_CONSUMERS.md @@ -18,3 +18,19 @@ This document maps **mechanism IDs** to known **consumers** (agents, digests, sc - Heartbeat replay runner: `scripts/replay-m010.mjs` (regen-heartbeat) - Heartbeat stub runner: `scripts/stub-run-signal-agent.mjs` (regen-heartbeat) - Heartbeat validator: `scripts/validate-signal-agent.mjs` (regen-heartbeat) + +## m013 — Value-Based Fee Routing +**Canonical spec** +- `mechanisms/m013-value-based-fee-routing/SPEC.md` + +**Outputs** +- KPI JSON block schema: `mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json` +- Fee event schema: `mechanisms/m013-value-based-fee-routing/schemas/m013_fee_event.schema.json` +- Fee config schema: `mechanisms/m013-value-based-fee-routing/schemas/m013_fee_config.schema.json` + +**Datasets (deterministic)** +- Replay fixtures: `mechanisms/m013-value-based-fee-routing/datasets/fixtures/v0_sample.json` + +**Known consumers** +- Reference implementation self-test: `mechanisms/m013-value-based-fee-routing/reference-impl/m013_fee.js` +- KPI computation: `mechanisms/m013-value-based-fee-routing/reference-impl/m013_kpi.js` diff --git a/mechanisms/m013-value-based-fee-routing/README.md b/mechanisms/m013-value-based-fee-routing/README.md new file mode 100644 index 0000000..a112ed2 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/README.md @@ -0,0 +1,29 @@ +# m013 — Value-Based Fee Routing (v0) + +m013 replaces flat gas fees with **value-proportional fees** on ecological credit transactions, routing fee revenue to four purpose-specific pools: Burn, Validator, Community, and Agent Infrastructure. + +## What it outputs +- A **fee amount** (in uregen) computed as `max(value * rate, min_fee)` for each credit transaction. +- A **distribution breakdown** splitting the fee across four pools according to governance-set shares. +- KPI metrics: total fees collected, fees by transaction type, distribution by pool, average fee rate. + +## What it does not do (v0) +- No on-chain module deployment; v0 is a spec + reference implementation for validation. +- Non-credit transactions remain on flat gas — unaffected by this mechanism. +- Fee denomination questions (OQ-M013-3) are deferred to WG resolution. + +## How to reference +- Canonical spec: `mechanisms/m013-value-based-fee-routing/SPEC.md` +- Fee calculation: SPEC.md sections 3-4 (schedule + formula) +- Distribution model: SPEC.md section 5 (Model A default) +- Security invariants: SPEC.md section 9 + +## Replay datasets +See `datasets/` for deterministic fixtures used to generate non-zero KPI outputs without MCP. +- `v0_sample.json` — fee events from diverse transaction types + +## Schemas +Canonical JSON schemas for m013 outputs live in `schemas/`. +- `m013_fee_event.schema.json` — fee collection event (tx_type, value, fee_amount, distribution) +- `m013_fee_config.schema.json` — fee configuration (rates by tx type, distribution shares) +- `m013_kpi.schema.json` — KPI output with `mechanism_id: "m013"` diff --git a/mechanisms/m013-value-based-fee-routing/SPEC.md b/mechanisms/m013-value-based-fee-routing/SPEC.md new file mode 100644 index 0000000..0ffb465 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/SPEC.md @@ -0,0 +1,236 @@ +# m013 — Value-Based Fee Routing (SPEC) + +## 0. Header +- **ID:** m013 +- **Name:** Value-Based Fee Routing +- **Status:** draft (v0) +- **Owner:** (unset) +- **Last updated:** 2026-02-18 +- **Scope:** Replace flat gas fees with value-proportional fees on ecological credit transactions, routing fee revenue to four purpose-specific pools. + +## 1. Problem +A $10 credit and a $10,000 credit pay the same gas fee — this disconnects protocol revenue from the value it facilitates. The current flat gas model produces negligible revenue from high-value credit transactions and fails to fund network operations (validators, community governance, agent infrastructure, supply management) without relying on inflationary token emission. + +## 2. Target actor and action +- **Actors:** Fee Payer (user initiating credit transaction), Burn Pool, Validator Fund, Community Pool, Agent Infrastructure Fund. +- **Action being evaluated:** a **credit transaction** (issuance, transfer, retirement, marketplace trade) that generates a value-proportional fee. +- **Event source:** `x/ecocredit` message handlers intercepted by `x/feerouter` module. + +## 3. Fee Schedule + +| Transaction Type | Message | Current Fee | Proposed Fee | Rationale | +|---|---|---|---|---| +| Credit Issuance | `MsgCreateBatch` | Flat gas (~0.01 REGEN) | 1-3% of credit value | Highest value event; primary revenue source | +| Credit Transfer | `MsgSend` | Flat gas | 0.1% of credit value | Minimal friction for transfers | +| Credit Retirement | `MsgRetire` | Flat gas | 0.5% of credit value | Exit fee; captures value at point of impact | +| Marketplace Trade | `MsgBuySellOrder` | Flat gas | 1% of trade value | Standard marketplace fee | +| Non-credit transactions | (various) | Flat gas | Flat gas (unchanged) | Standard Cosmos SDK transactions unaffected | + +Default v0 fee rates (reference implementation): +- `CreditIssuance`: 0.02 (2%) +- `CreditTransfer`: 0.001 (0.1%) +- `CreditRetirement`: 0.005 (0.5%) +- `MarketplaceTrade`: 0.01 (1%) + +## 4. Fee Calculation + +``` +fee_amount = max(transaction_value * fee_rate[tx_type], min_fee) +``` + +Where: +- `transaction_value` is denominated in uregen (1 REGEN = 1,000,000 uregen). +- `fee_rate[tx_type]` is the rate from the fee schedule (section 3). +- `min_fee` = 1,000,000 uregen (1 REGEN) — prevents zero-fee transactions on low-value credits. + +### Value estimation by transaction type + +```yaml +fee_calculation: + # For marketplace trades, value is explicit (sell order price x quantity) + marketplace_value: sell_order.price * quantity + + # For issuance, value must be estimated + # Option A: Use most recent marketplace price for credit class + # Option B: Use governance-set reference price per credit type + # Option C: Use KOI-sourced external market price + issuance_value_method: "marketplace_reference" # OQ-M013-2 + + # For transfers, value uses same estimation as issuance + transfer_value_method: "marketplace_reference" + + # For retirements, value at point of retirement + retirement_value_method: "marketplace_reference" + + # Minimum fee floor + min_fee: 1 REGEN # = 1,000,000 uregen +``` + +## 5. Fee Distribution + +``` +For each fee-generating transaction: + + fee_amount = max(transaction_value * fee_rate[transaction_type], min_fee) + + burn_pool += floor(fee_amount * burn_share) + community_pool += floor(fee_amount * community_share) + agent_infra += floor(fee_amount * agent_share) + validator_fund += fee_amount - burn_pool_share - community_pool_share - agent_infra_share + + where burn_share + validator_share + community_share + agent_share = 1.0 + + NOTE: Integer rounding strategy — three pools are computed with floor(), + and the validator fund receives the remainder. This ensures + fee_collected = sum(pool_distributions) (Fee Conservation invariant) + with no dust loss. The validator pool absorbs at most 3 uregen of + rounding surplus per transaction. +``` + +### Distribution Parameters + +**Model A** (default for v0): + +| Pool | Share | Purpose | +|---|---|---| +| Burn Pool | 30% | Supply reduction via M012 | +| Validator Fund | 40% | Fixed compensation for authority validators | +| Community Pool | 25% | Governance-directed spending | +| Agent Infrastructure | 5% | AI agent operational costs | + +**Model B** (from Network Coordination Architecture, for WG discussion): + +| Pool | Share | Purpose | +|---|---|---| +| Burn Pool | 25-35% | Supply reduction via M012 | +| Validator Fund | 15-25% | Authority validator compensation | +| Community Pool | 50-60% | Contributor distribution via M015 | +| Agent Infrastructure | (included in Community Pool) | Not separated | + +> **OQ-M013-1**: Which distribution model should be adopted? Model A provides a dedicated Agent Infrastructure fund; Model B routes a larger share through governance. + +> **OQ-M013-5**: Should the Burn Pool exist at all, and if so, at what share? See source material for full pro/con analysis. + +## 6. Token Flows + +``` ++--------------+ +| Credit | fee = max(value * rate, min_fee) +| Transaction | -------------------------------------------+ ++--------------+ | + v + +------------------+ + | Fee Collector | + | Module | + +------------------+ + | + +---------------+----------------+------+----------+ + | | | | + v v v v + +-----------+ +-----------+ +-----------+ +-----------+ + | Burn Pool | | Validator | | Community | | Agent | + | (-> M012 | | Fund | | Pool | | Infra | + | burn) | | (-> M014 | | (-> M015 | | Fund | + | | | validators) | rewards) | | (-> ops) | + +-----------+ +-----------+ +-----------+ +-----------+ +``` + +## 7. Participants + +| Role | Description | Token Interaction | +|---|---|---| +| Fee Payer | User initiating credit transaction | Pays % fee in REGEN (or allowed denom) | +| Burn Pool | Protocol supply reduction | Receives `burn_share` of fees -> permanent burn | +| Validator Fund | Authority validator compensation | Receives `validator_share` of fees -> fixed distribution | +| Community Pool | Governance-directed spending | Receives `community_share` of fees -> proposal-based allocation | +| Agent Infra Fund | AI agent operations | Receives `agent_share` of fees -> operational budget | + +## 8. State Transitions + +``` +States: {FLAT_GAS, TRANSITION, VALUE_BASED} + +FLAT_GAS -> TRANSITION + trigger: governance.approve(m013_fee_proposal) + guard: fee_collector_module deployed, pool addresses configured + action: enable dual-fee mode (flat gas + value fees) + note: transition period allows UI/tooling to adapt + +TRANSITION -> VALUE_BASED + trigger: transition_period_expired(90 days) OR governance.accelerate() + guard: fee_revenue > 0 for 30 consecutive days + action: disable legacy flat gas for credit transactions, full value-based fees +``` + +## 9. Security Invariants + +1. **Fee Conservation**: `fee_collected = sum(pool_distributions)` — no fee revenue lost or created. +2. **Share Sum Unity**: `burn_share + validator_share + community_share + agent_share = 1.0` +3. **Non-Negative Fees**: All fee rates >= 0; fee amounts >= `min_fee`. +4. **Rate Bound Safety**: Individual fee rates bounded `[0, 0.10]` (max 10%) to prevent governance attack. +5. **Pool Isolation**: Each pool's balance is independent; no pool can draw from another without governance. + +## 10. Governance Parameters + +The following parameters are governance-controlled: + +| Parameter | Default (v0) | Range | Description | +|---|---|---|---| +| `fee_rate.CreditIssuance` | 0.02 | [0, 0.10] | Fee rate for credit issuance | +| `fee_rate.CreditTransfer` | 0.001 | [0, 0.10] | Fee rate for credit transfers | +| `fee_rate.CreditRetirement` | 0.005 | [0, 0.10] | Fee rate for credit retirements | +| `fee_rate.MarketplaceTrade` | 0.01 | [0, 0.10] | Fee rate for marketplace trades | +| `distribution.burn_share` | 0.30 | [0, 1.0] | Share routed to burn pool | +| `distribution.validator_share` | 0.40 | [0, 1.0] | Share routed to validator fund | +| `distribution.community_share` | 0.25 | [0, 1.0] | Share routed to community pool | +| `distribution.agent_share` | 0.05 | [0, 1.0] | Share routed to agent infra fund | +| `min_fee` | 1,000,000 uregen | [0, inf) | Minimum fee floor (1 REGEN) | + +Constraint: `burn_share + validator_share + community_share + agent_share = 1.0` (Share Sum Unity). + +## 11. Open Questions (for WG Resolution) + +> **OQ-M013-2**: How is credit value determined for non-marketplace transactions (issuance, transfer, retirement)? Options: (A) most recent marketplace price for that credit class, (B) governance-set reference price per credit type, (C) external oracle via KOI. This is critical for fee calculation accuracy. + +> **OQ-M013-3**: In what denomination should fees be collected and distributed? See source material for full analysis of REGEN-only, native denom, and hybrid approaches, as well as distribution-side considerations. + +> **OQ-M013-4**: How should the Agent Infrastructure fund be governed? As a separate module account with its own spending authority, or as a tagged allocation within the Community Pool subject to governance proposals? + +## 12. Implementation Notes + +- **Module**: New `x/feerouter` module intercepting credit transaction messages. +- **Storage**: `FeeConfig` (rates, shares, min_fee), `PoolBalance` per pool, `FeeRecord` per transaction. +- **Events**: `EventFeeCollected`, `EventFeeDistributed`, `EventRateUpdated`. +- **Integration**: Hooks into `x/ecocredit` message handlers for credit transactions. +- **Migration**: Backward compatible; flat gas remains for non-credit transactions. +- **Dependencies**: M012 (burn pool feeds mint/burn algorithm), M014 (validator fund feeds PoA compensation). + +## 13. Acceptance Tests + +**Fee calculation:** +1. Credit issuance of 5,000,000,000 uregen at 2% rate produces fee of 100,000,000 uregen. +2. Credit transfer of 100,000,000 uregen at 0.1% rate computes 100,000 uregen; clamped to `min_fee` = 1,000,000 uregen. +3. Credit retirement of 1,000,000,000 uregen at 0.5% rate produces fee of 5,000,000 uregen. +4. Marketplace trade of 2,500,000,000 uregen at 1% rate produces fee of 25,000,000 uregen. +5. Low-value transfer (500,000 uregen) computes 500 uregen; clamped to `min_fee` = 1,000,000 uregen. + +**Fee distribution:** +6. A 100,000,000 uregen fee distributes: burn 30,000,000, validator 40,000,000, community 25,000,000, agent 5,000,000. +7. Sum of all pool distributions equals `fee_amount` (fee conservation). +8. Share Sum Unity: shares always sum to 1.0. + +**Invariant enforcement:** +9. Fee rate above 0.10 (10%) is rejected by governance parameter validation. +10. Distribution shares that do not sum to 1.0 are rejected. +11. Negative fee rates are rejected. +12. Pool balances are isolated; no cross-pool withdrawals without governance. + +**State transitions:** +13. System starts in FLAT_GAS; transition to TRANSITION requires governance approval + module deployment. +14. TRANSITION -> VALUE_BASED requires 30 consecutive days of fee_revenue > 0 and either 90-day expiry or governance acceleration. + +--- + +## Appendix A — Source anchors +- `phase-2/2.6-economic-reboot-mechanisms.md` lines 182-367 + - "PROTOCOL SPECIFICATION: M013" — full specification including fee schedule, distribution models, state transitions, security invariants, and implementation notes. diff --git a/mechanisms/m013-value-based-fee-routing/datasets/README.md b/mechanisms/m013-value-based-fee-routing/datasets/README.md new file mode 100644 index 0000000..bfca0a3 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/datasets/README.md @@ -0,0 +1,20 @@ +# m013 datasets (replay fixtures) + +These fixtures are **deterministic inputs** for generating non-zero m013 KPI outputs **without MCP**. + +## Files +- `schema.json` — JSON schema for replay datasets +- `fixtures/v0_sample.json` — fee events from diverse transaction types (issuance, transfer, retirement, marketplace) including low-value events that trigger the min_fee floor + +## How they are used +A replay runner (e.g., in `regen-heartbeat`) can read a fixture file and compute: +- `total_fees_uregen` — sum of all computed fees +- `fee_events_count` — number of fee events +- `fees_by_type` — fee totals broken down by transaction type +- `distribution_by_pool` — fee distribution totals by pool (burn, validator, community, agent) +- `avg_fee_rate` — average effective fee rate (total_fees / total_value) +- `min_fee_applied_count` — count of events where the min_fee floor was applied + +All monetary values are in **uregen** (1 REGEN = 1,000,000 uregen). + +These datasets are for validation and digest reporting only; they do not imply on-chain enforcement. diff --git a/mechanisms/m013-value-based-fee-routing/datasets/fixtures/v0_sample.json b/mechanisms/m013-value-based-fee-routing/datasets/fixtures/v0_sample.json new file mode 100644 index 0000000..4dcc8cb --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/datasets/fixtures/v0_sample.json @@ -0,0 +1,78 @@ +{ + "mechanism_id": "m013", + "scope": "v0_replay", + "as_of": "2026-02-18T12:00:00Z", + "fee_config": { + "fee_rates": { + "CreditIssuance": 0.02, + "CreditTransfer": 0.001, + "CreditRetirement": 0.005, + "MarketplaceTrade": 0.01 + }, + "distribution_shares": { + "burn": 0.30, + "validator": 0.40, + "community": 0.25, + "agent": 0.05 + }, + "min_fee_uregen": 1000000 + }, + "fee_events": [ + { + "tx_hash": "tx_issuance_001", + "tx_type": "CreditIssuance", + "timestamp": "2026-02-18T08:00:00Z", + "value_uregen": 5000000000, + "payer": "regen1issuer001" + }, + { + "tx_hash": "tx_transfer_001", + "tx_type": "CreditTransfer", + "timestamp": "2026-02-18T09:00:00Z", + "value_uregen": 100000000, + "payer": "regen1sender001" + }, + { + "tx_hash": "tx_retire_001", + "tx_type": "CreditRetirement", + "timestamp": "2026-02-18T10:00:00Z", + "value_uregen": 1000000000, + "payer": "regen1retiree001" + }, + { + "tx_hash": "tx_market_001", + "tx_type": "MarketplaceTrade", + "timestamp": "2026-02-18T10:30:00Z", + "value_uregen": 2500000000, + "payer": "regen1buyer001" + }, + { + "tx_hash": "tx_transfer_002", + "tx_type": "CreditTransfer", + "timestamp": "2026-02-18T11:00:00Z", + "value_uregen": 500000, + "payer": "regen1sender002" + }, + { + "tx_hash": "tx_issuance_002", + "tx_type": "CreditIssuance", + "timestamp": "2026-02-18T11:15:00Z", + "value_uregen": 750000000, + "payer": "regen1issuer002" + }, + { + "tx_hash": "tx_retire_002", + "tx_type": "CreditRetirement", + "timestamp": "2026-02-18T11:30:00Z", + "value_uregen": 200000000, + "payer": "regen1retiree002" + }, + { + "tx_hash": "tx_market_002", + "tx_type": "MarketplaceTrade", + "timestamp": "2026-02-18T11:45:00Z", + "value_uregen": 50000000, + "payer": "regen1buyer002" + } + ] +} diff --git a/mechanisms/m013-value-based-fee-routing/datasets/schema.json b/mechanisms/m013-value-based-fee-routing/datasets/schema.json new file mode 100644 index 0000000..8e6d995 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/datasets/schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "m013 replay dataset", + "type": "object", + "required": [ + "mechanism_id", + "scope", + "as_of", + "fee_config", + "fee_events" + ], + "properties": { + "mechanism_id": { + "const": "m013" + }, + "scope": { + "type": "string" + }, + "as_of": { + "type": "string", + "description": "ISO-8601 datetime", + "format": "date-time" + }, + "fee_config": { + "type": "object", + "required": [ + "fee_rates", + "distribution_shares", + "min_fee_uregen" + ], + "properties": { + "fee_rates": { + "type": "object", + "required": [ + "CreditIssuance", + "CreditTransfer", + "CreditRetirement", + "MarketplaceTrade" + ], + "properties": { + "CreditIssuance": { "type": "number" }, + "CreditTransfer": { "type": "number" }, + "CreditRetirement": { "type": "number" }, + "MarketplaceTrade": { "type": "number" } + } + }, + "distribution_shares": { + "type": "object", + "required": ["burn", "validator", "community", "agent"], + "properties": { + "burn": { "type": "number" }, + "validator": { "type": "number" }, + "community": { "type": "number" }, + "agent": { "type": "number" } + } + }, + "min_fee_uregen": { + "type": "integer", + "minimum": 0 + } + } + }, + "fee_events": { + "type": "array", + "items": { + "type": "object", + "required": [ + "tx_hash", + "tx_type", + "timestamp", + "value_uregen", + "payer" + ], + "properties": { + "tx_hash": { + "type": "string" + }, + "tx_type": { + "type": "string", + "enum": [ + "CreditIssuance", + "CreditTransfer", + "CreditRetirement", + "MarketplaceTrade" + ] + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "value_uregen": { + "type": "integer", + "minimum": 0 + }, + "payer": { + "type": "string" + } + } + } + } + } +} diff --git a/mechanisms/m013-value-based-fee-routing/reference-impl/README.md b/mechanisms/m013-value-based-fee-routing/reference-impl/README.md new file mode 100644 index 0000000..210228e --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/reference-impl/README.md @@ -0,0 +1,35 @@ +# m013 reference implementation (v0) + +This folder provides a **canonical computation** for m013 outputs so that different agents/runners +produce consistent numbers. + +## Inputs +A fee event input object: + +- `tx_type` — one of: `CreditIssuance`, `CreditTransfer`, `CreditRetirement`, `MarketplaceTrade` +- `value_uregen` — transaction value in uregen (1 REGEN = 1,000,000 uregen) +- `fee_config` (optional) — fee configuration with `fee_rates`, `distribution_shares`, `min_fee_uregen` + +## Outputs +### Per-event fee result +- `fee_amount` — computed fee in uregen (after min_fee clamping) +- `min_fee_applied` — boolean indicating whether the min_fee floor was applied +- `distribution` — breakdown: `{ burn, validator, community, agent }` in uregen + +### KPI block +- `total_fees_uregen` — sum of all fees in the period +- `fee_events_count` — number of fee events +- `fees_by_type` — fee totals broken down by transaction type +- `distribution_by_pool` — fee distribution totals by pool +- `avg_fee_rate` — average effective fee rate (total_fees / total_value) +- `min_fee_applied_count` — number of events where the min_fee floor was applied + +## Self-test +Run `node m013_fee.js` from this directory (or from repo root). The script reads +`test_vectors/vector_v0_sample.input.json`, computes fee results, compares against +`test_vectors/vector_v0_sample.expected.json`, and exits with code 1 on any mismatch. + +## Defaults (v0 Model A) +- Fee rates: CreditIssuance 2%, CreditTransfer 0.1%, CreditRetirement 0.5%, MarketplaceTrade 1% +- Distribution: Burn 30%, Validator 40%, Community 25%, Agent 5% +- Min fee: 1,000,000 uregen (1 REGEN) diff --git a/mechanisms/m013-value-based-fee-routing/reference-impl/m013_fee.js b/mechanisms/m013-value-based-fee-routing/reference-impl/m013_fee.js new file mode 100644 index 0000000..f7276d3 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/reference-impl/m013_fee.js @@ -0,0 +1,138 @@ +/** + * m013 — Value-Based Fee Routing: reference implementation (v0). + * + * Computes value-proportional fees for ecological credit transactions + * and distributes fee revenue across four purpose-specific pools. + * + * All monetary values are in uregen (1 REGEN = 1,000,000 uregen). + * + * @module m013_fee + */ + +/** Default fee rates by transaction type (v0 Model A). */ +export const DEFAULT_FEE_RATES = { + CreditIssuance: 0.02, + CreditTransfer: 0.001, + CreditRetirement: 0.005, + MarketplaceTrade: 0.01 +}; + +/** Default distribution shares (v0 Model A). */ +export const DEFAULT_DISTRIBUTION_SHARES = { + burn: 0.30, + validator: 0.40, + community: 0.25, + agent: 0.05 +}; + +/** Default minimum fee in uregen (1 REGEN). */ +export const DEFAULT_MIN_FEE_UREGEN = 1000000; + +/** + * Compute the fee and distribution for a single credit transaction. + * + * @param {Object} opts + * @param {string} opts.tx_type - One of: CreditIssuance, CreditTransfer, CreditRetirement, MarketplaceTrade + * @param {number} opts.value - Transaction value in uregen + * @param {Object} [opts.fee_config] - Fee configuration (rates, shares, min_fee) + * @param {Object} [opts.fee_config.fee_rates] - Fee rates by tx type + * @param {Object} [opts.fee_config.distribution_shares] - Distribution shares (must sum to 1.0) + * @param {number} [opts.fee_config.min_fee_uregen] - Minimum fee floor in uregen + * @returns {{ fee_amount: number, min_fee_applied: boolean, distribution: { burn: number, validator: number, community: number, agent: number } }} + */ +export function computeFee({ tx_type, value, fee_config }) { + const rates = fee_config?.fee_rates ?? DEFAULT_FEE_RATES; + const shares = fee_config?.distribution_shares ?? DEFAULT_DISTRIBUTION_SHARES; + const minFee = fee_config?.min_fee_uregen ?? DEFAULT_MIN_FEE_UREGEN; + + const rate = rates[tx_type]; + if (rate === undefined) { + throw new Error(`Unknown tx_type: ${tx_type}`); + } + + const rawFee = Math.floor(value * rate); + const minFeeApplied = rawFee < minFee; + const feeAmount = Math.max(rawFee, minFee); + + // Floor 3 pools, derive validator as remainder to preserve Fee Conservation invariant + const d_burn = Math.floor(feeAmount * shares.burn); + const d_community = Math.floor(feeAmount * shares.community); + const d_agent = Math.floor(feeAmount * shares.agent); + const d_validator = feeAmount - d_burn - d_community - d_agent; + + const distribution = { + burn: d_burn, + validator: d_validator, + community: d_community, + agent: d_agent + }; + + return { + fee_amount: feeAmount, + min_fee_applied: minFeeApplied, + distribution + }; +} + +// --------------------------------------------------------------------------- +// Self-test harness: reads test vectors, computes, compares, exit(1) on mismatch +// --------------------------------------------------------------------------- +import { fileURLToPath } from "node:url"; +import fs from "node:fs"; +import path from "node:path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const vectorDir = path.join(__dirname, "test_vectors"); +const inputPath = path.join(vectorDir, "vector_v0_sample.input.json"); +const expectedPath = path.join(vectorDir, "vector_v0_sample.expected.json"); + +if (fs.existsSync(inputPath) && fs.existsSync(expectedPath)) { + const input = JSON.parse(fs.readFileSync(inputPath, "utf8")); + const expected = JSON.parse(fs.readFileSync(expectedPath, "utf8")); + + const feeConfig = input.fee_config; + const results = []; + let failures = 0; + + for (let i = 0; i < input.fee_events.length; i++) { + const ev = input.fee_events[i]; + const result = computeFee({ + tx_type: ev.tx_type, + value: ev.value_uregen, + fee_config: feeConfig + }); + + const exp = expected.fee_results[i]; + + if (result.fee_amount !== exp.fee_amount_uregen) { + console.error(`FAIL [${ev.tx_hash}]: fee_amount ${result.fee_amount} !== expected ${exp.fee_amount_uregen}`); + failures++; + } + if (result.min_fee_applied !== exp.min_fee_applied) { + console.error(`FAIL [${ev.tx_hash}]: min_fee_applied ${result.min_fee_applied} !== expected ${exp.min_fee_applied}`); + failures++; + } + for (const pool of ["burn", "validator", "community", "agent"]) { + if (result.distribution[pool] !== exp.distribution[pool]) { + console.error(`FAIL [${ev.tx_hash}]: distribution.${pool} ${result.distribution[pool]} !== expected ${exp.distribution[pool]}`); + failures++; + } + } + + results.push({ + tx_hash: ev.tx_hash, + fee_amount_uregen: result.fee_amount, + min_fee_applied: result.min_fee_applied, + distribution: result.distribution + }); + } + + if (failures > 0) { + console.error(`\nm013_fee self-test: ${failures} failure(s)`); + process.exit(1); + } + + console.log("m013_fee self-test: PASS"); +} diff --git a/mechanisms/m013-value-based-fee-routing/reference-impl/m013_kpi.js b/mechanisms/m013-value-based-fee-routing/reference-impl/m013_kpi.js new file mode 100644 index 0000000..d4acf1c --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/reference-impl/m013_kpi.js @@ -0,0 +1,62 @@ +/** + * m013 — Value-Based Fee Routing: KPI computation (v0). + * + * Computes aggregate KPI metrics from a set of fee events. + * + * @module m013_kpi + */ + +import { computeFee } from "./m013_fee.js"; + +/** + * Compute m013 KPI block from fee events. + * + * @param {Object} opts + * @param {string} opts.as_of - ISO-8601 timestamp for the KPI snapshot + * @param {Array} opts.fee_events - Array of fee event inputs (tx_type, value_uregen, ...) + * @param {Object} [opts.fee_config] - Fee configuration (rates, shares, min_fee) + * @returns {Object} KPI block conforming to m013_kpi.schema.json + */ +export function computeM013KPI({ as_of, fee_events, fee_config }) { + const evs = fee_events ?? []; + + let totalFees = 0; + let totalValue = 0; + let minFeeCount = 0; + const feesByType = {}; + const distByPool = { burn: 0, validator: 0, community: 0, agent: 0 }; + + for (const ev of evs) { + const result = computeFee({ + tx_type: ev.tx_type, + value: ev.value_uregen, + fee_config + }); + + totalFees += result.fee_amount; + totalValue += ev.value_uregen; + if (result.min_fee_applied) minFeeCount++; + + feesByType[ev.tx_type] = (feesByType[ev.tx_type] ?? 0) + result.fee_amount; + + for (const pool of ["burn", "validator", "community", "agent"]) { + distByPool[pool] += result.distribution[pool]; + } + } + + const avgFeeRate = totalValue > 0 + ? Number((totalFees / totalValue).toFixed(4)) + : 0; + + return { + mechanism_id: "m013", + scope: "v0", + as_of, + total_fees_uregen: totalFees, + fee_events_count: evs.length, + fees_by_type: feesByType, + distribution_by_pool: distByPool, + avg_fee_rate: avgFeeRate, + min_fee_applied_count: minFeeCount + }; +} diff --git a/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.expected.json b/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.expected.json new file mode 100644 index 0000000..a22f5c2 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.expected.json @@ -0,0 +1,80 @@ +{ + "fee_results": [ + { + "tx_hash": "tx_issuance_001", + "fee_amount_uregen": 100000000, + "min_fee_applied": false, + "distribution": { + "burn": 30000000, + "validator": 40000000, + "community": 25000000, + "agent": 5000000 + } + }, + { + "tx_hash": "tx_transfer_001", + "fee_amount_uregen": 1000000, + "min_fee_applied": true, + "distribution": { + "burn": 300000, + "validator": 400000, + "community": 250000, + "agent": 50000 + } + }, + { + "tx_hash": "tx_retire_001", + "fee_amount_uregen": 5000000, + "min_fee_applied": false, + "distribution": { + "burn": 1500000, + "validator": 2000000, + "community": 1250000, + "agent": 250000 + } + }, + { + "tx_hash": "tx_market_001", + "fee_amount_uregen": 25000000, + "min_fee_applied": false, + "distribution": { + "burn": 7500000, + "validator": 10000000, + "community": 6250000, + "agent": 1250000 + } + }, + { + "tx_hash": "tx_transfer_002", + "fee_amount_uregen": 1000000, + "min_fee_applied": true, + "distribution": { + "burn": 300000, + "validator": 400000, + "community": 250000, + "agent": 50000 + } + } + ], + "kpi": { + "mechanism_id": "m013", + "scope": "v0", + "as_of": "2026-02-18T12:00:00Z", + "total_fees_uregen": 132000000, + "fee_events_count": 5, + "fees_by_type": { + "CreditIssuance": 100000000, + "CreditTransfer": 2000000, + "CreditRetirement": 5000000, + "MarketplaceTrade": 25000000 + }, + "distribution_by_pool": { + "burn": 39600000, + "validator": 52800000, + "community": 33000000, + "agent": 6600000 + }, + "avg_fee_rate": 0.0153, + "min_fee_applied_count": 2 + } +} diff --git a/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.input.json b/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.input.json new file mode 100644 index 0000000..31254d4 --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/reference-impl/test_vectors/vector_v0_sample.input.json @@ -0,0 +1,55 @@ +{ + "as_of": "2026-02-18T12:00:00Z", + "fee_config": { + "fee_rates": { + "CreditIssuance": 0.02, + "CreditTransfer": 0.001, + "CreditRetirement": 0.005, + "MarketplaceTrade": 0.01 + }, + "distribution_shares": { + "burn": 0.30, + "validator": 0.40, + "community": 0.25, + "agent": 0.05 + }, + "min_fee_uregen": 1000000 + }, + "fee_events": [ + { + "tx_hash": "tx_issuance_001", + "tx_type": "CreditIssuance", + "timestamp": "2026-02-18T08:00:00Z", + "value_uregen": 5000000000, + "payer": "regen1issuer001" + }, + { + "tx_hash": "tx_transfer_001", + "tx_type": "CreditTransfer", + "timestamp": "2026-02-18T09:00:00Z", + "value_uregen": 100000000, + "payer": "regen1sender001" + }, + { + "tx_hash": "tx_retire_001", + "tx_type": "CreditRetirement", + "timestamp": "2026-02-18T10:00:00Z", + "value_uregen": 1000000000, + "payer": "regen1retiree001" + }, + { + "tx_hash": "tx_market_001", + "tx_type": "MarketplaceTrade", + "timestamp": "2026-02-18T10:30:00Z", + "value_uregen": 2500000000, + "payer": "regen1buyer001" + }, + { + "tx_hash": "tx_transfer_002", + "tx_type": "CreditTransfer", + "timestamp": "2026-02-18T11:00:00Z", + "value_uregen": 500000, + "payer": "regen1sender002" + } + ] +} diff --git a/mechanisms/m013-value-based-fee-routing/schemas/README.md b/mechanisms/m013-value-based-fee-routing/schemas/README.md new file mode 100644 index 0000000..d5636be --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/schemas/README.md @@ -0,0 +1,15 @@ +# m013 output schemas + +These JSON Schemas define **canonical output shapes** for m013 (Value-Based Fee Routing) artifacts. + +## Files +- `m013_fee_event.schema.json` — schema for a single fee collection event (tx_type, value, fee_amount, distribution). +- `m013_fee_config.schema.json` — schema for fee configuration (rates by transaction type, distribution shares, min_fee). +- `m013_kpi.schema.json` — schema for the KPI JSON block emitted by agents/digests. + +## Notes +- These schemas are intended for **validation** and consistency across repos (Heartbeat, agent skills, etc.). +- v0 is a reference spec: schemas describe outputs for the proposed fee model. +- All monetary amounts are in **uregen** (1 REGEN = 1,000,000 uregen). +- Distribution shares must satisfy the **Share Sum Unity** invariant (sum = 1.0). +- Fee rates are bounded `[0, 0.10]` per the **Rate Bound Safety** invariant. diff --git a/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_config.schema.json b/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_config.schema.json new file mode 100644 index 0000000..c98e18d --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_config.schema.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "m013 fee configuration", + "description": "Fee configuration for the value-based fee router: rates by transaction type and distribution shares.", + "type": "object", + "additionalProperties": false, + "required": [ + "fee_rates", + "distribution_shares", + "min_fee_uregen" + ], + "properties": { + "fee_rates": { + "type": "object", + "additionalProperties": false, + "required": [ + "CreditIssuance", + "CreditTransfer", + "CreditRetirement", + "MarketplaceTrade" + ], + "properties": { + "CreditIssuance": { + "type": "number", + "minimum": 0, + "maximum": 0.10, + "description": "Fee rate for credit issuance (MsgCreateBatch)" + }, + "CreditTransfer": { + "type": "number", + "minimum": 0, + "maximum": 0.10, + "description": "Fee rate for credit transfer (MsgSend)" + }, + "CreditRetirement": { + "type": "number", + "minimum": 0, + "maximum": 0.10, + "description": "Fee rate for credit retirement (MsgRetire)" + }, + "MarketplaceTrade": { + "type": "number", + "minimum": 0, + "maximum": 0.10, + "description": "Fee rate for marketplace trade (MsgBuySellOrder)" + } + } + }, + "distribution_shares": { + "type": "object", + "additionalProperties": false, + "required": [ + "burn", + "validator", + "community", + "agent" + ], + "properties": { + "burn": { + "type": "number", + "minimum": 0, + "maximum": 1.0, + "description": "Share routed to burn pool" + }, + "validator": { + "type": "number", + "minimum": 0, + "maximum": 1.0, + "description": "Share routed to validator fund" + }, + "community": { + "type": "number", + "minimum": 0, + "maximum": 1.0, + "description": "Share routed to community pool" + }, + "agent": { + "type": "number", + "minimum": 0, + "maximum": 1.0, + "description": "Share routed to agent infrastructure fund" + } + }, + "description": "Distribution shares must sum to 1.0 (Share Sum Unity invariant)" + }, + "min_fee_uregen": { + "type": "integer", + "minimum": 0, + "description": "Minimum fee floor in uregen (1 REGEN = 1,000,000 uregen)" + } + } +} diff --git a/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_event.schema.json b/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_event.schema.json new file mode 100644 index 0000000..a633cec --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/schemas/m013_fee_event.schema.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "m013 fee collection event", + "description": "A single fee event generated by a credit transaction processed through the fee router.", + "type": "object", + "additionalProperties": false, + "required": [ + "tx_hash", + "tx_type", + "timestamp", + "value_uregen", + "fee_amount_uregen", + "distribution" + ], + "properties": { + "tx_hash": { + "type": "string", + "description": "Transaction hash" + }, + "tx_type": { + "type": "string", + "enum": [ + "CreditIssuance", + "CreditTransfer", + "CreditRetirement", + "MarketplaceTrade" + ], + "description": "Type of credit transaction" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 datetime with timezone" + }, + "value_uregen": { + "type": "integer", + "minimum": 0, + "description": "Transaction value in uregen (1 REGEN = 1,000,000 uregen)" + }, + "fee_rate": { + "type": "number", + "minimum": 0, + "maximum": 0.10, + "description": "Fee rate applied for this transaction type" + }, + "fee_amount_uregen": { + "type": "integer", + "minimum": 0, + "description": "Computed fee amount in uregen (after min_fee clamping)" + }, + "min_fee_applied": { + "type": "boolean", + "description": "Whether the min_fee floor was applied (computed fee < min_fee)" + }, + "distribution": { + "type": "object", + "additionalProperties": false, + "required": [ + "burn", + "validator", + "community", + "agent" + ], + "properties": { + "burn": { + "type": "integer", + "minimum": 0, + "description": "Amount routed to burn pool (uregen)" + }, + "validator": { + "type": "integer", + "minimum": 0, + "description": "Amount routed to validator fund (uregen)" + }, + "community": { + "type": "integer", + "minimum": 0, + "description": "Amount routed to community pool (uregen)" + }, + "agent": { + "type": "integer", + "minimum": 0, + "description": "Amount routed to agent infrastructure fund (uregen)" + } + } + }, + "payer": { + "type": "string", + "description": "Address of the fee payer" + } + } +} diff --git a/mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json b/mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json new file mode 100644 index 0000000..97c782d --- /dev/null +++ b/mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json @@ -0,0 +1,112 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "m013 KPI output", + "type": "object", + "additionalProperties": false, + "required": [ + "mechanism_id", + "scope", + "as_of", + "total_fees_uregen", + "fee_events_count", + "fees_by_type", + "distribution_by_pool", + "avg_fee_rate" + ], + "properties": { + "mechanism_id": { + "const": "m013" + }, + "scope": { + "type": "string" + }, + "as_of": { + "type": "string", + "description": "ISO-8601 datetime with timezone (e.g., 2026-02-18T12:00:00Z)", + "format": "date-time" + }, + "total_fees_uregen": { + "type": "integer", + "minimum": 0, + "description": "Total fees collected across all events (uregen)" + }, + "fee_events_count": { + "type": "integer", + "minimum": 0, + "description": "Number of fee events in the period" + }, + "fees_by_type": { + "type": "object", + "additionalProperties": false, + "properties": { + "CreditIssuance": { + "type": "integer", + "minimum": 0, + "description": "Total fees from credit issuance (uregen)" + }, + "CreditTransfer": { + "type": "integer", + "minimum": 0, + "description": "Total fees from credit transfers (uregen)" + }, + "CreditRetirement": { + "type": "integer", + "minimum": 0, + "description": "Total fees from credit retirements (uregen)" + }, + "MarketplaceTrade": { + "type": "integer", + "minimum": 0, + "description": "Total fees from marketplace trades (uregen)" + } + }, + "description": "Fee totals broken down by transaction type" + }, + "distribution_by_pool": { + "type": "object", + "additionalProperties": false, + "required": [ + "burn", + "validator", + "community", + "agent" + ], + "properties": { + "burn": { + "type": "integer", + "minimum": 0, + "description": "Total distributed to burn pool (uregen)" + }, + "validator": { + "type": "integer", + "minimum": 0, + "description": "Total distributed to validator fund (uregen)" + }, + "community": { + "type": "integer", + "minimum": 0, + "description": "Total distributed to community pool (uregen)" + }, + "agent": { + "type": "integer", + "minimum": 0, + "description": "Total distributed to agent infrastructure fund (uregen)" + } + }, + "description": "Fee distribution totals by pool" + }, + "avg_fee_rate": { + "type": "number", + "minimum": 0, + "description": "Average effective fee rate across all events (total_fees / total_value)" + }, + "min_fee_applied_count": { + "type": "integer", + "minimum": 0, + "description": "Number of events where min_fee floor was applied" + }, + "notes": { + "type": "string" + } + } +} diff --git a/package.json b/package.json index d174e09..bf7a676 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "agentic-tokenomics", "private": true, + "type": "module", "version": "0.0.0", "scripts": { "verify": "node scripts/verify.mjs", diff --git a/scripts/verify.mjs b/scripts/verify.mjs index 3253586..0d970a6 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -38,11 +38,29 @@ requireFile("mechanisms/m010-reputation-signal/datasets/fixtures/v0_sample.json" // Mechanism index check run("node", ["scripts/build-mechanism-index.mjs", "--check"]); -// Basic schema sanity +// m013 core files +requireFile("mechanisms/m013-value-based-fee-routing/SPEC.md"); +requireFile("mechanisms/m013-value-based-fee-routing/README.md"); +requireFile("mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json"); +requireFile("mechanisms/m013-value-based-fee-routing/schemas/m013_fee_event.schema.json"); +requireFile("mechanisms/m013-value-based-fee-routing/schemas/m013_fee_config.schema.json"); +requireFile("mechanisms/m013-value-based-fee-routing/datasets/fixtures/v0_sample.json"); + +// Basic schema sanity — m010 const kpiSchema = readJson("mechanisms/m010-reputation-signal/schemas/m010_kpi.schema.json"); if (!kpiSchema.required || !kpiSchema.required.includes("mechanism_id")) { - console.error("KPI schema missing required fields."); + console.error("m010 KPI schema missing required fields."); process.exit(4); } +// Basic schema sanity — m013 +const m013KpiSchema = readJson("mechanisms/m013-value-based-fee-routing/schemas/m013_kpi.schema.json"); +if (!m013KpiSchema.required || !m013KpiSchema.required.includes("mechanism_id")) { + console.error("m013 KPI schema missing required fields."); + process.exit(4); +} + +// m013 self-test +run("node", ["mechanisms/m013-value-based-fee-routing/reference-impl/m013_fee.js"]); + console.log("agentic-tokenomics verify: PASS");