Skip to content

Fee Specification

All revenue sources, fee distribution, referral splits, and what's configurable. Every claim is grounded in code with file:line references.

Fee Policy Package (@cabal/fees)

All fee constants, calculation logic, builder fee validation, trade value computation, incubator share, referral stats queries, and aggregation helpers live in packages/fees/. This is the single source of truth for fee policy across the monorepo.

Key modules:
  • src/protocols.ts — Fee schedule registry (FEE_SCHEDULES) + all named constants
  • src/calculate.ts — calculateFees(db, agentId, protocol) + calculateFeesBps() (pure)
  • src/builder.ts — isValidBuilderFill(), calculateBuilderRevenue() (pure math)
  • src/trade-utils.ts — computeFeeAmount(), computeTradeValues() (pure)
  • src/incubator.ts — computeIncubatorShare(feeValueUsd, protocol)
  • src/stats.ts — getReferralStats(), getPublicReferralStats(), getReferralList()
  • src/aggregation.ts — aggregateReferrerEarnings(), buildReferrerSummaries(), getCompletedPayoutTotals()
  • src/lookup.ts — lookupByReferralCode()
  • src/dbc.ts — recordDbcFeeClaim()

Dependencies: @cabal/client (Result type), @cabal/db (schema, CabalDb type), drizzle-orm.

Revenue Sources

Cabal earns fees from three trade types. Each has a different fee mechanism, rate, and collection method.

1. Jupiter / Solana Swaps

PropertyValue
Fee rate1% (100 BPS) of input amount, reduced to 0.9% (90 BPS) for referred agents
CollectionOn-chain: Jupiter embeds fee transfer to treasury ATA in swap tx
RecipientSQUADS_VAULT_ADDRESS (Squads v4 multisig vault PDA)
ConstantPLATFORM_FEE_BPS = 100 — packages/fees/src/protocols.ts:61 (derived from FEE_SCHEDULES.jupiter.baseBps)
Flow:
  1. handleSolanaTrade() calls calculateFees(db, agent.id, 'jupiter') — apps/web/app/api/v1/trade/route.ts
  2. buildTrade() passes fees.effectiveFeeBps (90 or 100) as platformFeeBps to Jupiter's /quote endpoint — packages/solana/src/jupiter/fees.ts
  3. Jupiter returns a quote with embedded platformFee.amount in the response
  4. buildUnsignedSwapTx() sends the treasury's ATA as feeAccount to Jupiter's /swap endpoint — packages/solana/src/jupiter/fees.ts
  5. Jupiter builds a versioned tx with a CPI that sends the fee directly to the treasury ATA

Fee mint selection (packages/solana/src/jupiter/fees.ts):

  • If output mint is SOL or stablecoin → fee collected in output token (standard /swap path)
  • Else if input mint is SOL or stablecoin → fee collected in input token
  • Else (exotic pair) → uses /swap-instructions path with createAssociatedTokenAccountIdempotentInstruction prepended to create the treasury ATA on-chain
  • Fallback: if /swap-instructions fails (route too complex for legacy tx), re-quotes with platformFeeBps: 0 — trade goes through but no fee is collected

Fee amount computation (packages/fees/src/trade-utils.ts):

computeFeeAmount(quote.platformFee?.amount, fromSmallestUnit, inputAmount, effectiveFeeBps)

Uses Jupiter's reported platformFee.amount if available; otherwise estimates as inputAmount * effectiveFeeBps / 10000.

2. Hyperliquid Builder Fees

