Platform Fee

How ClearPact takes a single, predictable cut on each settlement — and how it shows up in the receipt.

The platform fee is one trust surface, not three. There is no admin override, no off-chain billing loop, and no separate "fee" settlement transaction: a single BPS value declared at deploy time is the only knob, and the same value is what annotates every settlement receipt and every audit event the server emits.

1. One trust surface, not three

Pilots that integrate ClearPact for the first time almost always ask the same question: "how is the fee decided, who decides it, and where does it show up?" The answer fits in one sentence: one BPS value, one recipient, one receipt field — configured once on the server and never edited per-escrow.

There is no admin route to set a fee, no per-key fee tier, and no way for the settlement endpoint to override the configured value. The server reads CLEARPACT_PLATFORM_FEE_BPS at startup, validates the recipient address, and stamps the same fee object onto every settled response and every escrow_events row.

Trust surfaceWhat's configuredWhat an integrator sees
CLEARPACT_PLATFORM_FEE_BPS Integer basis-points (e.g. 100 = 1.00%) settlement.fee.bps in the API response
CLEARPACT_PLATFORM_FEE_RECIPIENT EVM address that receives the fee (must match /^0x[0-9a-fA-F]{40}$/) settlement.fee.recipient in the API response
PLATFORM_FEE_ACTIVE Derived: BPS > 0 and recipient looks valid Fee object absent from response when inactive; [fees] log line at boot

If either env var is missing or invalid, PLATFORM_FEE_ACTIVE evaluates to false, the server logs [fees] ∈️ Platform fee INACTIVE at boot, and settlements complete with no fee deduction. There is no fallback recipient and no silent default.

2. BPS → percentage, the only math

Basis points (BPS) are the same unit used by the underlying ERC-8183 contracts and by Uniswap, Aave, and most DEX aggregators. One BPS is 1/10000, so:

BPSPercentageExample: on a $1,000 USDC settlement
10 0.10% $1.00 to recipient, $999.00 net to payee
25 0.25% $2.50 to recipient, $997.50 net to payee
100 1.00% $10.00 to recipient, $990.00 net to payee
250 2.50% $25.00 to recipient, $975.00 net to payee

The integer-BPS choice exists for two reasons. First, it lets the server apply the fee with a single integer multiplication and a Math.floor, so no floating-point drift ever touches the receipt: feeAmount = Math.floor(grossAmount * feeBps) / 10000. Second, it matches what on-chain DEXes and the ERC-8183 fee surface already speak, so any future on-chain fee extension reuses the same shape.

// routes/escrow.js — single line of fee math, the only line.
const grossAmount = parseFloat(escrow.amount);
const feeBps      = PLATFORM_FEE_ACTIVE ? PLATFORM_FEE_BPS : 0;
const feeAmount   = feeBps > 0
  ? Math.floor(grossAmount * feeBps) / 10000
  : 0;
const netAmount   = grossAmount - feeAmount;

3. Why CLEARPACT_PLATFORM_FEE_RECIPIENT matters

The recipient address is what makes the fee economically real, not just numerically real. ClearPact's contracts are immutable and have no admin role, so the server is the only place a fee split can happen — and the recipient env var is the only place that split points.

At boot the server reads both env vars and validates the recipient with the same regex used by the SDK: /^0x[0-9a-fA-F]{40}$/. If the regex fails, PLATFORM_FEE_ACTIVE stays false. Integrators wiring their own treasury should:

No middleware address. The fee is taken on the settlement side, not via a separate token transfer, so there is no intermediate custody address to trust. The same transaction that pays the payee also credits the fee recipient — or, when PLATFORM_FEE_ACTIVE is false, the entire gross amount goes to the payee with no fee field on the receipt at all.

4. How the fee shows up in the settlement receipt

The settlement endpoint returns a flat settlement object on the success response. When the fee is active, three new keys appear alongside the existing fields — and they are the only fee-shaped keys on the entire API surface.

receipt
{
  "success": true,
  "network": "base-sepolia",
  "message": "Escrow settled. 1000 USDC released to 0xPROV… (platform fee: 10 USDC → 0xFEE…)",
  "escrow": { /* settled escrow record */ },
  "settlement": {
    "amount":        1000,          // gross, on-chain payout unchanged
    "net_amount":    990,            // payee receives net of fee
    "token":         "USDC",
    "from":          "0xPAY…",
    "to":            "0xPROV…",
    "network":       "base-sepolia",
    "conditions":    [ /* condition results */ ],
    "settled_at":    "2026-08-04T12:34:56.789Z",
    "settle_tx_hash": "0xSETTLE…",
    "explorer":      "https://sepolia.basescan.org/tx/0xSETTLE…",

    // Only present when PLATFORM_FEE_ACTIVE is true:
    "fee": {
      "bps":       100,
      "amount":    10,
      "recipient": "0xFEE_RECIPIENT…"
    }
  }
}

The same three fields appear on the audit row written to escrow_events. The event_type is 'settled', the actor is the caller (or 'system'), and the details JSONB carries the same platform_fee block — so a downstream audit query like SELECT details->'platform_fee' FROM escrow_events WHERE event_type = 'settled' AND created_at > NOW() - INTERVAL '30 days' reconstructs fee revenue for any window without joining the escrows table.

audit row
{
  "escrow_id":  "esc_…",
  "event_type": "settled",
  "actor":      "system",
  "details": {
    "amount":       1000,
    "net_amount":   990,
    "token":        "USDC",
    "payee":        "0xPROV…",
    "network":      "base-sepolia",
    "conditions":   [ /* condition results */ ],
    "settle_tx_hash": "0xSETTLE…",
    "platform_fee": {
      "bps":       100,
      "amount":    10,
      "recipient": "0xFEE_RECIPIENT…"
    }
  }
}

5. When the fee is inactive

If either env var is missing or invalid, three things happen and they all surface to the operator — not to the integrators:

  1. The server logs [fees] ∈️ Platform fee INACTIVE (set CLEARPACT_PLATFORM_FEE_BPS and CLEARPACT_PLATFORM_FEE_RECIPIENT to enable) at boot.
  2. settlement.fee is omitted from the API response entirely — integrators can detect this with a 'fee' in settlement check rather than reading a zero.
  3. details.platform_fee is omitted from the audit row, so fee dashboards keyed on the field automatically exclude inactive periods.

This is deliberate: the receipt shape is the same, the gross amount is the same, and only the fee surface disappears. There is no degraded mode where the fee silently defaults to zero — the operator either wired the env or they didn't, and the receipt makes that visible.

6. Pilot integration checklist

For the upcoming agent-to-agent B2B pilot, integrators wiring ClearPact for the first time should walk through this list once before the first live settlement:

  1. Confirm CLEARPACT_PLATFORM_FEE_BPS is set on the server you point your SDK at. The preflight script (scripts/preflight-mainnet.js) reads the same env and reports a missing value as a hard failure.
  2. Confirm CLEARPACT_PLATFORM_FEE_RECIPIENT resolves to a multisig your team controls. The address appears verbatim in every receipt and audit row, so it has to be an address you actually own.
  3. Drive a $0.01 test settlement and assert settlement.fee.bps, settlement.fee.amount, and settlement.fee.recipient all equal the values you'd expect. The SDK surfaces this object unchanged, so the assertion goes through the same code path the pilot will.
  4. Run SELECT details->'platform_fee' FROM escrow_events WHERE escrow_id = 'esc_…' AND event_type = 'settled' on the resulting audit row to confirm the same object round-trips into the database.