[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:
parent
6fb5ab483d
commit
0f7a9cc334
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue