Need help understanding Bitcoin DeFi?
→ START HERE
Need help understanding Bitcoin DeFi?
→ START HERE
Need help understanding Bitcoin DeFi?
→ START HERE
Need help understanding Bitcoin DeFi?
→ START HERE
Need help understanding Bitcoin DeFi?
→ START HERE

How Hermetica Uses Chainhook to Track Bitcoin Deposits

Hi, this is Jakob, the CEO of Hermetica. We are building the first Bitcoin-backed, interest-bearing stablecoin and tokens that let holders earn yield on their BTC through automated derivative strategies. In this post, we’re going to talk about how we use Chainhook to track BTC deposits.

Type
Tutorial
Topic(s)
Ecosystem
Stacks
Published
April 9, 2024
Author(s)
CEO of Hermetica
Discover how Chainhook powers Hermetica's app.
Contents

The Story of Hermetica

I spent my early career building an eyeglasses company in the developing world, primarily in West-Africa. As the company continued to grow, it was clear that we needed to move away from cash payments for payroll. When I proposed paying the team via bank wire, I was met with confused looks and empty stares. As it turns out, none of the people on my team (and nobody they knew!) had a bank account.

This experience fundamentally changed my understanding of our current financial infrastructure. It burst my Western bubble and made me realize that the majority of people, especially in the developing world, are completely excluded from the traditional financial system.

So when I discovered Bitcoin a couple years later, it clicked very quickly. My experiences in Africa allowed me to see Bitcoin as the inclusive freedom technology that it is. I realized that a financial system based on Bitcoin is naturally financially inclusive and as a result would unlock vast amounts of untapped human potential.

I was hooked. 

So I dropped everything I was doing and dedicated myself to understanding and building on Bitcoin.

A financial system based on Bitcoin is naturally inclusive.

Fast forward to 2021 and Bitcoin had come a long way from where it was when I first discovered it. There were efficient spot exchanges, liquid derivatives markets and accessible lending infrastructures. The only problem was that it was all happening through centralized means. In order to do something with your Bitcoin, you had to give up custody and trust a centralized institution that could deny you access or, God forbid, misappropriate your funds.

How was this any different from the old system that we were aiming to change?

I’m of the belief that for a Bitcoin native financial system to succeed we don’t just need to offer the full range of financial services, but we need them to be on-chain. We need them in the form of permissionless, non-custodial applications.

When I discovered Stacks in early 2021, the final piece of the puzzle clicked: We finally had the necessary technology stack (no pun intended) to actually build out Bitcoin DeFi. The security-first smart contract infrastructure coupled with Bitcoin finality is the perfect combination to make Bitcoin-based financial services a reality.

Hermetica brings institutional-grade trading strategies and infrastructure to Bitcoin DeFi. Our first product line is a set of tokens that combine market-tested derivatives strategies with best-in-class risk management to allow users to generate yield. Our goal is to make trading strategies that are typically reserved for high net-worth individuals and institutions available to every Bitcoiner in a permissionless and non-custodial way.

Learn more about Hermetica and my experience building Bitcoin DeFi in my conversation with Hiro:

How Hermetica Works

The Hermetica app is quite simple. You log in with your Stacks-enabled Bitcoin wallet (Leather or Xverse are excellent choices) and then proceed to select the strategy you feel attracted to. 

Hermetica brings institutional-grade trading strategies and infrastructure to Bitcoin DeFi. 

Our Stacks Earn strategy, for example, aims to make a return in all market regimes - the six year backtest shows an average historical APY of 16.2% on STX. Our Bitcoin Bull strategy, on the other hand, gives you high returns in bull markets - the strategy generated returns of 69% in 2017/18 and 44% in 2020/21.

Once you’ve made up your mind, you choose the amount you would like to deposit and sign a transaction. That’s it. That’s all you need to do to deposit your Bitcoin and mint a Hermetica liquid yield token. The rest is automated and taken care of by the smart contracts. 

Our smart contracts use a small portion of your deposit (i.e. 2% for the Earn token) to buy an exotic option strategy from a market maker. At the end of every weekly Epoch, the contracts calculate the profit and loss and settle the option. All of this happens automatically and in the background. As a user, you can track all of it on-chain and in our app (everything is 100% transparent) - but you don’t need to worry about the details of the trade execution or security.

Tracking Deposits With Chainhook

When you make a deposit, we use Chainhook to track your deposit transaction. We need to know how much you’ve deposited, and into which strategy, so we can update our database and display the right information to you on our frontend. Without access to Hiro’s Chainhook and API, we would need to run our own node and index the relevant on-chain events ourselves.

The benefit of running our own node and indexing infrastructure is that we would have full control over the codebase and deployment and would be able to fix any issues on our own time. However, the downsides are more significant. We would have to manage a second codebase, which is really unrelated to the core functionality of our app, and we would have to ensure high uptimes for our node, which in itself is a challenge. 

Chainhook lets us focus our dev resources on building out the core functionality of our app.

That’s why we ultimately decided to go with using Chainhook. Hiro streams the relevant data packets for all contract calls made to our smart contracts to our servers. This allows us to just manage a minimal Chainhook integration and focus the rest of our dev resources on building out the core functionality of our app.

Constructing a Chainhook Predicate

In order to tell Hiro’s Chainhook infrastructure what data we are interested in, we need to define if-this-then-that logic in so-called <code-rich-text>predicates<code-rich-text>.

Below we have the chainhook predicate for a deposit transaction. The <code-rich-text>if_this<code-rich-text> section defines that we are interested in contract calls to the <code-rich-text>queue-deposit<code-rich-text> method on our Earn vault contract.

The <code-rich-text>then_that<code-rich-text> section specifies the URL we want to receive a POST request to once the <code-rich-text>if_this<code-rich-text> conditions are met.

The <code-rich-text>then_that<code-rich-text> section also allows us to set additional metadata like an <code-rich-text>authorization_header<code-rich-text>, which is used by our servers to identify legitimate POST requests, a <code-rich-text>start_block<code-rich-text>, specifying the first block height we want the Chainhook to send data to our servers, and the <code-rich-text>expire_after_occurrence<code-rich-text> field where we can define if we want the chainhook to stop the subscription after a certain amount of contract calls.


{
  "name": "xBTC Earn Deposit Transaction",
  "uuid": "e5fa09b2-ec3e-4b6a-9a4a-0ebb454f6e19",
  "chain": "stacks",
  "version": 1,
  "networks": {
    "devnet": {
      "if_this": {
        "scope": "contract_call",
        "method": "queue-deposit",
        "contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.earn-vault-xbtc"
      },
      "end_block": null,
      "then_that": {
        "http_post": {
          "url": "http://localhost:3000/post/earn/chainhooks/deposit-tx",
          "authorization_header": ""
        }
      },
      "start_block": 1,
      "decode_clarity_values": true,
      "expire_after_occurrence": null
    }
  }
}

Processing the POST Request

The below code snippet describes an API endpoint handler function to process the data received by the POST request sent by the chainhook defined by the above predicate.

After checking that we are indeed handling a POST request (no other types are allowed), the code begins extracting the data:

We first need to access all the transactions that belong to the block which holds the deposit transaction. We want to access the <code-rich-text>timestamp<code-rich-text>, block metadata such as <code-rich-text>block_identifier<code-rich-text> and <code-rich-text>block_height<code-rich-text>, the chainhook method which in this case is <code-rich-text>queue-deposit<code-rich-text>, the <code-rich-text>contract_identifier<code-rich-text> which is composed of the <code-rich-text>deployerAddress<code-rich-text> and <code-rich-text>vaultContractName<code-rich-text>, and the <code-rich-text>transaction_identifier<code-rich-text> (tx id).

Once we get all this data we perform a number of checks. 

We check if the current chainhook structure is not a rollback, if the <code-rich-text>bitcoin_block_identifier<code-rich-text> has a value (indicating that the block containing the transaction has been mined) and that the chainhook method is in-fact <code-rich-text>queue-deposit<code-rich-text>.

Next, we iterate through the transactions array, and for each transaction we extract the needed metadata from the chainhook structure. This includes the <code-rich-text>success<code-rich-text> status, the <code-rich-text>sender<code-rich-text>’s STX wallet address, method, arguments used to call this function, and the <code-rich-text>transaction_identifier<code-rich-text>.

Now that we have collected all the necessary data, we write the <code-rich-text>queue-deposit<code-rich-text> transaction and all its metadata into our database. The goal is to have our database reflect the same state as the smart contract to which the deposit was made. We use this database to display user data in our frontend, so it’s important to have it accurately reflect on-chain state.

Lastly, we send a 201 status response indicating that the instances were successfully written to the database. This 201 status tells the chainhook that the request has been processed. If we send an error response to the chainhook instead, the chainhook will stop processing new events. 


import type { NextApiRequest, NextApiResponse } from 'next';
import type { ChainhookStructure } from '@/types/chainhooks-type';
import { db } from '@/utils/db';
import { dateFromSeconds } from '@/helpers/date-helpers';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    const { apply, rollback, chainhook } = req.body as ChainhookStructure;
    const { transactions, timestamp, block_identifier, metadata } = apply[0];
    const { bitcoin_anchor_block_identifier } = metadata;
    const { index: block_height } = block_identifier;
    const { contract_identifier, method } = chainhook.predicate;
    const [contractAddress, vaultContractName] = contract_identifier.split('.');
    const regex = /^(queue-withdrawal|queue-deposit)$/;
    let tx_id: string;
    // CHECKS IF THE CHAINHOOK POST REQUEST IS NOT A ROLLBACK, IF THERE IS A BTC ANCHOR BLOCK ID WHICH MEANS THAT IT IS ALREADY MINED AND IF IT MATCHES THE METHOD (SMART CONTRACT FUNCTION) THAT WAS EXECUTED.
    if (!rollback.length && bitcoin_anchor_block_identifier && regex.test(method)) {
      //... GET DATA TO THE VAULT THIS TRANSACTION BELONGS AND DATA OF THE CURRENT EPOCH PLUS DATA OF THE UNDERLYING TOKEN.

      // FOR EACH TRANSACTION FOUND IN THE CURRENT BLOCK MINED IT WILL UPDATE OR CREATE (IF THERE IS NONE), A NEW TRANSACTION INSTANCE INTO THE DATABASE.
      for (const transaction of transactions) {
        const { transaction_identifier, metadata: transactionMetadata } = transaction;
        const { kind, success, sender } = transactionMetadata;
        const { method, args } = kind.data;
        const { hash } = transaction_identifier;
        tx_id = hash.slice(2);

        //... GET DATA OF THE USER WHICH IS THE OWNER OF THE TRANSACTION BEING PROCESSED.

        // ALMOST ALL DATA NEEDED TO CREATE A TRANSACTION INSTANCE IN OUR DATABSE CAN BE FOUND IN THE CHAINHOOKS STRUCTURE PROVIDED IN THE POST REQUEST
        const status = success ? 'QUEUED' : 'FAILURE';
        const { underlying_stx_token_name, underlying_token_name } = underlying_token;
        await db.transactions.upsert({
          create: {
            tx_id,
            token_name: underlying_token_name,
            stx_token_name: underlying_stx_token_name,
            type: method === 'queue-deposit' ? 'DEPOSIT' : 'WITHDRAWAL',
            amount: Number(args[0].slice(1)),
            status,
            erko_cp_user_id,
            epoch_id,
            vault_id,
            block_height,
            tx_date: dateFromSeconds(timestamp)
          },
          where: {
            tx_id
          },
          update: {
            epoch_id,
            status,
            block_height
          }
        });
      }

      res.status(201);
    }
  }
}

Join Hermetica's Ongoing Journey

That’s a glimpse into how we’ve built the Hermetica app with Chainhooks. If you would like to learn more about our stablecoin, USDh, and try out our liquid yield tokens, visit our website at hermetica.fi, subscribe to our email newsletter, and follow us on X.

I’ll see you on-chain!

Copy link
Mailbox
Hiro news & product updates straight to your inbox
Only relevant communications. We promise we won’t spam.

Related stories