PropertyValue
Perps fee5 BPS (0.05%) of notional value
Spot sell fee50 BPS (0.5%) of sell value
Spot buy fee0 (no fee)
CollectionOff-chain: HL deducts from fill settlement
RecipientCABAL_HL_BUILDER_ADDRESS env var
ConstantsBUILDER_FEE_PERPS = 50, BUILDER_FEE_SPOT_SELL = 500 (tenths of BPS) — packages/fees/src/protocols.ts:74-76
Flow:
  1. On first trade, approveBuilderFee(account) calls client.approveBuilderFee({ maxFeeRate: '0.05%' }) — packages/hyperliquid/src/exchange.ts
  2. Orders include { builder: { b: CABAL_BUILDER_ADDRESS, f: 50|500 } } via getBuilderParams(isSpot) — packages/hyperliquid/src/builder-fee.ts:31-36
  3. HL deducts the fee during fill settlement
  4. recordHLFill() persists feeAmount: fill.builderFee || 0 — packages/hyperliquid/src/verification.ts
  5. Fill validation with 10% tolerance: isValidBuilderFill() — packages/fees/src/builder.ts:10-36

Builder wallet requirement: Must hold 100+ USDC on HL perps to collect fees.

3. DBC (Meteora Bonding Curve) Partner Fees

PropertyValue
Fee rateSet on-chain per pool (creator/partner fee config in DBC program)
CollectionManual batch claim via apps/web/scripts/claim-dbc-fees.ts
RecipientIncubator Privy wallet, then split
Trackingdbc_fee_claims table — packages/db/src/schema/referrals.ts:21-35

DBC pools accrue trading fees on-chain (base + quote). Partner fees are claimed periodically by calling claimPartnerTradingFee2() from the Meteora DBC SDK. Each claim is recorded in dbc_fee_claims with USD value and referrer attribution.


Fee Distribution

Every fee dollar flows through up to four destinations: treasury, referrer, incubator pool, and (for DBC) on-chain pool.

Jupiter / Solana

Gross input amount (e.g. $1,000)
  │
  ├─ Non-referred agent: 1.0% fee ($10)
  │   ├─ 100% → Treasury ($10) — on-chain via Jupiter CPI
  │   └─ 10% of fee → Incubator pool ($1) — virtual USD ledger
  │
  └─ Referred agent: 0.9% fee ($9) — on-chain only 90 BPS collected
      ├─ Treasury keeps: effectiveFeeBps - referrerRewardBps = 80 BPS ($8) — on-chain
      ├─ Referrer: 10 BPS of gross volume ($1) — matview-computed, paid out monthly
      └─ 10% of fee → Incubator pool ($0.90) — virtual USD ledger

Key detail: The referral split is not enforced on-chain. Jupiter collects 90 BPS total to the treasury. The referrer's 10 BPS share is computed in the materialized view and paid out separately.

Referrer earning formula (matview, packages/db/src/schema/referrals.ts):

SUM((t."valueUsd" + t."feeAmount") * t.referrer_fee_bps / 10000.0)

Incubator deposit (packages/fees/src/incubator.ts + packages/incubator/src/lifecycle/pool.ts):

incubatorShare = computeIncubatorShare(feeValueUsd, protocol) = feeValueUsd * (schedule.incubatorPct / 100)

Called fire-and-forget after trade confirmation — apps/web/app/api/v1/trade/route.ts.

Hyperliquid

Trade value (e.g. $10,000 perps)
  │
  ├─ Builder fee: 5 BPS ($5.00)
  │   └─ 100% → Builder wallet (all to Cabal, on HL settlement)
  │
  └─ 10% of estimated builder fee → Incubator pool ($0.50)

Incubator deposit (apps/web/app/api/v1/trade/route.ts):

const feeRate = FEE_SCHEDULES[hlProtocol].baseBps / 10_000 // 0.0005 for perps, 0.005 for spot sells
const builderFee = result.filled ? valueUsd * feeRate : 0

Uses the protocol-specific fee rate from the fee schedule registry. (Previously used hardcoded 0.0005 for all trade types — fixed in @cabal/fees extraction.)

Referral tracking (apps/web/app/api/v1/trade/route.ts): calculateFees(db, agent.id, hlProtocol) is called with the specific HL protocol, returning protocol-correct referral rates. The result is passed to recordHLFill() as referralInfo (verification.ts). HL perps get 1 BPS referrer reward; HL spot sells get 10 BPS. (Previously used Solana's 10 BPS for all HL trades — fixed in @cabal/fees extraction.)

DBC Partner Fees

Partner fee claim (e.g. 0.5 SOL = $75)
  │
  ├─ Non-referred agent: 100% → incubator wallet
  │
  └─ Referred agent:
      ├─ 50% → Referrer ($37.50)
      └─ 50% → Incubator wallet ($37.50)

DBC_REFERRER_SHARE_RATE = 0.5 — packages/fees/src/protocols.ts:69. Recorded per-claim in dbc_fee_claims.referrer_share_rate (packages/db/src/schema/referrals.ts:32). Aggregated in matview branch 3.


Fee Calculation Logic

calculateFees(db, agentId, protocol) — packages/fees/src/calculate.ts

Fee calculation is protocol-aware. Each protocol has its own fee schedule in FEE_SCHEDULES (packages/fees/src/protocols.ts):

ProtocolbaseBpsreferrerBpsdiscountBpsincubatorPct
jupiter100101010
hl-perps51010
hl-spot-sell5010010
hl-spot-buy0000
dbc00010
Input:  agentId, protocol (defaults to 'jupiter')
Output: { protocol, schedule, effectiveFeeBps, referrerRewardBps, treasuryFeeBps, referrerId, feeDiscount }
 
Jupiter, non-referred:
  effectiveFeeBps  = 100 (schedule.baseBps)
  referrerRewardBps = 0
  treasuryFeeBps    = 100
 
Jupiter, referred (user.feeDiscount defaults to 0.1):
  discount          = user.feeDiscount || (schedule.discountBps / schedule.baseBps) = 0.1
  effectiveFeeBps  = round(100 * (1 - 0.1)) = 90
  referrerRewardBps = 10 (schedule.referrerBps)
  treasuryFeeBps    = 90 - 10 = 80
 
HL perps, referred:
  discountBps = 0, so no discount applied
  effectiveFeeBps  = 5 (schedule.baseBps)
  referrerRewardBps = 1 (schedule.referrerBps)
  treasuryFeeBps    = 4
 
HL spot sell, referred:
  discountBps = 0, so no discount applied
  effectiveFeeBps  = 50 (schedule.baseBps)
  referrerRewardBps = 10 (schedule.referrerBps)
  treasuryFeeBps    = 40

Constants imported from @cabal/fees (packages/fees/src/protocols.ts):

  • PLATFORM_FEE_BPS = 100 (= FEE_SCHEDULES.jupiter.baseBps)
  • BASE_FEE_BPS = 100 (alias)
  • REFERRER_REWARD_BPS = 10 (= FEE_SCHEDULES.jupiter.referrerBps)
  • REFEREE_DISCOUNT_BPS = 10 (= FEE_SCHEDULES.jupiter.discountBps)

Referral linking happens at registration:

  • apps/web/lib/services/registration.ts — sets feeDiscount: 0.1
  • apps/web/lib/services/device-auth.ts — same feeDiscount: 0.1

Referral Economics Summary

SourceCabal's feeReferrer's cutReferrer as % of Cabal's fee
Jupiter (Solana)90 BPS (with referral)10 BPS of gross volume12.5% of treasury take (10/80)
HL perps5 BPS1 BPS of gross volume25% of treasury take (1/4)
HL spot sell50 BPS10 BPS of gross volume25% of treasury take (10/40)
HL spot buy0 BPSN/A (no fee)N/A
DBC poolvariable50% of partner share50%

What's Configurable

Per-Agent (DB columns)

ColumnTableSchema locationEffectCurrent usage
feeDiscountuserpackages/db/src/schema/core.ts:66Multiplier: effectiveFeeBps = BASE_FEE_BPS * (1 - feeDiscount)Set to 0.1 on registration if referred. Could support VIP tiers.
referredByuserpackages/db/src/schema/core.ts:63FK to referrer. If set, calculateFees() returns referrer info.Set at registration via --ref=code flag
referralCodeuserpackages/db/src/schema/core.ts:64Referral code = Twitter username (lowercase). Used for lookupByReferralCode().Set at agent creation
referralPayoutWalletuserpackages/db/src/schema/core.ts:65Payout destination for referral earningsSet by referrer via dashboard

Environment Variables

VariableDefaultEffect
INCUBATOR_FEE_PERCENT10% of trade fees deposited to incubator pool — packages/fees/src/protocols.ts:84
INCUBATOR_BIRTH_THRESHOLD_USD15Pool balance threshold to trigger agent birth — packages/fees/src/protocols.ts:87
SQUADS_VAULT_ADDRESS(required)Squads vault PDA for Jupiter fee collection. Treasury ATA derived in fees.ts:109-127
CABAL_HL_BUILDER_ADDRESS(required)EVM address for HL builder fee collection — packages/hyperliquid/src/builder-fee.ts:7
JUPITER_API_KEY(required)API key for Jupiter fee-bearing swap API (api.jup.ag/swap/v1)
METEORA_DBC_CONFIG_KEY(required)DBC config for partner fee queries

Hardcoded Constants

All fee constants are defined in packages/fees/src/protocols.ts (single source of truth):

ConstantValueNotes
PLATFORM_FEE_BPS100= FEE_SCHEDULES.jupiter.baseBps. Jupiter fee rate passed to quote API
BASE_FEE_BPS100Alias for PLATFORM_FEE_BPS
REFERRER_REWARD_BPS10= FEE_SCHEDULES.jupiter.referrerBps. Jupiter referrer's cut
REFEREE_DISCOUNT_BPS10= FEE_SCHEDULES.jupiter.discountBps. Jupiter fee discount for referred agents
DBC_REFERRER_SHARE_RATE0.5= FEE_SCHEDULES.dbc.referrerShareRate. DBC referrer split
BUILDER_FEE_PERPS50 (tenths of BPS = 5 BPS)HL perps builder fee (HL API format)
BUILDER_FEE_SPOT_SELL500 (tenths of BPS = 50 BPS)HL spot sell builder fee (HL API format)
BUILDER_FEE_RATE_PERPS0.0005= BUILDER_FEE_PERPS / 100_000. Decimal fraction for perps
BUILDER_FEE_RATE_SPOT0.005= BUILDER_FEE_SPOT_SELL / 100_000. Decimal fraction for spot sells
maxFeeRate (HL approval)'0.05%'Builder fee approval ceiling — packages/hyperliquid/src/exchange.ts

Fee Tracking in Database

trades table — packages/db/src/schema/core.ts:162-201

ColumnTypeLineDescription
feeAmountdoublePrecision177USD value of fee charged on this trade
referrerIduuid (nullable)178Who referred the trading agent
referrerFeeBpsinteger (nullable)179BPS the referrer earns — currently stores Solana constant for all trade types
treasuryFeeBpsinteger (nullable)180BPS treasury keeps — same problem

Partial index on referrer_id WHERE referrer_id IS NOT NULL — core.ts:200

trade_builds table — packages/db/src/schema/core.ts:204-214

ColumnTypeLineDescription
feeBpsinteger211Fee BPS stored at build time (default 100)
referrerIduuid (nullable)213Referrer at time of build

dbc_fee_claims table — packages/db/src/schema/referrals.ts:21-35

ColumnTypeLineDescription
poolAddresstext23DBC pool address
agentIduuid24Agent whose pool accrued the fee
referrerIduuid (nullable)25Referrer of the agent
claimTxSignaturetext (nullable)26On-chain tx for the claim
baseFeeLamportstext27Raw base token claim amount
quoteFeeLamportstext28Raw quote token claim amount
solPricedouble29SOL/USD price at claim time
feeUsddouble30Total USD value of claimed partner fees
earnedUsddouble31Referrer's USD share (feeUsd * referrerShareRate)
referrerShareRatedouble32Fraction of fee for referrer (default 0.5)

referral_payouts table — packages/db/src/schema/referrals.ts:92-107

ColumnTypeDescription
referrerIduuidReferrer being paid
amountdoublePayout amount
currencytextDefault 'SOL'
txSignaturetext (nullable)On-chain payout tx (unique where not null)
statustext`'pending'
periodStart/EnddatePayout period

referral_earnings_mv — packages/db/src/schema/referrals.ts:39-89

Materialized view with three UNION ALL branches. Refreshed by indexer cron.

Branch 1 — New trades (line 48-58): referrer_id IS NOT NULL

SUM((t."valueUsd" + t."feeAmount") * t.referrer_fee_bps / 10000.0) AS total_earned

Note: volume base is valueUsd + feeAmount (gross), not just valueUsd.

Branch 2 — Legacy trades (line 62-74): referrer_id IS NULL but user.referredBy IS NOT NULL

SUM((t."valueUsd" + t."feeAmount") * 0.001) AS total_earned

Hardcoded 0.1% for pre-referral-tracking trades.

Branch 3 — DBC claims (line 78-87):

SUM(earned_usd) AS total_earned

From dbc_fee_claims where referrer_id IS NOT NULL.

Both total_volume_usd and total_fees_generated are also computed per branch and aggregated in getReferralStats() — packages/fees/src/stats.ts.


Known Issues

  1. HL referral BPS is wrong. FIXED in @cabal/fees extraction. calculateFees() now takes a protocol param and returns protocol-specific referrer BPS: 1 BPS for hl-perps, 10 BPS for hl-spot-sell, 0 for hl-spot-buy.

  2. No HL referral rate defined. FIXED in @cabal/fees extraction. HL referral rates are defined in FEE_SCHEDULES (packages/fees/src/protocols.ts): perps 1 BPS, spot sell 10 BPS.

  3. referrer_fee_bps semantics are ambiguous. For Jupiter it means "BPS of gross volume". For HL it means the same thing but at different rates per protocol. The matview formula volume * referrer_fee_bps / 10000 works correctly now that per-trade referrer_fee_bps is protocol-accurate.

  4. Incubator pool is an internal virtual ledger. depositToPool() takes 10% of the fee amount, but this is not subtracted from what treasury or referrer receives. The incubator pool is a virtual USD balance in the incubator table, not an on-chain movement. It's an accounting fiction for tracking how much revenue has been "allocated" to birthing new agents.

  5. DBC fee claims are manual. No automated collection; depends on running apps/web/scripts/claim-dbc-fees.ts. Core accounting logic is now in packages/fees/src/dbc.ts.

  6. HL incubator deposit always uses perps rate. FIXED in @cabal/fees extraction. The trade route now uses FEE_SCHEDULES[hlProtocol].baseBps / 10_000 — returns 0.0005 for perps and 0.005 for spot sells.

  7. Zero-fee fallback on exotic pairs. When neither input nor output is SOL/stablecoin and the /swap-instructions path fails, Jupiter re-quotes with platformFeeBps: 0. The trade goes through but Cabal collects no fee. The feeAmount in the trade record will be 0.

  8. approveBuilderFee max rate may be too low for spot. The one-time approval uses maxFeeRate: '0.05%' (5 BPS) — sufficient for perps but below the 50 BPS spot sell rate. If HL enforces this ceiling across trade types, spot sell builder fees may be silently capped.


Implementation Files

@cabal/fees (single source of truth for fee policy)

ModuleKey exports
packages/fees/src/protocols.tsFEE_SCHEDULES, Protocol, FeeSchedule, all named constants (PLATFORM_FEE_BPS, BASE_FEE_BPS, REFERRER_REWARD_BPS, REFEREE_DISCOUNT_BPS, DBC_REFERRER_SHARE_RATE, BUILDER_FEE_*, INCUBATOR_FEE_PERCENT, BIRTH_THRESHOLD_USD)
packages/fees/src/calculate.tscalculateFees(db, agentId, protocol), calculateFeesBps(feeDiscount, referredBy, protocol)
packages/fees/src/builder.tsisValidBuilderFill(builderFee, isSpot, valueUsd), calculateBuilderRevenue(fills)
packages/fees/src/trade-utils.tscomputeFeeAmount(), computeTradeValues(), TradeValueInputs, TradeValues
packages/fees/src/incubator.tscomputeIncubatorShare(feeValueUsd, protocol)
packages/fees/src/stats.tsgetReferralStats(db, referrerId), getPublicReferralStats(db, referrerId), getReferralList(db, referrerId)
packages/fees/src/aggregation.tsaggregateReferrerEarnings(), buildReferrerSummaries(), getCompletedPayoutTotals()
packages/fees/src/lookup.tslookupByReferralCode(db, code)
packages/fees/src/dbc.tsrecordDbcFeeClaim(db, input)
packages/fees/src/types.tsFeeCalculation, ReferralInfo, ReferralStats, PublicReferralStats, ReferralListEntry

Other packages (consumers of @cabal/fees)

AreaFileKey functions/exports
Jupiter fee embeddingpackages/solana/src/jupiter/fees.tsbuildTrade(), getJupiterQuoteWithFee(), buildUnsignedSwapTx(), getTreasuryTokenAccount(). Imports PLATFORM_FEE_BPS from @cabal/fees.
Jupiter fee schemaspackages/solana/src/jupiter/schemas.tsjupiterQuoteWithFeeSchema, jupiterSwapWithFeeResponseSchema
Jupiter providerpackages/solana/src/swap/jupiter-provider.tsJupiterProvider (passes platformFeeBps through)
HL builder fee configpackages/hyperliquid/src/builder-fee.tsgetBuilderParams(), verifyBuilderApproval(), getBuilderInfo(), CABAL_BUILDER_ADDRESS. Re-exports isValidBuilderFill, calculateBuilderRevenue, BUILDER_FEE_* from @cabal/fees.
HL order + approvalpackages/hyperliquid/src/exchange.tsapproveBuilderFee(), order()
HL fill recordingpackages/hyperliquid/src/verification.tsrecordHLFill(), verifyHLFill()
Incubator pool depositpackages/incubator/src/lifecycle/pool.tsdepositToPool(). Imports computeIncubatorShare, BIRTH_THRESHOLD_USD from @cabal/fees.
DBC fee claimingapps/web/scripts/claim-dbc-fees.tsThin runner: on-chain claim → calls recordDbcFeeClaim() from @cabal/fees/dbc
Unified trade handlerapps/web/app/api/v1/trade/route.tshandleSolanaTrade(), handleHyperliquidTrade(). Imports calculateFees, computeFeeAmount, computeTradeValues, FEE_SCHEDULES from @cabal/fees.
Primary trade fieldsapps/web/lib/services/trade-utils.tsselectPrimaryTradeFields() (only non-fee function, stays in apps/web)

Database schema

AreaFileKey exports
DBC fee + matview schemapackages/db/src/schema/referrals.tsdbcFeeClaims, referralEarningsMv, referralPayouts
Trade schemapackages/db/src/schema/core.tstrades table — feeAmount, referrerId, referrerFeeBps, treasuryFeeBps
Trade build schemapackages/db/src/schema/core.tstradeBuilds table — feeBps
User fee columnspackages/db/src/schema/core.tsreferredBy, referralCode, referralPayoutWallet, feeDiscount

Other

AreaFileNotes
Registration (sets discount)apps/web/lib/services/registration.tsSets feeDiscount: 0.1 when referred. Uses lookupByReferralCode from @cabal/fees.
Device auth (sets discount)apps/web/lib/services/device-auth.tsSame feeDiscount: 0.1 path. Uses lookupByReferralCode from @cabal/fees.
Indexer (matview refresh)apps/indexer/src/index.tsdb.refreshMaterializedView(referralEarningsMv).concurrently()

Known Issues (Additional)

  • Fee discount not clamped. If feeDiscount is set to a value greater than 1.0, effectiveFeeBps goes negative. There is no guard against this — unlikely in practice but no validation exists.
  • DBC price impact always zero. priceImpactPct is hardcoded to '0' for DBC swaps. Agents get no slippage warning for bonding curve trades.
  • Migration fee claims record $0 USD values. DBC migration fee claims store zero USD in the database, making it impossible to audit actual migration fee revenue.