Conversation
📝 WalkthroughWalkthroughThe fee aggregation system in TermMax has been refactored from a treasury-based approach to a log-driven, multi-path classification system. This change introduces transfer log analysis to identify and categorize liquidation penalties, protocol fees, and performance fees from distinct sources, with extended support for additional blockchain networks. Changes
Sequence DiagramsequenceDiagram
participant Adapter as TermMax Adapter
participant Logs as ERC20 Transfer Logs
participant Market as Market Address Resolver
participant GT as GT Config Fetcher
participant Metrics as Fee Metrics Accumulator
Adapter->>Logs: getTransfers(blockRange) for TREASURY
Logs-->>Adapter: Transfer events
Adapter->>Market: Resolve market addresses
Market-->>Adapter: Market configs
Adapter->>GT: Batch multiCall for GT configs
GT-->>Adapter: GT configuration data
Adapter->>Adapter: handleLogs - classify each transfer
alt Transfer from liquidation (GT)
Adapter->>Metrics: LIQUIDATION_FEES += amount
else Transfer from FT market
Adapter->>Metrics: PROTOCOL_FEES += valuated amount
else Transfer from PERFORMANCE_FEE_MANAGER
Adapter->>Metrics: PERFORMANCE_FEES += amount
else Unknown source
Adapter->>Adapter: Ignore transfer
end
Metrics-->>Adapter: Accumulated dailyRevenue
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested Labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The termmax.ts adapter exports: |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@fees/termmax.ts`:
- Around line 83-86: The getLogs call in fees/termmax.ts that populates the logs
variable is missing parseLog: true, but later code accesses log.args.from and
log.args.value in the loop and other places (e.g., where froms/addresses are
built and fee parsing occurs), which will throw if logs are unparsed; update the
getLogs invocation (the call that sets const logs = await getLogs({...})) to
include parseLog: true alongside the existing entireLog and noTarget options so
the logs contain the args property and subsequent accesses to
log.args.from/log.args.value succeed.
- Around line 150-167: The fetch function in termmax.ts currently calls
dailyRevenue.add(dailyUserFees) but both dailyRevenue and dailyUserFees are
Balances objects; replace the call with dailyRevenue.addBalances(dailyUserFees)
to use the standard Balances merge method; update the invocation in the fetch
function (where dailyRevenue and dailyUserFees are defined and dailyRevenue is
mutated) to call addBalances instead of add for consistency with other helpers.
🧹 Nitpick comments (1)
fees/termmax.ts (1)
169-204: Consider reducing methodology duplication.All four categories (
Fees,UserFees,Revenue,ProtocolRevenue) have identical descriptions. You could define the strings once and reuse them:const feeDescription = "Protocol fees (FT valued at underlying 1:1)..."; const methodology = { Fees: feeDescription, UserFees: feeDescription, Revenue: feeDescription, ProtocolRevenue: feeDescription, };This is a minor readability improvement - the current approach works fine.
| for (const log of logs) { | ||
| froms.push(log.args.from); | ||
| addresses.push(log.address); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n -A 5 'entireLog:\s*true' --type=ts | head -150Repository: DefiLlama/dimension-adapters
Length of output: 7175
🏁 Script executed:
# Also search for getLogs usage generally
rg -n 'getLogs' --type=ts -B 2 -A 3 | head -200Repository: DefiLlama/dimension-adapters
Length of output: 11892
🏁 Script executed:
# Search for the log structure - what properties are typically accessed on logs
rg -n 'log\.args\.' --type=ts | head -100Repository: DefiLlama/dimension-adapters
Length of output: 4119
🏁 Script executed:
# Search for log.address usage
rg -n 'log\.address' --type=ts | head -100Repository: DefiLlama/dimension-adapters
Length of output: 3881
🏁 Script executed:
cat -n fees/termmax.ts | sed -n '55,150p'Repository: DefiLlama/dimension-adapters
Length of output: 4210
🏁 Script executed:
rg -n "parseLog:\s*true" --type=ts -B 2 -A 2 | head -80Repository: DefiLlama/dimension-adapters
Length of output: 2074
🏁 Script executed:
# Check if there are any Transfer event handlers without parseLog that still access log.args
rg -n "Transfer.*event" --type=ts -A 10 | grep -A 10 "entireLog" | head -60Repository: DefiLlama/dimension-adapters
Length of output: 54
Add parseLog: true to the getLogs call at line 61.
The code accesses log.args.from (line 84) and log.args.value (lines 107, 141) but the getLogs call doesn't include parseLog: true. Without it, logs are unparsed and lack the args property, causing runtime errors. Add parseLog: true to the getLogs configuration:
Diff
const logs = await getLogs({
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
from as any,
to as any,
],
fromBlock,
toBlock,
+ parseLog: true,
entireLog: true,
noTarget: true,
});
🤖 Prompt for AI Agents
In `@fees/termmax.ts` around lines 83 - 86, The getLogs call in fees/termmax.ts
that populates the logs variable is missing parseLog: true, but later code
accesses log.args.from and log.args.value in the loop and other places (e.g.,
where froms/addresses are built and fee parsing occurs), which will throw if
logs are unparsed; update the getLogs invocation (the call that sets const logs
= await getLogs({...})) to include parseLog: true alongside the existing
entireLog and noTarget options so the logs contain the args property and
subsequent accesses to log.args.from/log.args.value succeed.
| const fetch = async (options: FetchOptions) => { | ||
| const dailyRevenue = options.createBalances(); | ||
|
|
||
| const [fromBlock, toBlock] = await Promise.all([ | ||
| options.getFromBlock(), | ||
| options.getToBlock(), | ||
| ]); | ||
| const logs = await getTransfers(options, null, TREASURY, fromBlock, toBlock); | ||
| const { dailyUserFees } = await handleLogs(options, logs); | ||
| dailyRevenue.add(dailyUserFees); | ||
|
|
||
| return { | ||
| dailyFees, | ||
| dailyUserFees, | ||
| dailyFees: dailyUserFees, | ||
| dailyRevenue, | ||
| dailyProtocolRevenue: dailyRevenue, | ||
| dailySupplySideRevenue, | ||
| } | ||
| } | ||
| }; | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the Balances class/type definition
fd -e ts -e tsx | xargs rg -l "class Balances|interface Balances|type Balances" | head -20Repository: DefiLlama/dimension-adapters
Length of output: 97
🏁 Script executed:
# Search for add method within Balances
rg -n "add\(" fees/termmax.ts -B3 -A3Repository: DefiLlama/dimension-adapters
Length of output: 1271
🏁 Script executed:
# Look for Balances definition and add method
rg -n "add\(" --type=ts -B2 -A5 | grep -A7 "Balances\|\.add" | head -100Repository: DefiLlama/dimension-adapters
Length of output: 5643
🏁 Script executed:
# Find Balances class/interface definition
rg -n "class Balances|interface Balances|type Balances" --type=ts -A15 | head -100Repository: DefiLlama/dimension-adapters
Length of output: 857
🏁 Script executed:
# Look for the add method implementation
rg -n "add\s*\(" --type=ts -B3 -A10 | grep -B5 -A10 "Balances" | head -80Repository: DefiLlama/dimension-adapters
Length of output: 4213
🏁 Script executed:
# Check the helper functions directory for Balances
fd -e ts -e tsx helpers/ | xargs rg -l "Balances" | head -20Repository: DefiLlama/dimension-adapters
Length of output: 945
🏁 Script executed:
# Search for sdk import and definition
rg -n "@defillama/sdk|from.*sdk|import.*sdk" --type=ts fees/termmax.ts | head -5Repository: DefiLlama/dimension-adapters
Length of output: 54
🏁 Script executed:
# Look for sdk.Balances or Balances class definition in node_modules or src
rg -n "class Balances|\.add\(" --type=ts -B2 -A8 | grep -B4 -A8 "add.*Balances\|Balances.*add" | head -60Repository: DefiLlama/dimension-adapters
Length of output: 3221
🏁 Script executed:
# Search for type definition of add method
rg -n "add\s*\(\s*\w+\s*:\s*Balances" --type=ts -A5Repository: DefiLlama/dimension-adapters
Length of output: 54
🏁 Script executed:
# Check capybara-perp.ts around line 26 to see what type is being used
sed -n '15,35p' open-interest/capybara-perp.tsRepository: DefiLlama/dimension-adapters
Length of output: 764
🏁 Script executed:
# Look for more context on how these Balances objects are created
rg -n "addBalances\|\.add\(" fees/termmax.ts -B2 -A2Repository: DefiLlama/dimension-adapters
Length of output: 54
🏁 Script executed:
# Search for addBalances method definition in codebase
rg -n "addBalances" --type=ts -B2 -A5 | head -80Repository: DefiLlama/dimension-adapters
Length of output: 4646
Use .addBalances() instead of .add() to merge Balances objects.
Line 159 calls dailyRevenue.add(dailyUserFees) where both are Balances objects. Throughout the codebase, the standard method for merging Balances objects is .addBalances() (see usages in helpers/gmx.ts, helpers/token.ts, helpers/compoundV2.ts, options/thales/index.ts, etc.). Update this to dailyRevenue.addBalances(dailyUserFees) for consistency.
🤖 Prompt for AI Agents
In `@fees/termmax.ts` around lines 150 - 167, The fetch function in termmax.ts
currently calls dailyRevenue.add(dailyUserFees) but both dailyRevenue and
dailyUserFees are Balances objects; replace the call with
dailyRevenue.addBalances(dailyUserFees) to use the standard Balances merge
method; update the invocation in the fetch function (where dailyRevenue and
dailyUserFees are defined and dailyRevenue is mutated) to call addBalances
instead of add for consistency with other helpers.
| const { log } = tuples[i]; | ||
| const tokens = allTokens[i]; | ||
| if (tokens && tokens[4]) { | ||
| // FT contract -> Protocol fee (valued in underlying token) |
There was a problem hiding this comment.
FT trades at a discount. The actual value at fee collection time is lower than the underlying, this would be incorrect as price could move drastic until it unlocks, so it would be wrong to price it as 1:1 even tho it's discounted, maybe we could track the fees at when it matures?
This PR is intended to replace #5301.
The previous implementation only tracked the yield generated by the vault itself, but the calculation method was incorrect.
TermMax currently has three revenue streams:
TermMax charges fees in the form of FT (Fixed-rate Token). However, DefiLlama currently does not provide an oracle for FT, so the real value of the fees would be underestimated. These FT tokens are redeemable 1:1 into the underlying asset at maturity. To simplify the calculation (since the exact underlying asset price at redemption cannot be predicted), we treat the FT’s value as the underlying asset price at the time the fee is charged. Because FT trades at a discount, this approach still underestimates the fee value, but it is closer to the real fee value than treating FT as having zero value (as before).
If a borrower cannot repay the debt before maturity, the liquidation penalty at maturity will also flow into the treasury in the form of collateral, which is also revenue for TermMax.
The vault’s idle funds are deployed into pools and parked on Aave / Morpho to earn yield. After that yield is distributed according to TermMax’s documented rules, a designated wallet transfers TermMax’s allocated share of the revenue into the treasury.
Summary by CodeRabbit