Useful Functions

User Data Queries

Get User B3TR Balance

Retrieves the user's B3TR token balance:

import find from "lodash.find";
import ABI_ERC20 from "path/to/erc20.json";

async function getUserB3trBalance(account: string) {
  // Parameters:
  // - account: The user's wallet address
  
  const b3trContract = connex.thor.account(TOKEN_B3TR.address);
  const rawData = await b3trContract.method(find(ABI_ERC20, { name: "balanceOf" })).call(account);
  const balance = rawData.decoded[0];
  return BigInt(balance); // Returns the balance as a BigInt in wei (smallest unit)
}

Usage: Display this value divided by 10^18 to show the user's available B3TR tokens to stake. Refresh this value after transactions to show updated balances.

Get User Staked B3TR Amount

Retrieves the amount of B3TR the user has currently staked:

import find from "lodash.find";
import ABI_VEDELEGATE from "path/to/VeDelegateV2.json";

async function getUserStakedB3trAmount(account: string) {
  // Parameters:
  // - account: The user's wallet address
  
  const veDelegateContract = connex.thor.account(VEDELEGATE_CONTRACT_ADDRESS);
  const rawData = await veDelegateContract.method(find(ABI_VEDELEGATE, { name: "B3TRBalances" })).call(account);
  const balance = rawData.decoded[0];
  return BigInt(balance); // Returns the staked amount as a BigInt in wei
}

Usage: This value represents how many B3TR tokens the user has staked. Divide by 10^18 for display purposes. This amount can be used to calculate potential rewards and is needed when the user wants to unstake.

Get User Reward for a Specific Round

Retrieves the amount of reward tokens a user can claim for a specific round:

import find from "lodash.find";
import ABI_VEDELEGATE from "path/to/VeDelegateV2.json";

async function getUserReward(roundId: string, account: string) {
  // Parameters:
  // - roundId: The ID of the round to check rewards for
  // - account: The user's wallet address
  
  const veDelegateContract = connex.thor.account(VEDELEGATE_CONTRACT_ADDRESS);
  const rawData = await veDelegateContract.method(find(ABI_VEDELEGATE, { name: "checkUserReward" })).call(roundId, account);
  const reward = rawData.decoded[0];
  return BigInt(reward); // Returns the reward amount as a BigInt in wei
}

Important: This shows the total reward for a given round, not considering if the user has already claimed part or all of it. Always check against getUserClaimedReward to calculate the actual claimable amount.

Get User Claimed Reward for a Specific Round

Retrieves the amount of reward tokens a user has already claimed for a specific round:

import find from "lodash.find";
import ABI_VEDELEGATE from "path/to/VeDelegateV2.json";

async function getUserClaimedReward(roundId: string, account: string) {
  // Parameters:
  // - roundId: The ID of the round to check claimed rewards for
  // - account: The user's wallet address
  
  const veDelegateContract = connex.thor.account(VEDELEGATE_CONTRACT_ADDRESS);
  const rawData = await veDelegateContract.method(find(ABI_VEDELEGATE, { name: "checkuserClaimed" })).call(roundId, account);
  const claimed = rawData.decoded[0];
  return BigInt(claimed); // Returns the claimed amount as a BigInt in wei
}

Usage: Subtract this value from the total reward to determine how much is still available to claim. If this equals the total reward, the user has already claimed everything for this round.

Fetch Current Round Data

Retrieves information about the current and previous staking rounds from the subgraph:

async function fetchRoundData() {
  const response = await fetch(`https://graph.vet/subgraphs/name/vebetter/dao`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      query: `{rounds(orderBy: voteStart, orderDirection: desc, first: 2) { id }}`
    })
  });

  const { data } = await response.json();

  return {
    currentRoundId: data.rounds[0].id,  // The ID of the ongoing round
    lastEndedRoundId: data.rounds[1].id // The ID of the previous round
  };
}

Note: Round information is essential for reward calculations. The current round is used for staking operations, while the last ended round is typically used for claiming rewards. Implement error handling for cases where the subgraph might be temporarily unavailable.

Transaction Receipt Polling

This helper function waits for transaction confirmation by periodically checking for a transaction receipt:

type PollOptions = {
  timeout?: number;  // Maximum time to wait before timing out (in milliseconds)
  interval?: number; // Time between polling attempts (in milliseconds)
};

export default async function poll<T>(fn: () => Promise<T>, options: PollOptions = {}): Promise<T> {
  const timeout = options.timeout ?? 1000 * 60 * 5; // 5 minutes default timeout
  const interval = options.interval ?? 3000;        // 3 seconds default interval

  const endTime = Date.now() + timeout;

  return new Promise<T>((resolve, reject) => {
    const waitForConfirmation = async () => {
      if (Date.now() > endTime) {
        return reject(new Error("Timeout"));
      }

      try {
        const result = await fn();
        if (result) {
          resolve(result);
        } else {
          setTimeout(waitForConfirmation, interval);
        }
      } catch (error) {
        reject(error);
      }
    };

    waitForConfirmation();
  });
}

Important: Transaction confirmations may take varying amounts of time depending on network conditions. Adjust the timeout and interval parameters accordingly. For critical operations, consider implementing a UI that properly informs users about the transaction status.

Last updated