back to docs
contributions

Provenance Chain

Track Provenance Chain fee/revenue

My fifth contribution to DefiLlama, tracking chain-level fees and revenue for Provenance blockchain.

  • Protocol: Provenance Blockchain
  • Type: Chain-level fees
  • Start Date: 2021-05-06 (Chain genesis)
  • Approach: API-based historical data with USD price fallback logic
Status

Status: Merged

PR: #5369

Merged: December 27, 2024

Issue: #4838

1. Background story

a. After Variational

After Variational where maintainer couldn’t checkout my master branch, I was curious if that would be an issue again. This time I still used my master branch, but it worked fine. The maintainer could merge without problems.

Interesting: The git workflow issue in Variational might have been specific to that situation, not a hard rule.

b. Finding Provenance

I found issue #4838 requesting tracking for Provenance blockchain:

  • Chain-level fees (like Taiko, my first contribution!)
  • API endpoint: https://service-explorer.provenance.io/api/v3/txs/history
  • Revenue model: 40% to HASH Market auction pool, 60% to validators

This felt familiar, similar to my first contribution but with API instead of events.

2. My implementation

a. The code

import { FetchOptions, ProtocolType, SimpleAdapter } from "../adapters/types";
import { CHAIN } from "../helpers/chains";
import { httpGet } from "../utils/fetchURL";

interface ITxHistoryItem {
  date: string;
  feepayer: null | string;
  txCount: number;
  feeAmountInBaseToken: number;
  gasWanted: number;
  gasUsed: number;
  feeAmountInToken: number;
  feesPaidInUsd: number | null;  // ← Can be null!
  maxTokenPriceUsd: number | null;
  minTokenPriceUsd: number | null;
  avgTokenPriceUsd: number | null;  // ← Can be null!
}

const PROVENANCE_API_BASE =
  "https://service-explorer.provenance.io/api/v3/txs/history";

const getPreviousDate = (dateString: string): string => {
  const date = new Date(dateString + "T00:00:00Z");
  date.setUTCDate(date.getUTCDate() - 1);
  return date.toISOString().split("T")[0];
};

const fetch = async (_t: number, _b: any, options: FetchOptions) => {
  // Fetch 2 days of data to support fallback to previous day's price
  const prevDate = getPreviousDate(options.dateString);
  const url = `${PROVENANCE_API_BASE}?fromDate=${prevDate}&toDate=${options.dateString}&granularity=DAY`;
  const data: ITxHistoryItem[] = await httpGet(url);

  const dayData = data.find((item: ITxHistoryItem) => {
    const itemDate = item.date.split("T")[0];
    return itemDate === options.dateString;
  });

  if (!dayData) {
    throw new Error(`No data found for ${options.dateString}`);
  }

  let dailyFees: number;

  // Fallback logic for USD price
  if (dayData.feesPaidInUsd !== null) {
    // Best case: API provides USD value directly
    dailyFees = dayData.feesPaidInUsd;
  } else if (dayData.avgTokenPriceUsd !== null) {
    // Fallback 1: Use today's average price
    dailyFees = dayData.feeAmountInToken * dayData.avgTokenPriceUsd;
  } else {
    // Fallback 2: Use previous day's average price
    const prevDateString = getPreviousDate(options.dateString);
    const prevDayData = data.find((item: ITxHistoryItem) => {
      const itemDate = item.date.split("T")[0];
      return itemDate === prevDateString;
    });

    if (prevDayData?.avgTokenPriceUsd) {
      dailyFees = dayData.feeAmountInToken * prevDayData.avgTokenPriceUsd;
    } else {
      throw new Error(`No USD price data available for ${options.dateString}`);
    }
  }

  return {
    dailyFees,
    dailyRevenue: dailyFees * 0.4, // 40% to HASH Market auction pool
  };
};

const methodology = {
  Fees: "Transaction fees paid by users on Provenance blockchain in HASH tokens, converted to USD.",
  Revenue: "40% of transaction fees go to HASH Market auction pool (protocol revenue). 60% goes to validators.",
};

const adapter: SimpleAdapter = {
  adapter: {
    [CHAIN.PROVENANCE]: {
      fetch,
      start: "2021-05-06",  // Chain genesis
    },
  },
  protocolType: ProtocolType.CHAIN,
};

