Skip to content

feat: add hermetica hbtc token and adapter#18479

Open
anch09 wants to merge 7 commits intoDefiLlama:mainfrom
hermetica-fi:hermetica-hbtc
Open

feat: add hermetica hbtc token and adapter#18479
anch09 wants to merge 7 commits intoDefiLlama:mainfrom
hermetica-fi:hermetica-hbtc

Conversation

@anch09
Copy link
Copy Markdown
Contributor

@anch09 anch09 commented Mar 18, 2026

NOTE

Please enable "Allow edits by maintainers" while putting up the PR.


  • If you would like to add a volume/fees/revenue adapter please submit the PR here.
  • If you would like to add a liquidations adapter, please refer to this readme document for details.
  1. Once your adapter has been merged, it takes time to show on the UI. If more than 24 hours have passed, please let us know in Discord.
  2. Sorry, We no longer accept fetch adapter for new projects, we prefer the tvl to computed from blockchain data, if you have trouble with creating a the adapter, please hop onto our discord, we are happy to assist you.
  3. Please fill the form below only if the PR is for listing a new protocol else it can be ignored/replaced with reason/details about the PR
  4. For updating listing info It is a different repo, you can find your listing in this file: https://github.com/DefiLlama/defillama-server/blob/master/defi/src/protocols/data2.ts, you can edit it there and put up a PR
  5. Do not edit/push package-lock.json file as part of your changes, we use lockfileVersion 2, and most use v1 and using that messes up our CI
  6. No need to go to our discord and announce that you've created a PR, we monitor all PRs and will review it asap

Name (to be shown on DefiLlama):
  • hBTC
Twitter Link:
List of audit links if any:
Website Link:
Logo (High resolution, will be shown with rounded borders):
Current TVL:
  • $10.57M (9.49M USDh and 1.08M hBTC)
Treasury Addresses (if the protocol has treasury)
Chain:
  • Stacks
Coingecko ID (so your TVL can appear on Coingecko, leave empty if not listed): (https://api.coingecko.com/api/v3/coins/list)
Coinmarketcap ID (so your TVL can appear on Coinmarketcap, leave empty if not listed): (https://api.coinmarketcap.com/data-api/v3/map/all?listing_status=active,inactive,untracked&start=1&limit=10000)
Short Description (to be shown on DefiLlama):
  • Institutional-grade Bitcoin yield
Token address and ticker if any:
  • SP1S1HSFH0SQQGWKB69EYFNY0B1MHRMGXR3J1FH4D.token-hbtc
  • hBTC
Category (full list at https://defillama.com/categories) *Please choose only one:
  • Yield
Oracle Provider(s): Specify the oracle(s) used (e.g., Chainlink, Band, API3, TWAP, etc.):
Implementation Details: Briefly describe how the oracle is integrated into your project:
Documentation/Proof: Provide links to documentation or any other resources that verify the oracle's usage:
forkedFrom (Does your project originate from another project):
methodology (what is being counted as tvl, how is tvl being calculated):
  • TVL is calculated as total hBTC minted on Stacks multiplied by the current share price, expressed in BTC.
Github org/user (Optional, if your code is open source, we can track activity):
Does this project have a referral program?

Summary by CodeRabbit

  • New Features

    • Added support for hBTC token on Stacks.
    • Added TVL calculation for Hermetica hBTC protocol.
  • Improvements

    • Simplified and optimized TVL data fetching and parsing for Hermetica protocols on Stacks, improving reliability and performance.
    • Minor manifest/asset map updates to include the new hBTC entry.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a new Stacks TVL adapter for Hermetica hBTC and updated token mappings; refactored the Hermetica module to remove custom Clarity parsing and to use concurrent contract calls for supply/share-price retrievals.

Changes

Cohort / File(s) Summary
Token configuration
projects/helper/coreAssets.json
Added "hBTC": "SP1S1HSFH0SQQGWKB69EYFNY0B1MHRMGXR3J1FH4D.token-hbtc" to the stacks map and converted USDh entry to a non-terminal line (added trailing comma).
New hBTC adapter
projects/hermetica-hbtc/index.js
Added a new Stacks TVL adapter exporting methodology and flags; stacks.tvl concurrently reads total supply and share price from two contracts, normalizes micro-denominated values, multiplies them, and returns { bitcoin: <value> }.
Hermetica refactor
projects/hermetica/index.js
Removed custom parseClarityInt helper; replaced sequential Clarity parsing and HTTP patterns with call-based reads and Promise.all concurrency, simplified numeric parsing and updated imports (dropped post, added call).

Sequence Diagram(s)

sequenceDiagram
    participant Adapter as Hermetica-hBTC Adapter
    participant SDK as Stacks SDK (`call`)
    participant HB as hBTC Contract
    participant HS as hBTC State Contract

    Adapter->>SDK: call(get-total-supply) to HB
    Adapter->>SDK: call(get-share-price) to HS
    par Concurrent responses
        SDK-->>HB: returns totalSupply (micro-units)
        SDK-->>HS: returns sharePrice (micro-units)
    end
    Adapter->>Adapter: normalize values (/ 10**8) and compute TVL = totalSupply * sharePrice
    Adapter-->>Client: return { bitcoin: <value> }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped to the ledger, nose in the code,
Two calls in the meadow, both lighten my load,
Supplies and share-prices—together they play,
I multiply microbits and send Bitcoin away.
🌱 A tiny hop, a tidy refactor—cheers to the day!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding a new hBTC token entry to the coreAssets and introducing a corresponding adapter module.
Description check ✅ Passed The description follows the repository template and provides all required information for a new protocol listing, including name, links, chain, token address, category, and methodology.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica exports TVL:

stacks                    9.06 M
bitcoin                   426.56 k

total                    9.48 M 

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica-hbtc exports TVL:

stacks                    965.47 k

total                    965.47 k 

@anch09 anch09 marked this pull request as ready for review March 19, 2026 15:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
projects/hermetica/stacks-call.js (2)

13-20: Potential precision loss when converting large integers to Number.

The function converts the result to Number which can only safely represent integers up to 2^53 - 1. For token supplies with 8 decimals, this limits accurate representation to ~90 million tokens. If hBTC or USDh supplies exceed this, you'll get precision errors.

Consider returning the string representation or using BigInt throughout the calculation chain, or document this limitation if the expected supplies are well under this threshold.

💡 Option: Return BigInt and handle downstream
 async function makeReadOnlyContractCall({ contract, function_name }) {
   const [contract_address, contract_name] = contract.split('.');
   const response = await post(
     `https://api.mainnet.hiro.so/v2/contracts/call-read/${contract_address}/${contract_name}/${function_name}`,
     { sender: contract_address, arguments: [] }
   );
-  return Number(parseClarityInt(response.result));
+  return BigInt(parseClarityInt(response.result));
 }

Then handle BigInt division in the callers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/hermetica/stacks-call.js` around lines 13 - 20, The
makeReadOnlyContractCall function currently converts the Clarity integer result
to a JavaScript Number causing precision loss for large token supplies; instead,
preserve full precision by returning either the raw string from parseClarityInt
or a BigInt (e.g., BigInt(parseClarityInt(...))) and update callers to handle
string/BigInt arithmetic (or document the Number limitation). Locate
makeReadOnlyContractCall and replace the
Number(parseClarityInt(response.result)) conversion with a non-lossy return
value (string or BigInt) and ensure downstream code that consumes
makeReadOnlyContractCall (call sites) performs appropriate BigInt or
string-based division/formatting.

3-11: Consider adding input validation for robustness.

The parseClarityInt function will fail with unclear errors if response.result from the API is undefined or malformed (e.g., when the contract call fails). This could happen if the Hiro API returns an error response structure.

♻️ Proposed defensive check
 function parseClarityInt(hexString) {
+  if (!hexString || typeof hexString !== 'string') {
+    throw new Error(`Invalid Clarity response: expected hex string, got ${typeof hexString}`);
+  }
   let hex = hexString.startsWith("0x") ? hexString.slice(2) : hexString;
   let numberHex = hex.slice(4);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/hermetica/stacks-call.js` around lines 3 - 11, The parseClarityInt
function lacks input validation and will throw on undefined/malformed input;
update parseClarityInt to first validate that its hexString parameter is a
non-empty string (and that response.result is present where it's called), then
guard the "0x" check and slicing operations, and wrap the BigInt conversion in a
try/catch to throw or return a descriptive error/value when parsing fails;
reference the parseClarityInt function name and the BigInt conversion/overflow
check branches to add these guards and clear error messages.
projects/hermetica-hbtc/index.js (1)

9-9: Methodology description could be more precise.

The methodology states "Counts the number of hBTC tokens" but the implementation actually returns BTC value (hBTCSupply * sharePrice), not the raw token count. Consider updating to accurately describe the TVL calculation.

📝 Suggested methodology update
-  methodology: 'Counts the number of hBTC tokens on Stacks.',
+  methodology: 'TVL = hBTC supply on Stacks valued by hBTC share price (representing underlying BTC).',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/hermetica-hbtc/index.js` at line 9, Update the methodology string to
accurately describe the TVL calculation: replace the current "Counts the number
of hBTC tokens on Stacks." with a description stating that the module returns
the BTC value (TVL) computed as hBTCSupply multiplied by sharePrice (i.e.,
hBTCSupply * sharePrice), and reference the variables/logic used to compute TVL
(hBTCSupply and sharePrice) so the methodology matches the implementation in
this file's methodology property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/hermetica-hbtc/index.js`:
- Line 2: Remove the unused import of the get function: delete the require
statement that brings in get (const { get } = require('../helper/http')) from
the top of the file so there are no unused imports remaining in
projects/hermetica-hbtc index.js.

---

Nitpick comments:
In `@projects/hermetica-hbtc/index.js`:
- Line 9: Update the methodology string to accurately describe the TVL
calculation: replace the current "Counts the number of hBTC tokens on Stacks."
with a description stating that the module returns the BTC value (TVL) computed
as hBTCSupply multiplied by sharePrice (i.e., hBTCSupply * sharePrice), and
reference the variables/logic used to compute TVL (hBTCSupply and sharePrice) so
the methodology matches the implementation in this file's methodology property.

In `@projects/hermetica/stacks-call.js`:
- Around line 13-20: The makeReadOnlyContractCall function currently converts
the Clarity integer result to a JavaScript Number causing precision loss for
large token supplies; instead, preserve full precision by returning either the
raw string from parseClarityInt or a BigInt (e.g., BigInt(parseClarityInt(...)))
and update callers to handle string/BigInt arithmetic (or document the Number
limitation). Locate makeReadOnlyContractCall and replace the
Number(parseClarityInt(response.result)) conversion with a non-lossy return
value (string or BigInt) and ensure downstream code that consumes
makeReadOnlyContractCall (call sites) performs appropriate BigInt or
string-based division/formatting.
- Around line 3-11: The parseClarityInt function lacks input validation and will
throw on undefined/malformed input; update parseClarityInt to first validate
that its hexString parameter is a non-empty string (and that response.result is
present where it's called), then guard the "0x" check and slicing operations,
and wrap the BigInt conversion in a try/catch to throw or return a descriptive
error/value when parsing fails; reference the parseClarityInt function name and
the BigInt conversion/overflow check branches to add these guards and clear
error messages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d5aef8c-7aa3-4e32-a2ca-3127100ba336

📥 Commits

Reviewing files that changed from the base of the PR and between ffa3baf and a0a398c.

📒 Files selected for processing (4)
  • projects/helper/coreAssets.json
  • projects/hermetica-hbtc/index.js
  • projects/hermetica/index.js
  • projects/hermetica/stacks-call.js

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica exports TVL:

stacks                    9.04 M
bitcoin                   435.69 k

total                    9.48 M 

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica-hbtc exports TVL:

stacks                    1.08 M

total                    1.08 M 

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
projects/hermetica-hbtc/index.js (1)

5-5: Consider centralizing the state contract address in shared assets.

hBTCStateContract is hardcoded while hBTCContract comes from coreAssets.json. Putting both in one shared source reduces drift risk during contract upgrades.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/hermetica-hbtc/index.js` at line 5, hBTCStateContract is hardcoded
while hBTCContract is read from coreAssets.json—centralize both by moving
hBTCStateContract into the shared assets file (e.g., coreAssets.json) and update
the initializer that currently reads hBTCContract to also read
hBTCStateContract; modify references to the constant name hBTCStateContract in
projects/hermetica-hbtc/index.js to pull from the shared asset object (same key
name you add) so both addresses are sourced from the single canonical asset
(coreAssets) to avoid drift on upgrades.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/hermetica-hbtc/index.js`:
- Line 8: Update the exported adapter's methodology string to accurately
describe the BTC-equivalent calculation instead of saying it simply "counts the
number of hBTC tokens"; specifically state that the adapter computes BTC value
by multiplying totalSupply by sharePrice (variables totalSupply and sharePrice)
to derive BTC-equivalent holdings and returns that BTC amount. Ensure the new
wording appears where the methodology property is defined so consumers see the
correct description.

---

Nitpick comments:
In `@projects/hermetica-hbtc/index.js`:
- Line 5: hBTCStateContract is hardcoded while hBTCContract is read from
coreAssets.json—centralize both by moving hBTCStateContract into the shared
assets file (e.g., coreAssets.json) and update the initializer that currently
reads hBTCContract to also read hBTCStateContract; modify references to the
constant name hBTCStateContract in projects/hermetica-hbtc/index.js to pull from
the shared asset object (same key name you add) so both addresses are sourced
from the single canonical asset (coreAssets) to avoid drift on upgrades.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cdb4c5f4-7368-4fc1-90d4-75d7f112baa0

📥 Commits

Reviewing files that changed from the base of the PR and between a0a398c and 0ceaa0d.

📒 Files selected for processing (1)
  • projects/hermetica-hbtc/index.js

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica exports TVL:

stacks                    9.04 M
bitcoin                   435.69 k

total                    9.48 M 

@llamabutler
Copy link
Copy Markdown

The adapter at projects/hermetica-hbtc exports TVL:

stacks                    1.08 M

total                    1.08 M 

@anch09
Copy link
Copy Markdown
Contributor Author

anch09 commented Mar 20, 2026

  • The logo added corresponds to hBTC
  • The description is the new one for the Hermetica main page
  • Tokens should respect case type (USDh and hBTC)

@RohanNero RohanNero self-assigned this Mar 23, 2026
@@ -0,0 +1,22 @@
const { post } = require('../helper/http')

function parseClarityInt(hexString) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of using this new function, could you use the existing call helper, I tested it here and got the same output

Copy link
Copy Markdown
Contributor Author

@anch09 anch09 Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
I didn't use the call helper because when running the test it was throwing me Error loading module: Error: Cannot find module '@stacks/transactions' with MODULE_NOT_FOUND. And even after running npm install, seems that the module is not in the package.json file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why after the changes the bot doesn't display the TVL comments?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello, Im not sure why it wasnt working for you, but I tested your changes locally and it looked good to me (except the .value for microSharePrice)

tvl: async () => {
const [microhBTCSupplyStacks, microSharePrice] = await Promise.all([
makeReadOnlyContractCall({ contract: hBTCContract, function_name: 'get-total-supply' }),
makeReadOnlyContractCall({ contract: hBTCStateContract, function_name: 'get-share-price' })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please share the bitcoin addresses where hBTC collateral is held? The docs mention using the sBTC bridge, is there a way to track the btc deposited for hBTC specifically?

Copy link
Copy Markdown
Contributor Author

@anch09 anch09 Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, sure thing. sBTC is a 1:1 BTC backed token.

You can see the docs here.
The amount of sBTC circulating on-chain here.
The multisig address on Bitcoin here.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/hermetica/index.js`:
- Around line 26-29: The code calls an undefined function
makeReadOnlyContractCall and passes wrong parameter names; replace that call
with the imported call function and adapt its signature to match call(target,
abi, optionalArgs) (mirror the bitcoin.tvl usage): use call(USDhContract,
'get-total-supply') (or the correct ABI shape expected by call) to fetch total
supply and assign to supplyOnStacksuUsdh; ensure you remove/replace references
to makeReadOnlyContractCall and update parameter names from
contract/function_name to the call() parameters used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25da8338-218b-4960-a9d4-47da64bcaf36

📥 Commits

Reviewing files that changed from the base of the PR and between 5288679 and b334c22.

📒 Files selected for processing (2)
  • projects/hermetica-hbtc/index.js
  • projects/hermetica/index.js
✅ Files skipped from review due to trivial changes (1)
  • projects/hermetica-hbtc/index.js

call({ target: hBTCStateContract, abi: 'get-share-price' })
]);

const sharePrice = Number(microSharePrice.value) / (10 ** 8);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove .value here, get-share-price returns only a number

Copy link
Copy Markdown
Contributor Author

@anch09 anch09 Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed this. But I'm still not fully convinced that the call function will work. Looking at the main branch's package.json file doesn't contain the dependencies that the stacks call function wrapper need.

const stacks = require('@stacks/transactions')
const { STACKS_MAINNET, } = require('@stacks/network')
const { createApiKeyMiddleware, createFetchFn } = require('@stacks/common')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants