Repository: DefiLlama/DefiLlama-Adapters
Pull request #13835: Add Bitbond Token Tool adapters
Author: login: panteo
Created: 2025-03-12T08:04:56Z | Merged: 2025-03-13T12:36:13Z

##### Name (to be shown on DefiLlama):

Token Tool

##### Twitter Link:

https://x.com/Bitbond

##### List of audit links if any:

https://skynet.certik.com/projects/bitbond

##### Website Link:

https://tokentool.bitbond.com/

##### Logo (High resolution, will be shown with rounded borders):

https://tokentool.bitbond.com/images/bitbond_icon.svg

##### Current TVL:

329 k

##### Treasury Addresses (if the protocol has treasury)

##### Chain:

ethereum, polygon, bsc, avax, fantom, arbitrum, optimism, base, polygon_zkevm, blast, peaq

##### 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):

Token Tool by Bitbond is a no-code dApp that enables users to effortlessly create tokens and launch token sales across major chains. Use Token Tool's convenient user interface instead of coding your own smart contracts.

##### Token address and ticker if any:

##### Category (full list at https://defillama.com/categories) *Please choose only one:

Launchpad

##### 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):

This PR introduces two adapters: **Token Locker Adapter** and **Token Sale Adapter**, both of which derive deployed contract addresses from our Metis API and compute TVL using multicalls with the `resolveLP` option set to `true`.

**Token Locker Adapter**

