Added onchain_data/custody support (#5)
* Added oncahin_data/custody support * Delete erc20.json duplicate/unnecessary file * Delete allowList.json duplicate file * Update getCoinGeckoPrices.ts
This commit is contained in:
parent
a8b44bdbfc
commit
cc01fb6456
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
lib
|
|
@ -0,0 +1,20 @@
|
|||
# Onchain data for custody monitoring
|
||||
|
||||
For each connected wormhole chain, query the custody contract and get the updated locked value for each token.
|
||||
There are two modes:
|
||||
(1) query a known list of tracked tokens aka allow list
|
||||
(2) query either a scan site (if available) or covalent (also if available) to grab list of known tokens locked in the custody contract
|
||||
|
||||
Set env variable allowlist=true if using allow list found at: onchain_data/data/allowList.json
|
||||
taken from: https://github.com/wormhole-foundation/wormhole/blob/dev.v2/event_database/cloud_functions/token-allowlist-mainnet.json
|
||||
Has the added benefit of also including the coin gecko ids in order to mark positions to USD
|
||||
|
||||
```
|
||||
MONGODB_URI=mongodb://root:example@localhost:27017/ allowlist=true node lib/getCustodyData.js
|
||||
```
|
||||
|
||||
Currently Near, Algorand, Xpla, & Aptos are not supported. Feel free to add support ;)
|
||||
|
||||
The aggregated custody data gets pushed to the mongodb database - "onchain_data" into the table - "custody"
|
||||
|
||||
There are api endpoints that the server provides which powers the wormscan website.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.7.1",
|
||||
"@metaplex/js": "^4.12.0",
|
||||
"@terra-money/terra.js": "^3.1.3",
|
||||
"coingecko-api": "^1.0.10",
|
||||
"mongodb": "^4.10.0",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
{
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"1": {
|
||||
"ATLASXmbPQxBUYbxPsV97usA3fPQYEqzQBUHgiFCUsXx": "star-atlas",
|
||||
"NFTUkR4u7wKxy9QLaX2TGvd9oZSWoMo4jqSJqdMb7Nk": "blockasset",
|
||||
"AkhdZGVbJXPuQZ53u2LrimCjkRP6ZyxG1SoM85T98eE1": "starbots",
|
||||
"4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R": "raydium",
|
||||
"4Te4KJgjtnZe4aE2zne8G4NPfrPjCwDmaiEx9rKnyDVZ": "solclout",
|
||||
"So11111111111111111111111111111111111111112": "wrapped-solana",
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "usd-coin",
|
||||
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "tether",
|
||||
"7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": "lido-staked-sol"
|
||||
},
|
||||
"2": {
|
||||
"0x111111111117dc0aa78b770fa6a738034120c302": "1inch",
|
||||
"0x009178997aff09a67d4caccfeb897fb79d036214": "1sol",
|
||||
"0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9": "aave",
|
||||
"0x27702a26126e0b3702af63ee09ac4d1a084ef628": "aleph",
|
||||
"0xe0cca86b254005889ac3a81e737f56a14f4a38f5": "alta-finance",
|
||||
"0x9b83f827928abdf18cf1f7e67053572b9bceff3a": "artem",
|
||||
"0x18aaa7115705e8be94bffebde57af9bfc265b998": "audius",
|
||||
"0xbb0e17ef65f82ab018d8edd776e8dd940327b28b": "axie-infinity",
|
||||
"0x0d8775f648430679a709e98d2b0cb6250d2887ef": "basic-attention-token",
|
||||
"0xf17e65822b568b3903685a7c9f496cf7656cc6c2": "biconomy",
|
||||
"0xef19f4e48830093ce5bc8b3ff7f903a0ae3e9fa1": "botxcoin",
|
||||
"0xbba39fd2935d5769116ce38d46a71bde9cf03099": "choise",
|
||||
"0xd49efa7bc0d339d74f487959c573d518ba3f8437": "shield-finance",
|
||||
"0xc00e94cb662c3520282e6f5717214004a7f26888": "compound-governance-token",
|
||||
"0x2ba592f78db6436527729929aaf6c908497cb200": "cream-2",
|
||||
"0x6b175474e89094c44da98b954eedeac495271d0f": "dai",
|
||||
"0x92d6c1e31e14520e676a687f0a93788b716beff5": "dydx",
|
||||
"0x4da34f8264cb33a5c9f17081b9ef5ff6091116f4": "elyfi",
|
||||
"0xfd09911130e6930bf87f2b0554c44f400bd80d3e": "ethichub",
|
||||
"0x853d955acef822db058eb8505911ed77f175b99e": "frax",
|
||||
"0xf8c3527cc04340b208c854e985240c02f7b7793f": "frontier-token",
|
||||
"0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9": "ftx-token",
|
||||
"0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0": "frax-share",
|
||||
"0xc944e90c64b2c07662a292be6244bdf05cda44a7": "the-graph",
|
||||
"0x4674672bcddda2ea5300f5207e1158185c944bc0": "gem-exchange-and-trading",
|
||||
"0x0316eb71485b0ab14103307bf65a021042c6d380": "huobi-btc",
|
||||
"0x4bd70556ae3f8a6ec6c4080a0c327b24325438f3": "hxro",
|
||||
"0xe28b3b32b6c345a34ff64674606124dd5aceca30": "injective-protocol",
|
||||
"0x8a9c67fee641579deba04928c4bc45f66e26343a": "jarvis-reward-token",
|
||||
"0x85eee30c52b0b379b046fb0f85f4f3dc3009afec": "keep-network",
|
||||
"0x5a98fcbea516cf06857215779fd812ca3bef1b32": "lido-dao",
|
||||
"0x514910771af9ca656af840dff83e8264ecf986ca": "chainlink",
|
||||
"0x0f5d2fb29fb7d3cfee444a200298f468908cc942": "decentraland",
|
||||
"0x08d967bb0134f2d07f7cfb6e246680c53927dd30": "math",
|
||||
"0xe831f96a7a1dce1aa2eb760b1e296c6a74caa9d5": "nexum",
|
||||
"0xdfdb7f72c1f195c5951a234e8db9806eb0635346": "feisty-doge-nft",
|
||||
"0x727f064a78dc734d33eec18d5370aef32ffd46e4": "orion-money",
|
||||
"0x45804880de22913dafe09f4980848ece6ecbaf78": "pax-gold",
|
||||
"0x65e6b60ea01668634d68d0513fe814679f925bad": "pixelverse",
|
||||
"0xf1f955016ecbcd7321c7266bccfb96c68ea5e49b": "rally-2",
|
||||
"0x3845badade8e6dff049820680d1f14bd3903a5d0": "the-sandbox",
|
||||
"0x30d20208d987713f46dfd34ef128bb16c404d10f": "stader",
|
||||
"0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce": "shiba-inu",
|
||||
"0x5ab6a4f46ce182356b6fa2661ed8ebcafce995ad": "sportium",
|
||||
"0x476c5e26a75bd202a9683ffd34359c0cc15be0ff": "serum",
|
||||
"0x6b3595068778dd592e39a122f4f5a5cf09c90fe2": "sushi",
|
||||
"0x8ce9137d39326ad0cd6491fb5cc0cba0e089b6a9": "swipe",
|
||||
"0x2e95cea14dd384429eb3c4331b776c4cfbb6fcd9": "throne",
|
||||
"0x05d3606d5c81eb9b7b18530995ec9b29da05faba": "tomoe",
|
||||
"0x2c537e5624e4af88a7ae4060c022609376c8d0eb": "bilira",
|
||||
"0x8564653879a18c560e7c0ea0e084c516c62f5653": "upbots",
|
||||
"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984": "uniswap",
|
||||
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "usd-coin",
|
||||
"0xdac17f958d2ee523a2206206994597c13d831ec7": "tether",
|
||||
"0x0c572544a4ee47904d54aaa6a970af96b6f00e1b": "wasder",
|
||||
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": "wrapped-bitcoin",
|
||||
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "ethereum",
|
||||
"0x72b886d09c117654ab7da13a14d603001de0b777": "xdefi",
|
||||
"0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e": "yearn-finance",
|
||||
"0x1a7e4e63778b4f12a199c062f3efdd288afcbce8": "ageur",
|
||||
"0x707f9118e33a9b8998bea41dd0d46f38bb963fc8": "ethereum",
|
||||
"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0": "wrapped-steth",
|
||||
"0xa2cd3d43c775978a96bdbf12d733d5a1ed94fb18": "chain-2"
|
||||
},
|
||||
"3": {
|
||||
"terra193c42lfwmlkasvcw22l9qqzc5q2dx208tkd7wl": "bitlocus",
|
||||
"uluna": "terra-luna",
|
||||
"terra13awdgcx40tz5uygkgm79dytez3x87rpg4uhnvu": "playnity",
|
||||
"uusd": "terrausd",
|
||||
"terra1hzh9vpxhsk8253se0vv5jj6etdvxu3nv8z07zu": "anchorust"
|
||||
},
|
||||
"4": {
|
||||
"0x7e46d5eb5b7ca573b367275fee94af1945f5b636": "abitshadow-token",
|
||||
"0xe9e7cea3dedca5984780bafc599bd69add087d56": "binance-usd",
|
||||
"0x8ebc361536094fd5b4ffb8521e31900614c9f55d": "darcmatter-coin",
|
||||
"0x2170ed0880ac9a755fd29b2688956bd959f933f8": "weth",
|
||||
"0x3019bf2a2ef8040c242c9a4c5c4bd4c81678b2a1": "stepn",
|
||||
"0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d": "usd-coin",
|
||||
"0x55d398326f99059ff775485246999027b3197955": "tether",
|
||||
"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c": "wbnb",
|
||||
"0xfafd4cb703b25cb22f43d017e7e0d75febc26743": "weyu",
|
||||
"0xfa40d8fc324bcdd6bbae0e086de886c571c225d4": "wizardia"
|
||||
},
|
||||
"5": {
|
||||
"0x9c891326fd8b1a713974f73bb604677e1e63396d": "islamicoin",
|
||||
"0x2791bca1f2de4661ed88a30c99a7a9449aa84174": "usd-coin",
|
||||
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f": "tether",
|
||||
"0x7ceb23fd6bc0add59e62ac25578270cff1b9f619": "weth",
|
||||
"0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270": "matic-network"
|
||||
},
|
||||
"6": {
|
||||
"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": "usd-coin",
|
||||
"0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664": "usd-coin",
|
||||
"0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7": "tether",
|
||||
"0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7": "avalanche-2",
|
||||
"0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be": "benqi-liquid-staked-avax"
|
||||
},
|
||||
"7": {
|
||||
"0x94fbffe5698db6f54d6ca524dbe673a7729014be": "usd-coin",
|
||||
"0x366ef31c8dc715cbeff5fa54ad106dc9c25c6153": "tether-usd-wormhole-from-bsc",
|
||||
"0x3223f17957ba502cbe71401d55a0db26e5f7c68f": "ethereum-wormhole",
|
||||
"0x21c718c22d52d0f3a789b752d4c2fd5908a8a733": "oasis-network"
|
||||
},
|
||||
"8": {
|
||||
"0": "algorand",
|
||||
"31566704": "usd-coin",
|
||||
"312769": "tether"
|
||||
},
|
||||
"9": {
|
||||
"0x8BEc47865aDe3B172A928df8f990Bc7f2A3b9f79": "aurora",
|
||||
"0xE4B9e004389d91e4134a28F19BD833cBA1d994B6": "frax",
|
||||
"0xb12bfca5a55806aaf64e99521918a4bf0fc40802": "usd-coin",
|
||||
"0x4988a896b1227218e4a686fde5eabdcabd91571f": "tether",
|
||||
"0x5183e1b1091804bc2602586919e6880ac1cf2896": "usn",
|
||||
"0xc9bdeed33cd01541e1eed10f90519d2c06fe3feb": "weth",
|
||||
"0xC9BdeEd33CD01541e1eeD10f90519d2C06Fe3feB": "ethereum",
|
||||
"0xc4bdd27c33ec7daa6fcfd8532ddb524bf4038096": "wrapped-terra"
|
||||
},
|
||||
"10": {
|
||||
"0x321162cd933e2be498cd2267a90534a804051b11": "wrapped-bitcoin",
|
||||
"0x74b23882a30290451a17c44f4f05243b6b58c76d": "weth",
|
||||
"0x260b3e40c714ce8196465ec824cd8bb915081812": "iron-bsc",
|
||||
"0x04068da6c83afcfa0e13ba15a6696662335d5b75": "usd-coin",
|
||||
"0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83": "wrapped-fantom"
|
||||
},
|
||||
"11": {
|
||||
"0x0000000000000000000100000000000000000080": "karura",
|
||||
"0x0000000000000000000100000000000000000082": "kusama",
|
||||
"0x0000000000000000000500000000000000000007": "tether",
|
||||
"0x0000000000000000000100000000000000000081": "acala-dollar"
|
||||
},
|
||||
"12": {
|
||||
"0x0000000000000000000100000000000000000000": "acala",
|
||||
"0x0000000000000000000100000000000000000002": "polkadot",
|
||||
"0x0000000000000000000100000000000000000001": "acala-dollar"
|
||||
},
|
||||
"13": {
|
||||
"0x5c74070fdea071359b86082bd9f9b3deaafbe32b": "dai",
|
||||
"0x5fff3a6c16c2208103f318f4713d4d90601a7313": "kleva",
|
||||
"0x5096db80b21ef45230c9e423c373f1fc9c0198dd": "wemix-token",
|
||||
"0xe4f05a66ec68b54a58b17c22107b02e0232cc817": "klay-token",
|
||||
"0xcee8faf64bb97a73bb51e115aa89c17ffa8dd167": "tether"
|
||||
},
|
||||
"14": {
|
||||
"0x471ece3750da237f93b8e339c536989b8978a438": "celo",
|
||||
"0x46c9757c5497c5b1f2eb73ae79b6b67d119b0b58": "impactmarket",
|
||||
"0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73": "celo-euro",
|
||||
"0x765de816845861e75a25fca122bb6898b8b1282a": "celo-dollar"
|
||||
},
|
||||
"15": {
|
||||
"near": "near",
|
||||
"token.sweat": "sweatcoin"
|
||||
},
|
||||
"16": {
|
||||
"0xacc15dc74880c9944775448304b263d191c6077f": "moonbeam"
|
||||
},
|
||||
"18": {
|
||||
"uluna": "terra-luna-2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import axios from "axios";
|
||||
import { COIN_GECKO_EXCEPTIONS } from "./utils";
|
||||
const CoinGecko = require("coingecko-api");
|
||||
|
||||
function sleep(milliseconds) {
|
||||
const date = Date.now();
|
||||
let currentDate = 0;
|
||||
do {
|
||||
currentDate = Date.now();
|
||||
} while (currentDate - date < milliseconds);
|
||||
}
|
||||
|
||||
//2. Initiate the CoinGecko API Client
|
||||
const CoinGeckoClient = new CoinGecko(); //use justin's VIP coin gecko api key?
|
||||
|
||||
async function getCoinsList() {
|
||||
let data = await CoinGeckoClient.coins.list();
|
||||
//console.log(data)
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getTokenPrice(tokenAddress) {
|
||||
let tokenContractInfo = await CoinGeckoClient.coins.fetchCoinContractInfo(
|
||||
tokenAddress
|
||||
);
|
||||
let data = tokenContractInfo["data"];
|
||||
var price = 0;
|
||||
|
||||
try {
|
||||
price = data["market_data"]["current_price"].usd;
|
||||
} catch (e) {
|
||||
console.log("could not find price for address=", tokenAddress);
|
||||
}
|
||||
return price;
|
||||
}
|
||||
|
||||
export async function getTokenPrices(tokenAddresses) {
|
||||
//only for ethereum chain
|
||||
let tokenContractInfos;
|
||||
try {
|
||||
tokenContractInfos = await CoinGeckoClient.simple.fetchTokenPrice({
|
||||
contract_addresses: tokenAddresses,
|
||||
vs_currencies: "usd",
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("could not find prices for addresses");
|
||||
}
|
||||
return tokenContractInfos["data"];
|
||||
}
|
||||
|
||||
function getKeyByValue(object, value) {
|
||||
return Object.keys(object).find((key) => object[key] === value);
|
||||
}
|
||||
|
||||
export async function getCoinGeckoMap() {
|
||||
// pull coin gecko mapping of token address to coingecko id
|
||||
var coinMap;
|
||||
const map_query = `https://api.coingecko.com/api/v3/coins/list?include_platform=true`;
|
||||
try {
|
||||
const req = await axios.get(map_query);
|
||||
coinMap = req.data;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("could not find prices for addresses");
|
||||
}
|
||||
var coinMapTransformed: any[] = [];
|
||||
for (let i = 0; i < coinMap.length; i++) {
|
||||
const coin = coinMap[i];
|
||||
|
||||
const platforms = coin?.platforms;
|
||||
Object.entries(platforms).forEach((entry) => {
|
||||
const [platform, contractAddress] = entry;
|
||||
if (contractAddress != "" && contractAddress != null) {
|
||||
coinMapTransformed.push({
|
||||
contractAddress: contractAddress,
|
||||
coinGeckoId: coin.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return coinMapTransformed;
|
||||
}
|
||||
|
||||
export async function getTokenPricesGET(
|
||||
chainId: number,
|
||||
platform: String,
|
||||
tokenAddresses: String[]
|
||||
) {
|
||||
let data;
|
||||
|
||||
const addresses = tokenAddresses.join("%2C");
|
||||
const price_query = `https://api.coingecko.com/api/v3/simple/token_price/${platform}?contract_addresses=${addresses}&vs_currencies=usd`;
|
||||
//console.log(price_query)
|
||||
try {
|
||||
const tokenContractInfos = await axios.get(price_query);
|
||||
data = tokenContractInfos.data;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("could not find prices for addresses");
|
||||
}
|
||||
|
||||
// find prices included in exceptions using coin gecko id and add it back to the results
|
||||
const filteredIds = COIN_GECKO_EXCEPTIONS.filter(
|
||||
(x) => tokenAddresses.includes(x.tokenAddress) && x.chainId == chainId
|
||||
);
|
||||
const coinGeckoIds = filteredIds.map((x) => x.coinGeckoId);
|
||||
|
||||
const additional_data = await getTokenPricesCGID(coinGeckoIds);
|
||||
let gecko_id_data = {};
|
||||
for (let i = 0; i < filteredIds.length; i++) {
|
||||
let cg_id = filteredIds[i];
|
||||
gecko_id_data[cg_id.tokenAddress] = additional_data[cg_id.coinGeckoId];
|
||||
}
|
||||
|
||||
return { ...data, ...gecko_id_data };
|
||||
}
|
||||
|
||||
export async function getTokenPricesCGID(coinGeckoIds: String[]) {
|
||||
let data;
|
||||
const ids = coinGeckoIds.join("%2C");
|
||||
const price_query = `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`;
|
||||
// console.log(price_query);
|
||||
try {
|
||||
const tokenContractInfos = await axios.get(price_query);
|
||||
data = tokenContractInfos.data;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("could not find prices for ids");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import { grabTerraCustodyData } from "./getTerraCustody";
|
||||
import { grabSolanaCustodyData } from "./getSolanaCustody";
|
||||
import { grabEvmCustodyData } from "./getEvmCustody";
|
||||
import { MongoClient } from "mongodb";
|
||||
interface Token {
|
||||
tokenAddress: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
balance: BigInt;
|
||||
qty: number;
|
||||
tokenPrice: number;
|
||||
tokenBalanceUSD: number;
|
||||
}
|
||||
|
||||
interface CustodyInfo {
|
||||
_id: string;
|
||||
chainName: string;
|
||||
chainId: number;
|
||||
emitterAddress: string;
|
||||
custodyUSD: number;
|
||||
tokens: Token[];
|
||||
}
|
||||
|
||||
async function updateTable(chainInfo, client: MongoClient) {
|
||||
const custodyList = chainInfo.balances;
|
||||
try {
|
||||
const totalCustodyUSD = custodyList
|
||||
.map((x) => x.tokenBalanceUSD)
|
||||
.reduce((partialSum, a) => partialSum + a, 0);
|
||||
console.log("totalCustodyUSD=", totalCustodyUSD);
|
||||
|
||||
const database = client.db("onchain_data");
|
||||
// Specifying a Schema is optional, but it enables type hints on
|
||||
// finds and inserts
|
||||
const chainId = chainInfo.chain_id;
|
||||
const emitterAddress = chainInfo.emitter_address;
|
||||
const custody = database.collection<CustodyInfo>("custody");
|
||||
const result = await custody.updateOne(
|
||||
{ _id: `${chainId}/${emitterAddress}` },
|
||||
{
|
||||
$set: {
|
||||
chainName: chainInfo.name,
|
||||
chainId: chainId,
|
||||
emitterAddress: emitterAddress,
|
||||
custodyUSD: totalCustodyUSD,
|
||||
tokens: custodyList,
|
||||
_id: `${chainId}/${emitterAddress}`,
|
||||
},
|
||||
},
|
||||
{ upsert: true }
|
||||
);
|
||||
console.log(`A document was inserted with the _id: ${result.upsertedId}`);
|
||||
} catch (e) {
|
||||
console.log(encodeURIComponent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const useAllowListstr = process.env.allowlist || "false";
|
||||
|
||||
(async () => {
|
||||
const uri = process.env.MONGODB_URI;
|
||||
if (uri === "" || uri === undefined) {
|
||||
console.log("No mongodb uri supplied");
|
||||
return -1;
|
||||
}
|
||||
const client = new MongoClient(uri);
|
||||
|
||||
const useAllowList = true ? useAllowListstr === "true" : false;
|
||||
|
||||
const promises = [
|
||||
grabSolanaCustodyData("1", useAllowList),
|
||||
grabEvmCustodyData("2", useAllowList),
|
||||
grabTerraCustodyData("3", useAllowList),
|
||||
grabEvmCustodyData("4", useAllowList),
|
||||
grabEvmCustodyData("5", useAllowList),
|
||||
grabEvmCustodyData("6", useAllowList),
|
||||
grabEvmCustodyData("7", useAllowList),
|
||||
// grabAlgorandCustodyData("8", useAllowList),
|
||||
grabEvmCustodyData("9", useAllowList),
|
||||
grabEvmCustodyData("10", useAllowList),
|
||||
grabEvmCustodyData("11", useAllowList),
|
||||
grabEvmCustodyData("12", useAllowList),
|
||||
grabEvmCustodyData("13", useAllowList),
|
||||
grabEvmCustodyData("14", useAllowList),
|
||||
// grabNearustodyData("15", useAllowList),
|
||||
grabEvmCustodyData("16", useAllowList),
|
||||
grabTerraCustodyData("18", useAllowList),
|
||||
// grabTerraCustodyData("28", useAllowList),
|
||||
];
|
||||
|
||||
const output = await Promise.all(promises);
|
||||
// iterate through chains & insert into mongodb
|
||||
try {
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
const data = output[i];
|
||||
await updateTable(data, client);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,362 @@
|
|||
import { formatUnits } from "ethers/lib/utils";
|
||||
import { ethers } from "ethers";
|
||||
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
|
||||
|
||||
import {
|
||||
CHAIN_ID_OASIS,
|
||||
isEVMChain,
|
||||
CHAIN_ID_KARURA,
|
||||
CHAIN_ID_ACALA,
|
||||
CHAIN_ID_CELO,
|
||||
getEmitterAddressEth,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { abi as Erc20Abi } from "./abi/erc20.json";
|
||||
|
||||
import axios from "axios";
|
||||
import {
|
||||
DISALLOWLISTED_ADDRESSES,
|
||||
CHAIN_INFO_MAP,
|
||||
newProvider,
|
||||
sleepFor,
|
||||
} from "./utils";
|
||||
|
||||
// current allowlist used for stats/govenor
|
||||
import allowList = require("./allowList.json");
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
function getAllowList(chainId) {
|
||||
return allowList[chainId];
|
||||
}
|
||||
|
||||
function calcTokenQty(tokenInfo) {
|
||||
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
|
||||
}
|
||||
|
||||
async function getBridgeBalanceScanner(chainInfo) {
|
||||
const chainId = chainInfo.chain_id;
|
||||
const bridgeAddress = chainInfo.token_bridge_address;
|
||||
const url = chainInfo.urlStem;
|
||||
|
||||
// Get native token balances locked in contract
|
||||
const balance_apireqstring = `${url}/api?module=account&action=balance&address=${bridgeAddress}`;
|
||||
const balance_resp = await axios.get(balance_apireqstring);
|
||||
var tokenAccounts = [];
|
||||
if (balance_resp.status == 200) {
|
||||
const data = balance_resp["data"];
|
||||
if (data.hasOwnProperty("result")) {
|
||||
const balance = data["result"];
|
||||
var nativeInfo = {};
|
||||
var symbol = "";
|
||||
if (chainId == 7) {
|
||||
symbol = "ROSE";
|
||||
} else if (chainId == 11) {
|
||||
symbol = "KAR";
|
||||
} else if (chainId == 12) {
|
||||
symbol: "ACA";
|
||||
} else if (chainId == 14) {
|
||||
symbol: "CELO";
|
||||
}
|
||||
nativeInfo = {
|
||||
decimals: 18,
|
||||
name: symbol,
|
||||
tokenAddress: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2",
|
||||
balance: balance,
|
||||
symbol: symbol,
|
||||
};
|
||||
tokenAccounts.push(nativeInfo);
|
||||
} else {
|
||||
console.log("object has no native balance data");
|
||||
}
|
||||
} else {
|
||||
console.log(balance_resp.status);
|
||||
}
|
||||
|
||||
// Get locked token list from scan site
|
||||
const apiReqString = `${url}/api?module=account&action=tokenlist&address=${bridgeAddress}`;
|
||||
console.log(apiReqString);
|
||||
const response = await axios.get(apiReqString);
|
||||
if (response.status == 200) {
|
||||
const data = response["data"];
|
||||
if (data.hasOwnProperty("result")) {
|
||||
const tokenData = data["result"];
|
||||
tokenAccounts = [
|
||||
...tokenAccounts,
|
||||
...tokenData.map((x) => ({
|
||||
decimals: x.decimals,
|
||||
name: x.name,
|
||||
tokenAddress: x.contractAddress,
|
||||
balance: x.balance,
|
||||
symbol: x.symbol,
|
||||
})),
|
||||
];
|
||||
return tokenAccounts;
|
||||
} else {
|
||||
console.log("object has no data");
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
console.log(response.status);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getBridgeBalanceCovalent(chainInfo) {
|
||||
/* Get token list (also has bridge balance and prices, but could be stale, so will query onchain)*/
|
||||
|
||||
const covalentChainId: string = chainInfo.covalentChain;
|
||||
const bridgeAddress: string = chainInfo.token_bridge_address;
|
||||
const covalentApiKey = process.env.REACT_APP_COVALENT_API_KEY;
|
||||
const apiReqString = `https://api.covalenthq.com/v1/${covalentChainId}/address/${bridgeAddress}/balances_v2/?quote-currency=USD&format=JSON&nft=true&no-nft-fetch=false&key=${covalentApiKey}`;
|
||||
console.log(apiReqString);
|
||||
const response = await axios.get(apiReqString, {
|
||||
headers: { "User-Agent": "Mozilla/5.0" },
|
||||
});
|
||||
if (response.status == 200) {
|
||||
const data = response["data"];
|
||||
if (data.hasOwnProperty("data")) {
|
||||
const tokenData = data["data"];
|
||||
// tokenData["items"].forEach((x) => console.log(x));
|
||||
const tokenAccounts = tokenData["items"]
|
||||
.filter((item) => item.type !== "nft")
|
||||
.map((x) => ({
|
||||
decimals: x.contract_decimals,
|
||||
name: x.contract_name,
|
||||
tokenAddress: x.contract_address,
|
||||
balance: x.balance,
|
||||
price: x.quote_rate,
|
||||
}));
|
||||
return tokenAccounts;
|
||||
} else {
|
||||
console.log("object has no data");
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
console.log(response.status);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
//for evm chains
|
||||
async function getTokenContract(
|
||||
address: string,
|
||||
provider:
|
||||
| ethers.providers.JsonRpcProvider
|
||||
| ethers.providers.JsonRpcBatchProvider
|
||||
) {
|
||||
const contract = new ethers.Contract(address, Erc20Abi, provider);
|
||||
return contract;
|
||||
}
|
||||
|
||||
async function getBridgeBalanceOnChain(chainInfo, tokenList: any[]) {
|
||||
const bridgeAddress = chainInfo.token_bridge_address;
|
||||
let provider = newProvider(
|
||||
chainInfo.endpoint_url,
|
||||
true
|
||||
) as ethers.providers.JsonRpcBatchProvider;
|
||||
|
||||
var tokenAccounts = [];
|
||||
let i = 0;
|
||||
let chunksize = 100;
|
||||
while (i < tokenList.length) {
|
||||
const tokenContracts = await Promise.all(
|
||||
tokenList
|
||||
.slice(i, i + chunksize)
|
||||
.map((token) => getTokenContract(token.tokenAddress, provider))
|
||||
);
|
||||
const tokenInfos = await Promise.all(
|
||||
tokenContracts.map((tokenContract) =>
|
||||
Promise.all([
|
||||
tokenContract.address.toLowerCase(),
|
||||
tokenContract.name(),
|
||||
tokenContract.decimals(),
|
||||
tokenContract.symbol(),
|
||||
tokenContract.balanceOf(bridgeAddress),
|
||||
])
|
||||
)
|
||||
);
|
||||
|
||||
tokenInfos.forEach((tokenInfo) => {
|
||||
tokenAccounts.push({
|
||||
tokenAddress: tokenInfo[0],
|
||||
name: tokenInfo[1],
|
||||
decimals: tokenInfo[2],
|
||||
symbol: tokenInfo[3],
|
||||
balance: tokenInfo[4],
|
||||
});
|
||||
});
|
||||
|
||||
i += chunksize;
|
||||
}
|
||||
return tokenAccounts;
|
||||
}
|
||||
|
||||
export async function getEvmTokenAccounts(chainInfo, useAllowList) {
|
||||
const chainId = chainInfo.chain_id;
|
||||
console.log(chainId);
|
||||
if (!isEVMChain(chainId)) {
|
||||
console.log(`error. ${chainId} is not evm chain`);
|
||||
return [];
|
||||
}
|
||||
|
||||
var tokenAccounts = [];
|
||||
if (useAllowList) {
|
||||
const allowList = getAllowList(chainId);
|
||||
var tokenList = [];
|
||||
Object.keys(allowList).forEach((address) => {
|
||||
tokenList.push({
|
||||
tokenAddress: address,
|
||||
});
|
||||
});
|
||||
// console.log(tokenList);
|
||||
tokenAccounts = await getBridgeBalanceOnChain(
|
||||
chainInfo,
|
||||
tokenList.filter(
|
||||
(token) =>
|
||||
token.tokenAddress.toLowerCase() !=
|
||||
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" // filter out native token
|
||||
)
|
||||
);
|
||||
// console.log("tokenAccounts", tokenAccounts);
|
||||
} else {
|
||||
if (
|
||||
chainId == CHAIN_ID_OASIS ||
|
||||
chainId == CHAIN_ID_KARURA ||
|
||||
chainId == CHAIN_ID_ACALA ||
|
||||
chainId == CHAIN_ID_CELO
|
||||
) {
|
||||
tokenAccounts = await getBridgeBalanceScanner(chainInfo);
|
||||
} else {
|
||||
// use convalent to get token addresses
|
||||
const tokenList = await getBridgeBalanceCovalent(chainInfo);
|
||||
// console.log(tokenList.filter((token) => console.log(token)));
|
||||
tokenAccounts = await getBridgeBalanceOnChain(
|
||||
chainInfo,
|
||||
tokenList.filter(
|
||||
(token) =>
|
||||
token.tokenAddress.toLowerCase() !=
|
||||
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" // native token
|
||||
)
|
||||
);
|
||||
// console.log(tokenAccounts);
|
||||
}
|
||||
}
|
||||
return tokenAccounts;
|
||||
}
|
||||
|
||||
async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
|
||||
console.log("allowlist?", useAllowList);
|
||||
try {
|
||||
const custody = tokenInfos.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
qty: calcTokenQty(tokenInfo),
|
||||
}));
|
||||
const custodyFiltered = custody.filter((c) => c.qty > 0);
|
||||
var tokenPrices = {};
|
||||
var prices = [];
|
||||
|
||||
if (useAllowList) {
|
||||
// use coingecko ids from allowlist
|
||||
const allowList = getAllowList(chainInfo.chain_id);
|
||||
const cgids: string[] = Object.values(allowList);
|
||||
|
||||
// input array of cgids, returns json with cgid:price
|
||||
prices = await getTokenPricesCGID(cgids);
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
// have to map cgid: price to tokenAddress: price
|
||||
for (const [key, value] of Object.entries(allowList)) {
|
||||
for (const [key1, value1] of Object.entries(prices)) {
|
||||
if (key1 === value) {
|
||||
tokenPrices[key] = prices[key1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(tokenPrices);
|
||||
} else {
|
||||
// use tokenAddresses from tokenInfos/custody
|
||||
|
||||
let j = 0;
|
||||
let chunk_size = 100;
|
||||
while (j < custodyFiltered.length) {
|
||||
prices = await getTokenPricesGET(
|
||||
chainInfo.chain_id,
|
||||
chainInfo.platform,
|
||||
custodyFiltered.slice(j, j + chunk_size).map((x) => x.tokenAddress)
|
||||
);
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
tokenPrices = { ...tokenPrices, ...prices };
|
||||
j += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
// filter list by those with coin gecko prices
|
||||
const filteredBalances = custodyFiltered.filter((x) =>
|
||||
Object.keys(tokenPrices).includes(x.tokenAddress)
|
||||
);
|
||||
// calculate usd balances. add price and usd balance to tokenInfos
|
||||
const balancesUSD = filteredBalances.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
tokenPrice: tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
tokenBalanceUSD:
|
||||
tokenInfo.qty * tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
}));
|
||||
|
||||
// filter out disallowlist addresses
|
||||
const balancesUSDFiltered = balancesUSD.filter(
|
||||
(x) => !DISALLOWLISTED_ADDRESSES.includes(x.tokenAddress)
|
||||
);
|
||||
const sorted = balancesUSDFiltered.sort((a, b) =>
|
||||
a.tokenBalanceUSD < b.tokenBalanceUSD ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getEvmCustody(chainInfo, useAllowList = true) {
|
||||
const tokenAccounts = await getEvmTokenAccounts(chainInfo, useAllowList);
|
||||
console.log(
|
||||
`Num of ${chainInfo.platform} token accounts=${tokenAccounts.length}`
|
||||
);
|
||||
const custody = await getTokenValues(chainInfo, tokenAccounts, useAllowList);
|
||||
console.log(
|
||||
`Num of filtered ${chainInfo.platform} token accounts=${custody.length}`
|
||||
);
|
||||
return custody;
|
||||
}
|
||||
|
||||
export async function grabEvmCustodyData(chain, useAllowList) {
|
||||
const chainInfo = CHAIN_INFO_MAP[chain];
|
||||
const balances = await getEvmCustody(chainInfo, useAllowList);
|
||||
// await updateTable(chainInfo, balances);
|
||||
const chainInfo_ = {
|
||||
...chainInfo,
|
||||
emitter_address: getEmitterAddressEth(chainInfo.token_bridge_address),
|
||||
balances: balances,
|
||||
};
|
||||
return chainInfo_;
|
||||
}
|
||||
|
||||
// const chain = process.env.chain;
|
||||
// const useAllowListstr = process.env.chain || "false";
|
||||
|
||||
// (async () => {
|
||||
// const chainInfo = CHAIN_INFO_MAP[chain];
|
||||
// const useAllowList = true ? useAllowListstr === "true" : false;
|
||||
// const balances = await getEvmCustody(chainInfo, useAllowList);
|
||||
// console.log(balances);
|
||||
// await updateTable(chainInfo, balances);
|
||||
// })();
|
|
@ -0,0 +1,322 @@
|
|||
import { formatUnits } from "ethers/lib/utils";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { Connection, AccountInfo, PublicKey } from "@solana/web3.js";
|
||||
import { Connection as ConnectionMeta, programs } from "@metaplex/js";
|
||||
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
|
||||
|
||||
import { CHAIN_INFO_MAP, DISALLOWLISTED_ADDRESSES, sleepFor } from "./utils";
|
||||
|
||||
// current allowlist used for stats/govenor
|
||||
import allowList = require("./allowList.json");
|
||||
import { getEmitterAddressSolana } from "@certusone/wormhole-sdk";
|
||||
import { BigNumber } from "ethers";
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
function getAllowList(chainId) {
|
||||
return allowList[chainId];
|
||||
}
|
||||
|
||||
function calcTokenQty(tokenInfo) {
|
||||
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
|
||||
}
|
||||
|
||||
const {
|
||||
metadata: { Metadata },
|
||||
} = programs;
|
||||
|
||||
export async function getMultipleAccountsRPC(
|
||||
connection: Connection,
|
||||
pubkeys: PublicKey[]
|
||||
): Promise<(AccountInfo<Buffer> | null)[]> {
|
||||
return getMultipleAccounts(connection, pubkeys, "confirmed");
|
||||
}
|
||||
|
||||
export const getMultipleAccounts = async (
|
||||
connection: any,
|
||||
pubkeys: PublicKey[],
|
||||
commitment: string
|
||||
) => {
|
||||
return (
|
||||
await Promise.all(connection.getMultipleAccountsInfo(pubkeys, commitment))
|
||||
).flat();
|
||||
};
|
||||
|
||||
export function shortenAddress(address: string) {
|
||||
return address.length > 10
|
||||
? `${address.slice(0, 4)}...${address.slice(-4)}`
|
||||
: address;
|
||||
}
|
||||
|
||||
export const METADATA_REPLACE = new RegExp("\u0000", "g");
|
||||
export const EDITION_MARKER_BIT_SIZE = 248;
|
||||
export const METADATA_PREFIX = "metadata";
|
||||
export const EDITION = "edition";
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export type StringPublicKey = string;
|
||||
|
||||
export enum MetadataKey {
|
||||
Uninitialized = 0,
|
||||
MetadataV1 = 4,
|
||||
EditionV1 = 1,
|
||||
MasterEditionV1 = 2,
|
||||
MasterEditionV2 = 6,
|
||||
EditionMarker = 7,
|
||||
}
|
||||
|
||||
export const METADATA_PROGRAM_ID =
|
||||
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" as StringPublicKey;
|
||||
|
||||
export const getMetadataAddress = async (
|
||||
mintKey: string
|
||||
): Promise<[PublicKey, number]> => {
|
||||
const seeds = [
|
||||
Buffer.from("metadata"),
|
||||
new PublicKey(METADATA_PROGRAM_ID).toBuffer(),
|
||||
new PublicKey(mintKey).toBuffer(),
|
||||
];
|
||||
return PublicKey.findProgramAddress(
|
||||
seeds,
|
||||
new PublicKey(METADATA_PROGRAM_ID)
|
||||
);
|
||||
};
|
||||
|
||||
async function getSolanaMetaData(
|
||||
mintAddresses: string[],
|
||||
connection: Connection
|
||||
) {
|
||||
const metaAddresses = [];
|
||||
for (let i = 0; i < mintAddresses.length; i++) {
|
||||
let mintAddress = mintAddresses[i];
|
||||
try {
|
||||
const metaAddress = await getMetadataAddress(mintAddress);
|
||||
metaAddresses.push({
|
||||
mint: mintAddress,
|
||||
meta: metaAddress[0].toString(),
|
||||
});
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let storeMetadata = {};
|
||||
// Get store metadata
|
||||
for (let i = 0; i < metaAddresses.length; i++) {
|
||||
let mintkey = new PublicKey(metaAddresses[i].mint);
|
||||
let pubkey = new PublicKey(metaAddresses[i].meta);
|
||||
try {
|
||||
const metadata = await Metadata.load(connection, pubkey);
|
||||
const metadatadata = metadata.data?.data || {};
|
||||
metadatadata["metakey"] = pubkey.toString();
|
||||
metadatadata["tokenAddress"] = mintkey.toString();
|
||||
storeMetadata[mintkey.toString()] = metadatadata;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return storeMetadata;
|
||||
}
|
||||
|
||||
export async function getSolanaTokenAccounts(chainInfo, useAllowList) {
|
||||
const chainId = chainInfo.chain_id;
|
||||
const custodyAddress = chainInfo.custody_address;
|
||||
const connection = new Connection(chainInfo.endpoint_url);
|
||||
|
||||
try {
|
||||
var mintAddresses = [];
|
||||
var tokenAccounts = [];
|
||||
|
||||
if (useAllowList) {
|
||||
const allowList = getAllowList(chainId);
|
||||
|
||||
var mintAddresses = [];
|
||||
Object.keys(allowList).forEach((address) => {
|
||||
mintAddresses.push(address);
|
||||
});
|
||||
for (let i = 0; i < mintAddresses.length; i++) {
|
||||
const mintAddress = mintAddresses[i];
|
||||
const parsedAccount = await connection.getParsedTokenAccountsByOwner(
|
||||
new PublicKey(custodyAddress),
|
||||
{
|
||||
mint: new PublicKey(mintAddress),
|
||||
}
|
||||
);
|
||||
|
||||
const tokenAccount_ = parsedAccount.value.at(-1);
|
||||
const tokenAccount = tokenAccount_.account.data.parsed.info;
|
||||
if (tokenAccount.tokenAmount?.amount > 0) {
|
||||
// console.log(
|
||||
// `token=${mintAddress} has a balance=${tokenAccount.tokenAmount?.amount}`
|
||||
// );
|
||||
tokenAccounts.push(tokenAccount);
|
||||
} else {
|
||||
console.log(`${tokenAccount} has a 0 balance`);
|
||||
}
|
||||
await sleepFor(1000);
|
||||
}
|
||||
} else {
|
||||
const allAccounts = await connection.getParsedTokenAccountsByOwner(
|
||||
new PublicKey(custodyAddress),
|
||||
{ programId: TOKEN_PROGRAM_ID },
|
||||
"confirmed"
|
||||
);
|
||||
allAccounts.value.forEach((account) => {
|
||||
if (account.account.data.parsed?.info?.tokenAmount?.amount > 0) {
|
||||
// get all token accounts with nonzero balance
|
||||
tokenAccounts.push(account.account.data.parsed?.info);
|
||||
}
|
||||
});
|
||||
|
||||
// get mint addresses from token accounts and find metadata address
|
||||
mintAddresses = tokenAccounts.map((x) => x.mint);
|
||||
}
|
||||
|
||||
const storeMetadata = await getSolanaMetaData(mintAddresses, connection);
|
||||
tokenAccounts = tokenAccounts.map((custody) => ({
|
||||
...custody,
|
||||
...custody["tokenAmount"],
|
||||
...storeMetadata[custody.mint],
|
||||
}));
|
||||
|
||||
// do a little tidying up
|
||||
tokenAccounts = tokenAccounts.map((account) => ({
|
||||
tokenAddress: account["tokenAddress"],
|
||||
name: account["name"],
|
||||
decimals: account["decimals"],
|
||||
symbol: account["symbol"],
|
||||
balance: BigNumber.from(account["amount"]),
|
||||
}));
|
||||
return tokenAccounts;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
|
||||
try {
|
||||
const custody = tokenInfos.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
qty: calcTokenQty(tokenInfo),
|
||||
}));
|
||||
const custodyFiltered = custody.filter((c) => c.qty > 0);
|
||||
var tokenPrices = {};
|
||||
var prices = [];
|
||||
|
||||
if (useAllowList) {
|
||||
// use coingecko ids from allowlist
|
||||
const allowList = getAllowList(chainInfo.chain_id);
|
||||
const cgids: string[] = Object.values(allowList);
|
||||
|
||||
// input array of cgids, returns json with cgid:price
|
||||
prices = await getTokenPricesCGID(cgids);
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
// console.log(prices);
|
||||
// have to map cgid: price to tokenAddress: price
|
||||
for (const [key, value] of Object.entries(allowList)) {
|
||||
for (const [key1, value1] of Object.entries(prices)) {
|
||||
if (key1 === value) {
|
||||
tokenPrices[key] = prices[key1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(tokenPrices);
|
||||
} else {
|
||||
// use tokenAddresses from tokenInfos/custody
|
||||
let j = 0;
|
||||
let chunk_size = 100;
|
||||
while (j < custodyFiltered.length) {
|
||||
const prices = await getTokenPricesGET(
|
||||
chainInfo.chain_id,
|
||||
chainInfo.platform,
|
||||
custodyFiltered.slice(j, j + chunk_size).map((x) => x.tokenAddress)
|
||||
);
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
tokenPrices = { ...tokenPrices, ...prices };
|
||||
j += chunk_size;
|
||||
}
|
||||
// console.log("tokenPrices", tokenPrices);
|
||||
}
|
||||
// filter list by those with coin gecko prices
|
||||
const filteredBalances = custodyFiltered.filter((x) =>
|
||||
Object.keys(tokenPrices).includes(x.tokenAddress)
|
||||
);
|
||||
// calculate usd balances. add price and usd balance to tokenInfos
|
||||
const balancesUSD = filteredBalances.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
tokenPrice: tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
tokenBalanceUSD:
|
||||
tokenInfo.qty * tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
}));
|
||||
|
||||
// filter out disallowlist addresses
|
||||
const balancesUSDFiltered = balancesUSD.filter(
|
||||
(x) => !DISALLOWLISTED_ADDRESSES.includes(x.tokenAddress)
|
||||
);
|
||||
var sorted = balancesUSDFiltered.sort((a, b) =>
|
||||
a.tokenBalanceUSD < b.tokenBalanceUSD ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getSolanaCustody(chainInfo, useAllowList = true) {
|
||||
const endpoint = chainInfo.endpoint_url;
|
||||
const bridgeAddress = chainInfo.token_bridge_address;
|
||||
const connection = new Connection(endpoint);
|
||||
const nativeBalance = await connection.getBalance(
|
||||
new PublicKey(bridgeAddress)
|
||||
);
|
||||
// console.log("amount of sol in custody=", nativeBalance);
|
||||
const solanaCustodyNativeUSD = 0; //solanaCustodyNative.map(x => (parseInt(x.balance) / (10.0 ** x.decimals) * x.price)).reduce((partialSum, a) => partialSum + a, 0);
|
||||
|
||||
// grab token accounts from rpc/allowlist
|
||||
const tokenAccounts = await getSolanaTokenAccounts(chainInfo, useAllowList);
|
||||
console.log(
|
||||
`Num of ${chainInfo.platform} token accounts=${tokenAccounts.length}`
|
||||
);
|
||||
// tokenAccounts.forEach((x) => console.log(x));
|
||||
const custody = await getTokenValues(chainInfo, tokenAccounts, useAllowList);
|
||||
console.log(
|
||||
`Num of filtered ${chainInfo.platform} token accounts=${custody.length}`
|
||||
);
|
||||
return custody;
|
||||
}
|
||||
|
||||
export async function grabSolanaCustodyData(chain, useAllowList) {
|
||||
const chainInfo = CHAIN_INFO_MAP[chain];
|
||||
const balances = await getSolanaCustody(chainInfo, useAllowList);
|
||||
const chainInfo_ = {
|
||||
...chainInfo,
|
||||
emitter_address:
|
||||
"ec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
|
||||
balances: balances,
|
||||
};
|
||||
return chainInfo_;
|
||||
}
|
||||
|
||||
// const useAllowListstr = process.env.chain || "false";
|
||||
|
||||
// (async () => {
|
||||
// const useAllowList = true ? useAllowListstr === "true" : false;
|
||||
// const chainInfo = CHAIN_INFO_MAP["1"];
|
||||
// const balances = await getSolanaCustody(chainInfo, useAllowList);
|
||||
// await updateTable(chainInfo, balances);
|
||||
// })();
|
|
@ -0,0 +1,325 @@
|
|||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { formatUnits } from "ethers/lib/utils";
|
||||
|
||||
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
|
||||
|
||||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_TERRA2,
|
||||
isNativeDenom,
|
||||
getEmitterAddressTerra,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import axios from "axios";
|
||||
import { DISALLOWLISTED_ADDRESSES, CHAIN_INFO_MAP } from "./utils";
|
||||
|
||||
// current allowlist used for stats/govenor
|
||||
import allowList = require("./allowList.json");
|
||||
import { BigNumber } from "ethers";
|
||||
|
||||
require("dotenv").config();
|
||||
|
||||
function formatTerraNative(address: string) {
|
||||
let symbol = address.slice(1);
|
||||
if (address != "uluna") {
|
||||
symbol = symbol.slice(0, -1) + "t";
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
function getAllowList(chainId) {
|
||||
return allowList[chainId];
|
||||
}
|
||||
|
||||
function calcTokenQty(tokenInfo) {
|
||||
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
|
||||
}
|
||||
|
||||
async function contractQuery(
|
||||
address: string,
|
||||
query: object,
|
||||
host
|
||||
): Promise<any> {
|
||||
//return this.provider.wasm.contractQuery(address, query);
|
||||
const lcd = new LCDClient(host);
|
||||
let info = undefined;
|
||||
try {
|
||||
info = await lcd.wasm.contractQuery(address, query);
|
||||
} catch (e) {
|
||||
console.log("could not query contract info: ", address);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
async function queryTokenInfo(address: string, host): Promise<any> {
|
||||
return await contractQuery(
|
||||
address,
|
||||
{
|
||||
token_info: {},
|
||||
},
|
||||
host
|
||||
);
|
||||
}
|
||||
|
||||
function getNativeTerraTokenInfo(address, chainId) {
|
||||
if (address === "uluna") {
|
||||
if (chainId === CHAIN_ID_TERRA) {
|
||||
return {
|
||||
name: "Luna Classic",
|
||||
symbol: "LUNC",
|
||||
decimals: 6,
|
||||
tokenAddress: address,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: "Luna",
|
||||
symbol: "LUNA",
|
||||
decimals: 6,
|
||||
tokenAddress: address,
|
||||
};
|
||||
}
|
||||
} else if (address === "uusd") {
|
||||
return {
|
||||
name: "UST",
|
||||
symbol: "UST",
|
||||
decimals: 6,
|
||||
tokenAddress: address,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export async function getTerraTokenAccounts(chainInfo, useAllowList) {
|
||||
const chainId = chainInfo.chain_id;
|
||||
const bridgeAddress = chainInfo.token_bridge_address;
|
||||
|
||||
var TERRA_HOST;
|
||||
var network;
|
||||
if (chainId == CHAIN_ID_TERRA) {
|
||||
TERRA_HOST = {
|
||||
URL: "https://columbus-lcd.terra.dev",
|
||||
chainID: "columbus-5",
|
||||
name: "mainnet",
|
||||
};
|
||||
network = "classic";
|
||||
} else if (chainId == CHAIN_ID_TERRA2) {
|
||||
TERRA_HOST = {
|
||||
URL: "https://phoenix-lcd.terra.dev",
|
||||
chainID: "phoenix-1",
|
||||
name: "mainnet",
|
||||
};
|
||||
network = "mainnet";
|
||||
}
|
||||
const lcd = new LCDClient(TERRA_HOST);
|
||||
|
||||
var tokenList = [];
|
||||
var tokenData = {};
|
||||
if (useAllowList) {
|
||||
const allowList = getAllowList(chainId);
|
||||
Object.keys(allowList).forEach((address) => {
|
||||
tokenList.push(address);
|
||||
});
|
||||
for (let i = 0; i < tokenList.length; i++) {
|
||||
let address = tokenList[i];
|
||||
var tokenInfo = undefined;
|
||||
if (isNativeDenom(address)) {
|
||||
// console.log(address);
|
||||
tokenInfo = getNativeTerraTokenInfo(address, chainId);
|
||||
} else {
|
||||
tokenInfo = await queryTokenInfo(address, TERRA_HOST);
|
||||
}
|
||||
|
||||
tokenData[address] = tokenInfo;
|
||||
}
|
||||
// console.log("tokenData", tokenData);
|
||||
} else {
|
||||
const token_url = "https://assets.terra.money/cw20/tokens.json";
|
||||
const response = await axios.get(token_url);
|
||||
if (response.status == 200) {
|
||||
const data = response["data"];
|
||||
|
||||
if (data.hasOwnProperty(network)) {
|
||||
tokenData = data[network];
|
||||
tokenList = Object.keys(tokenData);
|
||||
} else {
|
||||
console.log("object has no data");
|
||||
}
|
||||
} else {
|
||||
console.log(response.status);
|
||||
}
|
||||
}
|
||||
|
||||
let tokenAccounts = [];
|
||||
let nativeTokens = [];
|
||||
try {
|
||||
const address = bridgeAddress;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
for (let i = 0; i < tokenList.length; i++) {
|
||||
const tokenAddress = tokenList[i];
|
||||
|
||||
try {
|
||||
let token = await lcd.wasm.contractQuery(tokenAddress, {
|
||||
balance: { address: bridgeAddress },
|
||||
});
|
||||
let tokenInfo = tokenData[tokenAddress];
|
||||
// console.log(tokenAddress, token, tokenInfo);
|
||||
|
||||
tokenAccounts.push({
|
||||
tokenAddress: tokenAddress,
|
||||
name: tokenInfo.name,
|
||||
decimals: tokenInfo?.decimals || 6,
|
||||
symbol: tokenInfo.symbol,
|
||||
balance: BigNumber.from(token["balance"]),
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("could not find address=", tokenAddress);
|
||||
}
|
||||
}
|
||||
// grab native tokens
|
||||
const [balance] = await lcd.bank.balance(bridgeAddress);
|
||||
balance.toData().forEach((token) => {
|
||||
if (useAllowList) {
|
||||
if (tokenList.includes(token.denom)) {
|
||||
let tokenInfo = tokenData[token.denom];
|
||||
tokenAccounts.push({
|
||||
tokenAddress: token.denom,
|
||||
name: tokenInfo.name,
|
||||
decimals: tokenInfo?.decimals || 6,
|
||||
symbol: tokenInfo.symbol,
|
||||
balance: BigNumber.from(token.amount),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
tokenAccounts.push({
|
||||
tokenAddress: token.denom,
|
||||
name: "native",
|
||||
symbol: formatTerraNative(token.denom),
|
||||
balance: token.amount,
|
||||
decimals: "6",
|
||||
});
|
||||
}
|
||||
});
|
||||
// console.log("tokenAccounts=", tokenAccounts);
|
||||
|
||||
return tokenAccounts;
|
||||
}
|
||||
|
||||
async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
|
||||
try {
|
||||
const custody = tokenInfos.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
qty: calcTokenQty(tokenInfo),
|
||||
}));
|
||||
|
||||
const custodyFiltered = custody.filter((c) => c.qty > 0);
|
||||
var tokenPrices = {};
|
||||
var prices = [];
|
||||
|
||||
if (useAllowList) {
|
||||
// use coingecko ids from allowlist
|
||||
const allowList = getAllowList(chainInfo.chain_id);
|
||||
const cgids: string[] = Object.values(allowList);
|
||||
|
||||
// input array of cgids, returns json with cgid:price
|
||||
prices = await getTokenPricesCGID(cgids);
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
// have to map cgid: price to tokenAddress: price
|
||||
for (const [key, value] of Object.entries(allowList)) {
|
||||
for (const [key1, value1] of Object.entries(prices)) {
|
||||
if (key1 === value) {
|
||||
tokenPrices[key] = prices[key1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let j = 0;
|
||||
let chunk_size = 100;
|
||||
while (j < custodyFiltered.length) {
|
||||
prices = await getTokenPricesGET(
|
||||
chainInfo.chain_id,
|
||||
chainInfo.platform,
|
||||
custodyFiltered.slice(j, j + chunk_size).map((x) => x.tokenAddress)
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(prices)) {
|
||||
if (!value.hasOwnProperty("usd")) {
|
||||
prices[key] = { usd: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
tokenPrices = { ...tokenPrices, ...prices };
|
||||
j += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
// filter list by those with coin gecko prices
|
||||
const filteredBalances = custodyFiltered.filter((x) =>
|
||||
Object.keys(tokenPrices).includes(x.tokenAddress)
|
||||
);
|
||||
// calculate usd balances. add price and usd balance to tokenInfos
|
||||
const balancesUSD = filteredBalances.map((tokenInfo) => ({
|
||||
...tokenInfo,
|
||||
tokenPrice: tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
tokenBalanceUSD:
|
||||
tokenInfo.qty * tokenPrices[tokenInfo.tokenAddress]["usd"],
|
||||
}));
|
||||
|
||||
// filter out disallowlist addresses
|
||||
const balancesUSDFiltered = balancesUSD.filter(
|
||||
(x) => !DISALLOWLISTED_ADDRESSES.includes(x.tokenAddress)
|
||||
);
|
||||
const sorted = balancesUSDFiltered.sort((a, b) =>
|
||||
a.tokenBalanceUSD < b.tokenBalanceUSD ? 1 : -1
|
||||
);
|
||||
|
||||
return sorted;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTerraCustody(chainInfo, useAllowList) {
|
||||
const tokenAccounts = await getTerraTokenAccounts(chainInfo, useAllowList);
|
||||
console.log(
|
||||
`Num of ${chainInfo.platform} token accounts=${tokenAccounts.length}`
|
||||
);
|
||||
const custody = await getTokenValues(chainInfo, tokenAccounts, useAllowList);
|
||||
console.log(
|
||||
`Num of filtered ${chainInfo.platform} token accounts=${custody.length}`
|
||||
);
|
||||
return custody;
|
||||
}
|
||||
|
||||
export async function grabTerraCustodyData(chain, useAllowList) {
|
||||
const chainInfo = CHAIN_INFO_MAP[chain];
|
||||
const balances = await getTerraCustody(chainInfo, useAllowList);
|
||||
const chainInfo_ = {
|
||||
...chainInfo,
|
||||
emitter_address: await getEmitterAddressTerra(
|
||||
chainInfo.token_bridge_address
|
||||
),
|
||||
balances: balances,
|
||||
};
|
||||
return chainInfo_;
|
||||
}
|
||||
|
||||
// const chain = process.env.chain;
|
||||
// const useAllowListstr = process.env.chain || "false";
|
||||
|
||||
// var func = async () => {
|
||||
// const chainInfo = CHAIN_INFO_MAP[chain];
|
||||
// const useAllowList = true ? useAllowListstr === "true" : false;
|
||||
// const balances = await getTerraCustody(chainInfo, useAllowList);
|
||||
// console.log(balances);
|
||||
// await updateTable(chainInfo, balances);
|
||||
// // console.log("terra custody (USD) = ", terraCustodyUSD);
|
||||
// };
|
||||
|
||||
// func().then((x) => console.log("end"));
|
|
@ -0,0 +1,412 @@
|
|||
import {
|
||||
CHAIN_ID_ACALA,
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_AURORA,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_CELO,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_ETHEREUM_ROPSTEN,
|
||||
CHAIN_ID_FANTOM,
|
||||
CHAIN_ID_KARURA,
|
||||
CHAIN_ID_KLAYTN,
|
||||
CHAIN_ID_MOONBEAM,
|
||||
CHAIN_ID_NEAR,
|
||||
CHAIN_ID_OASIS,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_SOLANA,
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_TERRA2,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
export const WORMHOLE_RPC_HOSTS = [
|
||||
"https://wormhole-v2-mainnet-api.certus.one",
|
||||
"https://wormhole.inotel.ro",
|
||||
"https://wormhole-v2-mainnet-api.mcf.rocks",
|
||||
"https://wormhole-v2-mainnet-api.chainlayer.network",
|
||||
"https://wormhole-v2-mainnet-api.staking.fund",
|
||||
"https://wormhole-v2-mainnet.01node.com",
|
||||
];
|
||||
|
||||
export const CHAIN_ID_MAP = {
|
||||
0: undefined,
|
||||
1: CHAIN_ID_SOLANA,
|
||||
2: CHAIN_ID_ETH,
|
||||
3: CHAIN_ID_TERRA,
|
||||
4: CHAIN_ID_BSC,
|
||||
5: CHAIN_ID_POLYGON,
|
||||
6: CHAIN_ID_AVAX,
|
||||
7: CHAIN_ID_OASIS,
|
||||
8: CHAIN_ID_ALGORAND,
|
||||
9: CHAIN_ID_AURORA,
|
||||
10: CHAIN_ID_FANTOM,
|
||||
11: CHAIN_ID_KARURA,
|
||||
12: CHAIN_ID_ACALA,
|
||||
13: CHAIN_ID_KLAYTN,
|
||||
14: CHAIN_ID_CELO,
|
||||
15: CHAIN_ID_NEAR,
|
||||
16: CHAIN_ID_MOONBEAM,
|
||||
18: CHAIN_ID_TERRA2,
|
||||
};
|
||||
|
||||
import { ethers } from "ethers";
|
||||
require("dotenv").config();
|
||||
|
||||
export const DISALLOWLISTED_ADDRESSES = [
|
||||
"0x04132bf45511d03a58afd4f1d36a29d229ccc574",
|
||||
"0xa79bd679ce21a2418be9e6f88b2186c9986bbe7d",
|
||||
"0x931c3987040c90b6db09981c7c91ba155d3fa31f",
|
||||
"0x8fb1a59ca2d57b51e5971a85277efe72c4492983",
|
||||
"0xd52d9ba6fcbadb1fe1e3aca52cbb72c4d9bbb4ec",
|
||||
"0x1353c55fd2beebd976d7acc4a7083b0618d94689",
|
||||
"0xf0fbdb8a402ec0fc626db974b8d019c902deb486",
|
||||
"0x1fd4a95f4335cf36cac85730289579c104544328",
|
||||
"0x358aa13c52544eccef6b0add0f801012adad5ee3",
|
||||
"0xbe32b7acd03bcc62f25ebabd169a35e69ef17601",
|
||||
"0x7ffb3d637014488b63fb9858e279385685afc1e2",
|
||||
"0x337dc89ebcc33a337307d58a51888af92cfdc81b",
|
||||
"0x5Cb89Ac06F34f73B1A6b8000CEb0AfBc97d58B6b",
|
||||
"0xd9F0446AedadCf16A12692E02FA26C617FA4D217",
|
||||
"0xD7b41531456b636641F7e867eC77120441D1E1E8",
|
||||
"0x9f607027b69f6e123bc3bd56a686b735fa75f30a",
|
||||
"0x2a35965bbad6fd3964ef815d011c51ab1c546e67",
|
||||
"0x053c070f0923a5b770cc59d7bf74ecff991cd0b8",
|
||||
"0x3dab0f14ea515d5c842b631bd6df0f7f989c47b3",
|
||||
"0x7ee4f716e3c716d61f6158bde3ed5ab03fb6b90c",
|
||||
"0x90285e9567be274ae892c88d3ffd76c87d6c7904",
|
||||
"0x2d4678e71590c56eb37869832a3642c405e1c252", // fake saitama on poly
|
||||
"0x1e49f85f8f5d4ef948ccb953c0172c648b75222f",
|
||||
"0x477c7802632f0d38f285a7fd7112a66c11b99db6",
|
||||
"0xdaff96cc3d5e2fa982812ec12ce74833deb51327", //fake btc on bsc
|
||||
"0xe389ac691bd2b0228daffff548fbce38470373e8", //fake wrapped matic on poly
|
||||
"0x7e347498dfef39a88099e3e343140ae17cde260e", //wrapped avax on bsc
|
||||
"0x86812b970bbdce75b4590243ba2cbff671d0b754", //fake tether on bsc
|
||||
"0x3d8babf3afd0e1bfc843f9638f650fa50ae6c22b", //fake tether on eth
|
||||
"0x0749902ae8ed9c6a508271bad18f185dba7185d4", //wrapped eth on polygon
|
||||
"0x8e1c62f03b995938233ffa3762bd69f889016b3c", //fake luna2.0 on bsc
|
||||
].map((x) => x.toLowerCase());
|
||||
|
||||
export const COIN_GECKO_EXCEPTIONS = [
|
||||
{
|
||||
chainId: 2,
|
||||
tokenAddress: "0x707f9118e33a9b8998bea41dd0d46f38bb963fc8".toLowerCase(),
|
||||
coinGeckoId: "ethereum",
|
||||
},
|
||||
{
|
||||
chainId: 2,
|
||||
tokenAddress: "0xdAF566020156297E2837fDfaA6Fbba929A29461E".toLowerCase(),
|
||||
coinGeckoId: "safe-coin-2",
|
||||
},
|
||||
{
|
||||
chainId: 2,
|
||||
tokenAddress: "0x5ab6A4F46Ce182356B6FA2661Ed8ebcAFce995aD".toLowerCase(),
|
||||
coinGeckoId: "sportium",
|
||||
},
|
||||
{
|
||||
chainId: 3,
|
||||
tokenAddress: "uluna",
|
||||
coinGeckoId: "terra-luna",
|
||||
},
|
||||
{
|
||||
chainId: 3,
|
||||
tokenAddress: "ukrw",
|
||||
coinGeckoId: "terra-krw",
|
||||
},
|
||||
{
|
||||
chainId: 7,
|
||||
tokenAddress: "0x21c718c22d52d0f3a789b752d4c2fd5908a8a733",
|
||||
coinGeckoId: "oasis-network", // wrapped-rose does not currently have prices
|
||||
},
|
||||
{
|
||||
chainId: 7,
|
||||
tokenAddress: "0x366ef31c8dc715cbeff5fa54ad106dc9c25c6153",
|
||||
coinGeckoId: "tether",
|
||||
},
|
||||
{
|
||||
chainId: 1,
|
||||
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
coinGeckoId: "usd-coin",
|
||||
},
|
||||
{
|
||||
chainId: 1,
|
||||
tokenAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
||||
coinGeckoId: "tether",
|
||||
},
|
||||
{
|
||||
chainId: 11,
|
||||
tokenAddress: "0x0000000000000000000500000000000000000007",
|
||||
coinGeckoId: "tether",
|
||||
},
|
||||
{
|
||||
chainId: 12,
|
||||
tokenAddress: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2",
|
||||
coinGeckoId: "acala",
|
||||
},
|
||||
{
|
||||
chainId: 12,
|
||||
tokenAddress: "0x0000000000000000000100000000000000000001",
|
||||
coinGeckoId: "acala-dollar",
|
||||
},
|
||||
{
|
||||
chainId: 12,
|
||||
tokenAddress: "0x0000000000000000000100000000000000000000",
|
||||
coinGeckoId: "acala",
|
||||
},
|
||||
];
|
||||
|
||||
export function newProvider(
|
||||
url: string,
|
||||
batch: boolean = false
|
||||
): ethers.providers.JsonRpcProvider | ethers.providers.JsonRpcBatchProvider {
|
||||
// only support http(s), not ws(s) as the websocket constructor can blow up the entire process
|
||||
// it uses a nasty setTimeout(()=>{},0) so we are unable to cleanly catch its errors
|
||||
if (url.includes("http")) {
|
||||
if (batch) {
|
||||
return new ethers.providers.JsonRpcBatchProvider(url);
|
||||
}
|
||||
return new ethers.providers.JsonRpcProvider(url);
|
||||
}
|
||||
throw new Error("url does not start with http/https!");
|
||||
}
|
||||
|
||||
export var CHAIN_INFO_MAP = {
|
||||
"1": {
|
||||
name: "solana",
|
||||
evm: false,
|
||||
chain_id: CHAIN_ID_SOLANA,
|
||||
endpoint_url:
|
||||
process.env.SOLANA_RPC || "https://solana-api.projectserum.com",
|
||||
core_bridge: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth",
|
||||
token_bridge_address: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
|
||||
custody_address: "GugU1tP7doLeTw9hQP51xRJyS8Da1fWxuiy2rVrnMD2m",
|
||||
platform: "solana",
|
||||
covalentChain: 1399811149,
|
||||
},
|
||||
"2": {
|
||||
name: "eth",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_ETH,
|
||||
endpoint_url: process.env.ETH_RPC || "https://rpc.ankr.com/eth",
|
||||
core_bridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
|
||||
token_bridge_address: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
custody_address: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
api_key: process.env.ETHERSCAN_API,
|
||||
urlStem: `https://api.etherscan.io`,
|
||||
platform: "ethereum",
|
||||
covalentChain: 1,
|
||||
},
|
||||
"3": {
|
||||
name: "terra",
|
||||
evm: false,
|
||||
chain_id: CHAIN_ID_TERRA,
|
||||
endpoint_url: "",
|
||||
core_bridge: "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5",
|
||||
token_bridge_address: "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf",
|
||||
custody_address: "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf",
|
||||
urlStem: "https://columbus-fcd.terra.dev",
|
||||
platform: "terra",
|
||||
covalentChain: 3,
|
||||
},
|
||||
"4": {
|
||||
name: "bsc",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_BSC,
|
||||
endpoint_url: process.env.BSC_RPC || "https://rpc.ankr.com/bsc ", //moralis_url
|
||||
core_bridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
|
||||
token_bridge_address: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||
custody_address: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||
api_key: process.env.BSCSCAN_API,
|
||||
urlStem: `https://api.bscscan.com`,
|
||||
platform: "binance-smart-chain",
|
||||
covalentChain: 56,
|
||||
},
|
||||
"5": {
|
||||
name: "polygon",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_POLYGON,
|
||||
endpoint_url: process.env.POLYGON_RPC || "https://rpc.ankr.com/polygon ",
|
||||
core_bridge: "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7",
|
||||
token_bridge_address: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
custody_address: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
api_key: process.env.POLYSCAN_API,
|
||||
urlStem: `https://api.polygonscan.com`,
|
||||
platform: "polygon-pos", //coingecko?,
|
||||
covalentChain: 137,
|
||||
},
|
||||
"6": {
|
||||
name: "avalanche",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_AVAX,
|
||||
endpoint_url: process.env.AVAX_RPC || "https://rpc.ankr.com/avalanche",
|
||||
core_bridge: "0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c",
|
||||
token_bridge_address: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
|
||||
custody_address: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
|
||||
api_key: process.env.SNOWTRACE_API,
|
||||
urlStem: `https://api.snowtrace.io`,
|
||||
platform: "avalanche", //coingecko?
|
||||
covalentChain: 43114,
|
||||
},
|
||||
"7": {
|
||||
name: "oasis",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_OASIS,
|
||||
endpoint_url: "https://emerald.oasis.dev",
|
||||
core_bridge: "0xfE8cD454b4A1CA468B57D79c0cc77Ef5B6f64585",
|
||||
token_bridge_address: "0x5848C791e09901b40A9Ef749f2a6735b418d7564",
|
||||
custody_address: "0x5848C791e09901b40A9Ef749f2a6735b418d7564",
|
||||
api_key: "",
|
||||
urlStem: `https://explorer.emerald.oasis.dev`,
|
||||
platform: "oasis", //coingecko?
|
||||
covalentChain: 42262,
|
||||
},
|
||||
"8": {
|
||||
name: "algorand",
|
||||
evm: false,
|
||||
chain_id: CHAIN_ID_ALGORAND,
|
||||
endpoint_url: "https://node.algoexplorerapi.io",
|
||||
core_bridge: "842125965",
|
||||
token_bridge_address: "842126029",
|
||||
custody_address: "842126029",
|
||||
api_key: "",
|
||||
urlStem: `https://algoexplorer.io`,
|
||||
platform: "algorand", //coingecko?
|
||||
covalentChain: undefined,
|
||||
},
|
||||
"9": {
|
||||
name: "aurora",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_AURORA,
|
||||
endpoint_url: "https://mainnet.aurora.dev",
|
||||
core_bridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge_address: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F",
|
||||
custody_address: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F",
|
||||
api_key: process.env.AURORA_API,
|
||||
urlStem: `https://api.aurorascan.dev`, //?module=account&action=txlist&address={addressHash}
|
||||
covalentChain: 1313161554,
|
||||
platform: "aurora", //coingecko?
|
||||
},
|
||||
"10": {
|
||||
name: "fantom",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_FANTOM,
|
||||
endpoint_url: "https://rpc.ftm.tools",
|
||||
core_bridge: "0x126783A6Cb203a3E35344528B26ca3a0489a1485",
|
||||
token_bridge_address: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
|
||||
custody_address: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
|
||||
api_key: process.env.FTMSCAN_API,
|
||||
urlStem: `https://api.FtmScan.com`,
|
||||
platform: "fantom", //coingecko?
|
||||
covalentChain: 250,
|
||||
},
|
||||
"11": {
|
||||
name: "karura",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_KARURA,
|
||||
endpoint_url: "https://eth-rpc-karura.aca-api.network",
|
||||
core_bridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge_address: "0xae9d7fe007b3327AA64A32824Aaac52C42a6E624",
|
||||
custody_address: "0xae9d7fe007b3327AA64A32824Aaac52C42a6E624",
|
||||
api_key: "",
|
||||
urlStem: `https://blockscout.karura.network`,
|
||||
platform: "karura", //coingecko?
|
||||
covalentChain: "",
|
||||
},
|
||||
"12": {
|
||||
name: "acala",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_ACALA,
|
||||
endpoint_url: "https://eth-rpc-acala.aca-api.network",
|
||||
core_bridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge_address: "0xae9d7fe007b3327AA64A32824Aaac52C42a6E624",
|
||||
custody_address: "0xae9d7fe007b3327AA64A32824Aaac52C42a6E624",
|
||||
api_key: "",
|
||||
urlStem: `https://blockscout.acala.network`,
|
||||
platform: "acala", //coingecko?
|
||||
covalentChain: "",
|
||||
},
|
||||
"13": {
|
||||
name: "klaytn",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_KLAYTN,
|
||||
endpoint_url: "https://klaytn-mainnet-rpc.allthatnode.com:8551",
|
||||
core_bridge: "0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7",
|
||||
token_bridge_address: "0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F",
|
||||
custody_address: "0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F",
|
||||
api_key: process.env.KLAYTN_API,
|
||||
urlStem: `https://scope.klaytn.com`,
|
||||
platform: "klay-token", //coingecko?
|
||||
covalentChain: "8217",
|
||||
},
|
||||
"14": {
|
||||
name: "celo",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_CELO,
|
||||
endpoint_url: "https://forno.celo.org",
|
||||
core_bridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
|
||||
token_bridge_address: "0x796Dff6D74F3E27060B71255Fe517BFb23C93eed",
|
||||
custody_address: "0x796Dff6D74F3E27060B71255Fe517BFb23C93eed",
|
||||
api_key: "",
|
||||
urlStem: `https://explorer.celo.org`,
|
||||
platform: "celo", //coingecko?
|
||||
covalentChain: "42220",
|
||||
},
|
||||
"15": {
|
||||
name: "near",
|
||||
evm: false,
|
||||
chain_id: CHAIN_ID_NEAR,
|
||||
endpoint_url: "https://rpc.ankr.com/near",
|
||||
core_bridge: "contract.wormhole_crypto.near",
|
||||
token_bridge_address: "contract.portalbridge.near",
|
||||
custody_address: "contract.portalbridge.near",
|
||||
urlStem: "",
|
||||
platform: "near",
|
||||
covalentChain: undefined,
|
||||
},
|
||||
"16": {
|
||||
name: "moonbeam",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_MOONBEAM,
|
||||
endpoint_url: "https://rpc.api.moonbeam.network",
|
||||
core_bridge: "0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3",
|
||||
token_bridge_address: "0xB1731c586ca89a23809861c6103F0b96B3F57D92",
|
||||
custody_address: "0xB1731c586ca89a23809861c6103F0b96B3F57D92",
|
||||
api_key: process.env.MOONBEAM_API,
|
||||
urlStem: "https://api-moonbeam.moonscan.io",
|
||||
platform: "moonbeam",
|
||||
covalentChain: 1284,
|
||||
},
|
||||
"18": {
|
||||
name: "terra2",
|
||||
evm: false,
|
||||
chain_id: CHAIN_ID_TERRA2,
|
||||
endpoint_url: "",
|
||||
core_bridge:
|
||||
"terra12mrnzvhx3rpej6843uge2yyfppfyd3u9c3uq223q8sl48huz9juqffcnhp",
|
||||
token_bridge_address:
|
||||
"terra153366q50k7t8nn7gec00hg66crnhkdggpgdtaxltaq6xrutkkz3s992fw9",
|
||||
custody_address:
|
||||
"terra153366q50k7t8nn7gec00hg66crnhkdggpgdtaxltaq6xrutkkz3s992fw9",
|
||||
urlStem: "https://phoenix-fcd.terra.dev",
|
||||
platform: "terra",
|
||||
covalentChain: 3,
|
||||
},
|
||||
"2_testnet": {
|
||||
name: "eth_testnet",
|
||||
evm: true,
|
||||
chain_id: CHAIN_ID_ETHEREUM_ROPSTEN,
|
||||
endpoint_url: process.env.ETH_RPC,
|
||||
core_bridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
|
||||
token_bridge_address: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
custody_address: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
api_key: process.env.ETHERSCAN_API,
|
||||
urlStem: `https://api.etherscan.io`,
|
||||
platform: "ethereum",
|
||||
covalentChain: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export async function sleepFor(timeInMs: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeInMs);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2020"],
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
}
|
|
@ -29,12 +29,19 @@ async function paginatedFind(collection, req, filter) {
|
|||
return cursor;
|
||||
}
|
||||
|
||||
async function findAndSendMany(res, collectionName, reqForPagination, filter) {
|
||||
const database = mongoClient.db("wormhole");
|
||||
async function findAndSendMany(
|
||||
db,
|
||||
res,
|
||||
collectionName,
|
||||
reqForPagination,
|
||||
filter,
|
||||
project
|
||||
) {
|
||||
const database = mongoClient.db(db);
|
||||
const collection = database.collection(collectionName);
|
||||
const cursor = await (reqForPagination
|
||||
? paginatedFind(collection, reqForPagination, filter)
|
||||
: collection.find(filter));
|
||||
: collection.find(filter).project(project));
|
||||
const result = await cursor.toArray();
|
||||
if (result.length === 0) {
|
||||
res.sendStatus(404);
|
||||
|
@ -43,10 +50,10 @@ async function findAndSendMany(res, collectionName, reqForPagination, filter) {
|
|||
res.send(result);
|
||||
}
|
||||
|
||||
async function findAndSendOne(res, collectionName, filter) {
|
||||
const database = mongoClient.db("wormhole");
|
||||
async function findAndSendOne(db, res, collectionName, filter, project) {
|
||||
const database = mongoClient.db(db);
|
||||
const collection = database.collection(collectionName);
|
||||
const result = await collection.findOne(filter);
|
||||
const result = await collection.findOne(filter, project);
|
||||
if (!result) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
|
@ -59,7 +66,7 @@ async function findAndSendOne(res, collectionName, filter) {
|
|||
*/
|
||||
|
||||
app.get("/api/heartbeats", async (req, res) => {
|
||||
await findAndSendMany(res, "heartbeats");
|
||||
await findAndSendMany("wormhole", res, "heartbeats");
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -67,28 +74,28 @@ app.get("/api/heartbeats", async (req, res) => {
|
|||
*/
|
||||
|
||||
app.get("/api/vaas", async (req, res) => {
|
||||
await findAndSendMany(res, "vaas", req);
|
||||
await findAndSendMany("wormhole", res, "vaas", req);
|
||||
});
|
||||
|
||||
app.get("/api/vaas/:chain", async (req, res) => {
|
||||
await findAndSendMany(res, "vaas", req, {
|
||||
await findAndSendMany("wormhole", res, "vaas", req, {
|
||||
_id: { $regex: `^${req.params.chain}/.*` },
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/vaas/:chain/:emitter", async (req, res) => {
|
||||
await findAndSendMany(res, "vaas", req, {
|
||||
await findAndSendMany("wormhole", res, "vaas", req, {
|
||||
_id: { $regex: `^${req.params.chain}/${req.params.emitter}/.*` },
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/vaas/:chain/:emitter/:sequence", async (req, res) => {
|
||||
const id = `${req.params.chain}/${req.params.emitter}/${req.params.sequence}`;
|
||||
await findAndSendOne(res, "vaas", { _id: id });
|
||||
await findAndSendOne("wormhole", res, "vaas", { _id: id });
|
||||
});
|
||||
|
||||
app.get("/api/vaas-sans-pythnet", async (req, res) => {
|
||||
await findAndSendMany(res, "vaas", req, {
|
||||
await findAndSendMany("wormhole", res, "vaas", req, {
|
||||
_id: { $not: { $regex: `^26/.*` } },
|
||||
});
|
||||
});
|
||||
|
@ -140,23 +147,23 @@ app.get("/api/vaa-counts", async (req, res) => {
|
|||
*/
|
||||
|
||||
app.get("/api/observations", async (req, res) => {
|
||||
await findAndSendMany(res, "observations", req);
|
||||
await findAndSendMany("wormhole", res, "observations", req);
|
||||
});
|
||||
|
||||
app.get("/api/observations/:chain", async (req, res) => {
|
||||
await findAndSendMany(res, "observations", req, {
|
||||
await findAndSendMany("wormhole", res, "observations", req, {
|
||||
_id: { $regex: `^${req.params.chain}/.*` },
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/observations/:chain/:emitter", async (req, res) => {
|
||||
await findAndSendMany(res, "observations", req, {
|
||||
await findAndSendMany("wormhole", res, "observations", req, {
|
||||
_id: { $regex: `^${req.params.chain}/${req.params.emitter}/.*` },
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/observations/:chain/:emitter/:sequence", async (req, res) => {
|
||||
await findAndSendMany(res, "observations", req, {
|
||||
await findAndSendMany("wormhole", res, "observations", req, {
|
||||
_id: {
|
||||
$regex: `^${req.params.chain}/${req.params.emitter}/${req.params.sequence}/.*`,
|
||||
},
|
||||
|
@ -167,10 +174,424 @@ app.get(
|
|||
"/api/observations/:chain/:emitter/:sequence/:signer/:hash",
|
||||
async (req, res) => {
|
||||
const id = `${req.params.chain}/${req.params.emitter}/${req.params.sequence}/${req.params.signer}/${req.params.hash}`;
|
||||
await findAndSendOne(res, "observations", { _id: id });
|
||||
await findAndSendOne("wormhole", res, "observations", { _id: id });
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* GovernorStatus
|
||||
*/
|
||||
app.get("/api/governorStatus", async (req, res) => {
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.find({}).project({
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodename: "$parsedStatus.nodename", //<-- rename fields to flatten
|
||||
chains: "$parsedStatus.chains",
|
||||
});
|
||||
const result = await cursor.toArray();
|
||||
if (result.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/:guardianaddr", async (req, res) => {
|
||||
const id = `${req.params.guardianaddr}`;
|
||||
await findAndSendOne(
|
||||
"wormhole",
|
||||
res,
|
||||
"governorStatus",
|
||||
{
|
||||
_id: id,
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodename: "$parsedStatus.nodename",
|
||||
chains: "$parsedStatus.chains",
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/chain/:chainNum", async (req, res) => {
|
||||
const id = `${req.params.chainNum}`;
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.aggregate([
|
||||
{
|
||||
$match: {},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: "$parsedStatus.nodename",
|
||||
"parsedStatus.chains": {
|
||||
$filter: {
|
||||
input: "$parsedStatus.chains",
|
||||
as: "chain",
|
||||
cond: { $eq: [`$$chain.chainid`, parseInt(id)] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: 1,
|
||||
availableNotional: { $arrayElemAt: ["$parsedStatus.chains", 0] },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: 1,
|
||||
chainId: "$availableNotional.chainid",
|
||||
availableNotional: "$availableNotional.remainingavailablenotional",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await cursor.toArray();
|
||||
if (result.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
res.send(result);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/chains/all", async (req, res) => {
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.aggregate([
|
||||
{
|
||||
$match: {},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
chains: "$parsedStatus.chains",
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: "$chains",
|
||||
},
|
||||
{
|
||||
$sort: {
|
||||
"chains.chainid": 1,
|
||||
"chains.remainingavailablenotional": -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: "$chains.chainid",
|
||||
availableNotionals: {
|
||||
$push: {
|
||||
availableNotional: "$chains.remainingavailablenotional",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
chainId: "$_id",
|
||||
availableNotionals: 1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await cursor.toArray();
|
||||
if (result.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
const minGuardianNum = 13;
|
||||
var agg = [];
|
||||
result.forEach((chain) => {
|
||||
agg.push({
|
||||
chainId: chain.chainId,
|
||||
availableNotional:
|
||||
chain.availableNotionals[minGuardianNum - 1]?.availableNotional || null,
|
||||
});
|
||||
});
|
||||
res.send(
|
||||
agg.sort(function (a, b) {
|
||||
return parseInt(a.chainId) - parseInt(b.chainId);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/availableNotional/:chainNum", async (req, res) => {
|
||||
const id = `${req.params.chainNum}`;
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.aggregate([
|
||||
{
|
||||
$match: {},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: "$parsedStatus.nodename",
|
||||
"parsedStatus.chains": {
|
||||
$filter: {
|
||||
input: "$parsedStatus.chains",
|
||||
as: "chain",
|
||||
cond: { $eq: [`$$chain.chainid`, parseInt(id)] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: 1,
|
||||
availableNotional: { $arrayElemAt: ["$parsedStatus.chains", 0] },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: 1,
|
||||
chainId: "$availableNotional.chainid",
|
||||
availableNotional: "$availableNotional.remainingavailablenotional",
|
||||
emitters: "$availableNotional.emitters",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await cursor.toArray();
|
||||
const sortedResult = result.sort(function (b, a) {
|
||||
return parseInt(a.availableNotional) - parseInt(b.availableNotional);
|
||||
});
|
||||
if (sortedResult.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
const minGuardianNum = 13;
|
||||
res.send(sortedResult[minGuardianNum - 1]);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/enqueuedVaass/all", async (req, res) => {
|
||||
const id = `${req.params.chainNum}`;
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.aggregate([
|
||||
{
|
||||
$match: {},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
chains: "$parsedStatus.chains",
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: "$chains",
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
chainId: "$chains.chainid",
|
||||
emitters: "$chains.emitters",
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: "$chainId",
|
||||
emitters: {
|
||||
$push: {
|
||||
emitterAddress: { $arrayElemAt: ["$emitters.emitteraddress", 0] },
|
||||
enqueuedVaas: { $arrayElemAt: ["$emitters.enqueuedvaas", 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await cursor.toArray();
|
||||
var filteredResult = [];
|
||||
var keys = [];
|
||||
result.forEach((res) => {
|
||||
const chainId = res._id;
|
||||
const emitters = res.emitters;
|
||||
emitters.forEach((emitter) => {
|
||||
const emitterAddress = emitter.emitterAddress;
|
||||
const enqueuedVaas = emitter.enqueuedVaas;
|
||||
if (enqueuedVaas != null) {
|
||||
enqueuedVaas.forEach((vaa) => {
|
||||
//add to dictionary
|
||||
const key = `${emitterAddress}/${vaa.sequence}/${vaa.txhash}`;
|
||||
if (!keys.includes(key)) {
|
||||
filteredResult.push({
|
||||
chainId: chainId,
|
||||
emitterAddress: emitterAddress,
|
||||
sequence: vaa.sequence,
|
||||
notionalValue: vaa.notionalvalue,
|
||||
txHash: vaa.txhash,
|
||||
});
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (filteredResult.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
const groups = filteredResult.reduce((groups, item) => {
|
||||
const group = groups[item.chainId] || [];
|
||||
group.push(item);
|
||||
groups[item.chainId] = group;
|
||||
return groups;
|
||||
}, {});
|
||||
const modifiedResult = [];
|
||||
for (const [key, value] of Object.entries(groups)) {
|
||||
modifiedResult.push({ chainId: key, enqueuedVaas: value });
|
||||
}
|
||||
res.send(modifiedResult);
|
||||
});
|
||||
|
||||
app.get("/api/governorStatus/enqueuedVaas/:chainNum", async (req, res) => {
|
||||
const id = `${req.params.chainNum}`;
|
||||
const database = mongoClient.db("wormhole");
|
||||
const collection = database.collection("governorStatus");
|
||||
const cursor = await collection.aggregate([
|
||||
{
|
||||
$match: {},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: "$parsedStatus.nodename",
|
||||
"parsedStatus.chains": {
|
||||
$filter: {
|
||||
input: "$parsedStatus.chains",
|
||||
as: "chain",
|
||||
cond: { $eq: [`$$chain.chainid`, parseInt(id)] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
nodeName: 1,
|
||||
emitters: "$parsedStatus.chains.emitters",
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: "$emitters",
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: { $arrayElemAt: ["$emitters.emitteraddress", 0] },
|
||||
enqueuedVaas: {
|
||||
$push: {
|
||||
enqueuedVaa: "$emitters.enqueuedvaas",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await cursor.toArray();
|
||||
var filteredResult = [];
|
||||
var keys = [];
|
||||
result.forEach((res) => {
|
||||
const emitterAddress = res._id;
|
||||
const enqueuedVaas = res.enqueuedVaas;
|
||||
enqueuedVaas.forEach((vaa) => {
|
||||
const enqueuedVaa = vaa.enqueuedVaa;
|
||||
enqueuedVaa.forEach((eV) => {
|
||||
if (eV != null) {
|
||||
eV.forEach((ev) => {
|
||||
if (ev != null) {
|
||||
//add to dictionary
|
||||
const key = `${emitterAddress}/${ev.sequence}/${ev.txhash}`;
|
||||
if (!keys.includes(key)) {
|
||||
filteredResult.push({
|
||||
chainId: id,
|
||||
emitterAddress: emitterAddress,
|
||||
sequence: ev.sequence,
|
||||
notionalValue: ev.notionalvalue,
|
||||
txHash: ev.txhash,
|
||||
releaseTime: ev.releasetime,
|
||||
});
|
||||
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if (filteredResult.length === 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedResult = filteredResult.sort(function (a, b) {
|
||||
return parseInt(a.sequence) - parseInt(b.sequence);
|
||||
});
|
||||
res.send(sortedResult);
|
||||
});
|
||||
|
||||
/*
|
||||
* Custody
|
||||
*/
|
||||
|
||||
app.get("/api/custody", async (req, res) => {
|
||||
await findAndSendMany("onchain_data", res, "custody", req);
|
||||
});
|
||||
|
||||
app.get("/api/custody/:chain/:emitter", async (req, res) => {
|
||||
const id = `${req.params.chain}/${req.params.emitter}`;
|
||||
await findAndSendOne(
|
||||
"onchain_data",
|
||||
res,
|
||||
"custody",
|
||||
{
|
||||
_id: id,
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
app.get("/api/custody/tokens", async (req, res) => {
|
||||
await findAndSendMany("onchain_data", res, "custody", req, {}, { tokens: 1 });
|
||||
});
|
||||
|
||||
app.get("/api/custody/tokens/:chain/:emitter", async (req, res) => {
|
||||
const id = `${req.params.chain}/${req.params.emitter}`;
|
||||
await findAndSendOne(
|
||||
"onchain_data",
|
||||
res,
|
||||
"custody",
|
||||
{
|
||||
_id: id,
|
||||
},
|
||||
{ projection: { tokens: 1 } }
|
||||
);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening on port ${port}`);
|
||||
});
|
||||
|
|
|
@ -20,6 +20,10 @@ import CustomThemeProvider from "./components/CustomThemeProvider";
|
|||
import ErrorFallback from "./components/ErrorFallback";
|
||||
import Governance from "./components/Governance";
|
||||
import Guardians from "./components/Guardians";
|
||||
// import Governor from "./components/Governor";
|
||||
// import GovernorStatus from "./components/GovernorStatus";
|
||||
import CustodyData from "./components/Custody";
|
||||
|
||||
import Home from "./components/Home";
|
||||
import VAAs from "./components/VAAs";
|
||||
import { NetworkContextProvider } from "./contexts/NetworkContext";
|
||||
|
@ -55,6 +59,12 @@ function App() {
|
|||
<Button component={Link} to="/guardians">
|
||||
Guardians
|
||||
</Button>
|
||||
<Button component={Link} to="/custody">
|
||||
Custody
|
||||
</Button>
|
||||
{/* <Button component={Link} to="/governorStatus">
|
||||
Governor Status
|
||||
</Button> */}
|
||||
</Box>
|
||||
<Box flexGrow={1} />
|
||||
<Box>
|
||||
|
@ -74,6 +84,12 @@ function App() {
|
|||
<Route exact path="/guardians">
|
||||
<Guardians />
|
||||
</Route>
|
||||
<Route exact path="/custody">
|
||||
<CustodyData />
|
||||
</Route>
|
||||
{/* <Route exact path="/governorStatus">
|
||||
<GovernorStatus />
|
||||
</Route> */}
|
||||
<Route exact path="/governance">
|
||||
<Governance />
|
||||
</Route>
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
import { ChevronRight } from "@mui/icons-material";
|
||||
import { Box } from "@mui/system";
|
||||
import { Card, IconButton, Typography } from "@mui/material";
|
||||
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
getExpandedRowModel,
|
||||
Row,
|
||||
} from "@tanstack/react-table";
|
||||
import numeral from "numeral";
|
||||
import { useState, ReactElement } from "react";
|
||||
import useCustodyData, { CustodyDataResponse } from "../hooks/useCustodyData";
|
||||
|
||||
import Table from "./Table";
|
||||
import TokenDetails from "./TokenDetails";
|
||||
|
||||
const columnHelper = createColumnHelper<CustodyDataResponse>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.display({
|
||||
id: "_expand",
|
||||
cell: ({ row }) =>
|
||||
row.getCanExpand() ? (
|
||||
<IconButton
|
||||
size="small"
|
||||
{...{
|
||||
onClick: row.getToggleExpandedHandler(),
|
||||
style: { cursor: "pointer" },
|
||||
}}
|
||||
>
|
||||
<ChevronRight
|
||||
sx={{
|
||||
transition: ".2s",
|
||||
transform: row.getIsExpanded() ? "rotate(90deg)" : undefined,
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
) : null,
|
||||
}),
|
||||
columnHelper.accessor("chainId", {
|
||||
header: () => "Chain Id",
|
||||
sortingFn: `text`,
|
||||
}),
|
||||
columnHelper.accessor("chainName", {
|
||||
header: () => "Chain Name",
|
||||
}),
|
||||
columnHelper.accessor("custodyUSD", {
|
||||
header: () => "Total Value Locked (USD)",
|
||||
cell: (info) => (
|
||||
<Box textAlign="left">${numeral(info.getValue()).format("0,0.0000")}</Box>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("tokens", {
|
||||
header: () => "Locked Tokens",
|
||||
cell: (info) => {
|
||||
const value = info.getValue();
|
||||
return `${value.length} Token` + (value.length == 1 ? "" : `s`);
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
/*
|
||||
interface Token {
|
||||
tokenAddress: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
balance: BigInt;
|
||||
qty: number;
|
||||
tokenPrice: number;
|
||||
tokenBalanceUSD: number;
|
||||
}
|
||||
*/
|
||||
|
||||
function AddTokenDetails({
|
||||
row,
|
||||
}: {
|
||||
row: Row<CustodyDataResponse>;
|
||||
}): ReactElement {
|
||||
const id = row.original._id;
|
||||
return TokenDetails(id);
|
||||
}
|
||||
|
||||
function CustodyData() {
|
||||
const custody = useCustodyData();
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const lockedValue = custody
|
||||
.map((x) => x.custodyUSD)
|
||||
.reduce((partialSum, a) => partialSum + a, 0);
|
||||
const table = useReactTable({
|
||||
columns,
|
||||
data: custody,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
getRowId: (chain) => chain.chainName,
|
||||
getRowCanExpand: () => true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getExpandedRowModel: getExpandedRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
});
|
||||
return (
|
||||
<Box m={2}>
|
||||
<Card>
|
||||
<Box m={2}>
|
||||
Total Locked Value (USD): ${numeral(lockedValue).format("0,0.0000")}
|
||||
</Box>
|
||||
<Table<CustodyDataResponse>
|
||||
table={table}
|
||||
renderSubComponent={AddTokenDetails}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustodyData;
|
|
@ -0,0 +1,84 @@
|
|||
import { Box, Card } from "@mui/material";
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import numeral from "numeral";
|
||||
import { useState } from "react";
|
||||
import useTokenDetails, {
|
||||
TokenDetailsResponse,
|
||||
} from "../hooks/useTokenDetails";
|
||||
|
||||
import Table from "./Table";
|
||||
/*
|
||||
export type TokenDetailsResponse {
|
||||
tokenAddress: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
balance: BigInt;
|
||||
qty: number;
|
||||
tokenPrice: number;
|
||||
tokenBalanceUSD: number;
|
||||
}
|
||||
*/
|
||||
const columnHelper = createColumnHelper<TokenDetailsResponse>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("tokenAddress", {
|
||||
header: () => "Token Address",
|
||||
sortingFn: `text`,
|
||||
}),
|
||||
columnHelper.accessor("name", {
|
||||
header: () => "Name",
|
||||
}),
|
||||
columnHelper.accessor("symbol", {
|
||||
header: () => "Symbol",
|
||||
}),
|
||||
columnHelper.accessor("qty", {
|
||||
header: () => "Token Balance",
|
||||
cell: (info) => (
|
||||
<Box textAlign="left">{numeral(info.getValue()).format("0,0.0000")}</Box>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("tokenPrice", {
|
||||
header: () => "Token Price",
|
||||
cell: (info) => (
|
||||
<Box textAlign="left">${numeral(info.getValue()).format("0,0.0000")}</Box>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("tokenBalanceUSD", {
|
||||
header: () => "Locked Value (USD)",
|
||||
cell: (info) => (
|
||||
<Box textAlign="left">${numeral(info.getValue()).format("0,0.0000")}</Box>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
function TokenDetails(id: string) {
|
||||
const tokenDetails = useTokenDetails(id);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const table = useReactTable({
|
||||
columns,
|
||||
data: tokenDetails,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
getRowId: (token) => token.name,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
});
|
||||
return (
|
||||
<Box m={2}>
|
||||
<Card>
|
||||
<Table<TokenDetailsResponse> table={table} />
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TokenDetails;
|
|
@ -0,0 +1,49 @@
|
|||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNetworkContext } from "../contexts/NetworkContext";
|
||||
import { POLL_TIME } from "../utils/consts";
|
||||
|
||||
export type Token = {
|
||||
tokenAddress: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
balance: BigInt;
|
||||
qty: number;
|
||||
tokenPrice: number;
|
||||
tokenBalanceUSD: number;
|
||||
};
|
||||
|
||||
export type CustodyDataResponse = {
|
||||
_id: string;
|
||||
chainId: number;
|
||||
chainName: string;
|
||||
custodyUSD: number;
|
||||
emitterAddress: string;
|
||||
tokens: Token[];
|
||||
};
|
||||
|
||||
function useCustodyData(): CustodyDataResponse[] {
|
||||
const { currentNetwork } = useNetworkContext();
|
||||
const [custodyData, setCustodyData] = useState<CustodyDataResponse[]>([]);
|
||||
useEffect(() => {
|
||||
setCustodyData([]);
|
||||
}, [currentNetwork]);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
while (!cancelled) {
|
||||
const response = await axios.get<CustodyDataResponse[]>(`/api/custody`);
|
||||
if (!cancelled) {
|
||||
setCustodyData(response.data);
|
||||
await new Promise((resolve) => setTimeout(resolve, POLL_TIME));
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [currentNetwork]);
|
||||
return custodyData;
|
||||
}
|
||||
export default useCustodyData;
|
|
@ -0,0 +1,47 @@
|
|||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNetworkContext } from "../contexts/NetworkContext";
|
||||
import { POLL_TIME } from "../utils/consts";
|
||||
|
||||
export type TokenDetailsResponse = {
|
||||
tokenAddress: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
balance: BigInt;
|
||||
qty: number;
|
||||
tokenPrice: number;
|
||||
tokenBalanceUSD: number;
|
||||
};
|
||||
|
||||
export type TokensResponse = {
|
||||
_id: string;
|
||||
tokens: TokenDetailsResponse[];
|
||||
};
|
||||
|
||||
function useTokenDetails(id?: string): TokenDetailsResponse[] {
|
||||
const { currentNetwork } = useNetworkContext();
|
||||
const [tokenDetails, setTokenDetails] = useState<TokenDetailsResponse[]>([]);
|
||||
useEffect(() => {
|
||||
setTokenDetails([]);
|
||||
}, [currentNetwork]);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
while (!cancelled) {
|
||||
const response = await axios.get<TokensResponse>(
|
||||
`/api/custody/tokens${id ? `/${id}` : ""}`
|
||||
);
|
||||
if (!cancelled) {
|
||||
setTokenDetails(response.data.tokens);
|
||||
await new Promise((resolve) => setTimeout(resolve, POLL_TIME));
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [currentNetwork, id]);
|
||||
return tokenDetails;
|
||||
}
|
||||
export default useTokenDetails;
|
|
@ -16,6 +16,7 @@ import {
|
|||
CHAIN_ID_TERRA2,
|
||||
ChainId,
|
||||
CHAIN_ID_NEAR,
|
||||
CHAIN_ID_MOONBEAM,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
require("dotenv").config();
|
||||
|
@ -204,6 +205,17 @@ export const CHAIN_INFO_MAP: { [key: string]: CHAIN_INFO } = {
|
|||
covalentChain: 0,
|
||||
explorerStem: `https://explorer.near.org`,
|
||||
},
|
||||
16: {
|
||||
name: "moonbeam",
|
||||
evm: true,
|
||||
chainId: CHAIN_ID_MOONBEAM,
|
||||
endpointUrl: "https://rpc.ankr.com/moonbeam",
|
||||
apiKey: "",
|
||||
urlStem: `https://api-moonbeam.moonscan.io`,
|
||||
platform: "moonbeam", //coingecko?
|
||||
covalentChain: 0,
|
||||
explorerStem: `https://moonscan.io/`,
|
||||
},
|
||||
18: {
|
||||
name: "terra2",
|
||||
evm: false,
|
||||
|
@ -216,3 +228,32 @@ export const CHAIN_INFO_MAP: { [key: string]: CHAIN_INFO } = {
|
|||
explorerStem: `https://finder.terra.money/mainnet`,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORMHOLE_RPC_HOSTS = [
|
||||
"https://wormhole-v2-mainnet-api.certus.one",
|
||||
"https://wormhole.inotel.ro",
|
||||
"https://wormhole-v2-mainnet-api.mcf.rocks",
|
||||
"https://wormhole-v2-mainnet-api.chainlayer.network",
|
||||
"https://wormhole-v2-mainnet-api.staking.fund",
|
||||
"https://wormhole-v2-mainnet.01node.com",
|
||||
];
|
||||
|
||||
export const CHAIN_ID_MAP = {
|
||||
"1": CHAIN_ID_SOLANA,
|
||||
"2": CHAIN_ID_ETH,
|
||||
"3": CHAIN_ID_TERRA,
|
||||
"4": CHAIN_ID_BSC,
|
||||
"5": CHAIN_ID_POLYGON,
|
||||
"6": CHAIN_ID_AVAX,
|
||||
"7": CHAIN_ID_OASIS,
|
||||
"8": CHAIN_ID_ALGORAND,
|
||||
"9": CHAIN_ID_AURORA,
|
||||
"10": CHAIN_ID_FANTOM,
|
||||
"11": CHAIN_ID_KARURA,
|
||||
"12": CHAIN_ID_ACALA,
|
||||
"13": CHAIN_ID_KLAYTN,
|
||||
"14": CHAIN_ID_CELO,
|
||||
"15": CHAIN_ID_NEAR,
|
||||
"16": CHAIN_ID_MOONBEAM,
|
||||
"18": CHAIN_ID_TERRA2,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue