UNPKG

@dojima-wallet/connection

Version:

Initialise and connection for layer 1&2 blockchain

386 lines (356 loc) 9.74 kB
import { cosmosclient, proto } from "@cosmos-client/core"; import { cosmos } from "@cosmos-client/core/cjs/proto"; import { FeeType, Fees, Network, RootDerivationPaths, Tx, TxFrom, TxTo, TxType, } from "../client"; import { Asset, AssetAtom, BaseAmount, CosmosChain, baseAmount, eqAsset, } from "@dojima-wallet/utils"; import axios from "axios"; import BigNumber from "bignumber.js"; import Long from "long"; import { COSMOS_DECIMAL, DEFAULT_GAS_LIMIT } from "./const"; import { APIQueryParam, TxResponse, UnsignedTxParams } from "./cosmos"; import { ChainId, ChainIds, ClientUrls as ClientUrls } from "./types"; /** * Type guard for MsgSend * * @param {Msg} msg * @returns {boolean} `true` or `false`. */ export const isMsgSend = ( msg: unknown ): msg is proto.cosmos.bank.v1beta1.MsgSend => (msg as proto.cosmos.bank.v1beta1.MsgSend)?.amount !== undefined && (msg as proto.cosmos.bank.v1beta1.MsgSend)?.from_address !== undefined && (msg as proto.cosmos.bank.v1beta1.MsgSend)?.to_address !== undefined; /** * Type guard for MsgMultiSend * * @param {Msg} msg * @returns {boolean} `true` or `false`. */ export const isMsgMultiSend = ( msg: unknown ): msg is proto.cosmos.bank.v1beta1.MsgMultiSend => (msg as proto.cosmos.bank.v1beta1.MsgMultiSend)?.inputs !== undefined && (msg as proto.cosmos.bank.v1beta1.MsgMultiSend)?.outputs !== undefined; /** * Get denomination from Asset - currently `ATOM` supported only * * @param {Asset} asset * @returns {string} The denomination of the given asset. */ export const getDenom = (asset: Asset): string | null => { if (eqAsset(asset, AssetAtom)) return "uatom"; return null; }; /** * Get Asset from denomination * * @param {string} denom * @returns {Asset|null} The asset of the given denomination. */ export const getAsset = (denom: string): Asset | null => { if (denom === getDenom(AssetAtom)) return AssetAtom; // IBC assets if (denom.startsWith("ibc/")) // Note: Don't use `assetFromString` here, it will interpret `/` as synth return { chain: CosmosChain.ticker, symbol: denom, // At the meantime ticker will be empty ticker: "", synth: false, }; return null; }; /** * Parses amount from `ICoin[]` * * @param {ICoin[]} coinst List of coins * * @returns {BaseAmount} Coin amount */ const getCoinAmount = (coins: proto.cosmos.base.v1beta1.ICoin[]): BaseAmount => coins .map((coin) => baseAmount(coin.amount || 0, COSMOS_DECIMAL)) .reduce( (acc, cur) => baseAmount(acc.amount().plus(cur.amount()), COSMOS_DECIMAL), baseAmount(0, COSMOS_DECIMAL) ); /** * Filters `ICoin[]` by given `Asset` * * @param {ICoin[]} coinst List of coins * @param {Asset} asset Asset to filter coins * * @returns {ICoin[]} Filtered list */ const getCoinsByAsset = ( coins: proto.cosmos.base.v1beta1.ICoin[], asset: Asset ): proto.cosmos.base.v1beta1.ICoin[] => coins.filter(({ denom }) => { const coinAsset = !!denom ? getAsset(denom) : null; return !!coinAsset ? eqAsset(coinAsset, asset) : false; }); /** * Parses transaction history * * @param {TxResponse[]} txs The transaction response from the node. * @param {Asset} asset Asset to get history of transactions from * * @returns {Tx[]} List of transactions */ export const getTxsFromHistory = (txs: TxResponse[], asset: Asset): Tx[] => { return ( txs // order list to have latest txs first in list .sort((a, b) => { if (a.timestamp === b.timestamp) return 0; return a.timestamp > b.timestamp ? -1 : 1; }) .reduce((acc, tx) => { const msgs = tx.tx?.body.messages ?? []; const from: TxFrom[] = []; const to: TxTo[] = []; msgs.map((msg) => { if (isMsgSend(msg)) { const msgSend = msg; const coins = getCoinsByAsset(msgSend.amount, asset); const amount = getCoinAmount(coins); let from_index = -1; from.forEach((value, index) => { if (value.from === msgSend.from_address) from_index = index; }); if (from_index === -1) { from.push({ from: msgSend.from_address, amount, }); } else { from[from_index].amount = baseAmount( from[from_index].amount.amount().plus(amount.amount()), COSMOS_DECIMAL ); } let to_index = -1; to.forEach((value, index) => { if (value.to === msgSend.to_address) to_index = index; }); if (to_index === -1) { to.push({ to: msgSend.to_address, amount, }); } else { to[to_index].amount = baseAmount( to[to_index].amount.amount().plus(amount.amount()), COSMOS_DECIMAL ); } } }); return [ ...acc, { asset, from, to, date: new Date(tx.timestamp), type: from.length > 0 || to.length > 0 ? TxType.Transfer : TxType.Unknown, hash: tx.txhash || "", }, ]; }, [] as Tx[]) ); }; /** * Get Query String * * @param {APIQueryParam} * @returns {string} The query string. */ export const getQueryString = (params: APIQueryParam): string => { return Object.keys(params) .filter((key) => key.length > 0) .map((key) => params[key] == null ? key : `${key}=${encodeURIComponent(params[key].toString())}` ) .join("&"); }; /** * Get the default fee. * * @returns {Fees} The default fee. */ export const getDefaultFees = (): Fees => { return { type: FeeType.FlatFee, fast: baseAmount(750, COSMOS_DECIMAL), fastest: baseAmount(2500, COSMOS_DECIMAL), average: baseAmount(0, COSMOS_DECIMAL), }; }; /** * Get address prefix based on the network. * * @returns {string} The address prefix based on the network. * **/ export const getPrefix = () => "cosmos"; /** * Default client urls * * @returns {ClientUrls} The client urls for Cosmos. */ export const getDefaultClientUrls = (): ClientUrls => { // const mainClientUrl = "https://api.cosmos.network"; const mainClientUrl = "https://rest.cosmos.directory/cosmoshub"; // Note: In case anyone facing into CORS issue, try the following URLs // https://lcd-cosmos.cosmostation.io/ // https://lcd-cosmoshub.keplr.app/ return { [Network.Testnet]: "https://rest.sentry-02.theta-testnet.polypore.xyz", [Network.Stagenet]: mainClientUrl, [Network.Mainnet]: mainClientUrl, }; }; /** * Default chain ids * * @returns {ChainIds} Chain ids for Cosmos. */ export const getDefaultChainIds = (): ChainIds => { const mainChainId = "cosmoshub-4"; return { [Network.Testnet]: "theta-testnet-001", [Network.Stagenet]: mainChainId, [Network.Mainnet]: mainChainId, }; }; export const getDefaultRootDerivationPaths = (): RootDerivationPaths => ({ [Network.Mainnet]: `44'/118'/0'/0/`, [Network.Testnet]: `44'/118'/0'/0/`, [Network.Stagenet]: `44'/118'/0'/0/`, }); export const protoFee = ({ denom, amount, gasLimit = new BigNumber(DEFAULT_GAS_LIMIT), }: { denom: string; amount: BaseAmount; gasLimit?: BigNumber; }): proto.cosmos.tx.v1beta1.Fee => new proto.cosmos.tx.v1beta1.Fee({ amount: [ { denom, amount: amount.amount().toFixed(0), }, ], gas_limit: Long.fromString(gasLimit.toFixed(0)), }); export const protoMsgSend = ({ from, to, amount, denom, }: { from: string; to: string; amount: BaseAmount; denom: string; }): proto.cosmos.bank.v1beta1.MsgSend => new proto.cosmos.bank.v1beta1.MsgSend({ from_address: from, to_address: to, amount: [ { amount: amount.amount().toFixed(0), denom, }, ], }); export const protoTxBody = ({ from, to, amount, denom, memo, }: UnsignedTxParams): proto.cosmos.tx.v1beta1.TxBody => { const msg = protoMsgSend({ from, to, amount, denom }); return new proto.cosmos.tx.v1beta1.TxBody({ messages: [cosmosclient.codec.instanceToProtoAny(msg)], memo, }); }; export const protoAuthInfo = ({ pubKey, sequence, mode, fee, }: { pubKey: cosmosclient.PubKey; sequence: Long.Long; mode: proto.cosmos.tx.signing.v1beta1.SignMode; fee?: cosmos.tx.v1beta1.IFee; }): proto.cosmos.tx.v1beta1.AuthInfo => new proto.cosmos.tx.v1beta1.AuthInfo({ signer_infos: [ { public_key: cosmosclient.codec.instanceToProtoAny(pubKey), mode_info: { single: { mode, }, }, sequence, }, ], fee, }); /** * Helper to get Cosmos' chain id * @param {string} url API url */ export const getChainId = async (url: string): Promise<ChainId> => { const { data } = await axios.get<{ node_info: { network: string } }>( `${url}/node_info` ); return data?.node_info?.network || Promise.reject("Could not parse chain id"); }; /** * Helper to get Cosmos' chain id for all networks * @param {ClientUrl} urls urls (use `getDefaultClientUrl()` if you don't need to use custom urls) */ export const getChainIds = async (urls: ClientUrls): Promise<ChainIds> => { return Promise.all([ getChainId(urls[Network.Testnet]), getChainId(urls[Network.Stagenet]), getChainId(urls[Network.Mainnet]), ]).then(([testnetId, stagenetId, mainnetId]) => ({ testnet: testnetId, stagenet: stagenetId, mainnet: mainnetId, })); };