Added Near, Aptos, Algorand to Custody (#21)

This commit is contained in:
ckeun 2022-11-07 09:49:49 -06:00 committed by GitHub
parent 57b478cf66
commit e141732514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 5693 additions and 889 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
{
"dependencies": {
"@certusone/wormhole-sdk": "^0.7.1",
"@certusone/wormhole-sdk": "^0.9.0",
"@metaplex/js": "^4.12.0",
"@terra-money/terra.js": "^3.1.3",
"coingecko-api": "^1.0.10",
"mongodb": "^4.10.0",
"typescript": "^4.5.2",
"serve": "^11.0.0"
"serve": "^11.0.0",
"typescript": "^4.5.2"
},
"scripts": {
"build": "tsc"
},
"devDependencies": {}
}
}

View File

@ -5,7 +5,7 @@
"AkhdZGVbJXPuQZ53u2LrimCjkRP6ZyxG1SoM85T98eE1": "starbots",
"4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R": "raydium",
"4Te4KJgjtnZe4aE2zne8G4NPfrPjCwDmaiEx9rKnyDVZ": "solclout",
"So11111111111111111111111111111111111111112": "wrapped-solana",
"So11111111111111111111111111111111111111112": "solana",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "usd-coin",
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "tether",
"7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": "lido-staked-sol"
@ -168,5 +168,11 @@
},
"18": {
"uluna": "terra-luna-2"
},
"22": {
"0x1::aptos_coin::AptosCoin": "aptos"
},
"28": {
"axpla": "xpla"
}
}

View File

@ -0,0 +1,265 @@
import { formatUnits } from "ethers/lib/utils";
import { BigNumber } from "ethers";
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
import { getEmitterAddressAlgorand } from "@certusone/wormhole-sdk";
import { calcLogicSigAccount } from "@certusone/wormhole-sdk/lib/cjs/algorand";
export const ALGORAND_HOST = {
algodToken: "",
algodServer: "https://mainnet-api.algonode.cloud", //"https://mainnet-idx.algonode.cloud", //"https://mainnet-api.algonode.cloud",
algodPort: "",
};
import { CHAIN_INFO_MAP, DISALLOWLISTED_ADDRESSES, sleepFor } from "./utils";
require("dotenv").config();
import allowList = require("./allowList.json");
import algosdk from "algosdk";
function getAllowList(chainId) {
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
}
async function findAlgoMetadata(provider, tokenAddress) {
const index = parseInt(tokenAddress);
if (index === 0) {
const definition = {
tokenAddress: tokenAddress,
decimals: Number(6),
name: "ALGO",
symbol: "ALGO",
};
return definition;
}
let decimals = undefined;
let name = undefined;
let symbol = undefined;
try {
const resp = await provider.getAssetByID(index).do();
decimals = resp.params.decimals;
name = resp.params.name;
symbol = resp.params["unit-name"];
} catch {
console.error(`could not get ${index} metadata`);
}
return {
tokenAddress: tokenAddress,
decimals: decimals,
name: name,
symbol: symbol,
};
}
export async function getNativeAlgoAddress(
algoClient: any,
token_bridge: any,
assetId: any
) {
const { doesExist, lsa } = await calcLogicSigAccount(
algoClient,
BigInt(token_bridge),
BigInt(assetId),
Buffer.from("native", "binary").toString("hex")
);
return lsa.address();
}
async function getBridgeBalanceOnChain(chainInfo, tokenList: any[]) {
const bridgeAddress = chainInfo.token_bridge_address;
var tokenAccounts = [];
const provider = new algosdk.Algodv2(
ALGORAND_HOST.algodToken,
ALGORAND_HOST.algodServer,
ALGORAND_HOST.algodPort
);
tokenAccounts = [];
let i = 0;
while (i < tokenList.length) {
let tokenAddress = tokenList[i].tokenAddress;
const tokenInfo = await findAlgoMetadata(provider, tokenAddress);
let balance = BigNumber.from("0");
try {
let nativeAlgoAddr = await getNativeAlgoAddress(
provider,
bridgeAddress,
parseInt(tokenAddress)
);
let nativeAlgoInfo = await provider
.accountInformation(nativeAlgoAddr)
.do();
if (tokenAddress == 0) {
// native ALGO balance
balance = nativeAlgoInfo?.amount;
} else {
// other native asset
if (nativeAlgoInfo?.assets.length == 0) {
balance = BigNumber.from("0");
} else {
balance = nativeAlgoInfo?.assets[0].amount;
}
}
} catch {
console.error(`could not get ${tokenAddress} balance`);
}
tokenAccounts.push({
tokenAddress: tokenAddress,
name: tokenInfo.name,
decimals: tokenInfo.decimals,
symbol: tokenInfo.symbol,
balance: balance,
});
i++;
}
return tokenAccounts;
}
export async function getAlgoTokenAccounts(chainInfo, useAllowList) {
const chainId = chainInfo.chain_id;
console.log(chainId);
var tokenAccounts = [];
if (useAllowList) {
const allowList = getAllowList(chainId);
var tokenList = [];
Object.keys(allowList).forEach((address) => {
tokenList.push({
tokenAddress: address,
});
});
tokenAccounts = await getBridgeBalanceOnChain(chainInfo, tokenList);
// console.log("tokenAccounts", tokenAccounts);
} /*else {
tokenAccounts = await getBridgeBalanceScanner(chainInfo);
}*/
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];
}
}
}
} 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 getAlgoCustody(chainInfo, useAllowList = true) {
const tokenAccounts = await getAlgoTokenAccounts(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 grabAlgoCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getAlgoCustody(chainInfo, useAllowList);
// await updateTable(chainInfo, balances);
const chainInfo_ = {
...chainInfo,
emitter_address: getEmitterAddressAlgorand(
BigInt(chainInfo.token_bridge_address)
),
balances: balances,
};
return chainInfo_;
}
// const chain = process.env.chain;
// const useAllowListstr = process.env.allowlist || "false";
// (async () => {
// const chainInfo = CHAIN_INFO_MAP[chain];
// const useAllowList = true ? useAllowListstr === "true" : false;
// const balances = await getAlgoCustody(chainInfo, useAllowList);
// console.log(balances);
// })();

View File

@ -0,0 +1,228 @@
import { formatUnits } from "ethers/lib/utils";
import { BigNumber } from "ethers";
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
import allowList = require("./allowList.json");
import { AptosClient } from "aptos";
import {
getTypeFromExternalAddress,
isValidAptosType,
} from "@certusone/wormhole-sdk";
import { CHAIN_INFO_MAP, DISALLOWLISTED_ADDRESSES } from "./utils";
require("dotenv").config();
async function findAptosMetadata(provider, tokenAddress) {
let decimals = 0;
let name = undefined;
let symbol = undefined;
try {
const coinInfo = await provider.getAccountResource(
tokenAddress.split("::")[0],
`0x1::coin::CoinInfo<${tokenAddress}>`
);
const metaData = coinInfo.data;
decimals = metaData["decimals"];
name = metaData["name"];
symbol = metaData["symbol"];
} catch (e) {
console.log(`could not find meta_data for address=${tokenAddress}`);
}
return {
tokenAddress: tokenAddress,
decimals: Number(decimals),
name: name,
symbol: symbol,
};
}
function getAllowList(chainId) {
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
}
async function getBridgeBalanceOnChain(chainInfo, tokenList: any[]) {
const bridgeAddress = chainInfo.token_bridge_address;
var tokenAccounts = [];
const provider = new AptosClient(chainInfo.endpoint_url);
tokenAccounts = [];
let i = 0;
while (i < tokenList.length) {
let tokenAddress = tokenList[i].tokenAddress;
let tokenAddressQualified = tokenAddress;
if (!isValidAptosType(tokenAddress)) {
tokenAddressQualified = await getTypeFromExternalAddress(
provider,
bridgeAddress,
tokenAddress
);
console.log("converting hash to aptos type", tokenAddressQualified);
}
const tokenInfo = await findAptosMetadata(provider, tokenAddressQualified);
let balance = BigNumber.from("0");
try {
const accountResource = await provider.getAccountResource(
bridgeAddress,
`0x1::coin::CoinStore<${tokenAddressQualified}>`
);
balance = accountResource?.data["coin"]["value"];
} catch {
console.error(`could not get aptos balance for ${tokenAddressQualified}`);
}
tokenAccounts.push({
tokenAddress: tokenAddress,
name: tokenInfo.name,
decimals: tokenInfo.decimals,
symbol: tokenInfo.symbol,
balance: BigNumber.from(balance),
});
i++;
}
return tokenAccounts;
}
export async function getAptosTokenAccounts(chainInfo, useAllowList) {
const chainId = chainInfo.chain_id;
console.log(chainId);
var tokenAccounts = [];
if (useAllowList) {
const allowList = getAllowList(chainId);
var tokenList = [];
Object.keys(allowList).forEach((address) => {
tokenList.push({
tokenAddress: address,
});
});
tokenAccounts = await getBridgeBalanceOnChain(chainInfo, tokenList);
} /*else {
// pull from transactions table
tokenAccounts = await getBridgeBalanceScanner(chainInfo);
}*/
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];
}
}
}
} 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 getAptosCustody(chainInfo, useAllowList = true) {
const tokenAccounts = await getAptosTokenAccounts(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 grabAptosCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getAptosCustody(chainInfo, useAllowList);
// await updateTable(chainInfo, balances);
const chainInfo_ = {
...chainInfo,
emitter_address:
"0000000000000000000000000000000000000000000000000000000000000001",
balances: balances,
};
return chainInfo_;
}
// const chain = process.env.chain;
// const useAllowListstr = process.env.allowlist || "false";
// (async () => {
// const chainInfo = CHAIN_INFO_MAP[chain];
// const useAllowList = true ? useAllowListstr === "true" : false;
// const balances = await getAptosCustody(chainInfo, useAllowList);
// console.log(balances);
// })();

View File

@ -120,14 +120,13 @@ 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);
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");
// console.log(e);
console.log("could not find prices for ids", coinGeckoIds);
}
return data;
}

View File

@ -2,6 +2,10 @@ import { grabTerraCustodyData } from "./getTerraCustody";
import { grabSolanaCustodyData } from "./getSolanaCustody";
import { grabEvmCustodyData } from "./getEvmCustody";
import { MongoClient } from "mongodb";
import { grabNearCustodyData } from "./getNearCustody";
import { grabAlgoCustodyData } from "./getAlgorandCustody";
import { grabAptosCustodyData } from "./getAptosCustody";
import { sleepFor } from "./utils";
interface Token {
tokenAddress: string;
name: string;
@ -24,6 +28,9 @@ interface CustodyInfo {
}
async function updateTable(chainInfo, client: MongoClient) {
if (chainInfo === undefined) {
return;
}
const custodyList = chainInfo.balances;
if (custodyList.length === 0) {
return;
@ -73,25 +80,43 @@ export async function getCustodyData() {
const client = new MongoClient(uri);
const useAllowList = true ? useAllowListstr === "true" : false;
const timeout = 5000;
const promises = [
grabSolanaCustodyData("1", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("2", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabTerraCustodyData("3", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("4", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("5", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("6", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("7", useAllowList),
// grabAlgorandCustodyData("8", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabAlgoCustodyData("8", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("9", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("10", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("11", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("12", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("13", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("14", useAllowList),
// grabNearustodyData("15", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabNearCustodyData("15", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabEvmCustodyData("16", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabTerraCustodyData("18", useAllowList),
await new Promise((res) => setTimeout(res, timeout)),
grabAptosCustodyData("22", useAllowList),
// grabTerraCustodyData("28", useAllowList),
];

View File

@ -27,7 +27,11 @@ import allowList = require("./allowList.json");
require("dotenv").config();
function getAllowList(chainId) {
return allowList[chainId];
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
@ -263,6 +267,10 @@ async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
// input array of cgids, returns json with cgid:price
prices = await getTokenPricesCGID(cgids);
if (prices === undefined) {
console.log(`could not find ids for ${chainInfo.chain_id}`);
return [];
}
for (const [key, value] of Object.entries(prices)) {
if (!value.hasOwnProperty("usd")) {
prices[key] = { usd: 0 };

View File

@ -0,0 +1,235 @@
import { formatUnits } from "ethers/lib/utils";
import { BigNumber } from "ethers";
import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
import {
ChainId,
CHAIN_ID_NEAR,
getEmitterAddressNear,
} from "@certusone/wormhole-sdk";
import {
connect as nearConnect,
keyStores as nearKeyStores,
utils as nearUtils,
Account as nearAccount,
providers as nearProviders,
} from "near-api-js";
import { CHAIN_INFO_MAP, DISALLOWLISTED_ADDRESSES, sleepFor } from "./utils";
require("dotenv").config();
async function findNearMetadata(userAccount, tokenAddress) {
if (tokenAddress === "near") {
//transfering native near
return {
decimals: 24,
name: "NEAR",
symbol: "NEAR",
};
} else {
const meta_data = await userAccount.viewFunction({
contractId: tokenAddress,
methodName: "ft_metadata",
args: {},
});
return meta_data;
}
}
import allowList = require("./allowList.json");
function getAllowList(chainId) {
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
return Number(formatUnits(tokenInfo.balance, tokenInfo.decimals));
}
async function getBridgeBalanceOnChain(chainInfo, tokenList: any[]) {
const bridgeAddress = chainInfo.token_bridge_address;
// const provider = new nearProviders.JsonRpcProvider(chainInfo.endpoint_url);
const ACCOUNT_ID = "sender.mainnet";
var tokenAccounts = [];
let near = await nearConnect({
headers: {},
networkId: "mainnet",
nodeUrl: chainInfo.endpoint_url,
});
tokenAccounts = [];
const userAccount = new nearAccount(near.connection, bridgeAddress);
let i = 0;
while (i < tokenList.length) {
let tokenAddress = tokenList[i].tokenAddress;
const tokenInfo = await findNearMetadata(userAccount, tokenAddress);
let balance = BigNumber.from("0");
if (tokenAddress === "near") {
const nearBalanceInfo = await userAccount.getAccountBalance();
balance = BigNumber.from(nearBalanceInfo.total);
} else {
balance = await userAccount.viewFunction({
contractId: tokenAddress,
methodName: "ft_balance_of",
args: {
account_id: "contract.portalbridge.near",
},
});
}
tokenAccounts.push({
tokenAddress: tokenAddress,
name: tokenInfo.name,
decimals: tokenInfo.decimals,
symbol: tokenInfo.symbol,
balance: balance,
});
i++;
}
return tokenAccounts;
}
export async function getNearTokenAccounts(chainInfo, useAllowList) {
const chainId = chainInfo.chain_id;
console.log(chainId);
var tokenAccounts = [];
if (useAllowList) {
const allowList = getAllowList(chainId);
var tokenList = [];
Object.keys(allowList).forEach((address) => {
tokenList.push({
tokenAddress: address,
});
});
tokenAccounts = await getBridgeBalanceOnChain(chainInfo, tokenList);
// console.log("tokenAccounts", tokenAccounts);
} /*else {
tokenAccounts = await getBridgeBalanceScanner(chainInfo);
}*/
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);
if (prices === undefined) {
console.log(`could not find ids for ${chainInfo.chain_id}`);
return [];
}
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 {
// 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 getNearCustody(chainInfo, useAllowList = true) {
const tokenAccounts = await getNearTokenAccounts(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 grabNearCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getNearCustody(chainInfo, useAllowList);
// await updateTable(chainInfo, balances);
const chainInfo_ = {
...chainInfo,
emitter_address: getEmitterAddressNear(chainInfo.token_bridge_address),
balances: balances,
};
return chainInfo_;
}
// const chain = process.env.chain;
// const useAllowListstr = process.env.allowlist || "false";
// (async () => {
// const chainInfo = CHAIN_INFO_MAP[chain];
// const useAllowList = true ? useAllowListstr === "true" : false;
// const balances = await getNearCustody(chainInfo, useAllowList);
// console.log(balances);
// })();

View File

@ -14,7 +14,11 @@ import { BigNumber } from "ethers";
require("dotenv").config();
function getAllowList(chainId) {
return allowList[chainId];
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
@ -215,6 +219,10 @@ async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
// input array of cgids, returns json with cgid:price
prices = await getTokenPricesCGID(cgids);
if (prices === undefined) {
console.log(`could not find ids for ${chainInfo.chain_id}`);
return [];
}
for (const [key, value] of Object.entries(prices)) {
if (!value.hasOwnProperty("usd")) {
prices[key] = { usd: 0 };
@ -303,6 +311,9 @@ export async function getSolanaCustody(chainInfo, useAllowList = true) {
export async function grabSolanaCustodyData(chain, useAllowList) {
const chainInfo = CHAIN_INFO_MAP[chain];
const balances = await getSolanaCustody(chainInfo, useAllowList);
if (balances.length === 0) {
console.log(`could not get ${chainInfo.name} custody data`);
}
const chainInfo_ = {
...chainInfo,
emitter_address:

View File

@ -6,7 +6,7 @@ import { getTokenPricesCGID, getTokenPricesGET } from "./getCoinGeckoPrices";
import {
CHAIN_ID_TERRA,
CHAIN_ID_TERRA2,
isNativeDenom,
isNativeCosmWasmDenom,
getEmitterAddressTerra,
} from "@certusone/wormhole-sdk";
@ -28,7 +28,11 @@ function formatTerraNative(address: string) {
}
function getAllowList(chainId) {
return allowList[chainId];
if (Object.keys(allowList).includes(chainId.toString())) {
return allowList[chainId];
} else {
return [];
}
}
function calcTokenQty(tokenInfo) {
@ -123,8 +127,8 @@ export async function getTerraTokenAccounts(chainInfo, useAllowList) {
for (let i = 0; i < tokenList.length; i++) {
let address = tokenList[i];
var tokenInfo = undefined;
if (isNativeDenom(address)) {
// console.log(address);
if (isNativeCosmWasmDenom(chainId, address)) {
console.log("isNativeCosmWasmDenom?", address);
tokenInfo = getNativeTerraTokenInfo(address, chainId);
} else {
tokenInfo = await queryTokenInfo(address, TERRA_HOST);
@ -225,6 +229,10 @@ async function getTokenValues(chainInfo, tokenInfos: any[], useAllowList) {
// input array of cgids, returns json with cgid:price
prices = await getTokenPricesCGID(cgids);
if (prices === undefined) {
console.log(`could not find ids for ${chainInfo.chain_id}`);
return [];
}
for (const [key, value] of Object.entries(prices)) {
if (!value.hasOwnProperty("usd")) {
prices[key] = { usd: 0 };

View File

@ -1,12 +1,12 @@
import {
CHAIN_ID_ACALA,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
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,
@ -17,6 +17,7 @@ import {
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
CHAIN_ID_TERRA2,
CHAIN_ID_XPLA,
} from "@certusone/wormhole-sdk";
export const WORMHOLE_RPC_HOSTS = [
@ -47,6 +48,8 @@ export const CHAIN_ID_MAP = {
15: CHAIN_ID_NEAR,
16: CHAIN_ID_MOONBEAM,
18: CHAIN_ID_TERRA2,
22: CHAIN_ID_APTOS,
28: CHAIN_ID_XPLA,
};
import { ethers } from "ethers";
@ -390,18 +393,21 @@ export var CHAIN_INFO_MAP = {
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,
"22": {
name: "aptos",
evm: false,
chain_id: CHAIN_ID_APTOS,
endpoint_url: "https://fullnode.mainnet.aptoslabs.com/v1",
core_bridge:
"0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625",
token_bridge_address:
"0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
custody_address:
"0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f",
api_key: "",
urlStem: "",
platform: "aptos",
covalentChain: 0,
},
};

View File

@ -16,6 +16,16 @@ module.exports = function override(config, env) {
test: /\.wasm$/,
type: "webassembly/async",
},
{
test: /\.m?js$/,
type: "javascript/auto",
},
{
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
},
],
},
plugins: [

3544
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"license": "Apache-2.0",
"proxy": "http://server:4000",
"dependencies": {
"@certusone/wormhole-sdk": "^0.6.0",
"@certusone/wormhole-sdk": "^0.7.6",
"@certusone/wormhole-sdk-proto-web": "^0.0.4",
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
@ -22,6 +22,7 @@
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3",
"assert": "^2.0.0",
"axios": "^0.27.2",
"binary-parser": "^2.2.1",
"coingecko-api": "^1.0.10",
@ -36,7 +37,9 @@
"react-router-dom": "^5.3.3",
"react-scripts": "5.0.1",
"serve": "^14.0.1",
"stream-http": "^3.2.0",
"typescript": "^4.7.4",
"url": "^0.11.0",
"web-vitals": "^2.1.4",
"web3": "^1.7.5"
},

View File

@ -1,6 +1,6 @@
import { ChevronRight } from "@mui/icons-material";
import { Box } from "@mui/system";
import { Card, IconButton, Typography } from "@mui/material";
import { Card, IconButton } from "@mui/material";
import {
createColumnHelper,
@ -58,7 +58,7 @@ const columns = [
header: () => "Locked Tokens",
cell: (info) => {
const value = info.getValue();
return `${value.length} Token` + (value.length == 1 ? "" : `s`);
return `${value.length} Token` + (value.length === 1 ? "" : `s`);
},
}),
columnHelper.accessor("updatedAt", {

View File

@ -36,7 +36,9 @@ function useCustodyData(): CustodyDataResponse[] {
while (!cancelled) {
const response = await axios.get<CustodyDataResponse[]>(`/api/custody`);
if (!cancelled) {
setCustodyData(response.data);
setCustodyData(
response.data.sort((a, b) => (a.chainId > b.chainId ? 1 : -1))
);
await new Promise((resolve) => setTimeout(resolve, POLL_TIME));
}
}

6
web/src/icons/aptos.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 5.0409C0.110314 4.58962 0.190542 4.18848 0.300856 3.78734C0.320913 3.7372 0.451285 3.67702 0.531513 3.67702C1.29368 3.667 2.04582 3.67702 2.80799 3.67702C3.68047 3.67702 4.54292 3.667 5.4154 3.67702C5.74634 3.68705 6.00708 3.58677 6.21768 3.316C6.58874 2.85469 6.69905 2.85469 7.08013 3.30597C7.30076 3.57674 7.55148 3.68705 7.90247 3.67702C8.77496 3.65697 9.65746 3.67702 10.5299 3.667C10.6904 3.667 10.7807 3.70711 10.8107 3.86757C10.891 4.23862 10.9712 4.60968 11.0615 5.03087C10.9311 5.03087 10.8408 5.03087 10.7506 5.03087C9.52709 5.03087 8.31364 5.02085 7.09016 5.0409C6.82942 5.0409 6.64891 4.96068 6.48845 4.7601C6.1174 4.28876 5.977 4.15839 5.49563 4.73002C5.29506 4.9707 5.07443 5.0409 4.7836 5.03087C3.30941 5.02085 1.82519 5.02085 0.350998 5.02085C0.27077 5.0409 0.180513 5.0409 0 5.0409Z" fill="black"/>
<path d="M0.0800781 6.52558C0.250563 6.52558 0.350846 6.52558 0.451131 6.52558C1.46401 6.52558 2.46686 6.51555 3.47974 6.52558C3.79063 6.52558 4.05137 6.45538 4.24191 6.18461C4.29206 6.11441 4.35222 6.06427 4.4124 5.99407C4.62299 5.76341 4.81354 5.74336 4.99405 5.99407C5.29491 6.43532 5.67599 6.53561 6.18744 6.52558C7.67167 6.49549 9.15588 6.51555 10.6401 6.51555C10.7304 6.51555 10.8206 6.51555 11.0011 6.51555C10.8407 6.97686 10.7003 7.39806 10.5398 7.80923C10.5198 7.84934 10.4195 7.87943 10.3493 7.87943C8.55417 7.87943 6.75907 7.88946 4.96396 7.87943C4.84362 7.87943 4.71325 7.7992 4.623 7.71897C4.49262 7.60866 4.39234 7.45823 4.272 7.33789C4.12157 7.18746 3.95109 7.17743 3.79063 7.32786C3.7806 7.33789 3.7806 7.33789 3.77057 7.34792C3.44966 7.87943 2.95826 7.9396 2.38664 7.90952C1.85513 7.8694 1.32361 7.88946 0.782074 7.90952C0.61159 7.90952 0.521332 7.84934 0.47119 7.69892C0.350847 7.31783 0.230506 6.94678 0.0800781 6.52558Z" fill="black"/>
<path d="M1.14453 2.20271C3.24049 -0.685505 7.72324 -0.775762 9.91948 2.18265C9.52837 2.18265 9.14729 2.22277 8.77623 2.16259C8.61577 2.14254 8.47538 1.94197 8.35503 1.80157C8.00404 1.42048 7.90375 1.40043 7.57281 1.79154C7.31207 2.09239 7.03127 2.21274 6.6201 2.21274C4.90522 2.19268 3.18032 2.20271 1.46544 2.20271C1.36516 2.20271 1.26487 2.20271 1.14453 2.20271Z" fill="black"/>
<path d="M1.52539 9.24251C1.85633 9.23249 2.00676 8.9918 2.18727 8.79123C2.46807 8.47032 2.63856 8.45026 2.88927 8.79123C3.15001 9.16229 3.48095 9.25254 3.91218 9.24251C5.68722 9.22246 7.4723 9.23249 9.24735 9.23249C9.33761 9.23249 9.42786 9.23249 9.52815 9.23249C7.74307 11.4187 3.77178 11.7496 1.52539 9.24251Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,7 @@
<svg width="18" height="21" viewBox="0 0 18 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.91443 12.9234L11.6441 17.2597L14.1658 15.7806L10.5807 10.0615L8.91443 12.9234Z" fill="black"/>
<path d="M16.4624 14.042L16.4605 12.8568L12.5432 6.68164L11.0899 9.17756L14.8715 15.3676L16.2388 14.5657C16.3729 14.4554 16.454 14.2936 16.4626 14.1191L16.4624 14.042Z" fill="black"/>
<path d="M0.046875 15.159L1.9777 16.2851L8.40306 5.85581L7.31257 5.82668C6.38342 5.8134 5.38252 6.05787 4.92314 6.81684L1.2744 12.5408L0.046875 14.4497V15.159V15.159Z" fill="black"/>
<path d="M12.1109 5.85547L9.23681 5.86603L2.7345 16.7261L5.00726 18.0505L5.62533 16.9895L12.1109 5.85547Z" fill="black"/>
<path d="M17.6733 5.84104C17.6493 5.2327 17.3239 4.6758 16.8141 4.35158L9.66906 0.192877C9.1648 -0.0640998 8.53488 -0.0644298 8.02972 0.192712C7.97001 0.223199 1.08136 4.26672 1.08136 4.26672C0.986014 4.313 0.894179 4.36812 0.807806 4.4307C0.352748 4.76078 0.0753671 5.27375 0.046875 5.83729V14.4499L1.2744 12.541L1.26368 5.8955C1.26511 5.8712 1.26812 5.84727 1.2724 5.82368C1.30016 5.66732 1.38641 5.52784 1.51628 5.43357C1.54868 5.4101 8.55501 1.30544 8.57727 1.29418C8.74194 1.21047 8.95125 1.20948 9.11613 1.29158L16.1687 5.39739C16.3356 5.50494 16.4402 5.68729 16.4495 5.88663V14.1187C16.4409 14.2933 16.3727 14.455 16.2386 14.5653L14.8713 15.3672L14.1658 15.781L11.6442 17.2601L9.08695 18.76C8.94922 18.8104 8.78817 18.8075 8.65158 18.7514L5.62595 16.9901L5.00789 18.051L7.72691 19.6354C7.81683 19.6872 7.89692 19.7331 7.96267 19.7704C8.06445 19.8282 8.13383 19.8668 8.15833 19.8789C8.35157 19.9739 8.62965 20.0292 8.88021 20.0292C9.10994 20.0292 9.33396 19.9865 9.54592 19.9024L16.9736 15.5488C17.4 15.2144 17.6508 14.7103 17.6733 14.1641V5.84104Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,12 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_35_159)">
<path d="M9.85708 20C15.301 20 19.7142 15.5228 19.7142 10C19.7142 4.47715 15.301 0 9.85708 0C4.41317 0 0 4.47715 0 10C0 15.5228 4.41317 20 9.85708 20Z" fill="white"/>
<path d="M6.98406 12.6578C6.39713 12.6578 5.91626 12.5177 5.54142 12.2375C5.1715 11.9523 4.98654 11.547 4.98654 11.0216C4.98654 10.9116 4.99888 10.7764 5.02353 10.6163C5.08768 10.2561 5.17891 9.82324 5.29728 9.31788C5.63265 7.94188 6.49826 7.25388 7.89407 7.25388C8.27384 7.25388 8.61415 7.31892 8.91502 7.449C9.2159 7.57408 9.45263 7.76424 9.62525 8.01944C9.79786 8.2696 9.88421 8.56984 9.88421 8.92008C9.88421 9.02516 9.87187 9.15776 9.84719 9.31788C9.77322 9.7632 9.68443 10.196 9.58085 10.6163C9.40823 11.3018 9.10984 11.8147 8.68567 12.155C8.2615 12.4902 7.69432 12.6578 6.98406 12.6578ZM7.08764 11.577C7.36387 11.577 7.59812 11.4945 7.79049 11.3294C7.98779 11.1642 8.12835 10.9116 8.21217 10.5713C8.32561 10.101 8.41196 9.69064 8.47114 9.3404C8.49085 9.23532 8.50071 9.12776 8.50071 9.01764C8.50071 8.56232 8.26643 8.33464 7.7979 8.33464C7.52167 8.33464 7.28494 8.4172 7.08764 8.58232C6.89531 8.74748 6.75719 9.00016 6.67336 9.3404C6.58457 9.67564 6.49578 10.086 6.40703 10.5713C6.38727 10.6714 6.37742 10.7764 6.37742 10.8865C6.37742 11.3469 6.61418 11.577 7.08764 11.577Z" fill="#050813"/>
<path d="M10.2239 12.5828C10.1696 12.5828 10.1277 12.5653 10.0981 12.5302C10.0734 12.4902 10.0661 12.4452 10.0759 12.3952L11.0969 7.51656C11.1067 7.46152 11.1339 7.41648 11.1783 7.38144C11.2226 7.34644 11.2695 7.32892 11.3188 7.32892H13.2868C13.8342 7.32892 14.2732 7.444 14.6036 7.67416C14.939 7.90436 15.1067 8.23708 15.1067 8.6724C15.1067 8.79748 15.0919 8.9276 15.0623 9.06268C14.939 9.63812 14.69 10.0634 14.3151 10.3386C13.9452 10.6138 13.4372 10.7514 12.7911 10.7514H11.7923L11.452 12.3952C11.4421 12.4502 11.415 12.4952 11.3706 12.5302C11.3262 12.5653 11.2793 12.5828 11.2301 12.5828H10.2239ZM12.8429 9.71568C13.05 9.71568 13.23 9.65812 13.3829 9.54304C13.5408 9.42796 13.6443 9.26284 13.6937 9.04768C13.7084 8.9626 13.7159 8.88756 13.7159 8.82252C13.7159 8.6774 13.6739 8.56732 13.5901 8.49228C13.5062 8.4122 13.3632 8.3722 13.161 8.3722H12.2732L11.9921 9.71568H12.8429Z" fill="#050813"/>
</g>
<defs>
<clipPath id="clip0_35_159">
<rect width="19.7142" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

3
web/src/icons/xpla.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="1000" height="1000" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M830.8 820.197C742.952 905.178 623.805 952.92 499.569 952.92C375.334 952.92 256.187 905.178 168.339 820.197C80.4911 735.217 31.1387 619.958 31.1387 499.777C31.1387 379.596 80.4911 264.338 168.339 179.357C256.187 94.3764 375.334 46.6348 499.569 46.6348C623.805 46.6348 742.952 94.3764 830.8 179.357C918.648 264.338 968 379.596 968 499.777C968 619.958 918.648 735.217 830.8 820.197ZM533.691 564.083L566.854 532.003L794.575 752.292L761.412 784.372L533.691 564.083ZM204.618 246.729L237.728 214.649L465.449 434.938L432.34 467.018L204.618 246.729ZM533.531 435.555L760.986 215.009L761.04 215.112L794.202 247.141L566.641 467.533L533.531 435.555ZM205.363 752.961L432.925 532.518L466.088 564.598L238.526 784.99L205.363 752.961Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 894 B

View File

@ -1,6 +1,8 @@
import {
CHAIN_ID_ACALA,
CHAIN_ID_ALGORAND,
CHAIN_ID_APTOS,
CHAIN_ID_ARBITRUM,
CHAIN_ID_AURORA,
CHAIN_ID_AVAX,
CHAIN_ID_BSC,
@ -17,9 +19,12 @@ import {
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
CHAIN_ID_TERRA2,
CHAIN_ID_XPLA,
} from "@certusone/wormhole-sdk";
import acalaIcon from "../icons/acala.svg";
import algorandIcon from "../icons/algorand.svg";
import aptosIcon from "../icons/aptos.svg";
import arbitrumIcon from "../icons/arbitrum.svg";
import auroraIcon from "../icons/aurora.svg";
import avaxIcon from "../icons/avax.svg";
import bscIcon from "../icons/bsc.svg";
@ -36,6 +41,7 @@ import polygonIcon from "../icons/polygon.svg";
import solanaIcon from "../icons/solana.svg";
import terraIcon from "../icons/terra.svg";
import terra2Icon from "../icons/terra2.svg";
import xplaIcon from "../icons/xpla.svg";
const chainIdToIconMap: { [id: number]: string } = {
[CHAIN_ID_SOLANA]: solanaIcon,
@ -56,6 +62,9 @@ const chainIdToIconMap: { [id: number]: string } = {
[CHAIN_ID_OASIS]: oasisIcon,
[CHAIN_ID_POLYGON]: polygonIcon,
[CHAIN_ID_PYTHNET]: pythnetIcon,
[CHAIN_ID_APTOS]: aptosIcon,
[CHAIN_ID_XPLA]: xplaIcon,
[CHAIN_ID_ARBITRUM]: arbitrumIcon,
};
const chainIdToIcon = (chainId: number) => chainIdToIconMap[chainId] || "";
export default chainIdToIcon;