back to docs
contributions

Bitfi BTC & Bitfi Basis

Track Bitfi BTC and Bitfi Basis fee/revenue

My second contribution to DefiLlama, fee/revenue adapters for Bitfi’s bfBTC and bfUSD (Basis) across multiple blockchain networks.

  • Protocols: Bitfi BTC, Bitfi Basis
  • Type: Protocol-level fees (multi-chain)
  • Start Date: Varies by chain (2024-09-17 earliest)
  • Approach: Event-based fee tracking with getLogs
Status

Status: Closed (Refactored by maintainer)

PR: #4917

Refactored PR: #5124 Merged

Closed: December 16, 2024

1. Background story

a. Gaining confidence

After my first contribution (Taiko) merged, I wanted to prove I could implement adapters independently without needing corrections. I saw an issue asking to track Bitfi fees/revenue.

b. The confusion

I looked at DefiLlama and saw: https://defillama.com/protocol/bitfi

It showed one protocol page called “Bitfi” with combined TVL and stats. Naturally, I thought: “This is one protocol called Bitfi”.

I started researching and found Bitfi had multiple products:

  • bfBTC (Bitfi Bitcoin) - Withdrawal fees on EVM chains
  • bfUSD (Bitfi Basis) - Stablecoin with redemption fees

But since the DefiLlama page showed them as one combined “Bitfi” protocol, I assumed I should track them together in one adapter. The UI made it look like one unified protocol!

c. The reality check

After I submitted my implementation, the reviewer pointed out something I missed:

“can you also add support for basis? https://defillama.com/protocol/bitfi-basis”

Wait… there’s a separate page for Bitfi Basis?

I then realized DefiLlama actually has three protocol pages:

  1. bitfi - https://defillama.com/protocol/bitfi (combined/aggregated view)
  2. bitfi-btc - https://defillama.com/protocol/bitfi-btc (bfBTC only)
  3. bitfi-basis - https://defillama.com/protocol/bitfi-basis (bfUSD only)

The “Bitfi” page I initially found was showing combined/aggregated data from both bitfi-btc and bitfi-basis. But in the backend, they need two separate adapters with their own slugs.

This was confusing because:

  • The combined “bitfi” page makes it look like one protocol
  • But the actual data tracking requires two separate implementations
  • Each product has its own dedicated protocol page (bitfi-btc, bitfi-basis)
  • The main “bitfi” page just aggregates data from both

2. Initial implementation

a. My original code structure

Since I thought “Bitfi” was one protocol (based on the combined UI page), I built one adapter that tracked both bfBTC and bfUSD together. It made sense to me: “They’re all under the ‘Bitfi’ protocol, so one adapter file should handle everything.”

// Separate configs for bfBTC and bfUSD
const bfBTCConfigs: Record<string, ChainConfig> = {
  [CHAIN.ETHEREUM]: { token: '0x...', start: '2025-02-06' },
  [CHAIN.BSC]: { token: '0x...', start: '2025-02-06' },
  // ... more chains
}

const bfUSDConfig: Record<string, BfUSDConfig> = {
  [CHAIN.ETHEREUM]: {
    token: '0x...',
    instantRedeemer: '0x...',
    start: '2025-02-06'
  },
}

// Separate fetch functions
async function fetchBfBTC(options: FetchOptions): Promise<FetchResultV2> {
  // Track FeeCollected events
  const feeCollectedLogs = await options.getLogs({
    target: token,
    eventAbi: 'event FeeCollected(...)',
  })

  for (const log of feeCollectedLogs) {
    dailyFees.add(token, log.amount, METRIC.DEPOSIT_WITHDRAW_FEES)
  }
}

async function fetchBfUSD(options: FetchOptions): Promise<FetchResultV2> {
  // Track CrossChainFeeCollected and InstantRedemption events
}

// Special handler for Ethereum (both bfBTC and bfUSD)
async function fetchEthereum(options: FetchOptions): Promise<FetchResultV2> {
  // Manually combine both protocols' fees
}

// Complex adapter structure
const adapter: Adapter = {
  adapter: {
    [CHAIN.ETHEREUM]: {
      fetch: fetchEthereum,  // Special combined handler
      start: '2025-02-06',
    },
    ...Object.fromEntries(
      Object.entries(bfBTCConfigs)
        .filter(([chain]) => chain !== CHAIN.ETHEREUM)
        .map(([chain, config]) => [
          chain,
          { fetch: fetchBfBTC, start: config.start }
        ])
    ),
  },
  version: 2,
}

b. What I thought

I was confident this was the right approach because:

  • The DefiLlama UI showed “Bitfi” as one protocol
  • Both bfBTC and bfUSD were clearly Bitfi products
  • Having them in one file matched the unified “Bitfi” branding
  • The special fetchEthereum function elegantly combined both on Ethereum
  • It used Object.fromEntries to dynamically generate chain configs
  • It felt organized and “DRY” (Don’t Repeat Yourself)

I thought: “This makes sense. One protocol page on DefiLlama = one adapter file in the codebase.”

I didn’t realize: The UI’s combined view was just for presentation. Backend tracking requires separate adapters for separate protocol slugs.

3. The problems

a. Wrong start dates

Issue: All chains were set to start: '2025-02-06'

Reality: Each contract was deployed on different dates:

  • Ethereum bfBTC: 2024-09-17 (much earlier!)
  • BSC: 2024-12-26
  • Base: 2025-05-21
  • And so on…

Impact: This would prevent historical data backfill, one of DefiLlama’s core features.

Reviewer feedback from treeoflife2:

“i can see you have added start date as 2025-02-06, is there anyway to refill for full history for older version? lmk if you need any help”

b. Wrong fee calculation

Issue: I used log.amount as the total fee

for (const log of feeCollectedLogs) {
  dailyFees.add(token, log.amount, METRIC.DEPOSIT_WITHDRAW_FEES)
}

Reality: The FeeCollected event structure is:

event FeeCollected(
  address indexed user,
  uint8 indexed feeType,
  uint256 id,
  uint256 amount,        // ← This is the TOTAL withdrawal amount
  uint256 percentageFee, // ← This is the percentage fee
  uint256 fixedFee       // ← This is the fixed fee
)

The correct calculation:

const totalFee = log.percentageFee + log.fixedFee  // Fee = percentage + fixed
dailyFees.add(token, totalFee, METRIC.DEPOSIT_WITHDRAW_FEES)

Impact: I was tracking withdrawal amounts instead of actual fees! This would massively inflate the fee statistics.

c. Misunderstanding protocol slugs

Issue: I combined both products in one file because I saw one “Bitfi” page on DefiLlama UI

Reality: DefiLlama has three protocol pages:

  • bitfi → combined/aggregated view of both products
  • bitfi-btc → tracks bfBTC only (needs its own adapter)
  • bitfi-basis → tracks bfUSD only (needs its own adapter)

Why they’re separate:

  • Two adapters needed: one for bitfi-btc, one for bitfi-basis
  • The bitfi page automatically aggregates data from both
  • Each product needs independent fees/revenue tracking
  • Users can look up each protocol individually OR see combined stats

What I learned:

  • UI presentation ≠ backend structure
  • One brand can have:
    • Individual protocol pages (bitfi-btc, bitfi-basis) ← need adapters
    • Combined aggregated page (bitfi) ← auto-generated from the two adapters
  • Always search for variations: protocol/{name}, protocol/{name}-*

The confusion came from:

  • Finding https://defillama.com/protocol/bitfi first (the combined page)
  • Not realizing there were separate pages for bitfi-btc and bitfi-basis
  • Assuming I should create one adapter because I saw one page
  • Not understanding that the “bitfi” page is just an aggregation layer

Maintainer action: Created two separate files (bitfi-btc.ts and bitfi-basis.ts) for two separate protocol slugs

d. Incomplete start date research

Issue: I used a placeholder date (2025-02-06) for all chains

What I should have done: Check contract deployment dates on block explorers:

Impact: Wrong start dates = incomplete historical data

4. The fix

a. Maintainer’s refactoring

Instead of asking me to fix multiple issues, treeoflife2 closed my PR and created a new one (#5124) with this comment:

“I’ve refactored and created new PR as can’t edit this one.”

This was actually kind. He saw that:

  1. I correctly identified both protocols and their events
  2. I found the right contract addresses
  3. The core logic was sound
  4. But the execution (combined file, wrong calculations, wrong dates) needed significant refactoring

Rather than making me go through multiple revision cycles, he cleaned it up himself. My research and initial structure still contributed to the final implementation.

b. Key changes in final implementation

Split into two files

bitfi-btc.ts (bfBTC only):

const chainConfig: Record<string, config> = {
  [CHAIN.ETHEREUM]: {
    token: '0xCdFb58c8C859Cb3F62ebe9Cf2767F9e036C7fb15',
    start: '2024-09-17',  // ← Correct date
  },
  // ... other chains with correct dates
}

async function fetch(options: FetchOptions): Promise<FetchResultV2> {
  const { token } = chainConfig[options.chain]

  const feeCollectedLogs = await options.getLogs({
    target: token,
    eventAbi: 'event FeeCollected(...)',
  })

  for (const log of feeCollectedLogs) {
    const totalFee = log.percentageFee + log.fixedFee  // ← Fixed calculation
    dailyFees.add(token, totalFee, METRIC.DEPOSIT_WITHDRAW_FEES)
  }

  return {
    dailyFees,
    dailyUserFees: dailyFees,
    dailyRevenue: dailyFees,
    dailyProtocolRevenue: dailyFees,
    dailySupplySideRevenue: 0,  // ← Explicit zero
  }
}

const adapter: Adapter = {
  version: 2,
  fetch,
  adapter: chainConfig,  // ← Simple, direct mapping
  methodology: {
    Fees: 'Withdrawal fees from bfBTC (on EVM chains and Bitcoin network) paid by users.',
    UserFees: 'Withdrawal fees from bfBTC (on EVM chains and Bitcoin network) paid by users.',
    Revenue: 'All fees collected are sent to BitFi protocol fee receiver address.',
    ProtocolRevenue: 'All fees collected are sent to BitFi protocol fee receiver address.',
    SupplySideRevenue: 'No withdrawal fees go to supply side. bfBTC holders earn yield through exchange rate appreciation from strategies.'
  },
}

bitfi-basis.ts (bfUSD only):

const bfUSDConfig: Record<string, BfUSDConfig> = {
  [CHAIN.ETHEREUM]: {
    token: '0xa3eB7A9e57FCa4e40b79E394eD5eB37fEd205A24',
    instantRedeemer: '0x0971cB672b4eF3E19284Aa64717aFb154A6fbeDF',
    start: '2025-11-03'  // ← Correct date
  },
}

const fetch = async (options: FetchOptions) => {
  const { token, instantRedeemer } = bfUSDConfig[options.chain]

  // Cross-chain transfer fees
  const crossChainFeeLogs = await options.getLogs({
    target: token,
    eventAbi: 'event CrossChainFeeCollected(address indexed user, uint256 amount, uint256 fee)',
  })

  for (const log of crossChainFeeLogs) {
    dailyFees.add(token, log.fee, METRIC.DEPOSIT_WITHDRAW_FEES)
  }

  // Instant redemption fees
  const instantRedemptionLogs = await options.getLogs({
    target: instantRedeemer,
    eventAbi: 'event InstantRedemption(..., uint256 feeAmount)',
  })

  for (const log of instantRedemptionLogs) {
    dailyFees.add(token, log.feeAmount, METRIC.MINT_REDEEM_FEES)
  }

  return {
    dailyFees,
    dailyUserFees: dailyFees,
    dailyRevenue: dailyFees,
    dailyProtocolRevenue: dailyFees,
  }
}

const adapter: Adapter = {
  version: 2,
  fetch,
  chains: [CHAIN.ETHEREUM],  // ← Simple array, not complex mapping
  start: '2025-11-02',
  methodology: {
    Fees: 'Instant redemption and cross-chain transfer fees from bfUSD paid by users.',
    UserFees: 'Instant redemption and cross-chain transfer fees from bfUSD paid by users.',
    Revenue: 'All fees collected are sent to BitFi protocol fee receiver address.',
    ProtocolRevenue: 'All fees collected are sent to BitFi protocol fee receiver address.',
  },
}

Key improvements:

  1. Separation of Concerns: Two protocols → two files
  2. Correct Fee Calculation: percentageFee + fixedFee instead of amount
  3. Accurate Start Dates: Researched from block explorers
  4. Simple Adapter Structure: Direct config mapping, no Object.fromEntries complexity
  5. Clear Methodology: Specific explanations for each protocol’s fee structure
  6. Supply Side Revenue: Explicitly set to 0 with explanation for bfBTC

5. Key learnings

a. Technical

  • Read Event Structures Carefully: I assumed amount was the fee, but it was actually the withdrawal amount. The actual fees were in separate fields (percentageFee + fixedFee).

  • Verify Start Dates: Never use placeholder dates. Always check block explorers for actual contract deployment timestamps.

  • Understand Protocol Slugs: One brand (Bitfi) can have multiple protocol pages:

    • Aggregated page (bitfi) - auto-generated, no adapter needed
    • Individual pages (bitfi-btc, bitfi-basis) - each needs its own adapter
  • UI ≠ Backend Structure: Combined views are just aggregation layers. The actual adapters track individual protocol slugs.

  • Simple > Smart: Object.fromEntries mapping felt smart, but direct config mapping is clearer and more maintainable.

  • Supply Side Revenue Matters: Always consider if there’s supply-side revenue. For bfBTC, explicitly noting it’s zero (holders earn through exchange rate appreciation) provides clarity.

b. Process

  • Verify Protocol Structure First: Before coding, check if a brand has multiple protocol slugs. Search for https://defillama.com/protocol/{brand-name}* variations.

  • Don’t Assume from UI: The UI might show aggregated/combined views for user convenience, but backend tracking follows protocol slug structure.

  • Confidence ≠ Correctness: I was overconfident after my first successful contribution. Being eager to prove independence made me skip verification steps.

  • Don’t Rush Research: I should have spent more time understanding:

    • Event structures (which fields are the actual fees?)
    • Contract deployment dates (not placeholder dates)
    • Protocol slug structure (are there multiple pages?)
  • Maintainers Will Help (Again): Even when I got multiple things wrong, the maintainer didn’t reject it outright. He refactored it himself. This is collaboration, not failure.

  • Close ≠ Rejection: My PR was closed, but my research and initial structure still contributed. The maintainer built on my work rather than starting from scratch.

c. Personal growth

  • Humility: I wrote in the PR that I was “too arrogant and overconfident”. This was a valuable lesson in staying humble and thorough.

  • Learning from Mistakes: Getting it wrong and seeing the corrections taught me more than getting it right the first time would have.

  • Contribution Still Valuable: Even though my code wasn’t used directly, I still helped by:

    • Identifying that Bitfi has two separate products (bfBTC and bfUSD)
    • Researching both protocols’ fee structures
    • Finding all contract addresses across multiple chains
    • Identifying the correct events to track
    • Drafting the initial structure
  • Question Your Assumptions: I assumed “one UI page = one adapter” without verifying. I should have searched for other Bitfi-related protocol pages first.

6. Comparison: then vs now

AspectMy ImplementationFinal Implementation
File StructureOne file, combined logicTwo separate files
Fee Calculationlog.amount (wrong)log.percentageFee + log.fixedFee (correct)
Start DatesAll 2025-02-06 (wrong)Researched actual dates
Adapter PatternComplex Object.fromEntriesSimple config mapping
Protocol UnderstandingOne combined “Bitfi” page = one adapterThree pages: bitfi (aggregated), bitfi-btc (adapter), bitfi-basis (adapter)
Ethereum HandlingSpecial fetchEthereum function combining bothSeparate files, no special case
Supply Side RevenueNot addressedExplicitly set to 0 with explanation

7. Resources

8. Final thoughts

This contribution was more valuable as a learning experience than my first successful one. Getting it wrong and seeing how it should be done taught me:

  1. Check protocol slug structure first - don’t assume from UI. Search for protocol/{name}* variations on DefiLlama.
  2. Always verify event structures - don’t assume field names. Read the actual event parameters carefully.
  3. Research deployment dates - block explorers are your friend. Never use placeholder dates.
  4. UI ≠ Backend - aggregated views don’t mean combined tracking. Each slug needs its own adapter.
  5. Test edge cases - don’t just test that it runs, verify the data makes sense.
  6. Stay humble - confidence is good, overconfidence leads to mistakes.

a. What I learned

I’m grateful treeoflife2 took the time to refactor rather than just reject. Seeing the corrected version taught me:

  1. Protocol slugs ≠ brand names - one brand can have multiple protocol pages
  2. Aggregated pages exist - combined views like bitfi auto-aggregate from individual pages
  3. Adapters track individual slugs - create adapters for bitfi-btc and bitfi-basis, not bitfi
  4. Verify before assuming - search for protocol/{name}* variations before starting
  5. Simple separate files beat “clever” combined implementations
  6. Event field names matter - amount ≠ fee

b. For next time

Before implementing any protocol adapter, I’ll:

1
Search for all protocol pages

Search for https://defillama.com/protocol/{name}* to find all related pages and variations.

2
Identify aggregated vs individual slugs

Determine which pages are aggregated (combined views) vs individual protocol slugs.

3
Understand structure
  • Aggregated pages (e.g., bitfi) = no adapter needed, auto-generated
  • Individual pages (e.g., bitfi-btc, bitfi-basis) = need separate adapters
4
Read event structures carefully

Check block explorer to understand the exact event parameters and field names.

5
Check actual deployment dates

Verify deployment dates from block explorer, don’t use placeholder dates.

6
Create one adapter per slug

Create one adapter per individual protocol slug, not for aggregated pages.

The key lesson: Don’t let aggregated UI pages fool you. DefiLlama can show combined views for user convenience, but adapters track individual protocol slugs. Always verify the protocol structure first:

  • Aggregated page = auto-generated from multiple adapters
  • Individual pages = each needs its own adapter
Command Palette
Search for a command to run...