From 242490ffdf78453472655408724e2096e0aa8980 Mon Sep 17 00:00:00 2001 From: Veado Date: Sun, 19 Jun 2022 19:38:28 +0200 Subject: [PATCH 1/2] [xchain-cosmos] Misc. updates to support ATOM --- packages/xchain-cosmos/CHANGELOG.md | 23 ++++ .../xchain-cosmos/__tests__/client.test.ts | 33 ++--- packages/xchain-cosmos/__tests__/util.test.ts | 18 ++- packages/xchain-cosmos/package.json | 12 +- packages/xchain-cosmos/src/client.ts | 114 +++++++++++------- packages/xchain-cosmos/src/const.ts | 20 +++ .../xchain-cosmos/src/cosmos/sdk-client.ts | 13 +- packages/xchain-cosmos/src/cosmos/types.ts | 2 + packages/xchain-cosmos/src/index.ts | 5 +- .../xchain-cosmos/src/types/client-types.ts | 19 +++ packages/xchain-cosmos/src/util.ts | 60 +++++++-- 11 files changed, 237 insertions(+), 82 deletions(-) create mode 100644 packages/xchain-cosmos/src/const.ts diff --git a/packages/xchain-cosmos/CHANGELOG.md b/packages/xchain-cosmos/CHANGELOG.md index ef226ecbd..fae508876 100644 --- a/packages/xchain-cosmos/CHANGELOG.md +++ b/packages/xchain-cosmos/CHANGELOG.md @@ -1,3 +1,26 @@ +# v.0.17.1-alpha.1 (2022-xx-xx) + +## Add + +- const `DEFAULT_GAS_LIMIT` +- const `DEFAULT_FEE` +- Optional parameter `gasLimit` in `transfer` and `transferOffline` +- Params `clientUrls`, `chainIds` for constructor +- Helper `getDefaultClientUrls` +- Helper `getDefaultChainIds` +- Setter `setNetwork` + +## Fix + +- `getFees` returns values based on `DEFAULT_FEE` +- Initial one instance of `CosmosSDKClient` only depending on network +- Support IBC assets in `getBalances` (#596) +- Get IBC assets from denom in `getAsset` + +## Update + +- Move all `const` into `const.ts` + # v.0.17.0 (2022-03-23) ## Update diff --git a/packages/xchain-cosmos/__tests__/client.test.ts b/packages/xchain-cosmos/__tests__/client.test.ts index aa38a865b..19beacb9d 100644 --- a/packages/xchain-cosmos/__tests__/client.test.ts +++ b/packages/xchain-cosmos/__tests__/client.test.ts @@ -78,7 +78,7 @@ describe('Client Test', () => { const address1_testnet = 'cosmos1924f27fujxqnkt74u4d3ke3sfygugv9qp29hmk' beforeEach(() => { - cosmosClient = new Client({ phrase, network: 'testnet' as Network }) + cosmosClient = new Client({ phrase, network: Network.Testnet }) }) afterEach(() => { @@ -86,22 +86,22 @@ describe('Client Test', () => { }) it('should start with empty wallet', async () => { - const cosmosClientEmptyMain = new Client({ phrase, network: 'mainnet' as Network }) + const cosmosClientEmptyMain = new Client({ phrase, network: Network.Mainnet }) expect(cosmosClientEmptyMain.getAddress()).toEqual(address0_mainnet) expect(cosmosClientEmptyMain.getAddress(1)).toEqual(address1_mainnet) - const cosmosClientEmptyTest = new Client({ phrase, network: 'testnet' as Network }) + const cosmosClientEmptyTest = new Client({ phrase, network: Network.Testnet }) expect(cosmosClientEmptyTest.getAddress()).toEqual(address0_testnet) expect(cosmosClientEmptyTest.getAddress(1)).toEqual(address1_testnet) }) it('throws an error passing an invalid phrase', async () => { expect(() => { - new Client({ phrase: 'invalid phrase', network: 'mainnet' as Network }) + new Client({ phrase: 'invalid phrase', network: Network.Mainnet }) }).toThrow() expect(() => { - new Client({ phrase: 'invalid phrase', network: 'testnet' as Network }) + new Client({ phrase: 'invalid phrase', network: Network.Testnet }) }).toThrow() }) @@ -118,8 +118,8 @@ describe('Client Test', () => { }) it('should update net', async () => { - const client = new Client({ phrase, network: 'mainnet' as Network }) - client.setNetwork('testnet' as Network) + const client = new Client({ phrase, network: Network.Mainnet }) + client.setNetwork(Network.Testnet) expect(client.getNetwork()).toEqual('testnet') const address = client.getAddress() @@ -129,12 +129,12 @@ describe('Client Test', () => { it('should init, should have right prefix', async () => { expect(cosmosClient.validateAddress(cosmosClient.getAddress())).toEqual(true) - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) expect(cosmosClient.validateAddress(cosmosClient.getAddress())).toEqual(true) }) it('has no balances', async () => { - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) mockAccountsBalance(getClientUrl(cosmosClient), address0_mainnet, { balances: [], @@ -148,19 +148,20 @@ describe('Client Test', () => { mockAccountsBalance(getClientUrl(cosmosClient), 'cosmos1gehrq0pr5d79q8nxnaenvqh09g56jafm82thjv', { balances: [ new proto.cosmos.base.v1beta1.Coin({ - denom: 'muon', + denom: 'umuon', amount: '75000000', }), ], }) const balances = await cosmosClient.getBalance('cosmos1gehrq0pr5d79q8nxnaenvqh09g56jafm82thjv') + const expected = balances[0].amount.amount().isEqualTo(baseAmount(75000000, 6).amount()) expect(expected).toBeTruthy() expect(balances[0].asset).toEqual(AssetMuon) }) it('has an empty tx history', async () => { - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) const expected: TxsPage = { total: 0, @@ -222,7 +223,7 @@ describe('Client Test', () => { let transactions = await cosmosClient.getTransactions({ address: 'cosmos1xvt4e7xd0j9dwv2w83g50tpcltsl90h52003e2' }) expect(transactions.total).toBeGreaterThan(0) - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) const msgSend2 = new proto.cosmos.bank.v1beta1.MsgSend({ from_address: 'cosmos1pjkpqxmvz47a5aw40l98fyktlg7k6hd9heq95z', to_address: 'cosmos155svs6sgxe55rnvs6ghprtqu0mh69kehrn0dqr', @@ -301,7 +302,7 @@ describe('Client Test', () => { }) it('get transaction data', async () => { - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) const msgSend = new proto.cosmos.bank.v1beta1.MsgSend({ from_address: 'cosmos1pjkpqxmvz47a5aw40l98fyktlg7k6hd9heq95z', @@ -346,7 +347,7 @@ describe('Client Test', () => { // Client created with network === 'testnet' expect(cosmosClient.getExplorerUrl()).toEqual('https://explorer.theta-testnet.polypore.xyz') - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) expect(cosmosClient.getExplorerUrl()).toEqual('https://cosmos.bigdipper.live') }) @@ -355,7 +356,7 @@ describe('Client Test', () => { 'https://explorer.theta-testnet.polypore.xyz/account/anotherTestAddressHere', ) - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) expect(cosmosClient.getExplorerAddressUrl('testAddressHere')).toEqual( 'https://cosmos.bigdipper.live/account/testAddressHere', ) @@ -366,7 +367,7 @@ describe('Client Test', () => { 'https://explorer.theta-testnet.polypore.xyz/transactions/anotherTestTxHere', ) - cosmosClient.setNetwork('mainnet' as Network) + cosmosClient.setNetwork(Network.Mainnet) expect(cosmosClient.getExplorerTxUrl('testTxHere')).toEqual('https://cosmos.bigdipper.live/transactions/testTxHere') }) }) diff --git a/packages/xchain-cosmos/__tests__/util.test.ts b/packages/xchain-cosmos/__tests__/util.test.ts index 854d18c49..8240ea52b 100644 --- a/packages/xchain-cosmos/__tests__/util.test.ts +++ b/packages/xchain-cosmos/__tests__/util.test.ts @@ -1,5 +1,5 @@ import { cosmosclient, proto } from '@cosmos-client/core' -import { baseAmount } from '@xchainjs/xchain-util' +import { baseAmount, eqAsset } from '@xchainjs/xchain-util' import { APIQueryParam, RawTxResponse, TxResponse } from '../src/cosmos/types' import { AssetAtom, AssetMuon } from '../src/types' @@ -84,15 +84,23 @@ describe('cosmos/util', () => { describe('getAsset', () => { it('get asset for umuon', () => { - expect(getAsset('umuon')).toEqual(AssetMuon) + const asset = getAsset('umuon') + const result = asset !== null && eqAsset(asset, AssetMuon) + expect(result).toBeTruthy() }) it('get asset for uatom', () => { - expect(getAsset('uatom')).toEqual(AssetAtom) + const asset = getAsset('uatom') + const result = asset !== null && eqAsset(asset, AssetAtom) + expect(result).toBeTruthy() }) - it('get asset for unknown', () => { - expect(getAsset('unknown')).toBeNull() + it('get asset for ibc asset (BTSG - Bitsong)', () => { + // see https://github.com/bitsongofficial/docs.bitsong.io/blob/main/relayer.md#official-bitsong-ibc-channels + const denom = 'ibc/E7D5E9D0E9BF8B7354929A817DD28D4D017E745F638954764AA88522A7A409EC' + const asset = getAsset(denom) + const expected = { ...AssetAtom, symbol: denom.toUpperCase(), ticker: '' } + expect(asset !== null && eqAsset(asset, expected)).toBeTruthy() }) }) }) diff --git a/packages/xchain-cosmos/package.json b/packages/xchain-cosmos/package.json index eff8e89ad..cfda61e55 100644 --- a/packages/xchain-cosmos/package.json +++ b/packages/xchain-cosmos/package.json @@ -32,9 +32,9 @@ "prepublishOnly": "yarn build" }, "devDependencies": { - "@xchainjs/xchain-client": "^0.11.1", - "@xchainjs/xchain-crypto": "^0.2.4", - "@xchainjs/xchain-util": "^0.5.1", + "@xchainjs/xchain-client": "^0.11.3", + "@xchainjs/xchain-crypto": "^0.2.6", + "@xchainjs/xchain-util": "^0.7.1", "@cosmos-client/core": "^0.45.1", "axios": "^0.25.0", "nock": "^13.0.5" @@ -43,9 +43,9 @@ "access": "public" }, "peerDependencies": { - "@xchainjs/xchain-client": "^0.11.1", - "@xchainjs/xchain-crypto": "^0.2.4", - "@xchainjs/xchain-util": "^0.5.1", + "@xchainjs/xchain-client": "^0.11.3", + "@xchainjs/xchain-crypto": "^0.2.6", + "@xchainjs/xchain-util": "^0.7.1", "@cosmos-client/core": "^0.45.1", "axios": "^0.25.0" } diff --git a/packages/xchain-cosmos/src/client.ts b/packages/xchain-cosmos/src/client.ts index a468dd254..8f079100e 100644 --- a/packages/xchain-cosmos/src/client.ts +++ b/packages/xchain-cosmos/src/client.ts @@ -1,4 +1,4 @@ -import { proto } from '@cosmos-client/core' +import { cosmosclient, proto } from '@cosmos-client/core' import { Address, Balance, @@ -13,13 +13,16 @@ import { TxsPage, XChainClient, XChainClientParams, + singleFee, } from '@xchainjs/xchain-client' -import { Asset, Chain, assetToString, baseAmount } from '@xchainjs/xchain-util' +import { Asset, Chain, baseAmount, eqAsset } from '@xchainjs/xchain-util' +import BigNumber from 'bignumber.js' +import { DECIMAL, DEFAULT_FEE, DEFAULT_GAS_LIMIT } from './const' import { CosmosSDKClient } from './cosmos/sdk-client' import { TxOfflineParams } from './cosmos/types' -import { AssetAtom, AssetMuon } from './types' -import { DECIMAL, getAsset, getDenom, getTxsFromHistory } from './util' +import { AssetAtom, AssetMuon, ChainIds, ClientUrls, CosmosClientParams } from './types' +import { getAsset, getDefaultChainIds, getDefaultClientUrls, getDenom, getTxsFromHistory } from './util' /** * Interface for custom Cosmos client @@ -29,20 +32,13 @@ export interface CosmosClient { getSDKClient(): CosmosSDKClient } -const MAINNET_SDK = new CosmosSDKClient({ - server: 'https://api.cosmos.network', - chainId: 'cosmoshub-4', -}) -const TESTNET_SDK = new CosmosSDKClient({ - server: 'https://rest.sentry-02.theta-testnet.polypore.xyz', - chainId: 'theta-testnet-001', -}) - /** * Custom Cosmos client */ class Client extends BaseXChainClient implements CosmosClient, XChainClient { - private sdkClients: Map = new Map() + private sdkClient: CosmosSDKClient + private clientUrls: ClientUrls + private chainIds: ChainIds /** * Constructor @@ -57,15 +53,40 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { constructor({ network = Network.Testnet, phrase, + clientUrls, + chainIds, rootDerivationPaths = { [Network.Mainnet]: `44'/118'/0'/0/`, [Network.Testnet]: `44'/118'/0'/0/`, [Network.Stagenet]: `44'/118'/0'/0/`, }, - }: XChainClientParams) { + }: XChainClientParams & CosmosClientParams) { super(Chain.Cosmos, { network, rootDerivationPaths, phrase }) - this.sdkClients.set(Network.Testnet, TESTNET_SDK) - this.sdkClients.set(Network.Mainnet, MAINNET_SDK) + + this.clientUrls = clientUrls || getDefaultClientUrls() + this.chainIds = chainIds || getDefaultChainIds() + + this.sdkClient = new CosmosSDKClient({ + server: this.clientUrls[network], + chainId: this.chainIds[network], + }) + } + + /** + * Updates current network. + * + * @param {Network} network + * @returns {void} + */ + setNetwork(network: Network): void { + // dirty check to avoid using and re-creation of same data + if (network === this.network) return + + super.setNetwork(network) + this.sdkClient = new CosmosSDKClient({ + server: this.clientUrls[network], + chainId: this.chainIds[network], + }) } /** @@ -119,7 +140,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { } getSDKClient(): CosmosSDKClient { - return this.sdkClients.get(this.network) || TESTNET_SDK + return this.sdkClient } /** @@ -168,19 +189,16 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { * @returns {Balance[]} The balance of the address. */ async getBalance(address: Address, assets?: Asset[]): Promise { - const balances = await this.getSDKClient().getBalance(address) - const mainAsset = this.getMainAsset() + const coins = await this.getSDKClient().getBalance(address) + + const balances = coins + .reduce((acc: Balance[], { denom, amount }) => { + const asset = getAsset(denom) + return asset ? [...acc, { asset, amount: baseAmount(amount || '0', DECIMAL) }] : acc + }, []) + .filter(({ asset: balanceAsset }) => !assets || assets.filter((asset) => eqAsset(balanceAsset, asset)).length) return balances - .map((balance) => { - return { - asset: (balance.denom && getAsset(balance.denom)) || mainAsset, - amount: baseAmount(balance.amount, DECIMAL), - } - }) - .filter( - (balance) => !assets || assets.filter((asset) => assetToString(balance.asset) === assetToString(asset)).length, - ) } /** @@ -238,10 +256,23 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { * @param {TxParams} params The transfer options. * @returns {TxHash} The transaction hash. */ - async transfer({ walletIndex, asset, amount, recipient, memo }: TxParams): Promise { + async transfer({ + walletIndex, + asset, + amount, + recipient, + memo, + gasLimit = new BigNumber(DEFAULT_GAS_LIMIT), + }: TxParams & { gasLimit?: BigNumber }): Promise { const fromAddressIndex = walletIndex || 0 const mainAsset = this.getMainAsset() + + const fee = new proto.cosmos.tx.v1beta1.Fee({ + amount: [], + gas_limit: cosmosclient.Long.fromString(gasLimit.toFixed(0)), + }) + return this.getSDKClient().transfer({ privkey: this.getPrivateKey(fromAddressIndex), from: this.getAddress(fromAddressIndex), @@ -249,6 +280,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { amount: amount.amount().toString(), asset: getDenom(asset || mainAsset), memo, + fee, }) } @@ -266,10 +298,15 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { memo, from_account_number, from_sequence, + gasLimit = new BigNumber(DEFAULT_GAS_LIMIT), }: TxOfflineParams): Promise { const fromAddressIndex = walletIndex || 0 - const mainAsset = this.getMainAsset() + const fee = new proto.cosmos.tx.v1beta1.Fee({ + amount: [], + gas_limit: cosmosclient.Long.fromString(gasLimit.toFixed(0)), + }) + return await this.getSDKClient().transferSignedOffline({ privkey: this.getPrivateKey(fromAddressIndex), from: this.getAddress(fromAddressIndex), @@ -277,24 +314,19 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { from_sequence, to: recipient, amount: amount.amount().toString(), - asset: getDenom(asset || mainAsset), + asset: getDenom(asset || this.getMainAsset()), memo, + fee, }) } /** - * Get the current fee. + * Returns (default) fees. * - * @returns {Fees} The current fee. + * @returns {Fees} Current fees */ async getFees(): Promise { - // there is no fixed fee, we set fee amount when creating a transaction. - return { - type: FeeType.FlatFee, - fast: baseAmount(750, DECIMAL), - fastest: baseAmount(2500, DECIMAL), - average: baseAmount(0, DECIMAL), - } + return singleFee(FeeType.FlatFee, DEFAULT_FEE) } } diff --git a/packages/xchain-cosmos/src/const.ts b/packages/xchain-cosmos/src/const.ts new file mode 100644 index 000000000..9adf221ff --- /dev/null +++ b/packages/xchain-cosmos/src/const.ts @@ -0,0 +1,20 @@ +import { baseAmount } from '@xchainjs/xchain-util' + +/** + * The decimal for cosmos chain. + */ +export const DECIMAL = 6 + +/** + * Default gas limit + * As same as definition in Cosmosstation's web wallet + * @see https://github.com/cosmostation/web-wallet-ts-react/blob/4d78718b613defbd6c92079b33aa8ce9f86d597c/src/constants/chain.ts#L76 + */ +export const DEFAULT_GAS_LIMIT = '200000' + +/** + * Default fee + * As same as definition in Cosmosstation's web wallet + * @see https://github.com/cosmostation/web-wallet-ts-react/blob/4d78718b613defbd6c92079b33aa8ce9f86d597c/src/constants/chain.ts#L66 + */ +export const DEFAULT_FEE = baseAmount(5000, DECIMAL) diff --git a/packages/xchain-cosmos/src/cosmos/sdk-client.ts b/packages/xchain-cosmos/src/cosmos/sdk-client.ts index 65ec42907..d91c02a31 100644 --- a/packages/xchain-cosmos/src/cosmos/sdk-client.ts +++ b/packages/xchain-cosmos/src/cosmos/sdk-client.ts @@ -4,6 +4,7 @@ import * as xchainCrypto from '@xchainjs/xchain-crypto' import axios from 'axios' import * as BIP32 from 'bip32' +import { DEFAULT_GAS_LIMIT } from '../const' import { getQueryString } from '../util' import { @@ -22,8 +23,9 @@ import { const DEFAULT_FEE = new proto.cosmos.tx.v1beta1.Fee({ amount: [], - gas_limit: cosmosclient.Long.fromString('300000'), + gas_limit: cosmosclient.Long.fromString(DEFAULT_GAS_LIMIT), }) + export class CosmosSDKClient { sdk: cosmosclient.CosmosSDK @@ -116,7 +118,14 @@ export class CosmosSDKClient { const accAddress = cosmosclient.AccAddress.fromString(address) const response = await rest.bank.allBalances(this.sdk, accAddress) - return response.data.balances as proto.cosmos.base.v1beta1.Coin[] + const balances: proto.cosmos.base.v1beta1.Coin[] = + response.data.balances?.reduce( + (acc: proto.cosmos.base.v1beta1.Coin[], { amount, denom }) => + !!amount && !!denom ? [...acc, new proto.cosmos.base.v1beta1.Coin({ amount, denom })] : acc, + [], + ) || [] + console.log('SDK getBalance() balances ', balances) + return balances } async getAccount(address: cosmosclient.AccAddress): Promise { diff --git a/packages/xchain-cosmos/src/cosmos/types.ts b/packages/xchain-cosmos/src/cosmos/types.ts index 03bcdd9b5..a84640dc9 100644 --- a/packages/xchain-cosmos/src/cosmos/types.ts +++ b/packages/xchain-cosmos/src/cosmos/types.ts @@ -1,5 +1,6 @@ import { proto } from '@cosmos-client/core' import { TxParams } from '@xchainjs/xchain-client' +import BigNumber from 'bignumber.js' export type CosmosSDKClientParams = { server: string @@ -44,6 +45,7 @@ export type TransferOfflineParams = TransferParams & { export type TxOfflineParams = TxParams & { from_account_number: string from_sequence: string + gasLimit?: BigNumber } export type BaseAccountResponse = { diff --git a/packages/xchain-cosmos/src/index.ts b/packages/xchain-cosmos/src/index.ts index 3871b9cd6..0f0532cc3 100644 --- a/packages/xchain-cosmos/src/index.ts +++ b/packages/xchain-cosmos/src/index.ts @@ -1,4 +1,5 @@ -export * from './cosmos' -export * from './client' export * from './types' +export * from './const' export * from './util' +export * from './cosmos' +export * from './client' diff --git a/packages/xchain-cosmos/src/types/client-types.ts b/packages/xchain-cosmos/src/types/client-types.ts index 608d66804..5e33ee22e 100644 --- a/packages/xchain-cosmos/src/types/client-types.ts +++ b/packages/xchain-cosmos/src/types/client-types.ts @@ -1,4 +1,23 @@ +import { Network } from '@xchainjs/xchain-client' import { Asset, Chain } from '@xchainjs/xchain-util' export const AssetAtom: Asset = { chain: Chain.Cosmos, symbol: 'ATOM', ticker: 'ATOM', synth: false } export const AssetMuon: Asset = { chain: Chain.Cosmos, symbol: 'MUON', ticker: 'MUON', synth: false } + +export type ClientUrls = Record + +export type ExplorerUrl = Record + +export type ExplorerUrls = { + root: ExplorerUrl + tx: ExplorerUrl + address: ExplorerUrl +} + +export type ChainId = string +export type ChainIds = Record + +export type CosmosClientParams = { + clientUrls?: ClientUrls + chainIds?: ChainIds +} diff --git a/packages/xchain-cosmos/src/util.ts b/packages/xchain-cosmos/src/util.ts index ca7e5d2ac..1bfc359cc 100644 --- a/packages/xchain-cosmos/src/util.ts +++ b/packages/xchain-cosmos/src/util.ts @@ -1,14 +1,10 @@ import { cosmosclient, proto } from '@cosmos-client/core' -import { FeeType, Fees, Tx, TxFrom, TxTo, TxType } from '@xchainjs/xchain-client' -import { Asset, assetToString, baseAmount } from '@xchainjs/xchain-util' +import { FeeType, Fees, Network, Tx, TxFrom, TxTo, TxType } from '@xchainjs/xchain-client' +import { Asset, CosmosChain, baseAmount, eqAsset } from '@xchainjs/xchain-util' +import { DECIMAL } from './const' import { APIQueryParam, RawTxResponse, TxResponse } from './cosmos/types' -import { AssetAtom, AssetMuon } from './types' - -/** - * The decimal for cosmos chain. - */ -export const DECIMAL = 6 +import { AssetAtom, AssetMuon, ChainIds, ClientUrls as ClientUrls } from './types' /** * Type guard for MsgSend @@ -38,8 +34,8 @@ export const isMsgMultiSend = (msg: unknown): msg is proto.cosmos.bank.v1beta1.M * @returns {string} The denomination of the given asset. */ export const getDenom = (asset: Asset): string => { - if (assetToString(asset) === assetToString(AssetAtom)) return 'uatom' - if (assetToString(asset) === assetToString(AssetMuon)) return 'umuon' + if (eqAsset(asset, AssetAtom)) return 'uatom' + if (eqAsset(asset, AssetMuon)) return 'umuon' return asset.symbol } @@ -52,6 +48,18 @@ export const getDenom = (asset: Asset): string => { export const getAsset = (denom: string): Asset | null => { if (denom === getDenom(AssetAtom)) return AssetAtom if (denom === getDenom(AssetMuon)) return AssetMuon + // IBC assets + if (denom.startsWith('ibc/')) + // Note: Don't use `assetFromString` here, it will interpret `/` as synth + return { + chain: CosmosChain, + symbol: denom.toUpperCase(), + // TODO (xchain-contributors) + // Get ticker (real asset name) from denom + // At the meantime ticker will be empty + ticker: '', + synth: false, + } return null } @@ -206,3 +214,35 @@ export const getDefaultFees = (): Fees => { * **/ export const getPrefix = () => 'cosmos' + +/** + * Default client urls + * + * @returns {ClientUrls} The client urls for Cosmos. + */ +export const getDefaultClientUrls = (): ClientUrls => { + const mainClientUrl = 'https://api.cosmos.network' + // Note: In case anyone facing into CORS issue, try the following URLs + // https://lcd-cosmos.cosmostation.io/ + // https://lcd-cosmoshub.keplr.app/ + // @see (Discord #xchainjs) https://discord.com/channels/838986635756044328/988096545926828082/988103739967688724 + 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, + } +} From cad668b99f264aa3ce5e9bd21b5175ecf9806ce2 Mon Sep 17 00:00:00 2001 From: Veado Date: Mon, 20 Jun 2022 12:30:37 +0200 Subject: [PATCH 2/2] Remove `AssetMuon`, filter `getTxsFromHistory` by asset, remove `Client.getMainAsset`, bump v0.18.0-alpha.1 --- packages/xchain-cosmos/CHANGELOG.md | 10 ++- .../xchain-cosmos/__tests__/client.test.ts | 10 +-- packages/xchain-cosmos/__tests__/util.test.ts | 14 +--- packages/xchain-cosmos/package.json | 2 +- packages/xchain-cosmos/src/client.ts | 44 ++++------ packages/xchain-cosmos/src/const.ts | 4 +- .../xchain-cosmos/src/cosmos/sdk-client.ts | 1 - .../xchain-cosmos/src/types/client-types.ts | 4 - packages/xchain-cosmos/src/util.ts | 80 ++++++++++++------- 9 files changed, 84 insertions(+), 85 deletions(-) diff --git a/packages/xchain-cosmos/CHANGELOG.md b/packages/xchain-cosmos/CHANGELOG.md index fae508876..8cf20e0ac 100644 --- a/packages/xchain-cosmos/CHANGELOG.md +++ b/packages/xchain-cosmos/CHANGELOG.md @@ -1,4 +1,4 @@ -# v.0.17.1-alpha.1 (2022-xx-xx) +# v.0.18.0-alpha.1 (2022-06-20) ## Add @@ -19,7 +19,13 @@ ## Update -- Move all `const` into `const.ts` +- Result of `getTxsFromHistory` is filtered by given asset +- Move misc. constants into `const.ts` + +## Breaking change + +- Remove deprecated `AssetMuon` +- Remove deprecated `Client.getMainAsset` # v.0.17.0 (2022-03-23) diff --git a/packages/xchain-cosmos/__tests__/client.test.ts b/packages/xchain-cosmos/__tests__/client.test.ts index 19beacb9d..96aa304a6 100644 --- a/packages/xchain-cosmos/__tests__/client.test.ts +++ b/packages/xchain-cosmos/__tests__/client.test.ts @@ -4,8 +4,8 @@ import { BaseAmount, baseAmount } from '@xchainjs/xchain-util' import nock from 'nock' import { Client } from '../src/client' +import { AssetAtom } from '../src/const' import { GetTxByHashResponse, TxHistoryResponse } from '../src/cosmos/types' -import { AssetMuon } from '../src/types' const getClientUrl = (client: Client): string => { return client.getNetwork() === Network.Testnet @@ -148,7 +148,7 @@ describe('Client Test', () => { mockAccountsBalance(getClientUrl(cosmosClient), 'cosmos1gehrq0pr5d79q8nxnaenvqh09g56jafm82thjv', { balances: [ new proto.cosmos.base.v1beta1.Coin({ - denom: 'umuon', + denom: 'uatom', amount: '75000000', }), ], @@ -157,7 +157,7 @@ describe('Client Test', () => { const expected = balances[0].amount.amount().isEqualTo(baseAmount(75000000, 6).amount()) expect(expected).toBeTruthy() - expect(balances[0].asset).toEqual(AssetMuon) + expect(balances[0].asset).toEqual(AssetAtom) }) it('has an empty tx history', async () => { @@ -187,7 +187,7 @@ describe('Client Test', () => { to_address: 'cosmos155svs6sgxe55rnvs6ghprtqu0mh69kehrn0dqr', amount: [ { - denom: 'umuon', + denom: 'uatom', amount: '4318994970', }, ], @@ -292,7 +292,7 @@ describe('Client Test', () => { assertTxsPost(getClientUrl(cosmosClient), expected_txsPost_result) const result = await cosmosClient.transfer({ - asset: AssetMuon, + asset: AssetAtom, recipient: to_address, amount: send_amount, memo, diff --git a/packages/xchain-cosmos/__tests__/util.test.ts b/packages/xchain-cosmos/__tests__/util.test.ts index 8240ea52b..3a864e97c 100644 --- a/packages/xchain-cosmos/__tests__/util.test.ts +++ b/packages/xchain-cosmos/__tests__/util.test.ts @@ -1,8 +1,8 @@ import { cosmosclient, proto } from '@cosmos-client/core' import { baseAmount, eqAsset } from '@xchainjs/xchain-util' +import { AssetAtom } from '../src/const' import { APIQueryParam, RawTxResponse, TxResponse } from '../src/cosmos/types' -import { AssetAtom, AssetMuon } from '../src/types' import { getAsset, getDenom, getQueryString, getTxsFromHistory, isMsgMultiSend, isMsgSend } from '../src/util' describe('cosmos/util', () => { @@ -76,19 +76,9 @@ describe('cosmos/util', () => { it('get denom for AssetAtom', () => { expect(getDenom(AssetAtom)).toEqual('uatom') }) - - it('get denom for AssetMuon', () => { - expect(getDenom(AssetMuon)).toEqual('umuon') - }) }) describe('getAsset', () => { - it('get asset for umuon', () => { - const asset = getAsset('umuon') - const result = asset !== null && eqAsset(asset, AssetMuon) - expect(result).toBeTruthy() - }) - it('get asset for uatom', () => { const asset = getAsset('uatom') const result = asset !== null && eqAsset(asset, AssetAtom) @@ -99,7 +89,7 @@ describe('cosmos/util', () => { // see https://github.com/bitsongofficial/docs.bitsong.io/blob/main/relayer.md#official-bitsong-ibc-channels const denom = 'ibc/E7D5E9D0E9BF8B7354929A817DD28D4D017E745F638954764AA88522A7A409EC' const asset = getAsset(denom) - const expected = { ...AssetAtom, symbol: denom.toUpperCase(), ticker: '' } + const expected = { ...AssetAtom, symbol: denom, ticker: '' } expect(asset !== null && eqAsset(asset, expected)).toBeTruthy() }) }) diff --git a/packages/xchain-cosmos/package.json b/packages/xchain-cosmos/package.json index cfda61e55..ffdb65807 100644 --- a/packages/xchain-cosmos/package.json +++ b/packages/xchain-cosmos/package.json @@ -1,6 +1,6 @@ { "name": "@xchainjs/xchain-cosmos", - "version": "0.17.0", + "version": "0.18.0-alpha.1", "description": "Custom Cosmos client and utilities used by XChainJS clients", "keywords": [ "XChain", diff --git a/packages/xchain-cosmos/src/client.ts b/packages/xchain-cosmos/src/client.ts index 8f079100e..573746cb1 100644 --- a/packages/xchain-cosmos/src/client.ts +++ b/packages/xchain-cosmos/src/client.ts @@ -18,17 +18,16 @@ import { import { Asset, Chain, baseAmount, eqAsset } from '@xchainjs/xchain-util' import BigNumber from 'bignumber.js' -import { DECIMAL, DEFAULT_FEE, DEFAULT_GAS_LIMIT } from './const' +import { AssetAtom, DECIMAL, DEFAULT_FEE, DEFAULT_GAS_LIMIT } from './const' import { CosmosSDKClient } from './cosmos/sdk-client' import { TxOfflineParams } from './cosmos/types' -import { AssetAtom, AssetMuon, ChainIds, ClientUrls, CosmosClientParams } from './types' +import { ChainIds, ClientUrls, CosmosClientParams } from './types' import { getAsset, getDefaultChainIds, getDefaultClientUrls, getDenom, getTxsFromHistory } from './util' /** * Interface for custom Cosmos client */ export interface CosmosClient { - getMainAsset(): Asset getSDKClient(): CosmosSDKClient } @@ -166,21 +165,6 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { return this.getSDKClient().checkAddress(address) } - /** - * Get the main asset based on the network. - * - * @returns {string} The main asset based on the network. - */ - getMainAsset(): Asset { - switch (this.network) { - case Network.Mainnet: - case Network.Stagenet: - return AssetAtom - case Network.Testnet: - return AssetMuon - } - } - /** * Get the balance of a given address. * @@ -202,7 +186,8 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { } /** - * Get transaction history of a given address with pagination options. + * Get transaction history of a given address and asset with pagination options. + * If `asset` is not set, history will include `ATOM` txs only * By default it will return the transaction history of the current wallet. * * @param {TxHistoryParams} params The options to get transaction history. (optional) @@ -214,11 +199,12 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { const limit = (params && params.limit) || undefined const txMinHeight = undefined const txMaxHeight = undefined + const asset = getAsset(params?.asset ?? '') || AssetAtom + const messageSender = params?.address ?? this.getAddress() - const mainAsset = this.getMainAsset() const txHistory = await this.getSDKClient().searchTx({ messageAction, - messageSender: (params && params.address) || this.getAddress(), + messageSender, page, limit, txMinHeight, @@ -227,12 +213,12 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { return { total: parseInt(txHistory.pagination?.total || '0'), - txs: getTxsFromHistory(txHistory.tx_responses || [], mainAsset), + txs: getTxsFromHistory(txHistory.tx_responses || [], asset), } } /** - * Get the transaction details of a given transaction id. + * Get the transaction details of a given transaction id. Supports `ATOM` txs only. * * @param {string} txId The transaction id. * @returns {Tx} The transaction details of the given transaction id. @@ -244,7 +230,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { throw new Error('transaction not found') } - const txs = getTxsFromHistory([txResult], this.getMainAsset()) + const txs = getTxsFromHistory([txResult], AssetAtom) if (txs.length === 0) throw new Error('transaction not found') return txs[0] @@ -258,7 +244,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { */ async transfer({ walletIndex, - asset, + asset = AssetAtom, amount, recipient, memo, @@ -266,8 +252,6 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { }: TxParams & { gasLimit?: BigNumber }): Promise { const fromAddressIndex = walletIndex || 0 - const mainAsset = this.getMainAsset() - const fee = new proto.cosmos.tx.v1beta1.Fee({ amount: [], gas_limit: cosmosclient.Long.fromString(gasLimit.toFixed(0)), @@ -278,7 +262,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { from: this.getAddress(fromAddressIndex), to: recipient, amount: amount.amount().toString(), - asset: getDenom(asset || mainAsset), + asset: getDenom(asset), memo, fee, }) @@ -292,7 +276,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { */ async transferOffline({ walletIndex, - asset, + asset = AssetAtom, amount, recipient, memo, @@ -314,7 +298,7 @@ class Client extends BaseXChainClient implements CosmosClient, XChainClient { from_sequence, to: recipient, amount: amount.amount().toString(), - asset: getDenom(asset || this.getMainAsset()), + asset: getDenom(asset), memo, fee, }) diff --git a/packages/xchain-cosmos/src/const.ts b/packages/xchain-cosmos/src/const.ts index 9adf221ff..9a9c9c942 100644 --- a/packages/xchain-cosmos/src/const.ts +++ b/packages/xchain-cosmos/src/const.ts @@ -1,10 +1,12 @@ -import { baseAmount } from '@xchainjs/xchain-util' +import { Asset, Chain, baseAmount } from '@xchainjs/xchain-util' /** * The decimal for cosmos chain. */ export const DECIMAL = 6 +export const AssetAtom: Asset = { chain: Chain.Cosmos, symbol: 'ATOM', ticker: 'ATOM', synth: false } + /** * Default gas limit * As same as definition in Cosmosstation's web wallet diff --git a/packages/xchain-cosmos/src/cosmos/sdk-client.ts b/packages/xchain-cosmos/src/cosmos/sdk-client.ts index d91c02a31..e4bfcf94c 100644 --- a/packages/xchain-cosmos/src/cosmos/sdk-client.ts +++ b/packages/xchain-cosmos/src/cosmos/sdk-client.ts @@ -124,7 +124,6 @@ export class CosmosSDKClient { !!amount && !!denom ? [...acc, new proto.cosmos.base.v1beta1.Coin({ amount, denom })] : acc, [], ) || [] - console.log('SDK getBalance() balances ', balances) return balances } diff --git a/packages/xchain-cosmos/src/types/client-types.ts b/packages/xchain-cosmos/src/types/client-types.ts index 5e33ee22e..bf4940101 100644 --- a/packages/xchain-cosmos/src/types/client-types.ts +++ b/packages/xchain-cosmos/src/types/client-types.ts @@ -1,8 +1,4 @@ import { Network } from '@xchainjs/xchain-client' -import { Asset, Chain } from '@xchainjs/xchain-util' - -export const AssetAtom: Asset = { chain: Chain.Cosmos, symbol: 'ATOM', ticker: 'ATOM', synth: false } -export const AssetMuon: Asset = { chain: Chain.Cosmos, symbol: 'MUON', ticker: 'MUON', synth: false } export type ClientUrls = Record diff --git a/packages/xchain-cosmos/src/util.ts b/packages/xchain-cosmos/src/util.ts index 1bfc359cc..4542a4e97 100644 --- a/packages/xchain-cosmos/src/util.ts +++ b/packages/xchain-cosmos/src/util.ts @@ -1,10 +1,10 @@ import { cosmosclient, proto } from '@cosmos-client/core' import { FeeType, Fees, Network, Tx, TxFrom, TxTo, TxType } from '@xchainjs/xchain-client' -import { Asset, CosmosChain, baseAmount, eqAsset } from '@xchainjs/xchain-util' +import { Asset, BaseAmount, CosmosChain, baseAmount, eqAsset } from '@xchainjs/xchain-util' -import { DECIMAL } from './const' -import { APIQueryParam, RawTxResponse, TxResponse } from './cosmos/types' -import { AssetAtom, AssetMuon, ChainIds, ClientUrls as ClientUrls } from './types' +import { AssetAtom, DECIMAL } from './const' +import { APIQueryParam, TxResponse } from './cosmos/types' +import { ChainIds, ClientUrls as ClientUrls } from './types' /** * Type guard for MsgSend @@ -35,7 +35,6 @@ export const isMsgMultiSend = (msg: unknown): msg is proto.cosmos.bank.v1beta1.M */ export const getDenom = (asset: Asset): string => { if (eqAsset(asset, AssetAtom)) return 'uatom' - if (eqAsset(asset, AssetMuon)) return 'umuon' return asset.symbol } @@ -47,15 +46,14 @@ export const getDenom = (asset: Asset): string => { */ export const getAsset = (denom: string): Asset | null => { if (denom === getDenom(AssetAtom)) return AssetAtom - if (denom === getDenom(AssetMuon)) return AssetMuon // IBC assets if (denom.startsWith('ibc/')) // Note: Don't use `assetFromString` here, it will interpret `/` as synth return { chain: CosmosChain, - symbol: denom.toUpperCase(), + symbol: denom, // TODO (xchain-contributors) - // Get ticker (real asset name) from denom + // Get readable ticker for IBC assets from denom #600 https://github.com/xchainjs/xchainjs-lib/issues/600 // At the meantime ticker will be empty ticker: '', synth: false, @@ -63,28 +61,49 @@ export const getAsset = (denom: string): Asset | null => { return null } -const getCoinAmount = (coins?: proto.cosmos.base.v1beta1.ICoin[]) => { - return coins - ? coins - .map((coin) => baseAmount(coin.amount || 0, DECIMAL)) - .reduce((acc, cur) => baseAmount(acc.amount().plus(cur.amount()), DECIMAL), baseAmount(0, DECIMAL)) - : baseAmount(0, DECIMAL) -} /** - * Parse transaction type + * 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, DECIMAL)) + .reduce((acc, cur) => baseAmount(acc.amount().plus(cur.amount()), DECIMAL), baseAmount(0, 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} mainAsset Current main asset which depends on the network. - * @returns {Tx[]} The parsed transaction result. + * @param {Asset} asset Asset to get history of transactions from + * + * @returns {Tx[]} List of transactions */ -export const getTxsFromHistory = (txs: TxResponse[], mainAsset: Asset): Tx[] => { +export const getTxsFromHistory = (txs: TxResponse[], asset: Asset): Tx[] => { return txs.reduce((acc, tx) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let msgs: Array - if ((tx.tx as RawTxResponse).body === undefined) { - msgs = cosmosclient.codec.packAnyFromCosmosJSON(tx.tx).msg - } else { - msgs = cosmosclient.codec.packAnyFromCosmosJSON((tx.tx as RawTxResponse).body.messages) + let msgs: Array = [] + if (tx.tx) { + if (!tx.tx.body) { + msgs = cosmosclient.codec.packAnyFromCosmosJSON(tx.tx).msg + } else { + msgs = cosmosclient.codec.packAnyFromCosmosJSON(tx.tx.body.messages) + } } const from: TxFrom[] = [] @@ -92,7 +111,8 @@ export const getTxsFromHistory = (txs: TxResponse[], mainAsset: Asset): Tx[] => msgs.map((msg) => { if (isMsgSend(msg)) { const msgSend = msg - const amount = getCoinAmount(msgSend.amount) + const coins = getCoinsByAsset(msgSend.amount, asset) + const amount = getCoinAmount(coins) let from_index = -1 @@ -127,7 +147,8 @@ export const getTxsFromHistory = (txs: TxResponse[], mainAsset: Asset): Tx[] => const msgMultiSend = msg msgMultiSend.inputs.map((input) => { - const amount = getCoinAmount(input.coins || []) + const coins = getCoinsByAsset(input.coins || [], asset) + const amount = getCoinAmount(coins) let from_index = -1 @@ -146,7 +167,8 @@ export const getTxsFromHistory = (txs: TxResponse[], mainAsset: Asset): Tx[] => }) msgMultiSend.outputs.map((output) => { - const amount = getCoinAmount(output.coins || []) + const coins = getCoinsByAsset(output.coins || [], asset) + const amount = getCoinAmount(coins) let to_index = -1 @@ -169,7 +191,7 @@ export const getTxsFromHistory = (txs: TxResponse[], mainAsset: Asset): Tx[] => return [ ...acc, { - asset: mainAsset, + asset, from, to, date: new Date(tx.timestamp),