b. Key design decisions

1. Fallback price logic

The API can return null for price data, so I implemented a 3-tier fallback:

1. Use feesPaidInUsd (if available)
2. Use avgTokenPriceUsd * feeAmountInToken (if price available)
3. Use previous day's avgTokenPriceUsd * feeAmountInToken
4. Throw error if no price data available

This handles cases where price oracles might be temporarily unavailable.

2. Fetch 2 days of data

const prevDate = getPreviousDate(options.dateString);
const url = `${PROVENANCE_API_BASE}?fromDate=${prevDate}&toDate=${options.dateString}&granularity=DAY`;

Why? To have previous day’s price available as fallback.

3. Revenue split

dailyRevenue: dailyFees * 0.4  // 40% to protocol

Revenue model:

  • 40% → HASH Market auction pool (protocol revenue)
  • 60% → Validators (supply side)

From my Solomon learning, I knew:

  • dailyRevenue = what protocol earns (40%)
  • Should also track dailySupplySideRevenue = validators (60%)

But I only returned dailyRevenue. Did I make a mistake?

4. Chain addition

I had to add Provenance to the chains enum:

// helpers/chains.ts
PROVENANCE = "provenance",

This was my first time modifying the core chains file!

c. My comment

Like before, I asked for validation:

“If this interpretation is incorrect, please let me know. Thank you.”

Learning to be proactive about feedback.

3. The CI issue

a. Test failure

llamabutler ran tests and got an error:

Request failed with status code 502

502 = Bad Gateway. The Provenance API endpoint was down or having issues!

This wasn’t a code problem. It was an external API availability issue.

b. Maintainer response

Despite the CI failure, noateden:

  • Self-assigned the PR (December 26)
  • Merged it (December 27)

The merge happened even though “1 of 2 checks passing”. The maintainer trusted the code despite temporary API issues.

4. What this shows

a. Code quality improvements

The maintainer merged despite CI failure because:

  • Code structure was correct
  • Error handling was proper
  • Fallback logic was sound
  • The 502 error was external, not code-related

Trust earned: My code quality was good enough that maintainers could see past temporary API issues.

b. Git workflow: master branch again

Interesting observation: I still used my master branch (like Variational), but this time:

  • Maintainer could checkout and work with it
  • No complaints about branch structure
  • Merged successfully

Lesson refined: The Variational issue might not have been about “never use master”, but rather a specific technical issue that time. Master branch CAN work if there are no conflicts or checkout problems.

c. Domain knowledge application

From previous contributions, I correctly:

  • Revenue taxonomy (dailyRevenue = protocol share)
  • SimpleAdapter pattern
  • Chain-level adapter structure
  • API-based fetching with proper error handling

5. Potential improvement: supply side revenue

Looking at my code, I only returned:

return {
  dailyFees,
  dailyRevenue: dailyFees * 0.4,  // Protocol (40%)
};

What I could have added:

return {
  dailyFees,
  dailyRevenue: dailyFees * 0.4,          // Protocol (40%)
  dailySupplySideRevenue: dailyFees * 0.6, // Validators (60%)
};

This would show:

  • 40% goes to HASH Market (protocol)
  • 60% goes to validators (supply side)

Similar to how I learned in Solomon that fees can split between protocol and supply side.

Why didn’t I add it? Probably focused on matching the issue requirements which mentioned “Fee/Revenue” but not explicitly supply side breakdown.

6. Technical highlights

a. Date handling

const getPreviousDate = (dateString: string): string => {
  const date = new Date(dateString + "T00:00:00Z");  // ← UTC timezone
  date.setUTCDate(date.getUTCDate() - 1);
  return date.toISOString().split("T")[0];
};

Proper UTC handling to avoid timezone issues!

b. Type safety

interface ITxHistoryItem {
  feesPaidInUsd: number | null;  // ← Explicitly nullable
  avgTokenPriceUsd: number | null;
  // ...
}

TypeScript interfaces that match API reality (null values possible).

c. Defensive programming

if (!dayData) {
  throw new Error(`No data found for ${options.dateString}`);
}

