[contract_manager] Add logic for tracking fee denominations and dollar values (#1394)

* tokens

* progress

* progress

* progress

* infra for storing tokens and using them in fee calculations

* precommit

* cleanup

* cleanup

* fix
This commit is contained in:
Jayant Krishnamurthy 2024-03-28 06:26:04 -07:00 committed by GitHub
parent 6fb5ab483d
commit 0f7a9cc334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 252 additions and 30 deletions

View File

@ -21,8 +21,8 @@
"url": "git+https://github.com/pyth-network/pyth-crosschain.git" "url": "git+https://github.com/pyth-network/pyth-crosschain.git"
}, },
"dependencies": { "dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@certusone/wormhole-sdk": "^0.9.8", "@certusone/wormhole-sdk": "^0.9.8",
"@coral-xyz/anchor": "^0.29.0",
"@injectivelabs/networks": "1.0.68", "@injectivelabs/networks": "1.0.68",
"@mysten/sui.js": "^0.49.1", "@mysten/sui.js": "^0.49.1",
"@pythnetwork/cosmwasm-deploy-tools": "*", "@pythnetwork/cosmwasm-deploy-tools": "*",
@ -31,6 +31,7 @@
"@pythnetwork/pyth-sui-js": "*", "@pythnetwork/pyth-sui-js": "*",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
"aptos": "^1.5.0", "aptos": "^1.5.0",
"axios": "^0.24.0",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"

View File

@ -19,6 +19,18 @@ const parser = yargs(hideBin(process.argv))
async function main() { async function main() {
const argv = await parser.argv; const argv = await parser.argv;
const prices: Record<string, number> = {};
for (const token of Object.values(DefaultStore.tokens)) {
const price = await token.getPriceForMinUnit();
// We're going to ignore the value of tokens that aren't configured
// in the store -- these are likely not worth much anyway.
if (price !== undefined) {
prices[token.id] = price;
}
}
let totalFeeUsd = 0;
for (const contract of Object.values(DefaultStore.contracts)) { for (const contract of Object.values(DefaultStore.contracts)) {
if (contract.getChain().isMainnet() === argv.testnet) continue; if (contract.getChain().isMainnet() === argv.testnet) continue;
if ( if (
@ -27,12 +39,26 @@ async function main() {
contract instanceof CosmWasmPriceFeedContract contract instanceof CosmWasmPriceFeedContract
) { ) {
try { try {
console.log(`${contract.getId()} ${await contract.getTotalFee()}`); const fee = await contract.getTotalFee();
let feeUsd = 0;
if (fee.denom !== undefined && prices[fee.denom] !== undefined) {
feeUsd = Number(fee.amount) * prices[fee.denom];
totalFeeUsd += feeUsd;
console.log(
`${contract.getId()} ${fee.amount} ${fee.denom} ($${feeUsd})`
);
} else {
console.log(
`${contract.getId()} ${fee.amount} ${fee.denom} ($ value unknown)`
);
}
} catch (e) { } catch (e) {
console.error(`Error fetching fees for ${contract.getId()}`, e); console.error(`Error fetching fees for ${contract.getId()}`, e);
} }
} }
} }
console.log(`Total fees in USD: $${totalFeeUsd}`);
} }
main(); main();

View File

@ -23,10 +23,12 @@ import { Network } from "@injectivelabs/networks";
import { SuiClient } from "@mysten/sui.js/client"; import { SuiClient } from "@mysten/sui.js/client";
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519"; import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
import { TransactionObject } from "web3/eth/types"; import { TransactionObject } from "web3/eth/types";
import { TokenId } from "./token";
export type ChainConfig = Record<string, string> & { export type ChainConfig = Record<string, string> & {
mainnet: boolean; mainnet: boolean;
id: string; id: string;
nativeToken: TokenId;
}; };
export abstract class Chain extends Storable { export abstract class Chain extends Storable {
public wormholeChainName: ChainName; public wormholeChainName: ChainName;
@ -37,12 +39,14 @@ export abstract class Chain extends Storable {
* @param mainnet whether this chain is mainnet or testnet/devnet * @param mainnet whether this chain is mainnet or testnet/devnet
* @param wormholeChainName the name of the wormhole chain that this chain is associated with. * @param wormholeChainName the name of the wormhole chain that this chain is associated with.
* Note that pyth has included additional chain names and ids to the wormhole spec. * Note that pyth has included additional chain names and ids to the wormhole spec.
* @param nativeToken the id of the token used to pay gas on this chain
* @protected * @protected
*/ */
protected constructor( protected constructor(
protected id: string, protected id: string,
protected mainnet: boolean, protected mainnet: boolean,
wormholeChainName: string wormholeChainName: string,
protected nativeToken: TokenId | undefined
) { ) {
super(); super();
this.wormholeChainName = wormholeChainName as ChainName; this.wormholeChainName = wormholeChainName as ChainName;
@ -65,6 +69,10 @@ export abstract class Chain extends Storable {
return this.mainnet; return this.mainnet;
} }
public getNativeToken(): TokenId | undefined {
return this.nativeToken;
}
/** /**
* Returns the payload for a governance SetFee instruction for contracts deployed on this chain * Returns the payload for a governance SetFee instruction for contracts deployed on this chain
* @param fee the new fee to set * @param fee the new fee to set
@ -125,7 +133,7 @@ export abstract class Chain extends Storable {
export class GlobalChain extends Chain { export class GlobalChain extends Chain {
static type = "GlobalChain"; static type = "GlobalChain";
constructor() { constructor() {
super("global", true, "unset"); super("global", true, "unset", undefined);
} }
generateGovernanceUpgradePayload(): Buffer { generateGovernanceUpgradePayload(): Buffer {
@ -163,12 +171,13 @@ export class CosmWasmChain extends Chain {
id: string, id: string,
mainnet: boolean, mainnet: boolean,
wormholeChainName: string, wormholeChainName: string,
nativeToken: TokenId | undefined,
public endpoint: string, public endpoint: string,
public gasPrice: string, public gasPrice: string,
public prefix: string, public prefix: string,
public feeDenom: string public feeDenom: string
) { ) {
super(id, mainnet, wormholeChainName); super(id, mainnet, wormholeChainName, nativeToken);
} }
static fromJson(parsed: ChainConfig): CosmWasmChain { static fromJson(parsed: ChainConfig): CosmWasmChain {
@ -180,7 +189,8 @@ export class CosmWasmChain extends Chain {
parsed.endpoint, parsed.endpoint,
parsed.gasPrice, parsed.gasPrice,
parsed.prefix, parsed.prefix,
parsed.feeDenom parsed.feeDenom,
parsed.nativeToken
); );
} }
@ -248,9 +258,10 @@ export class SuiChain extends Chain {
id: string, id: string,
mainnet: boolean, mainnet: boolean,
wormholeChainName: string, wormholeChainName: string,
nativeToken: TokenId | undefined,
public rpcUrl: string public rpcUrl: string
) { ) {
super(id, mainnet, wormholeChainName); super(id, mainnet, wormholeChainName, nativeToken);
} }
static fromJson(parsed: ChainConfig): SuiChain { static fromJson(parsed: ChainConfig): SuiChain {
@ -259,6 +270,7 @@ export class SuiChain extends Chain {
parsed.id, parsed.id,
parsed.mainnet, parsed.mainnet,
parsed.wormholeChainName, parsed.wormholeChainName,
parsed.nativeToken,
parsed.rpcUrl parsed.rpcUrl
); );
} }
@ -314,11 +326,12 @@ export class EvmChain extends Chain {
constructor( constructor(
id: string, id: string,
mainnet: boolean, mainnet: boolean,
nativeToken: TokenId | undefined,
private rpcUrl: string, private rpcUrl: string,
private networkId: number private networkId: number
) { ) {
// On EVM networks we use the chain id as the wormhole chain name // On EVM networks we use the chain id as the wormhole chain name
super(id, mainnet, id); super(id, mainnet, id, nativeToken);
} }
static fromJson(parsed: ChainConfig & { networkId: number }): EvmChain { static fromJson(parsed: ChainConfig & { networkId: number }): EvmChain {
@ -326,6 +339,7 @@ export class EvmChain extends Chain {
return new EvmChain( return new EvmChain(
parsed.id, parsed.id,
parsed.mainnet, parsed.mainnet,
parsed.nativeToken,
parsed.rpcUrl, parsed.rpcUrl,
parsed.networkId parsed.networkId
); );
@ -468,9 +482,10 @@ export class AptosChain extends Chain {
id: string, id: string,
mainnet: boolean, mainnet: boolean,
wormholeChainName: string, wormholeChainName: string,
nativeToken: TokenId | undefined,
public rpcUrl: string public rpcUrl: string
) { ) {
super(id, mainnet, wormholeChainName); super(id, mainnet, wormholeChainName, nativeToken);
} }
getClient(): AptosClient { getClient(): AptosClient {
@ -508,6 +523,7 @@ export class AptosChain extends Chain {
parsed.id, parsed.id,
parsed.mainnet, parsed.mainnet,
parsed.wormholeChainName, parsed.wormholeChainName,
parsed.nativeToken,
parsed.rpcUrl parsed.rpcUrl
); );
} }

View File

@ -3,6 +3,7 @@ import { ApiError, BCS, CoinClient, TxnBuilderTypes } from "aptos";
import { AptosChain, Chain } from "../chains"; import { AptosChain, Chain } from "../chains";
import { DataSource } from "xc_admin_common"; import { DataSource } from "xc_admin_common";
import { WormholeContract } from "./wormhole"; import { WormholeContract } from "./wormhole";
import { TokenQty } from "../token";
type WormholeState = { type WormholeState = {
chain_id: { number: string }; chain_id: { number: string };
@ -91,7 +92,11 @@ export class AptosPriceFeedContract extends PriceFeedContract {
static fromJson( static fromJson(
chain: Chain, chain: Chain,
parsed: { type: string; stateId: string; wormholeStateId: string } parsed: {
type: string;
stateId: string;
wormholeStateId: string;
}
): AptosPriceFeedContract { ): AptosPriceFeedContract {
if (parsed.type !== AptosPriceFeedContract.type) if (parsed.type !== AptosPriceFeedContract.type)
throw new Error("Invalid type"); throw new Error("Invalid type");
@ -260,9 +265,13 @@ export class AptosPriceFeedContract extends PriceFeedContract {
return AptosPriceFeedContract.type; return AptosPriceFeedContract.type;
} }
async getTotalFee(): Promise<bigint> { async getTotalFee(): Promise<TokenQty> {
const client = new CoinClient(this.chain.getClient()); const client = new CoinClient(this.chain.getClient());
return await client.checkBalance(this.stateId); const amount = await client.checkBalance(this.stateId);
return {
amount,
denom: this.chain.getNativeToken(),
};
} }
async getValidTimePeriod() { async getValidTimePeriod() {

View File

@ -17,6 +17,7 @@ import {
TxResult, TxResult,
} from "../base"; } from "../base";
import { WormholeContract } from "./wormhole"; import { WormholeContract } from "./wormhole";
import { TokenQty } from "../token";
/** /**
* Variables here need to be snake case to match the on-chain contract configs * Variables here need to be snake case to match the on-chain contract configs
@ -332,13 +333,16 @@ export class CosmWasmPriceFeedContract extends PriceFeedContract {
return this.chain; return this.chain;
} }
async getTotalFee(): Promise<bigint> { async getTotalFee(): Promise<TokenQty> {
const client = await CosmWasmClient.connect(this.chain.endpoint); const client = await CosmWasmClient.connect(this.chain.endpoint);
const coin = await client.getBalance( const coin = await client.getBalance(
this.address, this.address,
this.getChain().feeDenom this.getChain().feeDenom
); );
return BigInt(coin.amount); return {
amount: BigInt(coin.amount),
denom: this.chain.getNativeToken(),
};
} }
async getValidTimePeriod() { async getValidTimePeriod() {

View File

@ -5,6 +5,7 @@ import { PriceFeedContract, PrivateKey, Storable } from "../base";
import { Chain, EvmChain } from "../chains"; import { Chain, EvmChain } from "../chains";
import { DataSource, EvmExecute } from "xc_admin_common"; import { DataSource, EvmExecute } from "xc_admin_common";
import { WormholeContract } from "./wormhole"; import { WormholeContract } from "./wormhole";
import { TokenQty } from "../token";
// Just to make sure tx gas limit is enough // Just to make sure tx gas limit is enough
const EXTENDED_ENTROPY_ABI = [ const EXTENDED_ENTROPY_ABI = [
@ -724,9 +725,13 @@ export class EvmPriceFeedContract extends PriceFeedContract {
return Web3.utils.keccak256(strippedCode); return Web3.utils.keccak256(strippedCode);
} }
async getTotalFee(): Promise<bigint> { async getTotalFee(): Promise<TokenQty> {
const web3 = new Web3(this.chain.getRpcUrl()); const web3 = new Web3(this.chain.getRpcUrl());
return BigInt(await web3.eth.getBalance(this.address)); const amount = BigInt(await web3.eth.getBalance(this.address));
return {
amount,
denom: this.chain.getNativeToken(),
};
} }
async getLastExecutedGovernanceSequence() { async getLastExecutedGovernanceSequence() {

View File

@ -13,6 +13,7 @@ import {
EvmPriceFeedContract, EvmPriceFeedContract,
SuiPriceFeedContract, SuiPriceFeedContract,
} from "./contracts"; } from "./contracts";
import { Token } from "./token";
import { PriceFeedContract, Storable } from "./base"; import { PriceFeedContract, Storable } from "./base";
import { parse, stringify } from "yaml"; import { parse, stringify } from "yaml";
import { readdirSync, readFileSync, statSync, writeFileSync } from "fs"; import { readdirSync, readFileSync, statSync, writeFileSync } from "fs";
@ -22,11 +23,13 @@ export class Store {
public chains: Record<string, Chain> = { global: new GlobalChain() }; public chains: Record<string, Chain> = { global: new GlobalChain() };
public contracts: Record<string, PriceFeedContract> = {}; public contracts: Record<string, PriceFeedContract> = {};
public entropy_contracts: Record<string, EvmEntropyContract> = {}; public entropy_contracts: Record<string, EvmEntropyContract> = {};
public tokens: Record<string, Token> = {};
public vaults: Record<string, Vault> = {}; public vaults: Record<string, Vault> = {};
constructor(public path: string) { constructor(public path: string) {
this.loadAllChains(); this.loadAllChains();
this.loadAllContracts(); this.loadAllContracts();
this.loadAllTokens();
this.loadAllVaults(); this.loadAllVaults();
} }
@ -143,6 +146,20 @@ export class Store {
}); });
} }
loadAllTokens() {
this.getYamlFiles(`${this.path}/tokens/`).forEach((yamlFile) => {
const parsedArray = parse(readFileSync(yamlFile, "utf-8"));
for (const parsed of parsedArray) {
if (parsed.type !== Token.type) return;
const token = Token.fromJson(parsed);
if (this.tokens[token.getId()])
throw new Error(`Multiple tokens with id ${token.getId()} found`);
this.tokens[token.getId()] = token;
}
});
}
loadAllVaults() { loadAllVaults() {
this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => { this.getYamlFiles(`${this.path}/vaults/`).forEach((yamlFile) => {
const parsedArray = parse(readFileSync(yamlFile, "utf-8")); const parsedArray = parse(readFileSync(yamlFile, "utf-8"));

View File

@ -0,0 +1,81 @@
import axios from "axios";
import { KeyValueConfig, Storable } from "./base";
export type TokenId = string;
/**
* A quantity of a token, represented as an integer number of the minimum denomination of the token.
* This can also represent a quantity of an unknown token (represented by an undefined denom).
*/
export type TokenQty = {
amount: bigint;
denom: TokenId | undefined;
};
/**
* A token represents a cryptocurrency like ETH or BTC.
* The main use of this class is to calculate the dollar value of accrued fees.
*/
export class Token extends Storable {
static type = "token";
public constructor(
public id: TokenId,
// The hexadecimal pyth id of the tokens X/USD price feed
// (get this from hermes or the Pyth docs page)
public pythId: string | undefined,
public decimals: number
) {
super();
}
getId(): TokenId {
return this.id;
}
getType(): string {
return Token.type;
}
/**
* Get the dollar value of 1 token. Returns undefined for tokens that do
* not have a configured pricing method.
*/
async getPrice(): Promise<number | undefined> {
if (this.pythId) {
const url = `https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=${this.pythId}&parsed=true`;
const response = await axios.get(url);
const price = response.data.parsed[0].price;
// Note that this conversion can lose some precision.
// We don't really care about that in this application.
return parseInt(price.price) * Math.pow(10, price.expo);
} else {
// We may support other pricing methodologies in the future but whatever.
return undefined;
}
}
/**
* Get the dollar value of the minimum representable quantity of this token.
* E.g., for ETH, this method will return the dollar value of 1 wei.
*/
async getPriceForMinUnit(): Promise<number | undefined> {
const price = await this.getPrice();
return price ? price / Math.pow(10, this.decimals) : undefined;
}
toJson(): KeyValueConfig {
return {
id: this.id,
...(this.pythId !== undefined ? { pythId: this.pythId } : {}),
};
}
static fromJson(parsed: {
id: string;
pythId?: string;
decimals: number;
}): Token {
return new Token(parsed.id, parsed.pythId, parsed.decimals);
}
}

View File

@ -8,6 +8,7 @@
mainnet: true mainnet: true
rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1 rpcUrl: https://fullnode.mainnet.aptoslabs.com/v1
type: AptosChain type: AptosChain
nativeToken: APT
- id: movement_move_devnet - id: movement_move_devnet
wormholeChainName: movement_move_devnet wormholeChainName: movement_move_devnet
mainnet: false mainnet: false

View File

@ -13,6 +13,7 @@
rpcUrl: https://evmos-evm.publicnode.com rpcUrl: https://evmos-evm.publicnode.com
networkId: 9001 networkId: 9001
type: EvmChain type: EvmChain
nativeToken: EVMOS
- id: canto - id: canto
mainnet: true mainnet: true
rpcUrl: https://canto.slingshot.finance rpcUrl: https://canto.slingshot.finance
@ -68,6 +69,7 @@
rpcUrl: https://rpc.gnosischain.com rpcUrl: https://rpc.gnosischain.com
networkId: 100 networkId: 100
type: EvmChain type: EvmChain
nativeToken: DAI
- id: fantom_testnet - id: fantom_testnet
mainnet: false mainnet: false
rpcUrl: https://fantom-testnet.blastapi.io/$ENV_BLAST_API_KEY rpcUrl: https://fantom-testnet.blastapi.io/$ENV_BLAST_API_KEY
@ -83,6 +85,7 @@
rpcUrl: https://rpc.ankr.com/fantom rpcUrl: https://rpc.ankr.com/fantom
networkId: 250 networkId: 250
type: EvmChain type: EvmChain
nativeToken: FTM
- id: mumbai - id: mumbai
mainnet: false mainnet: false
rpcUrl: https://polygon-testnet.blastapi.io/$ENV_BLAST_API_KEY rpcUrl: https://polygon-testnet.blastapi.io/$ENV_BLAST_API_KEY
@ -108,6 +111,7 @@
rpcUrl: https://rpc.mantle.xyz/ rpcUrl: https://rpc.mantle.xyz/
networkId: 5000 networkId: 5000
type: EvmChain type: EvmChain
nativeToken: MNT
- id: kava_testnet - id: kava_testnet
mainnet: false mainnet: false
rpcUrl: https://evm.testnet.kava.io rpcUrl: https://evm.testnet.kava.io
@ -128,6 +132,7 @@
rpcUrl: https://eth-mainnet.blastapi.io/$ENV_BLAST_API_KEY rpcUrl: https://eth-mainnet.blastapi.io/$ENV_BLAST_API_KEY
networkId: 1 networkId: 1
type: EvmChain type: EvmChain
nativeToken: ETH
- id: bsc_testnet - id: bsc_testnet
mainnet: false mainnet: false
rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel rpcUrl: https://rpc.ankr.com/bsc_testnet_chapel
@ -143,6 +148,7 @@
rpcUrl: https://mainnet.aurora.dev rpcUrl: https://mainnet.aurora.dev
networkId: 1313161554 networkId: 1313161554
type: EvmChain type: EvmChain
nativeToken: NEAR
- id: bsc - id: bsc
mainnet: true mainnet: true
rpcUrl: https://rpc.ankr.com/bsc rpcUrl: https://rpc.ankr.com/bsc
@ -178,6 +184,7 @@
rpcUrl: https://polygon-rpc.com rpcUrl: https://polygon-rpc.com
networkId: 137 networkId: 137
type: EvmChain type: EvmChain
nativeToken: MATIC
- id: wemix_testnet - id: wemix_testnet
mainnet: false mainnet: false
rpcUrl: https://api.test.wemix.com rpcUrl: https://api.test.wemix.com
@ -188,11 +195,13 @@
rpcUrl: https://rpc-mainnet.kcc.network rpcUrl: https://rpc-mainnet.kcc.network
networkId: 321 networkId: 321
type: EvmChain type: EvmChain
nativeToken: KCS
- id: polygon_zkevm - id: polygon_zkevm
mainnet: true mainnet: true
rpcUrl: https://zkevm-rpc.com rpcUrl: https://zkevm-rpc.com
networkId: 1101 networkId: 1101
type: EvmChain type: EvmChain
nativeToken: ETH
- id: celo_alfajores_testnet - id: celo_alfajores_testnet
mainnet: false mainnet: false
rpcUrl: https://alfajores-forno.celo-testnet.org rpcUrl: https://alfajores-forno.celo-testnet.org
@ -208,21 +217,25 @@
rpcUrl: https://zksync2-mainnet.zksync.io rpcUrl: https://zksync2-mainnet.zksync.io
networkId: 324 networkId: 324
type: EvmChain type: EvmChain
nativeToken: ETH
- id: base - id: base
mainnet: true mainnet: true
rpcUrl: https://developer-access-mainnet.base.org/ rpcUrl: https://developer-access-mainnet.base.org/
networkId: 8453 networkId: 8453
type: EvmChain type: EvmChain
nativeToken: ETH
- id: arbitrum - id: arbitrum
mainnet: true mainnet: true
rpcUrl: https://arb1.arbitrum.io/rpc rpcUrl: https://arb1.arbitrum.io/rpc
networkId: 42161 networkId: 42161
type: EvmChain type: EvmChain
nativeToken: ETH
- id: optimism - id: optimism
mainnet: true mainnet: true
rpcUrl: https://rpc.ankr.com/optimism rpcUrl: https://rpc.ankr.com/optimism
networkId: 10 networkId: 10
type: EvmChain type: EvmChain
nativeToken: ETH
- id: kcc_testnet - id: kcc_testnet
mainnet: false mainnet: false
rpcUrl: https://rpc-testnet.kcc.network rpcUrl: https://rpc-testnet.kcc.network
@ -243,6 +256,7 @@
rpcUrl: https://linea.rpc.thirdweb.com rpcUrl: https://linea.rpc.thirdweb.com
networkId: 59144 networkId: 59144
type: EvmChain type: EvmChain
nativeToken: ETH
- id: shimmer_testnet - id: shimmer_testnet
mainnet: false mainnet: false
rpcUrl: https://json-rpc.evm.testnet.shimmer.network rpcUrl: https://json-rpc.evm.testnet.shimmer.network
@ -373,6 +387,7 @@
rpcUrl: https://rpc.coredao.org rpcUrl: https://rpc.coredao.org
networkId: 1116 networkId: 1116
type: EvmChain type: EvmChain
nativeToken: CORE
- id: tomochain - id: tomochain
mainnet: true mainnet: true
rpcUrl: https://rpc.tomochain.com rpcUrl: https://rpc.tomochain.com
@ -443,6 +458,7 @@
rpcUrl: https://mainnet.hashio.io/api rpcUrl: https://mainnet.hashio.io/api
networkId: 295 networkId: 295
type: EvmChain type: EvmChain
nativeToken: HBAR
- id: filecoin_calibration - id: filecoin_calibration
mainnet: false mainnet: false
rpcUrl: https://rpc.ankr.com/filecoin_testnet rpcUrl: https://rpc.ankr.com/filecoin_testnet

View File

@ -0,0 +1,44 @@
- id: ETH
pythId: ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
decimals: 18
type: token
- id: APT
pythId: 03ae4db29ed4ae33d323568895aa00337e658e348b37509f5372ae51f0af00d5
decimals: 8
type: token
- id: EVMOS
pythId: c19405e4c8bdcbf2a66c37ae05a27d385c8309e9d648ed20dc6ee717e7d30e17
decimals: 18
type: token
- id: MATIC
pythId: 5de33a9112c2b700b8d30b8a3402c103578ccfa2765696471cc672bd5cf6ac52
decimals: 18
type: token
- id: NEAR
pythId: c415de8d2eba7db216527dff4b60e8f3a5311c740dadb233e13e12547e226750
decimals: 18
type: token
- id: FTM
pythId: 5c6c0d2386e3352356c3ab84434fafb5ea067ac2678a38a338c4a69ddc4bdb0c
decimals: 18
type: token
- id: DAI
pythId: b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd
decimals: 18
type: token
- id: KCS
pythId: c8acad81438490d4ebcac23b3e93f31cdbcb893fcba746ea1c66b89684faae2f
decimals: 18
type: token
- id: MNT
pythId: 4e3037c822d852d79af3ac80e35eb420ee3b870dca49f9344a38ef4773fb0585
decimals: 18
type: token
- id: HBAR
pythId: 3728e591097635310e6341af53db8b7ee42da9b3a8d918f9463ce9cca886dfbd
decimals: 8
type: token
- id: CORE
pythId: 9b4503710cc8c53f75c30e6e4fda1a7064680ef2e0ee97acd2e3a7c37b3c830c
decimals: 18
type: token

30
package-lock.json generated
View File

@ -55,6 +55,7 @@
"@pythnetwork/pyth-sui-js": "*", "@pythnetwork/pyth-sui-js": "*",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
"aptos": "^1.5.0", "aptos": "^1.5.0",
"axios": "^0.24.0",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
@ -23808,11 +23809,11 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.5.1", "version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -31326,9 +31327,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -79140,11 +79141,11 @@
"integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==" "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg=="
}, },
"axios": { "axios": {
"version": "1.5.1", "version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"requires": { "requires": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -81094,6 +81095,7 @@
"@pythnetwork/pyth-sui-js": "*", "@pythnetwork/pyth-sui-js": "*",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
"aptos": "^1.5.0", "aptos": "^1.5.0",
"axios": "^0.24.0",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
@ -86038,9 +86040,9 @@
"peer": true "peer": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.15.2", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
}, },
"for-each": { "for-each": {
"version": "0.3.3", "version": "0.3.3",