diff --git a/fees/callput/index.ts b/fees/callput/index.ts index 90e038d7aa..2486159088 100644 --- a/fees/callput/index.ts +++ b/fees/callput/index.ts @@ -1,29 +1,21 @@ import { FetchOptions, FetchResult, SimpleAdapter } from "../../adapters/types"; import { CHAIN } from "../../helpers/chains"; -// CallPut Controller and FeeDistributor proxies on Base: +import { summarizeCallPutFees } from "./logic"; + +// CallPut Controller proxy on Base: // https://basescan.org/address/0xfc61ba50AE7B9C4260C9f04631Ff28D5A2Fa4EB2 -// https://basescan.org/address/0x780b6b94C0FfCf8E659727CE421e976C1b6784Bc const CONTROLLER = "0xfc61ba50AE7B9C4260C9f04631Ff28D5A2Fa4EB2"; -const FEE_DISTRIBUTOR = "0x780b6b94C0FfCf8E659727CE421e976C1b6784Bc"; -// USD values emitted by Vault fee events are scaled by 1e30. -const PRICE_PRECISION = 1e30; const POSITION_FEE_EVENT = "event CollectPositionFees(address indexed account, address indexed token, uint256 feeUsd, uint256 feeAmount, bool indexed isSettle)"; -const LIQUIDITY_FEE_EVENT = - "event CollectFees(address indexed token, uint256 feeUsd, uint256 feeAmount)"; -const FEE_REBATE_EVENT = - "event FeeRebate(address indexed from, address indexed to, address token, uint256 feeRebateAmount, uint256 feeAmount, uint256 afterFeePaidAmount, uint256 tokenSpotPrice, address indexed underlyingAsset, uint256 size, uint256 price, bool isSettle, bool isCopyTrade)"; +const PENDING_AMOUNT_EVENT = + "event NotifyPendingAmount(uint8 indexed priceType, address indexed token, uint256 pendingUsd, uint256 pendingAmount)"; -const OPTIONS_FEES = "Options Trading Fees"; -const LIQUIDITY_FEES = "Liquidity and Swap Fees"; -const OPTIONS_FEES_TO_PROTOCOL = "Options Trading Fees To Protocol"; -const LIQUIDITY_FEES_TO_PROTOCOL = "Liquidity and Swap Fees To Protocol"; -const OPTIONS_FEES_TO_LPS = "Options Trading Fees To LPs"; -const LIQUIDITY_FEES_TO_LPS = "Liquidity and Swap Fees To LPs"; -const REFERRAL_REBATES = "Options Fee Rebates To Referrers"; -const COPY_TRADING_REBATES = "Options Fee Rebates To Copy Traders"; +const TRADE_FEES = "Options Trade Fees"; +const RISK_PREMIUM = "Risk Premium"; +const TRADE_FEES_TO_PROTOCOL = "Options Trade Fees To Protocol"; +const RISK_PREMIUM_TO_OLPS = "Risk Premium To OLPs"; const fetch = async (options: FetchOptions): Promise => { const dailyFees = options.createBalances(); @@ -35,77 +27,34 @@ const fetch = async (options: FetchOptions): Promise => { target: CONTROLLER, abi: "function getVaults() view returns (address[3])", }); + const vaultUtils: string[] = await options.toApi.multiCall({ + calls: vaults, + abi: "address:vaultUtils", + }); const positionFeeLogs = await options.getLogs({ targets: vaults, eventAbi: POSITION_FEE_EVENT, }); - const liquidityFeeLogs = await options.getLogs({ - targets: vaults, - eventAbi: LIQUIDITY_FEE_EVENT, + const pendingAmountLogs = await options.getLogs({ + targets: vaultUtils, + eventAbi: PENDING_AMOUNT_EVENT, }); - const rebateLogs = await options.getLogs({ - targets: vaults, - eventAbi: FEE_REBATE_EVENT, - }); - - const positionFeesUsd = positionFeeLogs.reduce( - (sum, log) => sum + Number(log.feeUsd) / PRICE_PRECISION, - 0, - ); - const liquidityFeesUsd = liquidityFeeLogs.reduce( - (sum, log) => sum + Number(log.feeUsd) / PRICE_PRECISION, - 0, - ); - - dailyFees.addUSDValue(positionFeesUsd, OPTIONS_FEES); - dailyFees.addUSDValue(liquidityFeesUsd, LIQUIDITY_FEES); - // Rates sum to 100 in FeeDistributor. Treasury and governance allocations - // are retained by the protocol; OLP rewards are paid to liquidity providers. - const olpRewardRate = Number( - await options.toApi.call({ - target: FEE_DISTRIBUTOR, - abi: "uint256:olpRewardRate", - }), - ); - const lpShare = olpRewardRate / 100; - const protocolShare = 1 - lpShare; + const summary = summarizeCallPutFees(positionFeeLogs, pendingAmountLogs); - dailyRevenue.addUSDValue( - positionFeesUsd * protocolShare, - OPTIONS_FEES_TO_PROTOCOL, - ); - dailyRevenue.addUSDValue( - liquidityFeesUsd * protocolShare, - LIQUIDITY_FEES_TO_PROTOCOL, - ); - dailyProtocolRevenue.addUSDValue( - positionFeesUsd * protocolShare, - OPTIONS_FEES_TO_PROTOCOL, - ); + dailyFees.addUSDValue(summary.tradeFeesUsd, TRADE_FEES); + dailyFees.addUSDValue(summary.riskPremiumUsd, RISK_PREMIUM); + dailyRevenue.addUSDValue(summary.revenueUsd, TRADE_FEES_TO_PROTOCOL); dailyProtocolRevenue.addUSDValue( - liquidityFeesUsd * protocolShare, - LIQUIDITY_FEES_TO_PROTOCOL, + summary.revenueUsd, + TRADE_FEES_TO_PROTOCOL, ); dailySupplySideRevenue.addUSDValue( - positionFeesUsd * lpShare, - OPTIONS_FEES_TO_LPS, - ); - dailySupplySideRevenue.addUSDValue( - liquidityFeesUsd * lpShare, - LIQUIDITY_FEES_TO_LPS, + summary.supplySideRevenueUsd, + RISK_PREMIUM_TO_OLPS, ); - // Referral and copy-trading rebates are paid from the protocol's fee share, - // not additional user fees. - for (const log of rebateLogs) { - const rebateLabel = log.isCopyTrade ? COPY_TRADING_REBATES : REFERRAL_REBATES; - dailyRevenue.subtractToken(log.token, log.feeRebateAmount, OPTIONS_FEES_TO_PROTOCOL); - dailyProtocolRevenue.subtractToken(log.token, log.feeRebateAmount, OPTIONS_FEES_TO_PROTOCOL); - dailySupplySideRevenue.add(log.token, log.feeRebateAmount, rebateLabel); - } - return { dailyFees, dailyRevenue, @@ -114,51 +63,47 @@ const fetch = async (options: FetchOptions): Promise => { }; }; +const methodology = { + Fees: + "Options trade fees plus risk-premium execution-price spreads paid by CallPut users.", + Revenue: + "Options trade fees collected by CallPut. Risk premium is excluded from revenue and attributed to OLP liquidity providers.", + ProtocolRevenue: + "Options trade fees collected by CallPut, excluding risk premium.", + SupplySideRevenue: + "Risk premiums accrued to CallPut OLP liquidity providers.", +}; + +const breakdownMethodology = { + Fees: { + [TRADE_FEES]: + "Fees charged when users open, close, or settle options positions.", + [RISK_PREMIUM]: + "Execution-price spread relative to mark price charged to options traders.", + }, + Revenue: { + [TRADE_FEES_TO_PROTOCOL]: + "Options trade fees collected by CallPut, excluding risk premium.", + }, + ProtocolRevenue: { + [TRADE_FEES_TO_PROTOCOL]: + "Options trade fees collected by CallPut, excluding risk premium.", + }, + SupplySideRevenue: { + [RISK_PREMIUM_TO_OLPS]: + "Risk premiums accrued to OLP liquidity providers through VaultUtils pending RP amounts.", + }, +}; + const adapter: SimpleAdapter = { version: 2, pullHourly: true, chains: [CHAIN.BASE], fetch, - // First production Controller volume event on Base. + // First production Controller fee activity on Base. start: "2026-01-30", - methodology: { - Fees: - "Options trading fees and liquidity and swap fees paid by CallPut users.", - Revenue: - "The share of fees allocated to the CallPut treasury and governance addresses, based on the FeeDistributor's onchain OLP reward rate, minus referral and copy-trading rebates.", - ProtocolRevenue: - "The share of fees allocated to the CallPut treasury and governance addresses, minus referral and copy-trading rebates.", - SupplySideRevenue: - "Referral and copy-trading rebates, and any share of net fees allocated by FeeDistributor to OLPs.", - }, - breakdownMethodology: { - Fees: { - [OPTIONS_FEES]: - "Fees charged when users open, close, or settle options positions.", - [LIQUIDITY_FEES]: - "Fees charged when users mint or redeem vault liquidity tokens or swap supported collateral assets.", - }, - Revenue: { - [OPTIONS_FEES_TO_PROTOCOL]: - "Options trading fees allocated to CallPut treasury and governance addresses, net of referral and copy-trading rebates.", - [LIQUIDITY_FEES_TO_PROTOCOL]: - "Net liquidity and swap fees allocated to CallPut treasury and governance addresses.", - }, - ProtocolRevenue: { - [OPTIONS_FEES_TO_PROTOCOL]: - "Options trading fees allocated to CallPut treasury and governance addresses, net of referral and copy-trading rebates.", - [LIQUIDITY_FEES_TO_PROTOCOL]: - "Net liquidity and swap fees allocated to CallPut treasury and governance addresses.", - }, - SupplySideRevenue: { - [REFERRAL_REBATES]: "Options fee rebates paid to referral partners.", - [COPY_TRADING_REBATES]: "Options fee rebates paid to copy traders.", - [OPTIONS_FEES_TO_LPS]: - "Net options trading fees allocated to OLP liquidity providers.", - [LIQUIDITY_FEES_TO_LPS]: - "Net liquidity and swap fees allocated to OLP liquidity providers.", - }, - }, + methodology, + breakdownMethodology, }; export default adapter; diff --git a/fees/callput/logic.ts b/fees/callput/logic.ts new file mode 100644 index 0000000000..3acaff2b7d --- /dev/null +++ b/fees/callput/logic.ts @@ -0,0 +1,51 @@ +export type PositionFeeLog = { + feeUsd: string | number; +}; + +export type PendingAmountLog = { + priceType: string | number; + pendingUsd: string | number; +}; + +export type CallPutFeeSummary = { + tradeFeesUsd: number; + riskPremiumUsd: number; + feesUsd: number; + revenueUsd: number; + supplySideRevenueUsd: number; +}; + +// Vault and VaultUtils emit USD-denominated values with 1e30 precision. +const PRICE_PRECISION = 1e30; +// IVaultUtils.PriceType enum: MP = 0, RP = 1. +const RISK_PREMIUM_PRICE_TYPE = 1; + +function fromPricePrecision(value: string | number): number { + return Number(value) / PRICE_PRECISION; +} + +export function summarizeCallPutFees( + positionFeeLogs: PositionFeeLog[], + pendingAmountLogs: PendingAmountLog[], +): CallPutFeeSummary { + const tradeFeesUsd = positionFeeLogs.reduce( + (sum, log) => sum + fromPricePrecision(log.feeUsd), + 0, + ); + const riskPremiumUsd = pendingAmountLogs.reduce( + (sum, log) => + sum + + (Number(log.priceType) === RISK_PREMIUM_PRICE_TYPE + ? fromPricePrecision(log.pendingUsd) + : 0), + 0, + ); + + return { + tradeFeesUsd, + riskPremiumUsd, + feesUsd: tradeFeesUsd + riskPremiumUsd, + revenueUsd: tradeFeesUsd, + supplySideRevenueUsd: riskPremiumUsd, + }; +}