// Imports:
import { ethers } from 'ethers';
import axios from 'axios';

// Importing Types:
import type { Request } from 'express';
import type { APIResponse, Chain, ChainData, Address, Hash, ABI, URL, Token, NativeToken, LPToken, PricedToken, DebtToken, XToken, TokenType, TokenStatus, TransferTX, ApprovalTX, SimpleTX, TaxTransferTX, TaxApprovalTX, ChainTokenData, TokenData } from 'cookietrack-types';

// Fetching Required JSON Files:
const chains: Record<Chain, ChainData> = require('../static/chains.json');
const rpcs: Record<Chain, URL[]> = require('../static/rpcs.json');
const keys: Record<string, string> = require('../static/keys.json');
const blacklist: string[] = require('../static/blacklist.json');

// Importing Variables:
import { minABI, lpABI, aave, balancer, snowball, traderjoe, belt, alpaca, curve, bzx, iron, axial, mstable, cookiegame } from './ABIs';
import { eth_data, bsc_data, poly_data, ftm_data, avax_data, one_data, cronos_data } from './tokens';

// Initializations:
const defaultTokenLogo: URL = 'https://cdn.jsdelivr.net/gh/atomiclabs/cryptocurrency-icons@d5c68edec1f5eaec59ac77ff2b48144679cebca1/32/icon/generic.png';
const defaultAddress: Address = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const zero: Address = '0x0000000000000000000000000000000000000000';
const ignoreErrors: { chain: Chain, address: Address }[] = [
  {chain: 'poly', address: '0x8aaa5e259f74c8114e0a471d9f2adfc66bfe09ed'}, // QuickSwap Registry
  {chain: 'poly', address: '0x9dd12421c637689c3fc6e661c9e2f02c2f61b3eb'}  // QuickSwap Dual Rewards Registry
];
const ignoreTokenPrices: Record<string, string[]> = {
  eth: [],
  bsc: [],
  poly: [],
  ftm: [],
  avax: ['PGL', 'sPGL', 's3D', 's4D', 'sDAI.e', 'sUSDT.e', 'sUSDC.e', 'sLINK.e', 'sWETH.e', 'sWBTC.e', 'sWAVAX', 'YRT', 'AS4D', 'AC4D', 'AA3D', 'AM3D', 'sAS4D', 'sAC4D', 'sAA3D', 'sAM3D', 'sPNG'],
  one: [],
  cronos: []
}

/* ========================================================================================================================================================================= */

// Function to make blockchain queries:
export const query = async (chain: Chain, address: Address, abi: ABI[], method: string, args: any[]) => {
  let result;
  let errors = 0;
  while(!result && errors < 3) {
    try {
      let ethers_provider = new ethers.providers.JsonRpcProvider(rpcs[chain][0]);
      let contract = new ethers.Contract(address, abi, ethers_provider);
      result = await contract[method](...args);
    } catch {
      try {
        let ethers_provider = new ethers.providers.JsonRpcProvider(rpcs[chain][1]);
        let contract = new ethers.Contract(address, abi, ethers_provider);
        result = await contract[method](...args);
      } catch {
        if(++errors > 2) {
          if(!ignoreErrors.find(i => i.chain === chain && i.address === address.toLowerCase())) {
            console.error(`Calling ${method}(${args}) on ${address} (Chain: ${chain.toUpperCase()})`);
          }
        }
      }
    }
  }
  return result;
}

/* ========================================================================================================================================================================= */

// Function to initialize generic API route response:
export const initResponse = (req: Request) => {
  let wallet = req.query.address as Address;
  let response: APIResponse = {
    status: 'ok',
    data: [],
    request: req.originalUrl
  }
  if(wallet != undefined) {
    if(!blacklist.includes(wallet.toLowerCase())) {
      if(!ethers.utils.isAddress(wallet)) {
        response.status = 'error';
        response.data = [{error: 'Invalid Wallet Address'}];
      }
    } else {
      response.status = 'error';
      response.data = [{error: 'Request Spam Detected: Get in touch with us through Discord'}];
    }
  } else {
    response.status = 'error';
    response.data = [{error: 'No Wallet Address in Request'}];
  }
  return response;
}

/* ========================================================================================================================================================================= */

