@chorus-one/cosmos
Version:
All-in-one toolkit for building staking dApps on Cosmos SDK based networks
117 lines (101 loc) • 4.38 kB
text/typescript
import { Chain } from './registry/chain'
import { AssetList, Asset } from './registry/assetlist'
import { CosmosNetworkConfig } from './types'
/**
* This class provides the functionality to auto configure the Cosmos network
* based on the chain registry: https://github.com/cosmos/chain-registry
*
* The CosmosStaker requires a user to provide information about gas, fees and denom. This data varies per network and changes over time.
* With this class that configuration is done automatically, based on the external community-based chain registry.
*/
export class CosmosConfigurator {
/**
* This **static** method is used to generate the network configuration for the Cosmos network.
*
* @param network - Network name (e.g. celestia)
* @param gas - (Optional) Gas limit for the transaction
* @param gasPrice - (Optional) Gas price per unit of gas
*
* @returns Returns an CosmosNetworkConfig object
*/
static async genNetworkConfig (
network: string,
gas?: number | 'auto',
gasPrice?: string
): Promise<CosmosNetworkConfig> {
const chainPath = network.includes('testnet') ? `testnets/` : ''
const chainResponse = await fetch(
`https://raw.githubusercontent.com/cosmos/chain-registry/master/${chainPath}${network}/chain.json`
)
if (!chainResponse.ok) {
throw new Error(`Failed to fetch chain.json: ${chainResponse.statusText}`)
}
const chain: Chain = (await chainResponse.json()) as Chain
if (!chain.apis?.rpc || !chain.apis.rpc.length) {
throw new Error(`No RPC endpoints found for ${network}`)
}
if (!chain.apis?.rest || !chain.apis.rest.length) {
throw new Error(`No REST endpoints found for ${network}`)
}
if (!chain.staking?.staking_tokens || chain.staking.staking_tokens.length !== 1) {
throw new Error(`Expected exactly one staking token for ${network}`)
}
const stakingDenom = chain.staking.staking_tokens[0].denom
if (!chain.fees) {
throw new Error(`No fees found for ${network}`)
}
const feeTokens = chain.fees.fee_tokens.filter((feeToken) => feeToken.denom === stakingDenom)
if (feeTokens.length !== 1) {
throw new Error(`Expected exactly one fee token for ${network}`)
}
const feeToken = feeTokens[0]
const assetList = await this.getAssetList(network)
const activeAssets: Asset[] = assetList.assets.filter((asset) => !asset.deprecated && asset.base === stakingDenom)
if (activeAssets.length !== 1) {
throw new Error(`Expected exactly one active staking asset for ${network}`)
}
const macroDenoms = activeAssets[0].denom_units.filter((denomUnit) => denomUnit.denom !== stakingDenom)
if (macroDenoms.length !== 1) {
throw new Error(`Expected exactly one macro denom for ${network}`)
}
if (chain.fees.fee_tokens[0].denom !== stakingDenom) {
throw new Error(`Fee token ${chain.fees.fee_tokens[0].denom} is different from staking token ${stakingDenom}`)
}
const newGasPrice = gasPrice
? parseInt(gasPrice)
: (feeToken.average_gas_price ?? feeToken.low_gas_price ?? feeToken.fixed_min_gas_price ?? 0)
if (!newGasPrice) {
throw new Error(`No gas price found for ${network}`)
}
let newGas: number | 'auto' = 200000
let newFee: string | undefined = undefined
if (gas === 'auto') {
newGas = 'auto'
newFee = undefined
} else if (typeof gas === 'number') {
newGas = gas
newFee = (newGas * newGasPrice).toString(10)
}
return {
rpcUrl: chain.apis.rpc[0].address,
lcdUrl: chain.apis.rest[0].address,
bechPrefix: chain.bech32_prefix,
denom: stakingDenom,
denomMultiplier: BigInt(10 ** macroDenoms[0].exponent).toString(10),
gas: newGas,
gasPrice: newGasPrice.toString(),
fee: newFee,
isEVM: chain.key_algos?.includes('ethsecp256k1')
}
}
private static async getAssetList (network: string): Promise<AssetList> {
const chainPath = network.includes('testnet') ? `testnets/` : ''
const assetResponse = await fetch(
`https://raw.githubusercontent.com/cosmos/chain-registry/master/${chainPath}${network}/assetlist.json`
)
if (!assetResponse.ok) {
throw new Error(`Failed to fetch assetlist.json: ${assetResponse.statusText}`)
}
return (await assetResponse.json()) as AssetList
}
}