if (prevDayData?.avgTokenPriceUsd) {  // ← Optional chaining
  dailyFees = dayData.feeAmountInToken * prevDayData.avgTokenPriceUsd;
} else {
  throw new Error(`No USD price data available for ${options.dateString}`);
}

Clear error messages and safe optional chaining.

7. Comparison with previous contributions

a. Growth progression:

  1. Taiko (Event-based chain fees)

    • Reviewer helped with calculations
    • Merged
  2. Bitfi (Multi-protocol)

    • Protocol slug confusion
    • Research used
  3. Solomon (API + revenue taxonomy)

    • Revenue classification learned
    • Merged after fixes
  4. Variational (Git workflow)

    • Workflow issue, code good
    • Used in new PR
  5. Provenance (API + chain fees + fallback logic)

    • Merged despite CI failure
    • Git workflow correct
    • Code quality trusted

b. Key evolution:

ContributionMain ChallengeResolutionTrust Level
TaikoFee calculationReviewer fixedLearning
BitfiProtocol structureMaintainer refactoredContributing
SolomonRevenue taxonomyFixed after feedbackImproving
VariationalGit workflowCode reusedCode trusted
ProvenanceExternal API failureMerged anywayQuality trusted

8. Resources

9. Key takeaways

a. What I did right:

  • Proper git workflow (feature branch)
  • Defensive programming (3-tier fallback logic)
  • Type safety (proper nullable types)
  • Error handling (clear error messages)
  • Documentation (clear methodology)
  • Date handling (UTC timezone awareness)

b. What went well:

  • Merged despite CI failure - maintainer trusted code quality
  • No code review needed - structure was solid
  • Applied all previous learnings successfully

c. Potential enhancement:

// Could add:
dailySupplySideRevenue: dailyFees * 0.6  // Validators' 60%

Show the complete revenue split, not just protocol share.

d. The milestone:

First contribution merged without code changes required!

Previous contributions:

  • Taiko: Reviewer fixed calculation
  • Bitfi: Maintainer refactored
  • Solomon: Fixed after feedback
  • Variational: Workflow issue
  • Provenance: Merged as-is

This marks a turning point. Code quality is now consistently good enough to merge without modifications!

10. Lessons learned

a. External dependencies

502 errors are not always your fault!

When API endpoints fail:

  • It’s not necessarily code issues
  • Maintainers can see the difference
  • Good error handling helps diagnose external issues

b. Fallback logic is valuable

The 3-tier price fallback showed:

  • Thinking ahead about edge cases
  • Handling data unavailability gracefully
  • Production-ready mindset

c. Trust is earned

Being able to merge despite CI failure shows:

  • Consistent quality builds trust
  • Maintainers can evaluate code independently
  • Past contributions create credibility

d. Git workflow clarification

Updated understanding:

  • Master branch worked fine for Provenance
  • Variational’s issue might have been specific/temporary
  • Feature branches are recommended but not always mandatory
  • What matters more: code quality + clean commit history

11. Final thoughts

Provenance marks a milestone: This is the first contribution that was merged without any code changes required.

The journey:

  1. Taiko: Needed calculation fix
  2. Bitfi: Full refactor
  3. Solomon: Structure + concept fixes
  4. Variational: Workflow issue (code was good)
  5. Provenance: Merged as-is

Each contribution built on previous learnings:

  • Technical patterns (SimpleAdapter, API fetching)
  • Domain knowledge (revenue taxonomy, chain fees)
  • Process (git workflow, error handling)
  • Communication (asking for feedback)

The 502 error during CI didn’t stop the merge because the code quality spoke for itself. That’s the goal. Write code good enough that maintainers trust it even when external factors cause test failures.

Milestone Achieved

Finally! After four contributions with various issues:

  • Taiko: Needed calculation fix
  • Bitfi: Full refactor by maintainer
  • Solomon: Revenue taxonomy corrections
  • Variational: Git workflow closed

Provenance: Merged as-is. No corrections. No refactoring. No issues.

This is what consistent learning looks like. Each mistake taught something. Each feedback improved the next attempt. From needing heavy fixes to being trusted enough to merge despite external CI failures.

Not perfect, but ready. That’s progress!

Command Palette
Search for a command to run...