// Function to get native token info:
export const addNativeToken = async (chain: Chain, rawBalance: number, owner: Address): Promise<NativeToken> => {

  // Initializing Token Values:
  let type: TokenType = 'nativeToken';
  let location = 'wallet';
  let status: TokenStatus = 'none';
  let address = defaultAddress;
  let decimals = 18;
  let balance = rawBalance / (10 ** decimals);
  let price = await getTokenPrice(chain, defaultAddress, decimals);
  let symbol = '';

  // Finding Token Symbol:
  if(chain === 'bsc') {
    symbol = 'BNB';
  } else if(chain === 'poly') {
    symbol = 'MATIC';
  } else if(chain === 'cronos') {
    symbol = 'CRO';
  } else {
    symbol = chain.toUpperCase();
  }

  // Finding Token Logo:
  let logo = getTokenLogo(chain, symbol);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get tracked token info:
export const addTrackedToken = async (chain: Chain, location: string, status: TokenStatus, token: TokenData, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let address = token.address;
  let symbol = token.symbol;
  let logo = token.logo;
  let decimals = token.decimals;
  let balance = rawBalance / (10 ** decimals);
  let price = await getTokenPrice(chain, address, decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get token info:
export const addToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = '';
  let decimals = 18;
  let logo: URL;

  // Finding Token Info:
  if(address.toLowerCase() === defaultAddress) {
    if(chain === 'bsc') {
      symbol = 'BNB';
    } else if(chain === 'poly') {
      symbol = 'MATIC';
    } else if(chain === 'cronos') {
      symbol = 'CRO';
    } else {
      symbol = chain.toUpperCase();
    }
    logo = getTokenLogo(chain, symbol);
  } else {
    let token = getTrackedTokenInfo(chain, address);
    if(token) {
      symbol = token.symbol;
      decimals = token.decimals;
      logo = token.logo;
    } else {
      symbol = await query(chain, address, minABI, 'symbol', []);
      decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
      logo = getTokenLogo(chain, symbol);
    }
  }

  // Finding Missing Token Info:
  let balance = rawBalance / (10 ** decimals);
  let price = await getTokenPrice(chain, address, decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get LP token info:
export const addLPToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<LPToken> => {

  // Initializing Token Values:
  let type: TokenType = 'lpToken';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let symbol0 = '';
  let symbol1 = '';
  let decimals0 = 18;
  let decimals1 = 18;

  // Finding LP Token Info:
  let lpTokenReserves = await query(chain, address, lpABI, 'getReserves', []);
  let lpTokenSupply = await query(chain, address, lpABI, 'totalSupply', []) / (10 ** decimals);
  let address0 = await query(chain, address, lpABI, 'token0', []);
  let address1 = await query(chain, address, lpABI, 'token1', []);
  let trackedToken0 = getTrackedTokenInfo(chain, address0);
  let trackedToken1 = getTrackedTokenInfo(chain, address1);
  if(trackedToken0) {
    symbol0 = trackedToken0.symbol;
    decimals0 = trackedToken0.decimals;
  } else {
    symbol0 = await query(chain, address0, minABI, 'symbol', []);
    decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
  }
  if(trackedToken1) {
    symbol1 = trackedToken1.symbol;
    decimals1 = trackedToken1.decimals;
  } else {
    symbol1 = await query(chain, address1, minABI, 'symbol', []);
    decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
  }
  let supply0 = lpTokenReserves[0] / (10 ** decimals0);
  let supply1 = lpTokenReserves[1] / (10 ** decimals1);

  // First Paired Token:
  let token0: PricedToken = {
    symbol: symbol0,
    address: address0,
    balance: (supply0 * (balance / lpTokenSupply)),
    price: await getTokenPrice(chain, address0, decimals0),
    logo: getTokenLogo(chain, symbol0)
  }

  // Second Paired Token:
  let token1: PricedToken = {
    symbol: symbol1,
    address: address1,
    balance: (supply1 * (balance / lpTokenSupply)),
    price: await getTokenPrice(chain, address1, decimals1),
    logo: getTokenLogo(chain, symbol1)
  }

  return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };
}

/* ========================================================================================================================================================================= */

// Function to get debt token info:
export const addDebtToken = async (chain: Chain, location: string, address: Address, rawBalance: number, owner: Address): Promise<DebtToken> => {

  // Initializing Token Values:
  let type: TokenType = 'debt';
  let status: TokenStatus = 'borrowed';
  let symbol = '';
  let decimals = 18;
  let logo: URL;

  // Finding Token Info:
  if(address.toLowerCase() === defaultAddress) {
    if(chain === 'bsc') {
      symbol = 'BNB';
    } else if(chain === 'poly') {
      symbol = 'MATIC';
    } else if(chain === 'cronos') {
      symbol = 'CRO';
    } else {
      symbol = chain.toUpperCase();
    }
    logo = getTokenLogo(chain, symbol);
  } else {
    let token = getTrackedTokenInfo(chain, address);
    if(token) {
      symbol = token.symbol;
      decimals = token.decimals;
      logo = token.logo;
    } else {
      symbol = await query(chain, address, minABI, 'symbol', []);
      decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
      logo = getTokenLogo(chain, symbol);
    }
  }

  // Finding Missing Token Info:
  let balance = rawBalance / (10 ** decimals);
  let price = await getTokenPrice(chain, address, decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get derivative/composite token info:
export const addXToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address, underlyingAddress: Address, underlyingRawBalance: number): Promise<XToken> => {

  // Initializing Token Values:
  let type: TokenType = 'xToken';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let underlyingSymbol = '';
  let underlyingDecimals = 18;
  let underlyingLogo: URL;

  // Finding Token Logo:
  let logo = getTokenLogo(chain, symbol);

  // Finding Underlying Token Info:
  let token = getTrackedTokenInfo(chain, address);
  if(token) {
    underlyingSymbol = token.symbol;
    underlyingDecimals = token.decimals;
    underlyingLogo = token.logo;
  } else {
    underlyingSymbol = await query(chain, underlyingAddress, minABI, 'symbol', []);
    underlyingDecimals = parseInt(await query(chain, underlyingAddress, minABI, 'decimals', []));
    underlyingLogo = getTokenLogo(chain, underlyingSymbol);
  }

  // Underlying Token:
  let underlyingToken: PricedToken = {
    symbol: underlyingSymbol,
    address: underlyingAddress,
    balance: underlyingRawBalance / (10 ** underlyingDecimals),
    price: await getTokenPrice(chain, underlyingAddress, underlyingDecimals),
    logo: underlyingLogo
  }

  return { type, chain, location, status, owner, symbol, address, balance, logo, underlyingToken };
}

/* ========================================================================================================================================================================= */

// Function to get tracked tokens:
export const getTokens = (chain: Chain) => {

  // Selecting Token Data:
  let data: ChainTokenData;
  if(chain === 'eth') {
    data = eth_data;
  } else if(chain === 'bsc') {
    data = bsc_data;
  } else if(chain === 'poly') {
    data = poly_data;
  } else if(chain === 'ftm') {
    data = ftm_data;
  } else if(chain === 'avax') {
    data = avax_data;
  } else if(chain === 'one') {
    data = one_data;
  } else if(chain === 'cronos') {
    data = cronos_data;
  } else {
    return [];
  }

  return data.tokens;
}

/* ========================================================================================================================================================================= */

// Function to get a token's logo:
const getTokenLogo = (chain: Chain, symbol: string) => {

  // Initializing Default Token Logo:
  let logo = defaultTokenLogo;

  // Selecting Token Data:
  let data: ChainTokenData;
  if(chain === 'eth') {
    data = eth_data;
  } else if(chain === 'bsc') {
    data = bsc_data;
  } else if(chain === 'poly') {
    data = poly_data;
  } else if(chain === 'ftm') {
    data = ftm_data;
  } else if(chain === 'avax') {
    data = avax_data;
  } else if(chain === 'one') {
    data = one_data;
  } else if(chain === 'cronos') {
    data = cronos_data;
  } else {
    return logo;
  }

  // Finding Token Logo:
  let trackedToken = data.tokens.find(token => token.symbol === symbol);
  if(trackedToken) {
    logo = trackedToken.logo;
  } else {
    let token = data.logos.find(i => i.symbol === symbol);
    if(token) {
      logo = token.logo;
    }
  }

  return logo;
}

/* ========================================================================================================================================================================= */

// Function to get tracked token info:
const getTrackedTokenInfo = (chain: Chain, address: Address) => {

  // Initializations:
  let foundToken: TokenData | undefined;
  let data: ChainTokenData;

  // Selecting Token Data:
  if(chain === 'eth') {
    data = eth_data;
  } else if(chain === 'bsc') {
    data = bsc_data;
  } else if(chain === 'poly') {
    data = poly_data;
  } else if(chain === 'ftm') {
    data = ftm_data;
  } else if(chain === 'avax') {
    data = avax_data;
  } else if(chain === 'one') {
    data = one_data;
  } else if(chain === 'cronos') {
    data = cronos_data;
  } else {
    return undefined;
  }

  // Finding Token:
  foundToken = data.tokens.find(token => token.address.toLowerCase() === address.toLowerCase());

  return foundToken;
}

/* ========================================================================================================================================================================= */

// Function to get a token's current price:
export const getTokenPrice = async (chain: Chain, address: Address, decimals: number): Promise<number> => {

  // Initializations:
  let priceFound = false;
  let apiQuery: URL;

  // Fetching CoinGecko Price:
  if(address === defaultAddress) {
    apiQuery = `https://api.coingecko.com/api/v3/simple/price/?ids=${chains[chain].nativeID}&vs_currencies=usd`;
  } else {
    apiQuery = `https://api.coingecko.com/api/v3/simple/token_price/${chains[chain].cgID}?contract_addresses=${address}&vs_currencies=usd`;
  }
  try {
    let response = (await axios.get(apiQuery)).data;
    let tokens = Object.keys(response);
    if(tokens.length != 0) {
      priceFound = true;
      return response[tokens[0]].usd;
    }
  } catch {}

  // Fetching 1Inch Price:
  if(!priceFound && chains[chain].inch) {
    if(address.toLowerCase() === chains[chain].usdc) {
      return 1;
    } else {
      apiQuery = `https://api.1inch.exchange/v4.0/${chains[chain].id}/quote?fromTokenAddress=${address}&toTokenAddress=${chains[chain].usdc}&amount=${10 ** decimals}`;
      try {
        let response = (await axios.get(apiQuery)).data;
        if(response.protocols.length < 4) {
          priceFound = true;
          return response.toTokenAmount / (10 ** chains[chain].usdcDecimals);
        }
      } catch {}
    }
  }

  // Fetching ParaSwap Price:
  if(!priceFound && chains[chain].paraswap) {
    if(address.toLowerCase() === chains[chain].usdc) {
      return 1;
    } else {
      apiQuery = `https://apiv5.paraswap.io/prices?srcToken=${address}&srcDecimals=${decimals}&destToken=${chains[chain].usdc}&destDecimals=${chains[chain].usdcDecimals}&amount=${10 ** decimals}&side=SELL&network=${chains[chain].id}`;
      try {
        let response = (await axios.get(apiQuery)).data;
        let results = Object.keys(response);
        if(results.length != 0) {
          priceFound = true;
          return response[results[0]].destAmount / (10 ** chains[chain].usdcDecimals);
        }
      } catch {}
    }
  }

  // Polygon Redirections:
  if(chain === 'poly') {
    if(address.toLowerCase() === '0x7BDF330f423Ea880FF95fC41A280fD5eCFD3D09f'.toLowerCase()) { // EURT
      return getTokenPrice('eth', '0xc581b735a1688071a1746c968e0798d642ede491', 6);
    }
  }

  // Fantom Redirections:
  if(chain === 'ftm') {
    if(address.toLowerCase() === '0xb3654dc3d10ea7645f8319668e8f54d2574fbdc8'.toLowerCase()) { // LINK
      return getTokenPrice('eth', '0x514910771af9ca656af840dff83e8264ecf986ca', 18);
    } else if(address.toLowerCase() === '0x8d11ec38a3eb5e956b052f67da8bdc9bef8abf3e'.toLowerCase()) { // DAI
      return getTokenPrice('eth', '0x6b175474e89094c44da98b954eedeac495271d0f', 18);
    } else if(address.toLowerCase() === '0x049d68029688eabf473097a2fc38ef61633a3c7a'.toLowerCase()) { // fUSDT
      return getTokenPrice('eth', '0xdac17f958d2ee523a2206206994597c13d831ec7', 6);
    } else if(address.toLowerCase() === '0xDBf31dF14B66535aF65AaC99C32e9eA844e14501'.toLowerCase()) { // renBTC
      return getTokenPrice('eth', '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', 8);
    } else if(address.toLowerCase() === '0xc931f61b1534eb21d8c11b24f3f5ab2471d4ab50'.toLowerCase()) { // BUSD
      return getTokenPrice('bsc', '0xe9e7cea3dedca5984780bafc599bd69add087d56', 18);
    } else if(address.toLowerCase() === '0x3D8f1ACCEe8e263F837138829B6C4517473d0688'.toLowerCase()) { // fWINGS
      return getTokenPrice('bsc', '0x0487b824c8261462f88940f97053e65bdb498446', 18);
    }
  }

  // Avalanche Redirections:
  if(chain === 'avax') {
    if(address.toLowerCase() === '0x4f60a160D8C2DDdaAfe16FCC57566dB84D674BD6'.toLowerCase()) { // JEWEL
      return getTokenPrice('one', '0x72cb10c6bfa5624dd07ef608027e366bd690048f', 18);
    }
  }

  // Harmony Redirections:
  if(chain === 'one') {
    if(address.toLowerCase() === '0xcf664087a5bb0237a0bad6742852ec6c8d69a27a'.toLowerCase()) { // WONE
      return getTokenPrice('one', defaultAddress, 18);
    } else if(address.toLowerCase() === '0x224e64ec1bdce3870a6a6c777edd450454068fec'.toLowerCase()) { // UST
      return getTokenPrice('eth', '0xa47c8bf37f92abed4a126bda807a7b7498661acd', 18);
    } else if(address.toLowerCase() === '0x783ee3e955832a3d52ca4050c4c251731c156020'.toLowerCase()) { // bscETH
      return getTokenPrice('eth', defaultAddress, 18);
    } else if(address.toLowerCase() === '0x0ab43550a6915f9f67d0c454c2e90385e6497eaa'.toLowerCase()) { // bscBUSD
      return getTokenPrice('bsc', '0xe9e7cea3dedca5984780bafc599bd69add087d56', 18);
    } else if(address.toLowerCase() === '0x44ced87b9f1492bf2dcf5c16004832569f7f6cba'.toLowerCase()) { // bscUSDC
      return 1;
    } else if(address.toLowerCase() === '0x9a89d0e1b051640c6704dde4df881f73adfef39a'.toLowerCase()) { // bscUSDT
      return getTokenPrice('eth', '0xdac17f958d2ee523a2206206994597c13d831ec7', 6);
    } else if(address.toLowerCase() === '0x08cb2917245bbe75c8c9c6dc4a7b3765dae02b31'.toLowerCase()) { // bscDOT
      return getTokenPrice('bsc', '0x7083609fce4d1d8dc0c979aab8c869ea2c873402', 18);
    } else if(address.toLowerCase() === '0x6e7be5b9b4c9953434cd83950d61408f1ccc3bee'.toLowerCase()) { // bscMATIC
      return getTokenPrice('poly', defaultAddress, 18);
    } else if(address.toLowerCase() === '0x7a791e76bf4d4f3b9b492abb74e5108180be6b5a'.toLowerCase()) { // 1LINK
      return getTokenPrice('eth', '0x514910771af9ca656af840dff83e8264ecf986ca', 18);
    } else if(address.toLowerCase() === '0x352cd428efd6f31b5cae636928b7b84149cf369f'.toLowerCase()) { // 1CRV
      return getTokenPrice('eth', '0xD533a949740bb3306d119CC777fa900bA034cd52', 18);
    }
  }

  // Cronos Redirections:
  if(chain === 'cronos') {
    if(address.toLowerCase() === '0xbed48612bc69fa1cab67052b42a95fB30c1bcfee'.toLowerCase()) { // SHIB
      return getTokenPrice('eth', '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', 18);
    }
  }
  
  // Logging tokens with no working price feed for debugging purposes:
  console.error(`${chain.toUpperCase()}: Token Price Not Found - ${address}`);

  return 0;
}

/* ========================================================================================================================================================================= */

// Function to get the transaction history of a wallet address:
export const getTXs = async (chain: Chain, address: Address, last50?: boolean) => {

  // Initializations:
  let txs: (TransferTX | ApprovalTX)[] = [];
  let page = 0;
  let hasNextPage = false;
  let nullEventNativeSwapTXs: Hash[] = [];

  // Fetching TXs:
  do {
    let response;
    let errors = 0;
    while(!response && errors < 3) {
      try {
        response = (await axios.get(`https://api.covalenthq.com/v1/${chains[chain].id}/address/${address}/transactions_v2/?page-size=${last50 ? 50 : 1000}&page-number=${page++}&key=${keys.ckey}`)).data;
        if(!response.error) {
          last50 ? hasNextPage = false : hasNextPage = response.data.pagination.has_more;
          let promises = response.data.items.map((tx: any) => (async () => {
            if(tx.successful) {
    
              // Native Transfer TXs:
              if(parseInt(tx.value) > 0) {
                txs.push({
                  wallet: address,
                  chain: chain,
                  type: 'transfer',
                  hash: tx.tx_hash,
                  contract: tx.to_address === address.toLowerCase() ? await isContract(chain, tx.from_address) : await isContract(chain, tx.to_address),
                  time: (new Date(tx.block_signed_at)).getTime() / 1000,
                  direction: tx.to_address === address.toLowerCase() ? 'in' : 'out',
                  from: tx.from_address,
                  to: tx.to_address,
                  token: {
                    address: defaultAddress,
                    symbol: chains[chain].token,
                    logo: getTokenLogo(chain, chains[chain].token)
                  },
                  value: parseInt(tx.value) / (10 ** 18),
                  fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                  nativeToken: chains[chain].token
                });

                // Wrapping TXs:
                if(tx.to_address.toLowerCase() === chains[chain].wrappedToken) {
                  txs.push({
                    wallet: address,
                    chain: chain,
                    type: 'transfer',
                    hash: tx.tx_hash,
                    contract: true,
                    time: (new Date(tx.block_signed_at)).getTime() / 1000,
                    direction: 'in',
                    from: tx.to_address,
                    to: tx.from_address,
                    token: {
                      address: chains[chain].wrappedToken,
                      symbol: 'W' + chains[chain].token,
                      logo: getTokenLogo(chain, 'W' + chains[chain].token)
                    },
                    value: parseInt(tx.value) / (10 ** 18),
                    fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                    nativeToken: chains[chain].token
                  });
                }
    
              // Approval TXs:
              } else if(tx.log_events.length < 3) {
                tx.log_events.forEach((event: any) => {
                  if(event.decoded != null && event.sender_contract_ticker_symbol != null) {
                    if(event.decoded.name === 'Approval') {
                      if(event.decoded.params[0].name === 'owner' && event.decoded.params[0].value === address.toLowerCase()) {
                        txs.push({
                          wallet: address,
                          chain: chain,
                          type: parseInt(event.decoded.params[2].value) > 0 ? 'approve' : 'revoke',
                          hash: tx.tx_hash,
                          time: (new Date(tx.block_signed_at)).getTime() / 1000,
                          direction: 'out',
                          token: {
                            address: event.sender_address,
                            symbol: event.sender_contract_ticker_symbol,
                            logo: getTokenLogo(chain, event.sender_contract_ticker_symbol)
                          },
                          fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                          nativeToken: chains[chain].token
                        });
                      }
                    }
                  }
                });
              }
    
              // Other TXs:
              let log_promises = tx.log_events.map((event: any) => (async () => {
                if(event.decoded != null) {
    
                  // Token Transfers:
                  if(event.decoded.name === 'Transfer') {
    
                    // Filtering Blacklisted Tokens:
                    if(!isBlacklisted(chain, event.sender_address) && event.sender_contract_ticker_symbol != null) {
    
                      // Outbound:
                      if(event.decoded.params[0].name === 'from' && event.decoded.params[0].value === address.toLowerCase() && event.decoded.params[2].decoded) {
                        if(parseInt(event.decoded.params[2].value) > 0) {
                          txs.push({
                            wallet: address,
                            chain: chain,
                            type: 'transfer',
                            hash: tx.tx_hash,
                            contract: await isContract(chain, event.decoded.params[1].value),
                            time: (new Date(tx.block_signed_at)).getTime() / 1000,
                            direction: 'out',
                            from: address,
                            to: event.decoded.params[1].value,
                            token: {
                              address: event.sender_address,
                              symbol: event.sender_contract_ticker_symbol,
                              logo: getTokenLogo(chain, event.sender_contract_ticker_symbol)
                            },
                            value: parseInt(event.decoded.params[2].value) / (10 ** event.sender_contract_decimals),
                            fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                            nativeToken: chains[chain].token
                          });
                        }
                      
                      // Inbound:
                      } else if(event.decoded.params[1].name === 'to' && event.decoded.params[1].value === address.toLowerCase() && event.decoded.params[2].decoded) {
                        if(parseInt(event.decoded.params[2].value) > 0) {
                          txs.push({
                            wallet: address,
                            chain: chain,
                            type: 'transfer',
                            hash: tx.tx_hash,
                            contract: await isContract(chain, event.decoded.params[0].value),
                            time: (new Date(tx.block_signed_at)).getTime() / 1000,
                            direction: 'in',
                            from: event.decoded.params[0].value,
                            to: address,
                            token: {
                              address: event.sender_address,
                              symbol: event.sender_contract_ticker_symbol,
                              logo: getTokenLogo(chain, event.sender_contract_ticker_symbol)
                            },
                            value: parseInt(event.decoded.params[2].value) / (10 ** event.sender_contract_decimals),
                            fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                            nativeToken: chains[chain].token
                          });
                        }
                      }
                    }
                  
                  // Native Router Swaps:
                  } else if(event.decoded.name === 'Withdrawal' && event.decoded.params[0].value === tx.to_address) {
                    let nullEvent = tx.log_events.find((event: any) => event.decoded === null);
                    if(nullEvent) {
                      if(!nullEventNativeSwapTXs.includes(nullEvent.tx_hash)) {
                        nullEventNativeSwapTXs.push(nullEvent.tx_hash);
    
                        // ParaSwap TXs:
                        if(nullEvent.raw_log_topics[0] === '0x680ad12fcfabafe9b1f08214caef968eb651cf010bee4a2824adfaec965903e8' && nullEvent.raw_log_topics[3].endsWith('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee')) {
                          txs.push({
                            wallet: address,
                            chain: chain,
                            type: 'transfer',
                            hash: tx.tx_hash,
                            contract: true,
                            time: (new Date(tx.block_signed_at)).getTime() / 1000,
                            direction: 'in',
                            from: tx.to_address,
                            to: tx.from_address,
                            token: {
                              address: defaultAddress,
                              symbol: chains[chain].token,
                              logo: getTokenLogo(chain, chains[chain].token)
                            },
                            value: ((parseInt(nullEvent.raw_log_data.slice(194, 258), 16) + parseInt(nullEvent.raw_log_data.slice(258, 322), 16)) / 2) / (10 ** 18),
                            fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                            nativeToken: chains[chain].token
                          });
                        }
                      }
                    } else {
                      txs.push({
                        wallet: address,
                        chain: chain,
                        type: 'transfer',
                        hash: tx.tx_hash,
                        contract: true,
                        time: (new Date(tx.block_signed_at)).getTime() / 1000,
                        direction: 'in',
                        from: tx.to_address,
                        to: tx.from_address,
                        token: {
                          address: defaultAddress,
                          symbol: chains[chain].token,
                          logo: getTokenLogo(chain, chains[chain].token)
                        },
                        value: parseInt(event.decoded.params[1].value) / (10 ** 18),
                        fee: (tx.gas_spent * tx.gas_price) / (10 ** 18),
                        nativeToken: chains[chain].token
                      });
                    }
                  }
                }
              })());
              await Promise.all(log_promises);
            }
          })());
          await Promise.all(promises);
        } else {
          hasNextPage = false;
        }
      } catch(err: any) {
        if(++errors > 2) {
          console.error(`Covalent API Error: ${err.response.status}`);
        }
        hasNextPage = false;
      }
    }
  } while(hasNextPage);

  return txs.sort((a, b) => a.time - b.time);
}

/* ========================================================================================================================================================================= */

// Function to get the simple/quick transaction history of a wallet address:
export const getSimpleTXs = async (chain: Chain, address: Address) => {

  // Initializations:
  let txs: SimpleTX[] = [];
  let page = 0;
  let hasNextPage = false;

  // Fetching TXs:
  do {
    let response;
    let errors = 0;
    while(!response && errors < 3) {
      try {
        response = (await axios.get(`https://api.covalenthq.com/v1/${chains[chain].id}/address/${address}/transactions_v2/?no-logs=true&page-size=1000&page-number=${page++}&key=${keys.ckey}`)).data;
        if(!response.error) {
          hasNextPage = response.data.pagination.has_more;
          let promises = response.data.items.map((tx: any) => (async () => {
            txs.push({
              wallet: address,
              chain: chain,
              hash: tx.tx_hash,
              time: (new Date(tx.block_signed_at)).getTime() / 1000,
              direction: tx.from_address === address.toLowerCase() ? 'out' : 'in',
              fee: tx.gas_price < 10000000000000 ? (tx.gas_spent * tx.gas_price) / (10 ** 18) : tx.gas_price / (10 ** 18) // Workaround regarding Covalent gas pricing bugs - remove at a later date.
            });
          })());
          await Promise.all(promises);
        } else {
          hasNextPage = false;
        }
      } catch(err: any) {
        if(++errors > 2) {
          console.error(`Covalent API Error: ${err.response.status}`);
        }
        hasNextPage = false;
      }
    }
  } while(hasNextPage);

  return txs.sort((a, b) => a.time - b.time);
}

/* ========================================================================================================================================================================= */

// Function to get TXs for tax reporting:
export const getTaxTXs = async (chain: Chain, wallet: Address) => {

  // Initializations:
  let taxTXs: (TaxTransferTX | TaxApprovalTX)[] = [];
  let tokens: Set<Address> = new Set();
  let dates = { start: 9999999999, end: 0 }

  // Fetching TXs:
  let txs = await getTXs(chain, wallet);

  // Collecting Data From TXs:
  let promises = txs.map(tx => (async () => {
    if(tx.type === 'transfer') {
      if(!tx.token.symbol.includes('LP') && !ignoreTokenPrices[chain].includes(tx.token.symbol) && !tokens.has(tx.token.address)) {
        tokens.add(tx.token.address);
      }
      if(tx.time < dates.start) {
        dates.start = tx.time;
      }
      if(tx.time > dates.end) {
        dates.end = tx.time;
      }
    }
  })());
  await Promise.all(promises);

  // Fetching Token Price Histories:
  let tokenPrices = await getTokenPriceHistories(chain, tokens, dates);

  // Adding TX Token Prices:
  txs.forEach((tx: any) => {

    // Native Token:
    if(tokenPrices[defaultAddress].length > 0) {
      let txDate = Math.max(...(tokenPrices[defaultAddress].filter(entry => entry.time < tx.time).map(i => i.time)));
      let foundEntry = tokenPrices[defaultAddress].find(entry => entry.time === txDate);
      foundEntry ? tx.nativeTokenPrice = foundEntry.price : tx.nativeTokenPrice = 0;
    } else {
      tx.nativeTokenPrice = 0;
    }

    // Other Tokens:
    if(tx.type === 'transfer') {
      if(tokenPrices[tx.token.address]) {
        if(tokenPrices[tx.token.address].length > 0) {
          let txDate = Math.max(...(tokenPrices[tx.token.address].filter(entry => entry.time < tx.time).map(i => i.time)));
          let foundEntry = tokenPrices[tx.token.address].find(entry => entry.time === txDate);
          foundEntry ? tx.token.price = foundEntry.price : tx.token.price = 0;
        } else {
          tx.token.price = 0;
        }
      } else {
        tx.token.price = 0;
      }
    }
    
    taxTXs.push(tx);
  });

  return taxTXs.sort((a, b) => a.time - b.time);
}

/* ========================================================================================================================================================================= */

// Function to check if an address is a contract:
export const isContract = async (chain: Chain, address: Address) => {
  try {
    let ethers_provider = new ethers.providers.JsonRpcProvider(rpcs[chain][0]);
    let bytecode = await ethers_provider.getCode(address);
    return bytecode === '0x' ? false : true;
  } catch {
    let ethers_provider = new ethers.providers.JsonRpcProvider(rpcs[chain][1]);
    let bytecode = await ethers_provider.getCode(address);
    return bytecode === '0x' ? false : true;
  }
}

/* ========================================================================================================================================================================= */

// Function to check if a token is blacklisted:
export const isBlacklisted = (chain: Chain, address: Address) => {

  // Checking Blacklists:
  if(chain === 'eth') {
    if(eth_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'bsc') {
    if(bsc_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'poly') {
    if(poly_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'ftm') {
    if(ftm_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'avax') {
    if(avax_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'one') {
    if(one_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  } else if(chain === 'cronos') {
    if(cronos_data.blacklist.includes(address.toLowerCase() as Address)) {
      return true;
    }
  }

  return false;
}

/* ========================================================================================================================================================================= */

// Function to get many tokens' historical prices:
export const getTokenPriceHistories = async (chain: Chain, tokens: Set<Address>, dates: { start: number, end: number }) => {

  // Initializations:
  let tokenPrices: Record<Address, { time: number, price: number }[]> = {};
  let tokenString = '';

  // Adding Native Token:
  if(!tokens.has(defaultAddress)) {
    tokens.add(defaultAddress);
  }

  // Formatting Token String:
  let tokenArray = Array.from(tokens);
  tokenArray.forEach(token => {
    if(chain === 'ftm' && token === defaultAddress) {
      tokenString += '0x4e15361fd6b4bb609fa63c81a2be19d873717870,';
    } else if(chain === 'poly' && token === defaultAddress) {
      tokenString += '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270,';
    } else {
      tokenString += token + ',';
    }
  });
  tokenString = tokenString.slice(0, -1);

  // Fetching Token Prices:
  let response;
  let errors = 0;
  while(!response && errors < 3) {
    try {
      response = (await axios.get(`https://api.covalenthq.com/v1/pricing/historical_by_addresses_v2/${chains[chain].id}/USD/${tokenString}/?quote-currency=USD&format=JSON&from=${formatDate(dates.start)}&to=${formatDate(dates.end)}&page-size=9999&prices-at-asc=true&key=${keys.ckey}`)).data;
      if(!response.error) {
        response.data.forEach((token: any) => {
          if(chain === 'bsc') {
            if(token.contract_address === '0xb8c77482e45f1f44de1745f52c74426c631bdd52') {
              token.contract_address = defaultAddress;
            }
          } else if(chain === 'ftm') {
            if(token.contract_address === '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83') {
              tokenPrices[defaultAddress] = [];
              token.prices.forEach((entry: any) => {
                tokenPrices[defaultAddress].push({ time: (new Date(entry.date).getTime() / 1000), price: entry.price });
              });
            }
          } else if(chain == 'poly') {
            if(token.contract_address === '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270') {
              tokenPrices[defaultAddress] = [];
              token.prices.forEach((entry: any) => {
                tokenPrices[defaultAddress].push({ time: (new Date(entry.date).getTime() / 1000), price: entry.price });
              });
            }
          }
          tokenPrices[token.contract_address] = [];
          token.prices.forEach((entry: any) => {
            tokenPrices[token.contract_address].push({ time: (new Date(entry.date).getTime() / 1000), price: entry.price });
          });
        });
      }
    } catch(err: any) {
      if(++errors > 2) {
        console.error(`Covalent API Error: ${err.response.status}`);
      }
    }
  }

  return tokenPrices;
}

// Function to format a date for Covalent Query:
const formatDate = (rawDate: number): string => {
  let date = new Date((rawDate * 1000));
  return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
}

// Function to pad number if necessary for Covalent Query:
const pad = (num: number): string => {
  let str = num.toString();
  if(str.length < 2) {
    return '0' + str;
  } else {
    return str;
  }
}

/* ========================================================================================================================================================================= */

// Function to get S4D token info:
export const addS4DToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let controller = await query(chain, address, snowball.s4dABI, 'owner', []);
  let price = parseInt(await query(chain, controller, snowball.s4dControllerABI, 'getVirtualPrice', [])) / (10 ** decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Trader Joe token info (xJOE):
export const addTraderJoeToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let underlyingToken = await query(chain, address, traderjoe.joeABI, 'joe', []);
  let joeStaked = parseInt(await query(chain, underlyingToken, minABI, 'balanceOf', [address]));
  let xjoeSupply = parseInt(await query(chain, address, minABI, 'totalSupply', []));
  let multiplier = joeStaked / xjoeSupply;
  let price = multiplier * (await getTokenPrice(chain, underlyingToken, decimals));

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Aave BLP token info:
export const addAaveBLPToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<LPToken> => {

  // Initializing Token Values:
  let type: TokenType = 'lpToken';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  address = await query(chain, address, aave.lpABI, 'bPool', []);

  // Finding LP Token Info:
  let lpTokenSupply = await query(chain, address, balancer.tokenABI, 'totalSupply', []) / (10 ** decimals);
  let lpTokenAddresses = await query(chain, address, balancer.tokenABI, 'getCurrentTokens', []);
  let address0 = lpTokenAddresses[0];
  let address1 = lpTokenAddresses[1];
  let supply0 = await query(chain, address, balancer.tokenABI, 'getBalance', [address0]) / (10 ** decimals);
  let supply1 = await query(chain, address, balancer.tokenABI, 'getBalance', [address1]) / (10 ** decimals);
  let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
  let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
  let symbol0 = await query(chain, address0, minABI, 'symbol', []);
  let symbol1 = await query(chain, address1, minABI, 'symbol', []);

  // First Paired Token:
  let token0: PricedToken = {
    symbol: symbol0,
    address: address0,
    balance: supply0 * (balance / lpTokenSupply),
    price: await getTokenPrice(chain, address0, decimals0),
    logo: getTokenLogo(chain, symbol0)
  }

  // Second Paired Token:
  let token1: PricedToken = {
    symbol: symbol1,
    address: address1,
    balance: supply1 * (balance / lpTokenSupply),
    price: await getTokenPrice(chain, address1, decimals1),
    logo: getTokenLogo(chain, symbol1)
  }

  return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };
}

/* ========================================================================================================================================================================= */

// Function to get 4Belt token info:
export const add4BeltToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = '4Belt';
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);
  let price = 1;

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Belt token info:
export const addBeltToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let multiplier = parseInt(await query(chain, address, belt.tokenABI, 'getPricePerFullShare', [])) / (10 ** decimals);
  let underlyingToken = await query(chain, address, belt.tokenABI, 'token', []);
  let price = multiplier * (await getTokenPrice(chain, underlyingToken, decimals));

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Alpaca token info:
export const addAlpacaToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let totalToken = parseInt(await query(chain, address, alpaca.tokenABI, 'totalToken', []));
  let totalSupply = parseInt(await query(chain, address, minABI, 'totalSupply', []));
  let multiplier = totalToken / totalSupply;
  let underlyingToken = await query(chain, address, alpaca.tokenABI, 'token', []);
  let price = multiplier * (await getTokenPrice(chain, underlyingToken, decimals));

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Curve token info:
export const addCurveToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token | LPToken> => {
  
  // Ethereum Token:
  if(chain === 'eth') {

    // Generic Token Values:
    let registry: Address = '0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5';
    let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
    let balance = rawBalance / (10 ** decimals);
    let symbol = await query(chain, address, minABI, 'symbol', []);
    let lpTokenSupply = await query(chain, address, minABI, 'totalSupply', []) / (10 ** decimals);
    let poolAddress = await query(chain, registry, curve.registryABI, 'get_pool_from_lp_token', [address]);
    let tokens = (await query(chain, registry, curve.registryABI, 'get_underlying_coins', [poolAddress])).filter((token: Address) => token != zero);
    let reserves = (await query(chain, registry, curve.registryABI, 'get_underlying_balances', [poolAddress])).filter((balance: number) => balance != 0);
    let multiplier = parseInt(await query(chain, registry, curve.registryABI, 'get_virtual_price_from_lp_token', [address])) / (10 ** decimals);

    // Function to redirect synthetic asset price fetching:
    const getPrice = async (chain: Chain, address: Address, decimals: number): Promise<number> => {
      if(address.toLowerCase() === '0xbBC455cb4F1B9e4bFC4B73970d360c8f032EfEE6'.toLowerCase()) { // sLINK
        return await getTokenPrice(chain, '0x514910771af9ca656af840dff83e8264ecf986ca', decimals);
      } else if(address.toLowerCase() === '0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6'.toLowerCase()) { // sBTC
        return await getTokenPrice(chain, '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', decimals);
      } else if(address.toLowerCase() === '0xd71ecff9342a5ced620049e616c5035f1db98620'.toLowerCase()) { // sEUR
        return await getTokenPrice(chain, '0xdb25f211ab05b1c97d595516f45794528a807ad8', decimals);
      } else {
        return await getTokenPrice(chain, address, decimals);
      }
    }

    // 3+ Asset Tokens:
    if(tokens.length > 2) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let price = 0;
      for(let i = 0; i < tokens.length; i++) {
        let tokenDecimals = parseInt(await query(chain, tokens[i], minABI, 'decimals', []));
        let tokenPrice = await getPrice(chain, tokens[i], tokenDecimals);
        price += (parseInt(reserves[i]) / (10 ** tokenDecimals)) * tokenPrice;
      }
      price /= lpTokenSupply;
      price *= multiplier;

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // Standard LP Tokens:
    } else if(tokens.length === 2) {

      // Initializing Token Values:
      let type: TokenType = 'lpToken';

      // Finding LP Token Info:
      let address0 = tokens[0];
      let address1 = tokens[1];
      let decimals0 = 18;
      let decimals1 = 18;
      let symbol0 = '';
      let symbol1 = '';
      if(tokens[0].toLowerCase() === defaultAddress) {
        symbol0 = 'ETH';
      } else {
        decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
        symbol0 = await query(chain, address0, minABI, 'symbol', []);
      }
      if(tokens[1].toLowerCase() === defaultAddress) {
        symbol1 = 'ETH';
      } else {
        decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
        symbol1 = await query(chain, address1, minABI, 'symbol', []);
      }

      // First Paired Token:
      let token0: PricedToken = {
        symbol: symbol0,
        address: address0,
        balance: (parseInt(reserves[0]) / (10 ** 18)) * (balance / lpTokenSupply),
        price: await getTokenPrice(chain, address0, decimals0),
        logo: getTokenLogo(chain, symbol0)
      }

      // Second Paired Token:
      let token1: PricedToken = {
        symbol: symbol1,
        address: address1,
        balance: (parseInt(reserves[1]) / (10 ** 18)) * (balance / lpTokenSupply),
        price: await getTokenPrice(chain, address1, decimals1),
        logo: getTokenLogo(chain, symbol1)
      }

      return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };
    }

  // Polygon Token:
  } else if(chain === 'poly') {

    // Generic Token Values:
    let symbol = await query(chain, address, minABI, 'symbol', []);
    let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
    let balance = rawBalance / (10 ** decimals);
    
    // crvUSDBTCETH (Atricrypto V3):
    if(address.toLowerCase() === '0xdAD97F7713Ae9437fa9249920eC8507e5FbB23d3'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let lpTokenSupply = await query(chain, address, minABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.polyTokenABI, 'minter', []);
      let multiplier = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);
      let address0 = await query(chain, minter, curve.minterABI, 'coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'coins', [1]);
      let address2 = await query(chain, minter, curve.minterABI, 'coins', [2]);
      let token0 = await query(chain, address0, curve.polyTokenABI, 'minter', []);
      let token1 = await query(chain, address1, curve.intermediaryABI, 'UNDERLYING_ASSET_ADDRESS', []);
      let token2 = await query(chain, address2, curve.intermediaryABI, 'UNDERLYING_ASSET_ADDRESS', []);
      let decimals0 = 18;
      let decimals1 = parseInt(await query(chain, token1, minABI, 'decimals', []));
      let decimals2 = parseInt(await query(chain, token2, minABI, 'decimals', []));
      let supply0 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [0])) / (10 ** decimals0);
      let supply1 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [1])) / (10 ** decimals1);
      let supply2 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [2])) / (10 ** decimals2);
      let price0 = parseInt(await query(chain, token0, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals0);
      let price1 = await getTokenPrice(chain, token1, decimals1);
      let price2 = await getTokenPrice(chain, token2, decimals2);
      let price = multiplier * (((supply0 * price0) + (supply1 * price1) + (supply2 * price2)) / lpTokenSupply);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // am3CRV (Aave):
    } else if(address.toLowerCase() === '0xE7a24EF0C5e95Ffb0f6684b813A78F2a3AD7D171'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let minter = await query(chain, address, curve.polyTokenABI, 'minter', []);
      let price = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // btcCRV (Ren):
    } else if(address.toLowerCase() === '0xf8a57c1d3b9629b77b6726a042ca48990A84Fb49'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'lpToken';

      // Finding LP Token Info:
      let lpTokenSupply = await query(chain, address, lpABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.polyTokenABI, 'minter', []);
      let address0 = await query(chain, minter, curve.minterABI, 'underlying_coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'underlying_coins', [1]);
      let symbol0 = await query(chain, address0, minABI, 'symbol', []);
      let symbol1 = await query(chain, address1, minABI, 'symbol', []);
      let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
      let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
      let supply0 = await query(chain, minter, curve.minterABI, 'balances', [0]) / (10 ** decimals);
      let supply1 = await query(chain, minter, curve.minterABI, 'balances', [1]) / (10 ** decimals);

      // First Paired Token:
      let token0: PricedToken = {
        symbol: symbol0,
        address: address0,
        balance: (supply0 * (balance / lpTokenSupply)) / (10 ** decimals0),
        price: await getTokenPrice(chain, address0, decimals0),
        logo: getTokenLogo(chain, symbol0)
      }

      // Second Paired Token:
      let token1: PricedToken = {
        symbol: symbol1,
        address: address1,
        balance: (supply1 * (balance / lpTokenSupply)) / (10 ** decimals1),
        price: await getTokenPrice(chain, address1, decimals1),
        logo: getTokenLogo(chain, symbol1)
      }

      return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };

    // crvEURTUSD (EURtUSD):
    } else if(address.toLowerCase() === '0x600743B1d8A96438bD46836fD34977a00293f6Aa'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let lpTokenSupply = await query(chain, address, minABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.polyTokenABI, 'minter', []);
      let multiplier = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);
      let token0 = await query(chain, minter, curve.minterABI, 'coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'coins', [1]);
      let token1 = await query(chain, address1, curve.polyTokenABI, 'minter', []);
      let decimals0 = parseInt(await query(chain, token0, minABI, 'decimals', []));
      let decimals1 = 18;
      let supply0 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [0])) / (10 ** decimals0);
      let supply1 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [1])) / (10 ** decimals1);
      let price0 = await getTokenPrice(chain, token0, decimals0);
      let price1 = parseInt(await query(chain, token1, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals1);
      let price = multiplier * (((supply0 * price0) + (supply1 * price1)) / lpTokenSupply);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };
    }

  // Fantom Token:
  } else if(chain === 'ftm') {

    // Generic Token Values:
    let symbol = await query(chain, address, minABI, 'symbol', []);
    let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
    let balance = rawBalance / (10 ** decimals);
    
    // DAI+USDC (2pool):
    if(address.toLowerCase() === '0x27E611FD27b276ACbd5Ffd632E5eAEBEC9761E40'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'lpToken';

      // Finding LP Token Info:
      let lpTokenSupply = await query(chain, address, lpABI, 'totalSupply', []) / (10 ** decimals);
      let address0 = await query(chain, address, curve.ftmTokenABI, 'coins', [0]);
      let address1 = await query(chain, address, curve.ftmTokenABI, 'coins', [1]);
      let symbol0 = await query(chain, address0, minABI, 'symbol', []);
      let symbol1 = await query(chain, address1, minABI, 'symbol', []);
      let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
      let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
      let supply0 = await query(chain, address, curve.ftmTokenABI, 'balances', [0]) / (10 ** decimals);
      let supply1 = await query(chain, address, curve.ftmTokenABI, 'balances', [1]) / (10 ** decimals);

      // First Paired Token:
      let token0: PricedToken = {
        symbol: symbol0,
        address: address0,
        balance: (supply0 * (balance / lpTokenSupply)) / (10 ** decimals0),
        price: await getTokenPrice(chain, address0, decimals0),
        logo: getTokenLogo(chain, symbol0)
      }

      // Second Paired Token:
      let token1: PricedToken = {
        symbol: symbol1,
        address: address1,
        balance: (supply1 * (balance / lpTokenSupply)) / (10 ** decimals1),
        price: await getTokenPrice(chain, address1, decimals1),
        logo: getTokenLogo(chain, symbol1)
      }

      return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };

    // fUSDT+DAI+USDC (fUSDT):
    } else if(address.toLowerCase() === '0x92D5ebF3593a92888C25C0AbEF126583d4b5312E'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);
      symbol = 'fUSDTCRV';

      // Finding Token Price:
      let price = parseInt(await query(chain, address, curve.ftmTokenABI, 'get_virtual_price', [])) / (10 ** decimals);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // btcCRV (Ren):
    } else if(address.toLowerCase() === '0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'lpToken';

      // Finding LP Token Info:
      let lpTokenSupply = await query(chain, address, lpABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.ftmTokenABI, 'minter', []);
      let address0 = await query(chain, minter, curve.minterABI, 'coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'coins', [1]);
      let symbol0 = await query(chain, address0, minABI, 'symbol', []);
      let symbol1 = await query(chain, address1, minABI, 'symbol', []);
      let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
      let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
      let supply0 = await query(chain, minter, curve.minterABI, 'balances', [0]) / (10 ** decimals);
      let supply1 = await query(chain, minter, curve.minterABI, 'balances', [1]) / (10 ** decimals);

      // First Paired Token:
      let token0: PricedToken = {
        symbol: symbol0,
        address: address0,
        balance: (supply0 * (balance / lpTokenSupply)) / (10 ** decimals0),
        price: await getTokenPrice(chain, address0, decimals0),
        logo: getTokenLogo(chain, symbol0)
      }

      // Second Paired Token:
      let token1: PricedToken = {
        symbol: symbol1,
        address: address1,
        balance: (supply1 * (balance / lpTokenSupply)) / (10 ** decimals1),
        price: await getTokenPrice(chain, address1, decimals1),
        logo: getTokenLogo(chain, symbol1)
      }

      return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };

    // crv3crypto (Tricrypto):
    } else if(address.toLowerCase() === '0x58e57cA18B7A47112b877E31929798Cd3D703b0f'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let lpTokenSupply = await query(chain, address, minABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.ftmTokenABI, 'minter', []);
      let multiplier = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);
      let token0 = await query(chain, minter, curve.minterABI, 'coins', [0]);
      let token1 = await query(chain, minter, curve.minterABI, 'coins', [1]);
      let token2 = await query(chain, minter, curve.minterABI, 'coins', [2]);
      let decimals0 = parseInt(await query(chain, token0, minABI, 'decimals', []));
      let decimals1 = parseInt(await query(chain, token1, minABI, 'decimals', []));
      let decimals2 = parseInt(await query(chain, token2, minABI, 'decimals', []));
      let supply0 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [0])) / (10 ** decimals0);
      let supply1 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [1])) / (10 ** decimals1);
      let supply2 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [2])) / (10 ** decimals2);
      let price0 = await getTokenPrice(chain, token0, decimals0);
      let price1 = await getTokenPrice(chain, token1, decimals1);
      let price2 = await getTokenPrice(chain, token2, decimals2);
      let price = multiplier * (((supply0 * price0) + (supply1 * price1) + (supply2 * price2)) / lpTokenSupply);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // g3CRV (Geist):
    } else if(address.toLowerCase() === '0xD02a30d33153877BC20e5721ee53DeDEE0422B2F'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let minter = await query(chain, address, curve.ftmTokenABI, 'minter', []);
      let price = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };
    }

  // Avalanche Token:
  } else if(chain === 'avax') {

    // Generic Token Values:
    let symbol = await query(chain, address, minABI, 'symbol', []);
    let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
    let balance = rawBalance / (10 ** decimals);
    
    // crvUSDBTCETH (Atricrypto V2):
    if(address.toLowerCase() === '0x1daB6560494B04473A0BE3E7D83CF3Fdf3a51828'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let lpTokenSupply = await query(chain, address, minABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.avaxTokenABI, 'minter', []);
      let multiplier = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);
      let address0 = await query(chain, minter, curve.minterABI, 'coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'coins', [1]);
      let address2 = await query(chain, minter, curve.minterABI, 'coins', [2]);
      let token0 = await query(chain, address0, curve.avaxTokenABI, 'minter', []);
      let token1 = await query(chain, address1, curve.intermediaryABI, 'UNDERLYING_ASSET_ADDRESS', []);
      let token2 = await query(chain, address2, curve.intermediaryABI, 'UNDERLYING_ASSET_ADDRESS', []);
      let decimals0 = 18;
      let decimals1 = parseInt(await query(chain, token1, minABI, 'decimals', []));
      let decimals2 = parseInt(await query(chain, token2, minABI, 'decimals', []));
      let supply0 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [0])) / (10 ** decimals0);
      let supply1 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [1])) / (10 ** decimals1);
      let supply2 = parseInt(await query(chain, minter, curve.minterABI, 'balances', [2])) / (10 ** decimals2);
      let price0 = parseInt(await query(chain, token0, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals0);
      let price1 = await getTokenPrice(chain, token1, decimals1);
      let price2 = await getTokenPrice(chain, token2, decimals2);
      let price = multiplier * (((supply0 * price0) + (supply1 * price1) + (supply2 * price2)) / lpTokenSupply);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // am3CRV (Aave):
    } else if(address.toLowerCase() === '0x1337BedC9D22ecbe766dF105c9623922A27963EC'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'token';
      let logo = getTokenLogo(chain, symbol);

      // Finding Token Price:
      let minter = await query(chain, address, curve.avaxTokenABI, 'minter', []);
      let price = parseInt(await query(chain, minter, curve.minterABI, 'get_virtual_price', [])) / (10 ** decimals);

      return { type, chain, location, status, owner, symbol, address, balance, price, logo };

    // btcCRV (Ren):
    } else if(address.toLowerCase() === '0xC2b1DF84112619D190193E48148000e3990Bf627'.toLowerCase()) {

      // Initializing Token Values:
      let type: TokenType = 'lpToken';

      // Finding LP Token Info:
      let lpTokenSupply = await query(chain, address, lpABI, 'totalSupply', []) / (10 ** decimals);
      let minter = await query(chain, address, curve.avaxTokenABI, 'minter', []);
      let address0 = await query(chain, minter, curve.minterABI, 'underlying_coins', [0]);
      let address1 = await query(chain, minter, curve.minterABI, 'underlying_coins', [1]);
      let symbol0 = await query(chain, address0, minABI, 'symbol', []);
      let symbol1 = await query(chain, address1, minABI, 'symbol', []);
      let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
      let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));
      let supply0 = await query(chain, minter, curve.minterABI, 'balances', [0]) / (10 ** decimals);
      let supply1 = await query(chain, minter, curve.minterABI, 'balances', [1]) / (10 ** decimals);

      // First Paired Token:
      let token0: PricedToken = {
        symbol: symbol0,
        address: address0,
        balance: (supply0 * (balance / lpTokenSupply)) / (10 ** decimals0),
        price: await getTokenPrice(chain, address0, decimals0),
        logo: getTokenLogo(chain, symbol0)
      }

      // Second Paired Token:
      let token1: PricedToken = {
        symbol: symbol1,
        address: address1,
        balance: (supply1 * (balance / lpTokenSupply)) / (10 ** decimals1),
        price: await getTokenPrice(chain, address1, decimals1),
        logo: getTokenLogo(chain, symbol1)
      }

      return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };
    }
  }

  // No Token Identified:
  console.warn(`Unidentified Curve Token: ${address}`);
  return {
    type: 'token',
    chain: chain,
    location: location,
    status: 'none',
    owner: owner,
    symbol: '???',
    address: address,
    balance: 0,
    price: 0,
    logo: defaultTokenLogo
  }
}

/* ========================================================================================================================================================================= */

// Function to get BZX token info:
export const addBZXToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let multiplier = parseInt(await query(chain, address, bzx.tokenABI, 'tokenPrice', [])) / (10 ** decimals);
  let underlyingToken = await query(chain, address, bzx.tokenABI, 'loanTokenAddress', []);
  let price = multiplier * (await getTokenPrice(chain, underlyingToken, decimals));

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Balancer LP token info:
export const addBalancerToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address, id: Address) => {
  return await addBalancerLikeToken(chain, location, status, address, rawBalance, owner, id, '0xBA12222222228d8Ba445958a75a0704d566BF2C8');
}

// Function to get Balancer-like LP token info:
export const addBalancerLikeToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address, id: Address, vault: Address): Promise<Token | LPToken> => {

  // Generic Token Values:
  let poolInfo = await query(chain, vault, balancer.vaultABI, 'getPoolTokens', [id]);
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let lpTokenSupply = parseInt(await query(chain, address, minABI, 'totalSupply', []));

  // 3+ Asset Tokens:
  if(poolInfo.tokens.length > 2) {

    // Initializing Token Values:
    let type: TokenType = 'token';
    let logo = getTokenLogo(chain, symbol);

    // Finding Token Price:
    let priceSum = 0;
    for(let i = 0; i < poolInfo.tokens.length; i++) {
      let tokenDecimals = parseInt(await query(chain, poolInfo.tokens[i], minABI, 'decimals', []));
      let tokenPrice = await getTokenPrice(chain, poolInfo.tokens[i], tokenDecimals);
      priceSum += (parseInt(poolInfo.balances[i]) / (10 ** tokenDecimals)) * tokenPrice;
    }
    let price = priceSum / (lpTokenSupply / (10 ** decimals));

    return { type, chain, location, status, owner, symbol, address, balance, price, logo };

  // Standard LP Tokens:
  } else if(poolInfo.tokens.length === 2) {

    // Initializing Token Values:
    let type: TokenType = 'lpToken';

    // Finding LP Token Info:
    let address0 = poolInfo.tokens[0];
    let address1 = poolInfo.tokens[1];
    let symbol0 = await query(chain, address0, minABI, 'symbol', []);
    let symbol1 = await query(chain, address1, minABI, 'symbol', []);
    let decimals0 = parseInt(await query(chain, address0, minABI, 'decimals', []));
    let decimals1 = parseInt(await query(chain, address1, minABI, 'decimals', []));

    // First Paired Token:
    let token0: PricedToken = {
      symbol: symbol0,
      address: address0,
      balance: (parseInt(poolInfo.balances[0]) * (balance / lpTokenSupply)) / (10 ** decimals0),
      price: await getTokenPrice(chain, address0, decimals0),
      logo: getTokenLogo(chain, symbol0)
    }

    // Second Paired Token:
    let token1: PricedToken = {
      symbol: symbol1,
      address: address1,
      balance: (parseInt(poolInfo.balances[1]) * (balance / lpTokenSupply)) / (10 ** decimals1),
      price: await getTokenPrice(chain, address1, decimals1),
      logo: getTokenLogo(chain, symbol1)
    }

    return { type, chain, location, status, owner, symbol, address, balance, token0, token1 };
  }

  // No Token Identified:
  return {
    type: 'token',
    chain: chain,
    location: location,
    status: 'none',
    owner: owner,
    symbol: '???',
    address: address,
    balance: 0,
    price: 0,
    logo: defaultTokenLogo
  }
}

/* ========================================================================================================================================================================= */

// Function to get Iron token info:
export const addIronToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let swapAddress = await query(chain, address, iron.tokenABI, 'swap', []);
  let price = parseInt(await query(chain, swapAddress, iron.swapABI, 'getVirtualPrice', [])) / (10 ** decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Axial token info:
export const addAxialToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let swapAddress = await query(chain, address, axial.tokenABI, 'owner', []);
  let price = parseInt(await query(chain, swapAddress, axial.swapABI, 'getVirtualPrice', [])) / (10 ** decimals);

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get mStable token info:
export const addStableToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = defaultTokenLogo;

  // Finding Token Price:
  let price = parseInt((await query(chain, address, mstable.stableABI, 'getPrice', [])).price) / (10 ** decimals);

  // Finding Token Symbol:
  logo = price > 1000 ? getTokenLogo(chain, 'mBTC') : getTokenLogo(chain, 'mUSD');

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Cookie token info:
export const addCookieToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let fortunePrice = await getTokenPrice(chain, '0xd8187f630A93A1d841dbBC99cd5fe06587A984DE', 9);
  let exchangeRate = parseInt(await query(chain, '0x9eE8817Fe46f4620708a9FA1119972bC4c131641', cookiegame.exchangeABI, 'price', []));
  let price = fortunePrice / exchangeRate;

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}

/* ========================================================================================================================================================================= */

// Function to get Alligator token info (xGTR):
export const addAlligatorToken = async (chain: Chain, location: string, status: TokenStatus, address: Address, rawBalance: number, owner: Address): Promise<Token> => {

  // Initializing Token Values:
  let type: TokenType = 'token';
  let symbol = await query(chain, address, minABI, 'symbol', []);
  let decimals = parseInt(await query(chain, address, minABI, 'decimals', []));
  let balance = rawBalance / (10 ** decimals);
  let logo = getTokenLogo(chain, symbol);

  // Finding Token Price:
  let gtr: Address = '0x43c812ba28cb061b1be7514145a15c9e18a27342';
  let gtrStaked = parseInt(await query(chain, gtr, minABI, 'balanceOf', [address]));
  let xgtrSupply = parseInt(await query(chain, address, minABI, 'totalSupply', []));
  let multiplier = gtrStaked / xgtrSupply;
  let price = multiplier * (await getTokenPrice(chain, gtr, decimals));

  return { type, chain, location, status, owner, symbol, address, balance, price, logo };
}