* Fetches the list of Token Locker contracts deployed via our [Token Tool](https://tokentool.bitbond.com/) factory contract from the Metis API (e.g., for Ethereum: https://metis.bitbond.com/api/defillama/ethereum/token-lockers).
* Calls the `getLockedTokensAmount` function on each contract via `multicall`.
* Sums the returned token amounts, resolving LP tokens where applicable.

**Token Sale Adapter**

* Fetches the list of Token Sale contracts deployed via our [Token Tool](https://tokentool.bitbond.com/) factory contract from the Metis API (e.g., for Ethereum: https://metis.bitbond.com/api/defillama/ethereum/token-sales).
* For each contract, retrieves:
  * The balance of the **currency token** (native or ERC-20) held in the contract.
  * The amount of **sale tokens still available for purchase**.
  * The amount of **sale tokens still to be claimed**.
* Sums the values from all contracts, resolving LP tokens where applicable.


##### Github org/user (Optional, if your code is open source, we can track activity):



Diff:
diff --git a/projects/BitbondLockers/config.json b/projects/BitbondLockers/config.json
new file mode 100644
index 00000000000..0a877b47ad8
--- /dev/null
+++ b/projects/BitbondLockers/config.json
@@ -0,0 +1,20 @@
+{
+  "chainMapping": {
+    "arbitrum": "arbitrum",
+    "avax": "avalanche",
+    "base": "base",
+    "blast": "blast",
+    "bsc": "bsc",
+    "ethereum": "ethereum",
+    "fantom": "fantom",
+    "optimism": "optimism",
+    "polygon": "polygon",
+    "polygon_zkevm": "polygon-zkevm"
+  },
+  "metisBaseUrl": "https://metis.bitbond.com/api/defillama",
+  "userAgents": [
+    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
+    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
+    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
+  ]
+}
diff --git a/projects/BitbondLockers/index.js b/projects/BitbondLockers/index.js
new file mode 100644
index 00000000000..8f2d44139f6
--- /dev/null
+++ b/projects/BitbondLockers/index.js
@@ -0,0 +1,56 @@
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchLockers(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-lockers?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const lockers = await fetchLockers(networkName);
+
+  const lockedValues = await api.multiCall({
+    abi: "erc20:balanceOf",
+    calls: lockers.map((locker) => ({ target: locker.tokenAddress, params: [locker.address] })),
+    requery: true
+  });
+
+  const tokenAddresses = lockers.map((locker) => (locker.tokenAddress));
+
+  api.addTokens(tokenAddresses, lockedValues)
+  
+  return sumTokens2({api, resolveLP: true});
+}
+
+Object.keys(chainMapping).forEach((chain) => {
+  module.exports[chain] = { tvl };
+});
diff --git a/projects/BitbondSales/config.json b/projects/BitbondSales/config.json
new file mode 100644
index 00000000000..0a877b47ad8
--- /dev/null
+++ b/projects/BitbondSales/config.json
@@ -0,0 +1,20 @@
+{
+  "chainMapping": {
+    "arbitrum": "arbitrum",
+    "avax": "avalanche",
+    "base": "base",
+    "blast": "blast",
+    "bsc": "bsc",
+    "ethereum": "ethereum",
+    "fantom": "fantom",
+    "optimism": "optimism",
+    "polygon": "polygon",
+    "polygon_zkevm": "polygon-zkevm"
+  },
+  "metisBaseUrl": "https://metis.bitbond.com/api/defillama",
+  "userAgents": [
+    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
+    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
+    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
+  ]
+}
diff --git a/projects/BitbondSales/index.js b/projects/BitbondSales/index.js
new file mode 100644
index 00000000000..2bc6d9e9bdf
--- /dev/null
+++ b/projects/BitbondSales/index.js
@@ -0,0 +1,81 @@
+const { default: BigNumber } = require('bignumber.js')
+const sdk = require('@defillama/sdk')
+
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchSales(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-sales?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const sales = await fetchSales(networkName);
+  const tokenAddresses = sales.map((sale) => (sale.tokenAddress));
+
+  // Native currencies TVL
+  const salesWithNativeCurrency = sales.filter((sale) => sale.currencyAddress === "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithNativeCurrency = salesWithNativeCurrency.map((sale) => (sale.currencyAddress));
+  const availableNativeCurrenciesValues = await Promise.all(
+    salesWithNativeCurrency.map(async (sale) => 
+      await sdk.api.eth.getBalance({
+        chain: api.chain,
+        target: sale.address
+      }).then((balance) => balance.output)
+    )
+  );
+  api.addTokens(tokenAddressesWithNativeCurrency, availableNativeCurrenciesValues)
+
+  // Other currencies TVL
+  const salesWithOtherCurrency = sales.filter((sale) => sale.currencyAddress !== "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithOtherCurrency = salesWithOtherCurrency.map((sale) => (sale.tokenAddress));
+  const availableOtherCurrenciesValues = await api.multiCall({
+    abi: "erc20:balanceOf",
+    calls: salesWithOtherCurrency.map((sale) => ({ target: sale.currencyAddress, params: [sale.address] })),
+    requery: true
+  });
+  api.addTokens(tokenAddressesWithOtherCurrency, availableOtherCurrenciesValues)
+  
+  // Tokens TVL
+  const tokensValues = await api.multiCall({
+    abi: "erc20:balanceOf",
+    calls: sales.map((sale) => ({ target: sale.tokenAddress, params: [sale.address] })),
+    requery: true
+  });
+  api.addTokens(tokenAddresses, tokensValues)
+  
+  return sumTokens2({api, resolveLP: true});
+}
+
+Object.keys(chainMapping).forEach((chain) => {
+  module.exports[chain] = { tvl };
+});


Reviews:
user: login: waynebruce0x
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T09:34:56Z
user: login: waynebruce0x
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T09:35:09Z
user: login: panteo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:25:33Z
user: login: panteo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:25:36Z

Review comments:
diff_hunk: @@ -0,0 +1,101 @@
+const { default: BigNumber } = require('bignumber.js')
+const sdk = require('@defillama/sdk')
+
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchSales(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-sales?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const sales = await fetchSales(networkName);
+  const tokenAddresses = sales.map((sale) => (sale.tokenAddress));
+
+  // Native currencies TVL
+  const salesWithNativeCurrency = sales.filter((sale) => sale.currencyAddress === "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithNativeCurrency = salesWithNativeCurrency.map((sale) => (sale.currencyAddress));
+  const availableNativeCurrenciesValues = await Promise.all(
+    salesWithNativeCurrency.map(async (sale) => 
+      await sdk.api.eth.getBalance({
+        chain: api.chain,
+        target: sale.address
+      }).then((balance) => balance.output)
+    )
+  );
+  api.addTokens(tokenAddressesWithNativeCurrency, availableNativeCurrenciesValues)
+
+  // Other currencies TVL
+  const salesWithOtherCurrency = sales.filter((sale) => sale.currencyAddress !== "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithOtherCurrency = salesWithOtherCurrency.map((sale) => (sale.tokenAddress));
+  const availableOtherCurrenciesValues = await api.multiCall({
+    abi: "erc20:balanceOf",
+    calls: salesWithOtherCurrency.map((sale) => ({ target: sale.currencyAddress, params: [sale.tokenAddress] })),
+    requery: true
+  });
+  api.addTokens(tokenAddressesWithOtherCurrency, availableOtherCurrenciesValues)
+  
+  // Available tokens TVL
+  const availableTokensValues = await api.multiCall({
+    abi: "uint256:totalTokensLeft",
+    calls: sales.map((sale) => ({ target: sale.address })),
+    requery: true
+  });
+  api.addTokens(tokenAddresses, availableTokensValues)
+
+  // Tokens to claim TVL
+  const tokensToClaimExpandedValues = await api.multiCall({
path: projects/BitbondSales/index.js
user: login: waynebruce0x
body: is it possible to use erc20:balanceOf here instead?
created_at: 2025-03-12T09:34:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,56 @@
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchLockers(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-lockers?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const lockers = await fetchLockers(networkName);
+
+  const lockedValues = await api.multiCall({
path: projects/BitbondLockers/index.js
user: login: waynebruce0x
body: is it possible to use erc20:balanceOf here instead?
created_at: 2025-03-12T09:35:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+const { default: BigNumber } = require('bignumber.js')
+const sdk = require('@defillama/sdk')
+
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchSales(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-sales?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const sales = await fetchSales(networkName);
+  const tokenAddresses = sales.map((sale) => (sale.tokenAddress));
+
+  // Native currencies TVL
+  const salesWithNativeCurrency = sales.filter((sale) => sale.currencyAddress === "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithNativeCurrency = salesWithNativeCurrency.map((sale) => (sale.currencyAddress));
+  const availableNativeCurrenciesValues = await Promise.all(
+    salesWithNativeCurrency.map(async (sale) => 
+      await sdk.api.eth.getBalance({
+        chain: api.chain,
+        target: sale.address
+      }).then((balance) => balance.output)
+    )
+  );
+  api.addTokens(tokenAddressesWithNativeCurrency, availableNativeCurrenciesValues)
+
+  // Other currencies TVL
+  const salesWithOtherCurrency = sales.filter((sale) => sale.currencyAddress !== "0x0000000000000000000000000000000000000000");
+  const tokenAddressesWithOtherCurrency = salesWithOtherCurrency.map((sale) => (sale.tokenAddress));
+  const availableOtherCurrenciesValues = await api.multiCall({
+    abi: "erc20:balanceOf",
+    calls: salesWithOtherCurrency.map((sale) => ({ target: sale.currencyAddress, params: [sale.tokenAddress] })),
+    requery: true
+  });
+  api.addTokens(tokenAddressesWithOtherCurrency, availableOtherCurrenciesValues)
+  
+  // Available tokens TVL
+  const availableTokensValues = await api.multiCall({
+    abi: "uint256:totalTokensLeft",
+    calls: sales.map((sale) => ({ target: sale.address })),
+    requery: true
+  });
+  api.addTokens(tokenAddresses, availableTokensValues)
+
+  // Tokens to claim TVL
+  const tokensToClaimExpandedValues = await api.multiCall({
path: projects/BitbondSales/index.js
user: login: panteo
body: Yes, thanks for the suggestion! The code has been refactored.
created_at: 2025-03-13T08:25:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+const { get } = require('../helper/http');
+const { sumTokens2 } = require("../helper/unwrapLPs");
+
+const { chainMapping, userAgents, metisBaseUrl } = require("./config.json");
+
+async function fetchLockers(networkName) {
+  let contracts = [];
+  let currentPage = 1;
+  let isLastPage = false;
+
+  while (!isLastPage) {
+    const response = await get(
+      `${metisBaseUrl}/${networkName}/token-lockers?page=${currentPage}&perPage=100`,
+      {
+        headers: {
+          Accept: "application/json",
+          "User-Agent": getRandomUserAgent(),
+        }
+      }
+    );
+
+    contracts.push(...response.meta.contracts);
+
+    const pagination = response.meta.pagination;
+    isLastPage = pagination ? pagination.isLastPage : true;
+    currentPage = pagination ? pagination.nextPage : currentPage;
+  }
+
+  return contracts;
+}
+
+function getRandomUserAgent() {
+  const randomIndex = Math.floor(Math.random() * userAgents.length);
+  return userAgents[randomIndex];
+}
+
+async function tvl(api) {
+  const networkName = chainMapping[api.chain];
+  const lockers = await fetchLockers(networkName);
+
+  const lockedValues = await api.multiCall({
path: projects/BitbondLockers/index.js
user: login: panteo
body: Yes, thanks for the suggestion! The code has been refactored.
created_at: 2025-03-13T08:25:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: llamabutler
created_at: 2025-03-12T08:05:28Z
body: The adapter at projects/BitbondLockers exports TVL: 
        
 
 
    bsc                       233.32 k
    ethereum                  76.87 k
    base                      7.01 k
    polygon_zkevm             0
    blast                     0
    fantom                    0
    avax                      0
    arbitrum                  0
    optimism                  0
    polygon                   0
    
    total                    317.21 k 
    
    
author_association: NONE
user: login: llamabutler
created_at: 2025-03-12T08:06:06Z
body: The adapter at projects/BitbondSales exports TVL: 
        
 
 
    ethereum                  9.00 k
    base                      1.00 k
    bsc                       692
    blast                     192
    optimism                  165
    polygon                   27
    fantom                    0
    arbitrum                  0
    avax                      0
    polygon_zkevm             0
    
    total                    11.08 k 
    
    
author_association: NONE
user: login: llamabutler
created_at: 2025-03-13T07:35:47Z
body: The adapter at projects/BitbondLockers exports TVL: 
        
 
 
    bsc                       246.35 k
    ethereum                  75.38 k
    base                      6.88 k
    blast                     0
    polygon_zkevm             0
    arbitrum                  0
    fantom                    0
    avax                      0
    optimism                  0
    polygon                   0
    
    total                    328.61 k 
    
    
author_association: NONE
user: login: llamabutler
created_at: 2025-03-13T08:25:20Z
body: The adapter at projects/BitbondLockers exports TVL: 
        
 
 
    bsc                       246.82 k
    ethereum                  75.15 k
    base                      6.86 k
    polygon_zkevm             0
    arbitrum                  0
    blast                     0
    fantom                    0
    optimism                  0
    avax                      0
    polygon                   0
    
    total                    328.83 k 
    
    
author_association: NONE
user: login: llamabutler
created_at: 2025-03-13T08:26:25Z
body: The adapter at projects/BitbondSales exports TVL: 
        
 
 
    ethereum                  7.45 k
    base                      983
    bsc                       717
    blast                     187
    optimism                  118
    polygon                   27
    fantom                    0
    arbitrum                  0
    avax                      0
    polygon_zkevm             0
    
    total                    9.48 k 
    
    
author_association: NONE