diff --git a/.changeset/chilly-ties-battle.md b/.changeset/chilly-ties-battle.md new file mode 100644 index 000000000..d82c858fb --- /dev/null +++ b/.changeset/chilly-ties-battle.md @@ -0,0 +1,16 @@ +--- +'@xchainjs/xchain-thorchain': major +--- + +Breaking changes + - `getPrivateKey` is now async and response is Uint8Array type + - `getPubKey` is now async and response is Uint8Array type + - `getDepositTransaction` is deprecated in favour of `getTransactionData` + - `fetchTransaction` removed + - `setClientUrl` removed + - `getClientUrl` removed + - `setExplorerUrls` removed + - `getCosmosClient` removed + - `setNetwork` removed + - `setChainId` removed + - `getChainId` removed diff --git a/.changeset/few-wolves-build.md b/.changeset/few-wolves-build.md new file mode 100644 index 000000000..5cca82d63 --- /dev/null +++ b/.changeset/few-wolves-build.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-cosmos-sdk': minor +--- + +New util functions: makeClientPath, bech32toBase64 format and base64ToBech32 diff --git a/.changeset/plenty-tools-live.md b/.changeset/plenty-tools-live.md new file mode 100644 index 000000000..c9b3b96f5 --- /dev/null +++ b/.changeset/plenty-tools-live.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-thorchain': major +--- + +Client compatible with @xchainjs/xchain-cosmos-sdk package diff --git a/.changeset/polite-radios-talk.md b/.changeset/polite-radios-talk.md new file mode 100644 index 000000000..778b9c5b5 --- /dev/null +++ b/.changeset/polite-radios-talk.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-cosmos-sdk': minor +--- + +New constructor parameter 'registry' to allow the client to work with custom message types. diff --git a/.changeset/seven-falcons-think.md b/.changeset/seven-falcons-think.md new file mode 100644 index 000000000..b8333cb79 --- /dev/null +++ b/.changeset/seven-falcons-think.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-cosmos-sdk': patch +--- + +Broadcast bug fix diff --git a/.changeset/weak-seahorses-sell.md b/.changeset/weak-seahorses-sell.md new file mode 100644 index 000000000..2da1cc94f --- /dev/null +++ b/.changeset/weak-seahorses-sell.md @@ -0,0 +1,5 @@ +--- +'@xchainjs/xchain-kujira': patch +--- + +New version compatible with cosmos-sdk version diff --git a/packages/xchain-cosmos-sdk/package.json b/packages/xchain-cosmos-sdk/package.json index 1e2772845..4dcada927 100644 --- a/packages/xchain-cosmos-sdk/package.json +++ b/packages/xchain-cosmos-sdk/package.json @@ -46,7 +46,11 @@ "@xchainjs/xchain-util": "^0.13.1" }, "dependencies": { + "@cosmjs/crypto": "0.31.1", "@cosmjs/stargate": "^0.31.1", + "@cosmjs/encoding": "0.31.1", + "@cosmjs/proto-signing": "0.31.1", + "@scure/base": "1.1.5", "bech32": "^1.1.3", "bip32": "^2.0.6", "secp256k1": "^5.0.0" diff --git a/packages/xchain-cosmos-sdk/src/client.ts b/packages/xchain-cosmos-sdk/src/client.ts index 137534643..5838cb47d 100644 --- a/packages/xchain-cosmos-sdk/src/client.ts +++ b/packages/xchain-cosmos-sdk/src/client.ts @@ -1,14 +1,13 @@ -import { fromBase64, fromBech32, toBase64 } from '@cosmjs/encoding' -import { DecodedTxRaw, DirectSecp256k1HdWallet, Registry, TxBodyEncodeObject, decodeTxRaw } from '@cosmjs/proto-signing' +import { fromBase64, fromBech32 } from '@cosmjs/encoding' import { - GasPrice, - IndexedTx, - MsgSendEncodeObject, - SigningStargateClient, - StargateClient, - StdFee, - calculateFee, -} from '@cosmjs/stargate' + DecodedTxRaw, + DirectSecp256k1HdWallet, + EncodeObject, + GeneratedType, + Registry, + decodeTxRaw, +} from '@cosmjs/proto-signing' +import { IndexedTx, SigningStargateClient, StargateClient, StdFee, defaultRegistryTypes } from '@cosmjs/stargate' import { AssetInfo, Balance, @@ -32,10 +31,11 @@ import * as xchainCrypto from '@xchainjs/xchain-crypto' import { Address, Asset, BaseAmount, CachedValue, Chain, baseAmount } from '@xchainjs/xchain-util' import * as bech32 from 'bech32' import * as BIP32 from 'bip32' -import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' import * as crypto from 'crypto' import * as secp256k1 from 'secp256k1' +import { makeClientPath } from './utils' + export type CosmosSdkClientParams = XChainClientParams & { chain: Chain clientUrls: Record @@ -43,19 +43,24 @@ export type CosmosSdkClientParams = XChainClientParams & { defaultDecimals: number defaultFee: BaseAmount baseDenom: string + registryTypes: Iterable<[string, GeneratedType]> +} + +export enum MsgTypes { + TRANSFER = 'transfer', } /** * Generic implementation of the XChainClient interface chains built with cosmos-sdk (https://docs.cosmos.network/) using the dependencies of the official @cosmjs monorepo. */ export default abstract class Client extends BaseXChainClient implements XChainClient { - private readonly startgateClient: CachedValue - private readonly clientUrls: Record - private readonly signer: CachedValue | undefined - private readonly prefix: string private readonly defaultDecimals: number private readonly defaultFee: BaseAmount + protected readonly startgateClient: CachedValue + protected readonly clientUrls: Record + protected readonly prefix: string protected readonly baseDenom: string + protected readonly registry: Registry /** * Constructor * @constructor @@ -68,12 +73,7 @@ export default abstract class Client extends BaseXChainClient implements XChainC this.defaultDecimals = params.defaultDecimals this.defaultFee = params.defaultFee this.baseDenom = params.baseDenom - if (params.phrase) { - this.signer = new CachedValue( - async () => - await DirectSecp256k1HdWallet.fromMnemonic(params.phrase as string, { prefix: params.prefix || 'cosmos' }), - ) - } + this.registry = new Registry([...defaultRegistryTypes, ...params.registryTypes]) this.startgateClient = new CachedValue(() => this.connectClient(this.clientUrls[params.network || Network.Mainnet]), ) @@ -343,8 +343,6 @@ export default abstract class Client extends BaseXChainClient implements XChainC } public async transfer(params: TxParams): Promise { - if (!this.signer) throw Error('Invalid signer') - const sender = await this.getAddressAsync(params.walletIndex || 0) const { rawUnsignedTx } = await this.prepareTx({ @@ -355,81 +353,52 @@ export default abstract class Client extends BaseXChainClient implements XChainC memo: params.memo, }) - // TODO: Support fee configuration (subsided fee) - const denom = this.getDenom(params.asset || this.getAssetInfo().asset) - const defaultGasPrice = GasPrice.fromString(`0.025${denom}`) - const defaultSendFee: StdFee = calculateFee(90_000, defaultGasPrice) - const unsignedTx: DecodedTxRaw = decodeTxRaw(fromBase64(rawUnsignedTx)) - const signer = await this.signer.getValue() - const signingClient = await SigningStargateClient.connectWithSigner(this.clientUrls[this.network], signer) + const signer = await DirectSecp256k1HdWallet.fromMnemonic(this.phrase as string, { + prefix: this.prefix, + hdPaths: [makeClientPath(this.getFullDerivationPath(params.walletIndex || 0))], + }) - const messages: MsgSendEncodeObject[] = unsignedTx.body.messages.map((message) => { - return { typeUrl: '/cosmos.bank.v1beta1.MsgSend', value: signingClient.registry.decode(message) } + const signingClient = await SigningStargateClient.connectWithSigner(this.clientUrls[this.network], signer, { + registry: this.registry, }) - const tx = await signingClient.signAndBroadcast(sender, messages, defaultSendFee, unsignedTx.body.memo) + + const messages: EncodeObject[] = unsignedTx.body.messages.map((message) => { + return { typeUrl: this.getMsgTypeUrlByType(MsgTypes.TRANSFER), value: signingClient.registry.decode(message) } + }) + + const tx = await signingClient.signAndBroadcast( + sender, + messages, + this.getStandardFee(params.asset || this.getAssetInfo().asset), + unsignedTx.body.memo, + ) return tx.transactionHash } public async broadcastTx(txHex: string): Promise { const client = await this.startgateClient.getValue() - const txResponse = await client.broadcastTx(new Uint8Array(Buffer.from(txHex, 'hex'))) + const txResponse = await client.broadcastTx(fromBase64(txHex)) return txResponse.transactionHash } - /** - * Prepare transfer. - * - * @param {TxParams&Address} params The transfer options. - * @returns {PreparedTx} The raw unsigned transaction. - */ - public async prepareTx({ + public abstract prepareTx({ sender, recipient, asset, amount, memo, - }: TxParams & { sender: Address }): Promise { - if (!this.validateAddress(sender)) throw Error('Invalid sender address') - if (!this.validateAddress(recipient)) throw Error('Invalid recipient address') - - const denom = this.getDenom(asset || this.getAssetInfo().asset) - if (!denom) - throw Error(`Invalid asset ${asset?.symbol} - Only ${this.baseDenom} asset is currently supported to transfer`) - - const demonAmount = { amount: amount.amount().toString(), denom } - - const txBody: TxBodyEncodeObject = { - typeUrl: '/cosmos.tx.v1beta1.TxBody', - value: { - messages: [ - { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: { - fromAddress: sender, - toAddress: recipient, - amount: [demonAmount], - }, - }, - ], - memo: memo, - }, - } - - const rawTx = TxRaw.fromPartial({ - bodyBytes: new Registry().encode(txBody), - }) - return { rawUnsignedTx: toBase64(TxRaw.encode(rawTx).finish()) } - } - + }: TxParams & { sender: Address }): Promise abstract getAssetInfo(): AssetInfo abstract getExplorerUrl(): string abstract getExplorerAddressUrl(_address: string): string abstract getExplorerTxUrl(txID: string): string abstract assetFromDenom(denom: string): Asset | null abstract getDenom(asset: Asset): string | null + protected abstract getMsgTypeUrlByType(msgType: MsgTypes): string + protected abstract getStandardFee(asset: Asset): StdFee } export { Client } diff --git a/packages/xchain-cosmos-sdk/src/index.ts b/packages/xchain-cosmos-sdk/src/index.ts index 83dae7638..4b21328e0 100644 --- a/packages/xchain-cosmos-sdk/src/index.ts +++ b/packages/xchain-cosmos-sdk/src/index.ts @@ -1 +1,2 @@ export * from './client' +export { base64ToBech32, bech32ToBase64, makeClientPath } from './utils' diff --git a/packages/xchain-cosmos-sdk/src/utils.ts b/packages/xchain-cosmos-sdk/src/utils.ts new file mode 100644 index 000000000..a398872b0 --- /dev/null +++ b/packages/xchain-cosmos-sdk/src/utils.ts @@ -0,0 +1,35 @@ +import { HdPath, stringToPath } from '@cosmjs/crypto' +import { toBech32 } from '@cosmjs/encoding' +import { base64, bech32 } from '@scure/base' + +/** + * Transform string path in HdPath compatible with cosmjs + * + * @example + * makeClientPath(`44'/118'/0'/0/0`) // returns [Slip10RawIndex.hardened(44), Slip10RawIndex.hardened(118), Slip10RawIndex.hardened(0), Slip10RawIndex.normal(0), Slip10RawIndex.normal(0) ] + * * @example + * makeClientPath(`m/44'/118'/0'/0/0`) // returns [Slip10RawIndex.hardened(44), Slip10RawIndex.hardened(118), Slip10RawIndex.hardened(0), Slip10RawIndex.normal(0), Slip10RawIndex.normal(0) ] + * @param fullDerivationPath + * @returns {HdPath} + */ +export function makeClientPath(fullDerivationPath: string): HdPath { + const path = fullDerivationPath.startsWith('m/') ? fullDerivationPath : `m/${fullDerivationPath}` // To be compatible with previous versions + return stringToPath(path) +} + +/** + * Transform address from Bech32 to Base64 + * + * @param {string} address The address to change the format + * @returns the address in Base64 format + */ +export const bech32ToBase64 = (address: string) => + base64.encode(Uint8Array.from(bech32.fromWords(bech32.decode(address).words))) + +/** + * Transform address from Base64 to Bech32 + * + * @param {string} address The address to change the format + * @returns the address in Bech32 format + */ +export const base64ToBech32 = (address: string, prefix: string) => toBech32(prefix, base64.decode(address)) diff --git a/packages/xchain-kujira/package.json b/packages/xchain-kujira/package.json index dea248442..c5dbda275 100644 --- a/packages/xchain-kujira/package.json +++ b/packages/xchain-kujira/package.json @@ -31,10 +31,17 @@ "lint": "eslint \"{src,__tests__}/**/*.ts\" --fix --max-warnings 0", "prepublishOnly": "yarn build" }, + "dependencies": { + "@cosmjs/encoding": "0.31.1", + "@cosmjs/stargate": "0.31.1" + }, "devDependencies": { + "@cosmjs/amino": "0.31.1", + "@cosmjs/proto-signing": "0.31.1", "@xchainjs/xchain-client": "^0.16.1", "@xchainjs/xchain-util": "^0.13.2", - "@xchainjs/xchain-cosmos-sdk": "^0.1.6" + "@xchainjs/xchain-cosmos-sdk": "^0.1.6", + "cosmjs-types": "0.8.0" }, "publishConfig": { "access": "public" diff --git a/packages/xchain-kujira/src/client.ts b/packages/xchain-kujira/src/client.ts index 91788ba72..e11be5a5a 100644 --- a/packages/xchain-kujira/src/client.ts +++ b/packages/xchain-kujira/src/client.ts @@ -1,8 +1,13 @@ -import { AssetInfo } from '@xchainjs/xchain-client' -import { Client as CosmosSdkClient, CosmosSdkClientParams } from '@xchainjs/xchain-cosmos-sdk' +import { StdFee } from '@cosmjs/amino' +import { toBase64 } from '@cosmjs/encoding' +import { TxBodyEncodeObject } from '@cosmjs/proto-signing' +import { GasPrice, calculateFee } from '@cosmjs/stargate' +import { AssetInfo, PreparedTx, TxParams } from '@xchainjs/xchain-client' +import { Client as CosmosSdkClient, CosmosSdkClientParams, MsgTypes } from '@xchainjs/xchain-cosmos-sdk' import { Address, Asset, eqAsset } from '@xchainjs/xchain-util' +import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' -import { AssetKUJI, KUJI_DECIMAL } from './const' +import { AssetKUJI, KUJI_DECIMAL, MSG_SEND_TYPE_URL } from './const' import { defaultClientConfig, getDefaultExplorers } from './utils' export type KujiraClientParams = Partial @@ -49,4 +54,72 @@ export class Client extends CosmosSdkClient { getExplorerTxUrl(txID: string): string { return `${this.getExplorerUrl()}/tx/${txID}` } + + /** + * Prepare transfer. + * + * @param {TxParams&Address} params The transfer options. + * @returns {PreparedTx} The raw unsigned transaction. + */ + public async prepareTx({ + sender, + recipient, + asset, + amount, + memo, + }: TxParams & { sender: Address }): Promise { + if (!this.validateAddress(sender)) throw Error('Invalid sender address') + if (!this.validateAddress(recipient)) throw Error('Invalid recipient address') + + const denom = this.getDenom(asset || this.getAssetInfo().asset) + if (!denom) + throw Error(`Invalid asset ${asset?.symbol} - Only ${this.baseDenom} asset is currently supported to transfer`) + + const demonAmount = { amount: amount.amount().toString(), denom } + + const txBody: TxBodyEncodeObject = { + typeUrl: '/cosmos.tx.v1beta1.TxBody', + value: { + messages: [ + { + typeUrl: this.getMsgTypeUrlByType(MsgTypes.TRANSFER), + value: { + fromAddress: sender, + toAddress: recipient, + amount: [demonAmount], + }, + }, + ], + memo: memo, + }, + } + + const rawTx = TxRaw.fromPartial({ + bodyBytes: this.registry.encode(txBody), + }) + return { rawUnsignedTx: toBase64(TxRaw.encode(rawTx).finish()) } + } + + /** + * Get the message type url by type used by the cosmos-sdk client to make certain actions + * @param msgType + * @returns {string} the type url of the message + */ + protected getMsgTypeUrlByType(msgType: MsgTypes): string { + const messageTypeUrls: Record = { + [MsgTypes.TRANSFER]: MSG_SEND_TYPE_URL, + } + return messageTypeUrls[msgType] + } + + /** + * Returns the standard fee used by the client for an asset + * @param {Asset} asset the asset to retrieve the fee of + * @returns {StdFee} the standard fee + */ + protected getStandardFee(asset: Asset): StdFee { + const denom = this.getDenom(asset) + const defaultGasPrice = GasPrice.fromString(`0.025${denom}`) + return calculateFee(90_000, defaultGasPrice) + } } diff --git a/packages/xchain-kujira/src/const.ts b/packages/xchain-kujira/src/const.ts index e3653ae35..52adc2090 100644 --- a/packages/xchain-kujira/src/const.ts +++ b/packages/xchain-kujira/src/const.ts @@ -9,3 +9,5 @@ export const DEFAULT_FEE = baseAmount(5000, KUJI_DECIMAL) export const KUJIChain = 'KUJI' as const export const AssetKUJI: Asset = { chain: KUJIChain, symbol: 'KUJI', ticker: 'KUJI', synth: false } + +export const MSG_SEND_TYPE_URL = '/cosmos.bank.v1beta1.MsgSend' as const diff --git a/packages/xchain-kujira/src/utils.ts b/packages/xchain-kujira/src/utils.ts index 07328aa84..a93442c8d 100644 --- a/packages/xchain-kujira/src/utils.ts +++ b/packages/xchain-kujira/src/utils.ts @@ -32,4 +32,5 @@ export const defaultClientConfig: CosmosSdkClientParams = { defaultDecimals: 6, defaultFee: DEFAULT_FEE, baseDenom: 'ukuji', + registryTypes: [], } diff --git a/packages/xchain-thorchain/__e2e__/thorchain-client.e2e.ts b/packages/xchain-thorchain/__e2e__/thorchain-client.e2e.ts index 4be47ada8..ba3a28e23 100644 --- a/packages/xchain-thorchain/__e2e__/thorchain-client.e2e.ts +++ b/packages/xchain-thorchain/__e2e__/thorchain-client.e2e.ts @@ -1,327 +1,154 @@ -import cosmosclient from '@cosmos-client/core' -import { Client as BnbClient } from '@xchainjs/xchain-binance' -import { Network, TxParams } from '@xchainjs/xchain-client' -import { - Asset, - BaseAmount, - assetFromString, - assetToString, - baseAmount, - delay, - register9Rheader, -} from '@xchainjs/xchain-util' -import axios from 'axios' - -import { AssetRuneNative } from '../src' -import { Client as ThorClient } from '../src/index' - -export type Swap = { - fromBaseAmount: BaseAmount - to: Asset -} - -// Mock chain ids -const chainIds = { - [Network.Mainnet]: 'thorchain-mainnet-v1', - [Network.Stagenet]: 'chain-id-stagenet', - [Network.Testnet]: 'deprecated', +import { Tx } from '@xchainjs/xchain-client' +import { assetAmount, assetToBase, assetToString, baseToAsset } from '@xchainjs/xchain-util' + +import { AssetRuneNative as AssetRune, Client, DepositTx } from '../lib' + +const getPrintableTx = (tx: Tx) => { + return { + hash: tx.hash, + date: tx.date.toDateString(), + asset: assetToString(tx.asset), + type: tx.type, + from: tx.from.map((sender) => { + return { + from: sender.from, + asset: sender.asset ? assetToString(sender.asset) : 'undefined', + amount: baseToAsset(sender.amount).amount().toString(), + } + }), + to: tx.to.map((recipient) => { + return { + to: recipient.to, + asset: recipient.asset ? assetToString(recipient.asset) : 'undefined', + amount: baseToAsset(recipient.amount).amount().toString(), + } + }), + } } -const thorClient = new ThorClient({ - network: Network.Mainnet, - phrase: process.env.MAINNET_PHRASE, - chainIds: chainIds, -}) -const thorchainClient = thorClient -const bnbClient = new BnbClient({ network: Network.Mainnet, phrase: process.env.MAINNET_PHRASE }) - -register9Rheader(axios) -register9Rheader(cosmosclient.config.globalAxios) - -// axios.interceptors.request.use((request) => { -// console.log('Starting Request', JSON.stringify(request, null, 2)) -// return request -// }) - -// axios.interceptors.response.use((response) => { -// console.log('Response:', JSON.stringify(response, null, 2)) -// return response -// }) - -describe('thorchain Integration Tests', () => { - it('should fetch thorchain balances', async () => { - const address = await thorClient.getAddressAsync(0) - console.log(address) - const balances = await thorClient.getBalance('thor1tqpyn3athvuj8dj7nu5fp0xm76ut86sjcl3pqu') - balances.forEach((bal) => { - console.log(`${assetToString(bal.asset)} = ${bal.amount.amount()}`) - }) - expect(balances.length).toBeGreaterThan(0) - }) - it('should xfer rune from wallet 0 -> 1, with a memo and custom sequence', async () => { - try { - const addressTo = await thorClient.getAddressAsync(1) - const transferTx = { - walletIndex: 0, - asset: AssetRuneNative, - amount: baseAmount('100'), - recipient: addressTo, - memo: 'Hi!', - sequence: 1, +const getPrintableDepositTx = (depositTx: DepositTx) => { + return { + hash: depositTx.hash, + asset: assetToString(depositTx.asset), + type: depositTx.type, + from: depositTx.from.map((sender) => { + return { + from: sender.from, + asset: sender.asset ? assetToString(sender.asset) : 'undefined', + amount: baseToAsset(sender.amount).amount().toString(), } - await thorClient.transfer(transferTx) - fail() - } catch (error: any) { - expect(error.toString().includes('account sequence mismatch')).toBe(true) - } - }) - it('should transfer rune from wallet 0 -> 1, with a memo x', async () => { - try { - const addressTo = await thorClient.getAddressAsync(1) - const transferTx: TxParams = { - walletIndex: 0, - asset: AssetRuneNative, - amount: baseAmount('100'), - recipient: addressTo, - memo: 'Hi!', + }), + to: depositTx.to.map((recipient) => { + return { + to: recipient.to, + asset: recipient.asset ? assetToString(recipient.asset) : 'undefined', + amount: baseToAsset(recipient.amount).amount().toString(), } - const hash = await thorClient.transfer(transferTx) - expect(hash.length).toBeGreaterThan(0) - } catch (error) { - console.log(error) - throw error - } - }) - it('should swap some rune for BNB', async () => { - try { - // Wait 10 seconds, make sure previous test has finished to avoid sequnce conflict - await delay(10 * 1000) + }), + } +} - const address = await bnbClient.getAddressAsync() - const memo = `=:BNB.BNB:${address}` +describe('Thorchain client e2e', () => { + let client: Client - const hash = await thorchainClient.deposit({ - walletIndex: 0, - amount: baseAmount('100'), - asset: AssetRuneNative, - memo, - }) - - expect(hash.length).toBeGreaterThan(5) - } catch (error) { - console.log(error) - throw error - } + beforeAll(() => { + client = new Client({ + phrase: process.env.PHRASE_MAINNET, + }) }) - it('should swap some RUNE for BNB/BNB', async () => { - try { - // Wait 10 seconds, make sure previous test has finished to avoid sequnce conflict - await delay(10 * 1000) - - const address = await thorClient.getAddressAsync() - const memo = `=:BNB/BNB:${address}` - - const hash = await thorchainClient.deposit({ - walletIndex: 0, - amount: baseAmount('100000000'), - asset: AssetRuneNative, - memo, - }) - expect(hash.length).toBeGreaterThan(5) - } catch (error) { - console.log(error) - throw error - } + it('Should get private key', async () => { + const privateKey = await client.getPrivateKey(0) + console.log(privateKey) }) - it('should transfer some BNB/BNB', async () => { - try { - const address = await thorClient.getAddressAsync() - const asset = assetFromString('BNB/BNB') as Asset - const transferTx: TxParams = { - walletIndex: 0, - asset: asset, - amount: baseAmount('2000'), - recipient: address, - } - const hash = await thorClient.transfer(transferTx) - expect(hash.length).toBeGreaterThan(5) - } catch (error) { - console.log(error) - throw error - } + it('Should get public key', async () => { + const publicKey = await client.getPubKey(0) + console.log(publicKey) }) - it('should fetch thorchain txs', async () => { - const address = 'thor1nx3yxgdw94nfw0uzwns2ay5ap85nk9p6hjaqn9' - const txPage = await thorClient.getTransactions({ address }) - expect(txPage.total).toBeGreaterThan(0) - expect(txPage.txs.length).toBeGreaterThan(0) - }) - it('should fetch thorchain tx data', async () => { - const txId = '6F75AAE03F9C50827DBA89BC00F09F4D47A3D378DF0893291C04A8C32095FCE1' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - expect(tx.hash).toBe('6F75AAE03F9C50827DBA89BC00F09F4D47A3D378DF0893291C04A8C32095FCE1') - expect(tx.from[0].amount.amount().toNumber()).toBe(110000000000) + it('Should get address', async () => { + const address = await client.getAddressAsync() + console.log(address) }) - it('should get synth asset from synth tx', async () => { - const txId = 'FF900F04B145799668AB9975E40C51E42024D8761330D2210DCC8447F44218AF' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - - expect(tx.hash).toBe('FF900F04B145799668AB9975E40C51E42024D8761330D2210DCC8447F44218AF') - expect(tx.asset.ticker).toBe('BTC') - expect(tx.asset.synth).toBeTruthy() - expect(tx.from[0].asset?.chain).toBe('BTC') - expect(tx.from[1].amount.amount().toNumber()).toBe(8734) - expect(tx.from[0].asset?.symbol).toBe('BTC') + it('Should get address balances', async () => { + const balances = await client.getBalance(await client.getAddressAsync()) + console.log( + balances.map((balance) => { + return { + asset: assetToString(balance.asset), + amount: baseToAsset(balance.amount).amount().toString(), + } + }), + ) }) - it('should get transaction data from a rune to pool module', async () => { - const txId = 'EAC3D95D9160D4CF5A0BD861BDD9A7C5ACBA102B3A825FECD01581393BF76AEF' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - expect(tx.hash).toBe('EAC3D95D9160D4CF5A0BD861BDD9A7C5ACBA102B3A825FECD01581393BF76AEF') - expect(tx.asset.ticker).toBe('RUNE') - expect(tx.to[1].amount.amount().toNumber()).toBe(30371900000) + it('Should get address transaction history', async () => { + const history = await client.getTransactions({ address: await client.getAddressAsync() }) + console.log({ total: history.total }) + history.txs.forEach((tx) => { + console.log(getPrintableTx(tx)) + }) }) - it('should get THOR.RUNE to ETH.ETH outbound', async () => { - // thor.rune msgDeposit (inbound) - const txId = '3F763B3F874DC5EEEA965D570A0C8DCA68915669D38A486A826B2238447E5498' - const tx = await thorClient.getTransactionData(txId) - - //console.log(JSON.stringify(tx, null, 2)) - - expect(tx.hash).toBe(txId) - expect(tx.from[0].asset?.chain).toBe('THOR') - expect(tx.from[0].asset?.symbol).toBe('RUNE') - expect(tx.from[0].amount.amount().toFixed()).toBe('2000000000') - expect(tx.from[0].from).toBe('thor1zdf2n0jx9nqvdnd2u3y93t5y0rs4znnv9rn5zc') - expect(tx.to[0].asset?.chain).toBe('ETH') - expect(tx.to[0].asset?.symbol).toBe('ETH') - expect(tx.to[0].to).toBe('0x17AF7fd6Eb8D414be10296dcac9b922D9c9F0076') - // // asgard -> eth (outbound) - const outboundTxId = '0049ECD2785F84D845DC2FA29E1046CBB39F0EFB1D991CB48F97A577887D5613' - const outboundTx = await thorClient.getTransactionData(outboundTxId) - console.log(JSON.stringify(outboundTx, null, 2)) - expect(outboundTx.hash).toBe(outboundTxId) - expect(outboundTx.from[0].asset?.chain).toBe('ETH') - expect(outboundTx.from[0].asset?.symbol).toBe('ETH') - - expect(tx.to[0].asset?.chain).toBe('ETH') - expect(tx.to[0].asset?.symbol).toBe('ETH') + it('Should get transaction', async () => { + const tx = await client.getTransactionData('B949762DE7679D2C2343B135FF11EBF059DBB5DCEB1045F1D9D3DED5EB648C4C') + console.log(getPrintableTx(tx)) }) - it('should get ETH.ETH to THOR.RUNE inbound', async () => { - // eth.eth (inbound) - const txId = '7FDFBD0B884376B2ED4F615476787C08FF569C181566052A3907535529347FBA' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - - expect(tx.hash).toBe(txId) - expect(tx.from[0].asset?.chain).toBe('ETH') - expect(tx.from[0].asset?.symbol).toBe('ETH') - expect(tx.from[0].from).toBe('0xd4d99d205e67e88e5e19d91afd6fcab665b532e8') - expect(tx.from[0].amount.amount().toNumber()).toBe(50000000) - - expect(tx.to[0].asset?.chain).toBe('THOR') - expect(tx.to[0].asset?.symbol).toBe('RUNE') - expect(tx.to[0].to).toBe('thor1auu0xc7zzcestqt60g429gpfkk9ynhqazw3epa') - expect(tx.to[0].amount.amount().toNumber()).toBe(46443816698) - // asgard -> eth (outbound) - const outboundTxId = '0049ECD2785F84D845DC2FA29E1046CBB39F0EFB1D991CB48F97A577887D5613' - const outboundTx = await thorClient.getTransactionData(outboundTxId) - console.log(JSON.stringify(outboundTx, null, 2)) - expect(outboundTx.hash).toBe(outboundTxId) - expect(outboundTx.from[0].asset?.chain).toBe('ETH') - expect(outboundTx.from[0].asset?.symbol).toBe('ETH') - - expect(outboundTx.to[0].asset?.chain).toBe('ETH') - expect(outboundTx.to[0].asset?.symbol).toBe('ETH') + it('Should get deposit transaction', async () => { + const tx = await client.getDepositTransaction('3F763B3F874DC5EEEA965D570A0C8DCA68915669D38A486A826B2238447E5498') + console.log(getPrintableDepositTx(tx)) }) - it('should get tx data from transfer of rune to rune', async () => { - const txId = 'C948F21D5218A2A20218B99B7A37C9274FED26D31619FD054383D8E98A866AEB' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - - expect(tx.hash).toBe('C948F21D5218A2A20218B99B7A37C9274FED26D31619FD054383D8E98A866AEB') - expect(tx.asset.ticker).toBe('RUNE') - expect(tx.type).toBe('transfer') - expect(tx.from[1].amount.amount().toNumber()).toBe(356890907) - expect(tx.to[1].to).toBe('thor1nx3yxgdw94nfw0uzwns2ay5ap85nk9p6hjaqn9') - expect(tx.to[1].amount.amount().toNumber()).toBe(356890907) + it('Should prepare transaction', async () => { + const unsignedTx = await client.prepareTx({ + sender: await client.getAddressAsync(0), + recipient: await client.getAddressAsync(1), + amount: assetToBase(assetAmount(1, 8)), + memo: 'test', + }) + console.log(unsignedTx) }) - it('should get transaction data from Bond tx', async () => { - const txId = '06576BAF9F56A05828485B8585FFD31583EE226C9E794F013D462CCB7138C42D' - const tx = await thorClient.getTransactionData(txId) - console.log(JSON.stringify(tx, null, 2)) - - expect(tx.hash).toBe('06576BAF9F56A05828485B8585FFD31583EE226C9E794F013D462CCB7138C42D') - expect(tx.asset.ticker).toBe('RUNE') - expect(tx.type).toBe('transfer') - expect(tx.from[1].amount.amount().toNumber()).toBe(13744300000000) + it('Should make transfer to address 0', async () => { + const hash = await client.transfer({ + recipient: await client.getAddressAsync(0), + amount: assetToBase(assetAmount(1, 8)), + memo: 'test', + }) + console.log(hash) }) - it('should fetch thorchain native Tx fee', async () => { - const fees = await thorClient.getFees() - expect(fees.average.amount().toNumber()).toEqual(2000000) - }) - it('should prepare transaction', async () => { + it('Should make deposit', async () => { try { - const sender = await thorClient.getAddressAsync(3) - const recipient = await thorClient.getAddressAsync(0) + /** + * MAKE SURE TO TEST THIS FUNCTION WITH YOUR ADDRESS BNB, OTHERWISE, YOU COULD LOSE FUNDS + */ + const address: string = '' || 'TO_BE_DEFINED' + if (address === 'TO_BE_DEFINED') throw Error('Set an address to try the deposit e2e function') + const memo = `=:BNB.BNB:${address}` - const amount = baseAmount('80000000') - const unsignedTxData = await thorClient.prepareTx({ - sender, - recipient, - amount, - memo: 'test', + const hash = await client.deposit({ + walletIndex: 0, + amount: assetToBase(assetAmount(1, 8)), + asset: AssetRune, + memo, }) - - const decodedTx = cosmosclient.proto.cosmos.tx.v1beta1.TxRaw.decode( - Buffer.from(unsignedTxData.rawUnsignedTx, 'base64'), - ) - - const privKey = thorClient - .getCosmosClient() - .getPrivKeyFromMnemonic(process.env.MAINNET_PHRASE as string, "44'/931'/0'/0/3") - - const authInfo = cosmosclient.proto.cosmos.tx.v1beta1.AuthInfo.decode(decodedTx.auth_info_bytes) - - if (!authInfo.signer_infos[0].public_key) { - authInfo.signer_infos[0].public_key = cosmosclient.codec.instanceToProtoAny(privKey.pubKey()) - } - - const txBuilder = new cosmosclient.TxBuilder( - thorClient.getCosmosClient().sdk, - cosmosclient.proto.cosmos.tx.v1beta1.TxBody.decode(decodedTx.body_bytes), - authInfo, - ) - - const { account_number: accountNumber } = await thorClient - .getCosmosClient() - .getAccount(cosmosclient.AccAddress.fromString(sender)) - - if (!accountNumber) throw Error(`Transfer failed - missing account number`) - - const signDocBytes = txBuilder.signDocBytes(accountNumber) - txBuilder.addSignature(privKey.sign(signDocBytes)) - - const signedTx = txBuilder.txBytes() - - const hash = await thorClient.broadcastTx(signedTx) - - console.log('hash', hash) - } catch (err) { - console.error('ERR running test', err) - fail() + console.log(hash) + } catch (error) { + console.log(error) + throw error } }) + + it('Should transfer offline', async () => { + const txRaw = await client.transferOffline({ + walletIndex: 0, + recipient: await client.getAddressAsync(0), + amount: assetToBase(assetAmount(1, 8)), + }) + console.log(txRaw) + }) }) diff --git a/packages/xchain-thorchain/__mocks__/responses/thorchain/constants.json b/packages/xchain-thorchain/__mocks__/responses/thorchain/constants.json deleted file mode 100644 index 4e9e278cb..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/thorchain/constants.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "int_64_values": { - "AsgardSize": 40, - "BadValidatorRate": 43200, - "BadValidatorRedline": 3, - "BlocksPerYear": 5256000, - "ChurnInterval": 43200, - "ChurnRetryInterval": 720, - "DesiredValidatorSet": 100, - "DoubleSignMaxAge": 24, - "EmissionCurve": 6, - "FailKeygenSlashPoints": 720, - "FailKeysignSlashPoints": 2, - "FullImpLossProtectionBlocks": 1440000, - "FundMigrationInterval": 360, - "IncentiveCurve": 100, - "JailTimeKeygen": 4320, - "JailTimeKeysign": 60, - "LackOfObservationPenalty": 2, - "LiquidityLockUpBlocks": 0, - "LowBondValidatorRate": 43200, - "MaxAvailablePools": 100, - "MaxSwapsPerBlock": 100, - "MaxSynthPerAssetDepth": 3300, - "MaxTxOutOffset": 720, - "MinRunePoolDepth": 1000000000000, - "MinSlashPointsForBadValidator": 100, - "MinSwapsPerBlock": 10, - "MinTxOutVolumeThreshold": 100000000000, - "MinimumBondInRune": 100000000000000, - "MinimumNodesForBFT": 4, - "MinimumNodesForYggdrasil": 6, - "NativeTransactionFee": 2000000, - "NodePauseChainBlocks": 720, - "ObservationDelayFlexibility": 10, - "ObserveSlashPoints": 1, - "OldValidatorRate": 43200, - "OutboundTransactionFee": 2000000, - "PermittedSolvencyGap": 100, - "PoolCycle": 43200, - "PoolDepthForYggFundingMin": 50000000000000, - "SigningTransactionPeriod": 300, - "StagedPoolCost": 1000000000, - "TNSFeeOnSale": 1000, - "TNSFeePerBlock": 20, - "TNSRegisterFee": 1000000000, - "TxOutDelayMax": 17280, - "TxOutDelayRate": 2500000000, - "ValidatorMaxRewardRatio": 1, - "VirtualMultSynths": 2, - "YggFundLimit": 50, - "YggFundRetry": 1000 - }, - "bool_values": { - "StrictBondLiquidityRatio": true - }, - "string_values": { - "DefaultPoolStatus": "Staged" - } -} diff --git a/packages/xchain-thorchain/__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json b/packages/xchain-thorchain/__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json deleted file mode 100644 index 810045dc4..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json +++ /dev/null @@ -1,157 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": -1, - "result": { - "txs": [ - { - "hash": "9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C", - "height": "891275", - "index": 0, - "tx_result": { - "code": 0, - "data": "CgkKB2RlcG9zaXQ=", - "log": "[{\"events\":[{\"type\":\"bond\",\"attributes\":[{\"key\":\"amount\",\"value\":\"170000000000\"},{\"key\":\"bond_type\",\"value\":\"\\u0000\"},{\"key\":\"id\",\"value\":\"9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C\"},{\"key\":\"chain\",\"value\":\"THOR\"},{\"key\":\"from\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"to\",\"value\":\"tthor1g98cy3n9mmjrpn0sxmn63lztelera37nrytwp2\"},{\"key\":\"coin\",\"value\":\"170000000000 THOR.RUNE\"},{\"key\":\"memo\",\"value\":\"BOND:tthor12dkcgy5gq2aqtntpv4kzkcmnagzkjuv765nle9\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"deposit\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"recipient\",\"value\":\"tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"amount\",\"value\":\"170000000000rune\"}]}]}]", - "info": "", - "gas_wanted": "10000000", - "gas_used": "790101", - "events": [ - { - "type": "message", - "attributes": [ - { - "key": "YWN0aW9u", - "value": "ZGVwb3NpdA==", - "index": true - } - ] - }, - { - "type": "transfer", - "attributes": [ - { - "key": "cmVjaXBpZW50", - "value": "dHRob3IxZGhleWNkZXZxMzlxbGt4czJhNnd1dXp5bjRhcXhodmUzaGhtbHc=", - "index": true - }, - { - "key": "c2VuZGVy", - "value": "dHRob3IxMzdrZWVzNjVqbWhqbTNneHl1bmUwa201ZWEwemtwbmo0bHcyOWY=", - "index": true - }, - { - "key": "YW1vdW50", - "value": "MjAwMDAwMHJ1bmU=", - "index": true - } - ] - }, - { - "type": "message", - "attributes": [ - { - "key": "c2VuZGVy", - "value": "dHRob3IxMzdrZWVzNjVqbWhqbTNneHl1bmUwa201ZWEwemtwbmo0bHcyOWY=", - "index": true - } - ] - }, - { - "type": "transfer", - "attributes": [ - { - "key": "cmVjaXBpZW50", - "value": "dHRob3IxN2d3NzVheGNucjg3NDdwa2FueWU0NXBucndrN3A5YzN1aHpnZmY=", - "index": true - }, - { - "key": "c2VuZGVy", - "value": "dHRob3IxMzdrZWVzNjVqbWhqbTNneHl1bmUwa201ZWEwemtwbmo0bHcyOWY=", - "index": true - }, - { - "key": "YW1vdW50", - "value": "MTcwMDAwMDAwMDAwcnVuZQ==", - "index": true - } - ] - }, - { - "type": "message", - "attributes": [ - { - "key": "c2VuZGVy", - "value": "dHRob3IxMzdrZWVzNjVqbWhqbTNneHl1bmUwa201ZWEwemtwbmo0bHcyOWY=", - "index": true - } - ] - }, - { - "type": "bond", - "attributes": [ - { - "key": "YW1vdW50", - "value": "MTcwMDAwMDAwMDAw", - "index": true - }, - { - "key": "Ym9uZF90eXBl", - "value": "AA==", - "index": true - }, - { - "key": "aWQ=", - "value": "OUMxNzVBRjdBQ0U5RkNEQzkzMEI3ODkwOUZGRjU5OEMxOENCRUFGOUYzOUQ3QUEyQzREOUEyN0JCN0U1NUE1Qw==", - "index": true - }, - { - "key": "Y2hhaW4=", - "value": "VEhPUg==", - "index": true - }, - { - "key": "ZnJvbQ==", - "value": "dHRob3IxMzdrZWVzNjVqbWhqbTNneHl1bmUwa201ZWEwemtwbmo0bHcyOWY=", - "index": true - }, - { - "key": "dG8=", - "value": "dHRob3IxZzk4Y3kzbjltbWpycG4wc3htbjYzbHp0ZWxlcmEzN25yeXR3cDI=", - "index": true - }, - { - "key": "Y29pbg==", - "value": "MTcwMDAwMDAwMDAwIFRIT1IuUlVORQ==", - "index": true - }, - { - "key": "bWVtbw==", - "value": "Qk9ORDp0dGhvcjEyZGtjZ3k1Z3EyYXF0bnRwdjRremtjbW5hZ3pranV2NzY1bmxlOQ==", - "index": true - } - ] - } - ], - "codespace": "" - }, - "tx": "CoUBCoIBChEvdHlwZXMuTXNnRGVwb3NpdBJtCiIKEgoEVEhPUhIEUlVORRoEUlVORRIMMTcwMDAwMDAwMDAwEjFCT05EOnR0aG9yMTJka2NneTVncTJhcXRudHB2NGt6a2NtbmFnemtqdXY3NjVubGU5GhSPrZzDVJbvLcUGJyeX23TPXisGchJXCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDiON/kWcA0X6NUidt0YNCLxvTYGXrdPRoXwmEnpDf17sSBAoCCH8SBRCAreIEGkDpcQpgQLgC6AEXsxjXLiLH1eqlU+AC3/7HRUNbx6cCqTqd/HbF6j6F8PA0Xw8OARK6VzuV1sc3pB5ChKuX0DPq" - }, - { - "hash": "99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC", - "height": "891274", - "index": 8, - "tx_result": { - "code": 0, - "data": "ChMKES90eXBlcy5Nc2dEZXBvc2l0", - "log": "[{\"events\":[{\"type\":\"burn\",\"attributes\":[{\"key\":\"burner\",\"value\":\"thor1v8ppstuf6e3x0r4glqc68d5jqcs2tf38cg2q6y\"},{\"key\":\"amount\",\"value\":\"250300gaia/atom\"}]},{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"receiver\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"3539039251gaia/atom\"},{\"key\":\"receiver\",\"value\":\"thor1v8ppstuf6e3x0r4glqc68d5jqcs2tf38cg2q6y\"},{\"key\":\"amount\",\"value\":\"250300gaia/atom\"},{\"key\":\"receiver\",\"value\":\"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt\"},{\"key\":\"amount\",\"value\":\"1999285rune\"},{\"key\":\"receiver\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"amount\",\"value\":\"3538788951gaia/atom\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"spender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"amount\",\"value\":\"3539039251gaia/atom\"},{\"key\":\"spender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"250300gaia/atom\"},{\"key\":\"spender\",\"value\":\"thor1v8ppstuf6e3x0r4glqc68d5jqcs2tf38cg2q6y\"},{\"key\":\"amount\",\"value\":\"250300gaia/atom\"},{\"key\":\"spender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"1999285rune\"},{\"key\":\"spender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"3538788951gaia/atom\"}]},{\"type\":\"fee\",\"attributes\":[{\"key\":\"tx_id\",\"value\":\"99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC\"},{\"key\":\"coins\",\"value\":\"250300 GAIA/ATOM\"},{\"key\":\"pool_deduct\",\"value\":\"1999285\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"deposit\"},{\"key\":\"sender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"sender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"}]},{\"type\":\"mint_burn\",\"attributes\":[{\"key\":\"supply\",\"value\":\"burn\"},{\"key\":\"denom\",\"value\":\"gaia/atom\"},{\"key\":\"amount\",\"value\":\"250300\"},{\"key\":\"reason\",\"value\":\"burn_synth_fee\"}]},{\"type\":\"outbound\",\"attributes\":[{\"key\":\"in_tx_id\",\"value\":\"99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC\"},{\"key\":\"id\",\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},{\"key\":\"chain\",\"value\":\"THOR\"},{\"key\":\"from\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"to\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"coin\",\"value\":\"3538788951 GAIA/ATOM\"},{\"key\":\"memo\",\"value\":\"REFUND:99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC\"}]},{\"type\":\"refund\",\"attributes\":[{\"key\":\"code\",\"value\":\"4\"},{\"key\":\"reason\",\"value\":\"trading halted\"},{\"key\":\"id\",\"value\":\"99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC\"},{\"key\":\"chain\",\"value\":\"THOR\"},{\"key\":\"from\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"to\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"coin\",\"value\":\"3539039251 GAIA/ATOM\"},{\"key\":\"memo\",\"value\":\"=:BNB/BUSD-BD1:thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq:49682034796\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt\"},{\"key\":\"sender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"recipient\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"sender\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"amount\",\"value\":\"3539039251gaia/atom\"},{\"key\":\"recipient\",\"value\":\"thor1v8ppstuf6e3x0r4glqc68d5jqcs2tf38cg2q6y\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"250300gaia/atom\"},{\"key\":\"recipient\",\"value\":\"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"1999285rune\"},{\"key\":\"recipient\",\"value\":\"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq\"},{\"key\":\"sender\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"amount\",\"value\":\"3538788951gaia/atom\"}]}]}]", - "info": "", - "gas_wanted": "5000000000", - "gas_used": "421268", - "events": [], - "codespace": "" - }, - "tx": "CpsBCpgBChEvdHlwZXMuTXNnRGVwb3NpdBKCAQoiChQKBEdBSUESBEFUT00aBEFUT00gARIKMzUzOTAzOTI1MRJGPTpCTkIvQlVTRC1CRDE6dGhvcjF5dG10bXNmY2NwMmplZnI0bTRwdXgycHoyeTJwazc0a2oweDJycTo0OTY4MjAzNDc5NhoUIva9wTjAVSykdd1DwygiURQberYSZwpRCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA2RCK2ltXbxKGM4trDqp8O/n97uT9Ob6v6HbrHiRFG/3EgQKAggBGO8REhIKCgoFdXJ1bmUSATAQgOSX0BIaQNzmqyidqoB8Byp0c4PmH6Fm2VZhbjozsW35wnyeD0qfAe1hxAGTc3CJqiWt9sDuVL1XcP66/5amYvR36Iu7R1M=" - } - ], - "total_count": "1" - } -} diff --git a/packages/xchain-thorchain/__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json b/packages/xchain-thorchain/__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json deleted file mode 100644 index 38cada4d8..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "height": "891275", - "txhash": "9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C", - "data": "0A090A076465706F736974", - "raw_log": "[{\"events\":[{\"type\":\"bond\",\"attributes\":[{\"key\":\"amount\",\"value\":\"170000000000\"},{\"key\":\"bond_type\",\"value\":\"\\u0000\"},{\"key\":\"id\",\"value\":\"9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C\"},{\"key\":\"chain\",\"value\":\"THOR\"},{\"key\":\"from\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"to\",\"value\":\"tthor1g98cy3n9mmjrpn0sxmn63lztelera37nrytwp2\"},{\"key\":\"coin\",\"value\":\"170000000000 THOR.RUNE\"},{\"key\":\"memo\",\"value\":\"BOND:tthor12dkcgy5gq2aqtntpv4kzkcmnagzkjuv765nle9\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"deposit\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"recipient\",\"value\":\"tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff\"},{\"key\":\"sender\",\"value\":\"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f\"},{\"key\":\"amount\",\"value\":\"170000000000rune\"}]}]}]", - "logs": [ - { - "events": [ - { - "type": "bond", - "attributes": [ - { "key": "amount", "value": "170000000000" }, - { "key": "bond_type", "value": "\u0000" }, - { "key": "id", "value": "9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C" }, - { "key": "chain", "value": "THOR" }, - { "key": "from", "value": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" }, - { "key": "to", "value": "tthor1g98cy3n9mmjrpn0sxmn63lztelera37nrytwp2" }, - { "key": "coin", "value": "170000000000 THOR.RUNE" }, - { "key": "memo", "value": "BOND:tthor12dkcgy5gq2aqtntpv4kzkcmnagzkjuv765nle9" } - ] - }, - { - "type": "message", - "attributes": [ - { "key": "action", "value": "deposit" }, - { "key": "sender", "value": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" }, - { "key": "sender", "value": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw" }, - { "key": "sender", "value": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" }, - { "key": "amount", "value": "2000000rune" }, - { "key": "recipient", "value": "tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff" }, - { "key": "sender", "value": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" }, - { "key": "amount", "value": "170000000000rune" } - ] - } - ] - } - ], - "gas_wanted": "10000000", - "gas_used": "790101", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "thorchain/MsgDeposit", - "value": { - "coins": [{ "asset": "THOR.RUNE", "amount": "170000000000" }], - "memo": "BOND:tthor12dkcgy5gq2aqtntpv4kzkcmnagzkjuv765nle9", - "signer": "tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f" - } - } - ], - "fee": { "amount": [], "gas": "10000000" }, - "signatures": [ - { - "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": "A4jjf5FnANF+jVInbdGDQi8b02Bl63T0aF8JhJ6Q39e7" }, - "signature": "6XEKYEC4AugBF7MY1y4ix9XqpVPgAt/+x0VDW8enAqk6nfx2xeo+hfDwNF8PDgESulc7ldbHN6QeQoSrl9Az6g==" - } - ], - "memo": "", - "timeout_height": "0" - } - }, - "timestamp": "2021-06-08T05:19:34Z" -} diff --git a/packages/xchain-thorchain/__mocks__/responses/txs/send-AB7DDB79CAFBB402B2E75D03FB15BB2E449B9A8A59563C74090D20D6A3F73627.json b/packages/xchain-thorchain/__mocks__/responses/txs/send-AB7DDB79CAFBB402B2E75D03FB15BB2E449B9A8A59563C74090D20D6A3F73627.json deleted file mode 100644 index e65a06dd7..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/txs/send-AB7DDB79CAFBB402B2E75D03FB15BB2E449B9A8A59563C74090D20D6A3F73627.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "height": "1071801", - "txhash": "AB7DDB79CAFBB402B2E75D03FB15BB2E449B9A8A59563C74090D20D6A3F73627", - "data": "0A060A0473656E64", - "raw_log": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl\"},{\"key\":\"sender\",\"value\":\"thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl\"},{\"key\":\"module\",\"value\":\"governance\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt\"},{\"key\":\"sender\",\"value\":\"thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"recipient\",\"value\":\"thor1mryx88xxhvwu9yepmg968zcdaza2nzz4rltjcp\"},{\"key\":\"sender\",\"value\":\"thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl\"},{\"key\":\"amount\",\"value\":\"500000000rune\"}]}]}]", - "logs": [ - { - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "send" }, - { "key": "sender", "value": "thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl" }, - { "key": "sender", "value": "thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl" }, - { "key": "module", "value": "governance" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt" }, - { "key": "sender", "value": "thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl" }, - { "key": "amount", "value": "2000000rune" }, - { "key": "recipient", "value": "thor1mryx88xxhvwu9yepmg968zcdaza2nzz4rltjcp" }, - { "key": "sender", "value": "thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl" }, - { "key": "amount", "value": "500000000rune" } - ] - } - ] - } - ], - "gas_wanted": "2000000", - "gas_used": "197638", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "thorchain/MsgSend", - "value": { - "from_address": "thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl", - "to_address": "thor1mryx88xxhvwu9yepmg968zcdaza2nzz4rltjcp", - "amount": [{ "denom": "rune", "amount": "500000000" }] - } - } - ], - "fee": { "amount": [], "gas": "2000000" }, - "signatures": [ - { - "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": "Ay/3Z8Ceun3Z4Qvx7OmojUAHPw/KokSjdUy6tEoW6h4/" }, - "signature": "980AAdH7+neJ4WS/nBdCTOamMOJDYAo0pcrRDNOco+dtsNX2pm2pD8sy3Iyc77TaPa590QKpYS8MFldEBG91Jg==" - } - ], - "memo": "shrek:thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl", - "timeout_height": "0" - } - }, - "timestamp": "2021-06-18T12:31:45Z" -} diff --git a/packages/xchain-thorchain/__mocks__/responses/txs/swap-1C10434D59A460FD0BE76C46A333A583B8C7761094E26C0B2548D07A5AF28356.json b/packages/xchain-thorchain/__mocks__/responses/txs/swap-1C10434D59A460FD0BE76C46A333A583B8C7761094E26C0B2548D07A5AF28356.json deleted file mode 100644 index abf20f57f..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/txs/swap-1C10434D59A460FD0BE76C46A333A583B8C7761094E26C0B2548D07A5AF28356.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "height": "1071825", - "txhash": "1C10434D59A460FD0BE76C46A333A583B8C7761094E26C0B2548D07A5AF28356", - "data": "0A090A076465706F736974", - "raw_log": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"deposit\"},{\"key\":\"sender\",\"value\":\"thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr\"},{\"key\":\"sender\",\"value\":\"thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt\"},{\"key\":\"sender\",\"value\":\"thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr\"},{\"key\":\"amount\",\"value\":\"2000000rune\"},{\"key\":\"recipient\",\"value\":\"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0\"},{\"key\":\"sender\",\"value\":\"thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr\"},{\"key\":\"amount\",\"value\":\"3600000000000rune\"}]}]}]", - "logs": [ - { - "events": [ - { - "type": "message", - "attributes": [ - { "key": "action", "value": "deposit" }, - { "key": "sender", "value": "thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr" }, - { "key": "sender", "value": "thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr" } - ] - }, - { - "type": "transfer", - "attributes": [ - { "key": "recipient", "value": "thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt" }, - { "key": "sender", "value": "thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr" }, - { "key": "amount", "value": "2000000rune" }, - { "key": "recipient", "value": "thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0" }, - { "key": "sender", "value": "thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr" }, - { "key": "amount", "value": "3600000000000rune" } - ] - } - ] - } - ], - "gas_wanted": "20000000", - "gas_used": "432661", - "tx": { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "thorchain/MsgDeposit", - "value": { - "coins": [{ "asset": "THOR.RUNE", "amount": "3600000000000" }], - "memo": "SWAP:BTC.BTC:bc1qxnvnp47605ph7udsd45c49ya4xespq4nppr9gf:737605111", - "signer": "thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr" - } - } - ], - "fee": { "amount": [], "gas": "20000000" }, - "signatures": [ - { - "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": "Ak59ahiPACUIGsL+/x5WJLSwLclXG3S/hM3fjWTd70ue" }, - "signature": "LeGmEvBnhBNEJjNiJVnI+tYKLZfCx0A9xhc/jFpR4gxVbTSdlDBBVuiiNz5gtjde+HbfATx9tcJASFXwMlSd2A==" - } - ], - "memo": "", - "timeout_height": "0" - } - }, - "timestamp": "2021-06-18T12:34:01Z" -} diff --git a/packages/xchain-thorchain/__mocks__/responses/txs/transactions.json b/packages/xchain-thorchain/__mocks__/responses/txs/transactions.json deleted file mode 100644 index a00f1273f..000000000 --- a/packages/xchain-thorchain/__mocks__/responses/txs/transactions.json +++ /dev/null @@ -1 +0,0 @@ - {"total":2,"txs":[{"asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false},"from":[{"amount":{"type":"BASE","decimal":8},"from":"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}},{"amount":{"type":"BASE","decimal":8},"from":"tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}}],"to":[{"amount":{"type":"BASE","decimal":8},"to":"tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}},{"amount":{"type":"BASE","decimal":8},"to":"tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}}],"date":"2021-06-08T05:19:34.000Z","type":"transfer","hash":"9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C"},{"asset":{"chain":"GAIA","symbol":"ATOM","ticker":"ATOM","synth":true},"from":[{"amount":{"type":"BASE","decimal":8},"from":"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq","asset":{"chain":"GAIA","symbol":"ATOM","ticker":"ATOM","synth":true}},{"amount":{"type":"BASE","decimal":8},"from":"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq","asset":{"chain":"GAIA","symbol":"ATOM","ticker":"ATOM","synth":true}},{"amount":{"type":"BASE","decimal":8},"from":"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0","asset":{"chain":"GAIA","symbol":"ATOM","ticker":"ATOM","synth":true}}],"to":[{"amount":{"type":"BASE","decimal":8},"to":"thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}},{"amount":{"type":"BASE","decimal":8},"to":"thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}},{"amount":{"type":"BASE","decimal":8},"to":"thor1ytmtmsfccp2jefr4m4pux2pz2y2pk74kj0x2rq","asset":{"chain":"THOR","symbol":"RUNE","ticker":"RUNE","synth":false}}],"date":"2023-02-18T02:53:44.000Z","type":"transfer","hash":"99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC"}]} diff --git a/packages/xchain-thorchain/__mocks__/thornode-api.ts b/packages/xchain-thorchain/__mocks__/thornode-api.ts deleted file mode 100644 index 23259d586..000000000 --- a/packages/xchain-thorchain/__mocks__/thornode-api.ts +++ /dev/null @@ -1,9 +0,0 @@ -import nock from 'nock' - -import { NodeInfoResponse, SimulateResponse } from '../src/types' - -export const mockTendermintNodeInfo = (url: string, result: NodeInfoResponse) => - nock(url).get('/cosmos/base/tendermint/v1beta1/node_info').reply(200, result) - -export const mockTendermintSimulate = (url: string, result: SimulateResponse) => - nock(url).post('/cosmos/tx/v1beta1/simulate').reply(200, result) diff --git a/packages/xchain-thorchain/__tests__/client.test.ts b/packages/xchain-thorchain/__tests__/client.test.ts index 526990906..f9162d18e 100644 --- a/packages/xchain-thorchain/__tests__/client.test.ts +++ b/packages/xchain-thorchain/__tests__/client.test.ts @@ -1,646 +1,126 @@ -import cosmosclient from '@cosmos-client/core' -import { AssetBNB, BNBChain } from '@xchainjs/xchain-binance' -import { Network, TxsPage } from '@xchainjs/xchain-client' -import { CosmosSDKClient, RPCResponse, RPCTxSearchResult, TxResponse } from '@xchainjs/xchain-cosmos' -import { Asset, BaseAmount, assetAmount, assetToBase, baseAmount } from '@xchainjs/xchain-util' -import nock from 'nock' +import { Network } from '@xchainjs/xchain-client' +import { Asset } from '@xchainjs/xchain-util' -import { mockTendermintNodeInfo, mockTendermintSimulate } from '../__mocks__/thornode-api' -import { Client } from '../src/client' -import { AssetRuneNative, MAX_TX_COUNT_PER_FUNCTION_CALL, MAX_TX_COUNT_PER_PAGE } from '../src/const' +import { AssetRuneNative as AssetRUNE, Client } from '../' -const chainIds = { - [Network.Mainnet]: 'thorchain-mainnet-v1', - [Network.Stagenet]: 'thorchain-stagenet-v2', - [Network.Testnet]: 'deprecated', -} +describe('Thorchain client', () => { + describe('Instantiation', () => { + it('Should throw error with invalid phrase', async () => { + expect(() => { + new Client({ phrase: 'invalid phrase', network: Network.Mainnet }) + }).toThrow() -// register9Rheader(axios) -// register9Rheader(cosmosclient.config.globalAxios) - -const mockAccountsAddress = ( - url: string, - address: string, - result: { - account: { - '@type': string - address: string - pub_key?: { - '@type': string - key: string - } - account_number: string - sequence: string - } - }, -) => { - nock(url).persist().get(`/cosmos/auth/v1beta1/accounts/${address}`).reply(200, result) -} - -const mockGetChainId = (url: string, chainId: string) => { - const response = { - default_node_info: { - network: chainId, - }, - } - nock(url).get('/cosmos/base/tendermint/v1beta1/node_info').reply(200, response) -} - -const mockAccountsBalance = ( - url: string, - address: string, - result: { - balances: cosmosclient.proto.cosmos.base.v1beta1.Coin[] - }, -) => { - nock(url).get(`/cosmos/bank/v1beta1/balances/${address}`).reply(200, result) -} - -const mockThorchainConstants = (url: string) => { - const response = require('../__mocks__/responses/thorchain/constants.json') - nock(url).get('/thorchain/constants').reply(200, response) -} - -const assertTxsPost = ( - url: string, - result: { - tx_response: { - txhash: string - code: number - } - }, -): void => { - nock(url, { allowUnmocked: true }) - .post(`/cosmos/tx/v1beta1/txs`, (body) => { - expect(body.mode).toEqual('BROADCAST_MODE_SYNC') - expect(body.tx_bytes.length).toBeGreaterThan(0) - return true - }) - .reply(200, result) -} - -const mockTxHistory = (url: string, result: RPCResponse): void => { - nock(url) - .get(`/tx_search`) - .twice() - .query((_) => true) - .reply(200, result) -} - -const assertTxHashGet = (url: string, hash: string, result: { tx_response: TxResponse }): void => { - nock(url).get(`/cosmos/tx/v1beta1/txs/${hash}`).reply(200, result) -} - -describe('Client Test', () => { - let thorClient: Client - let thorMainClient: Client - const phrase = 'rural bright ball negative already grass good grant nation screen model pizza' - const mainnet_address_path0 = 'thor19kacmmyuf2ysyvq3t9nrl9495l5cvktjs0yfws' - const mainnet_address_path1 = 'thor1hrf34g3lxwvpk7gjte0xvahf3txnq8ecgaf4nc' - const stagenet_address_path0 = 'sthor19kacmmyuf2ysyvq3t9nrl9495l5cvktjykclcw' - const stagenet_address_path1 = 'sthor1hrf34g3lxwvpk7gjte0xvahf3txnq8ecuy4r9x' - - beforeEach(() => { - thorClient = new Client({ phrase, network: Network.Stagenet }) - thorMainClient = new Client({ phrase, network: Network.Mainnet }) - mockGetChainId(thorClient.getClientUrl().node, chainIds[Network.Stagenet]) - }) - - afterEach(() => { - thorClient.purgeClient() - thorMainClient.purgeClient() - }) - - it('should start with empty wallet', async () => { - const thorClientEmptyMain = new Client({ phrase, network: Network.Mainnet }) - const addressMain = await thorClientEmptyMain.getAddressAsync() - expect(addressMain).toEqual(mainnet_address_path0) - - const thorClientEmptyTest = new Client({ phrase, network: Network.Stagenet }) - const addressTest = await thorClientEmptyTest.getAddressAsync() - expect(addressTest).toEqual(stagenet_address_path0) - }) - - it('should derive address accordingly to the user param', async () => { - const thorClientEmptyMain = new Client({ - phrase, - network: Network.Mainnet /*, derivationPath: "44'/931'/0'/0/0" */, - }) - const addressMain = await thorClientEmptyMain.getAddressAsync() - expect(addressMain).toEqual(mainnet_address_path0) - - const viaSetPhraseAddr1 = await thorClientEmptyMain.getAddressAsync(1 /*, "44'/931'/0'/0/1" */) - expect(viaSetPhraseAddr1).toEqual(mainnet_address_path1) - - const thorClientEmptyTest = new Client({ - phrase, - network: Network.Stagenet /*, derivationPath: "44'/931'/0'/0/0"*/, - }) - const addressTest = await thorClientEmptyTest.getAddressAsync() - expect(addressTest).toEqual(stagenet_address_path0) - - const viaSetPhraseAddr1Test = await thorClientEmptyTest.getAddressAsync(1 /*, "44'/931'/0'/0/1"*/) - expect(viaSetPhraseAddr1Test).toEqual(stagenet_address_path1) - - const thorClientEmptyMain1 = new Client({ - phrase, - network: Network.Mainnet /*, derivationPath: "44'/931'/0'/0/1"*/, - }) - const addressMain1 = await thorClientEmptyMain1.getAddressAsync(1) - expect(addressMain1).toEqual(mainnet_address_path1) - - const thorClientEmptyTest1 = new Client({ - phrase, - network: Network.Stagenet /*, derivationPath: "44'/931'/0'/0/1"*/, - }) - const addressTest1 = await thorClientEmptyTest1.getAddressAsync(1) - expect(addressTest1).toEqual(stagenet_address_path1) - }) - - it('throws an error passing an invalid phrase', async () => { - expect(() => { - new Client({ phrase: 'invalid phrase', network: Network.Mainnet }) - }).toThrow() - - expect(() => { - new Client({ phrase: 'invalid phrase', network: Network.Stagenet }) - }).toThrow() - }) - - it('should not throw on a client without a phrase', () => { - expect(() => { - new Client({}) - }).not.toThrow() - }) - - it('should have right address', async () => { - expect(await thorClient.getAddressAsync()).toEqual(stagenet_address_path0) - - expect(await thorMainClient.getAddressAsync()).toEqual(mainnet_address_path0) - }) - - it('should allow to get the CosmosSDKClient', async () => { - expect(thorClient.getCosmosClient()).toBeInstanceOf(CosmosSDKClient) - }) - - it('should update net', async () => { - thorMainClient.setNetwork(Network.Stagenet) - expect(thorMainClient.getNetwork()).toEqual('stagenet') - - const address = await thorMainClient.getAddressAsync() - expect(address).toEqual(stagenet_address_path0) - }) - - it('should init, should have right prefix', async () => { - expect(thorClient.validateAddress(await thorClient.getAddressAsync())).toBeTruthy() - - thorClient.setNetwork(Network.Mainnet) - expect(thorClient.validateAddress(await thorClient.getAddressAsync())).toBeTruthy() - - thorClient.setNetwork(Network.Stagenet) - expect(thorClient.validateAddress(await thorClient.getAddressAsync())).toBeTruthy() - }) - - it('should have right client url', async () => { - thorClient.setClientUrl({ - mainnet: { - node: 'new mainnet client', - rpc: 'new mainnet client', - }, - stagenet: { - node: 'new stagenet client', - rpc: 'new stagenet client', - }, - testnet: { - node: 'new testnet client', - rpc: 'new testnet client', - }, + expect(() => { + new Client({ phrase: 'invalid phrase', network: Network.Stagenet }) + }).toThrow() }) - thorClient.setNetwork(Network.Mainnet) - expect(thorClient.getClientUrl().node).toEqual('new mainnet client') - - thorClient.setNetwork(Network.Stagenet) - expect(thorClient.getClientUrl().node).toEqual('new stagenet client') - }) - - it('returns private key', async () => { - const privKey = thorClient.getPrivateKey() - expect(Buffer.from(privKey.bytes()).toString('base64')).toEqual('CHCbyYWorMZVRFtfJzt72DigvZeRNi3jo2c3hGEQ46I=') - }) - - describe('chainId', () => { - it('get chainId', () => { - const chainId = thorClient.getChainId() - expect(chainId).toEqual('thorchain-stagenet-v2') - }) - it('update chainId', () => { - thorClient.setChainId('another-testnet-id') - const chainId = thorClient.getChainId() - expect(chainId).toEqual('another-testnet-id') - }) - it('update chainId for testnet', () => { - thorClient.setChainId('another-testnet-id', Network.Stagenet) - const chainId = thorClient.getChainId(Network.Stagenet) - expect(chainId).toEqual('another-testnet-id') - }) - it('get default chainId for stagenet', () => { - const chainId = thorClient.getChainId(Network.Stagenet) - expect(chainId).toEqual('thorchain-stagenet-v2') + it('Should not throw error on a client without a phrase', () => { + expect(() => { + new Client() + }).not.toThrow() }) - it('update chainId for stagenet', () => { - thorClient.setChainId('another-stagenet-id', Network.Stagenet) - const chainId = thorClient.getChainId(Network.Stagenet) - expect(chainId).toEqual('another-stagenet-id') - }) - it('get default chainId for mainnet', () => { - const chainId = thorClient.getChainId(Network.Mainnet) - expect(chainId).toEqual('thorchain-mainnet-v1') - }) - it('update chainId for mainnet', () => { - thorClient.setChainId('another-mainnet-id', Network.Mainnet) - const chainId = thorClient.getChainId(Network.Mainnet) - expect(chainId).toEqual('another-mainnet-id') - }) - }) - - it('returns public key', async () => { - const pubKey = thorClient.getPubKey() - const pkString = Buffer.from(pubKey.bytes()).toString('base64') - expect(pkString).toEqual('AsL4F+rvFMqDkZYpVVnZa0OBa0EXwscjNrODbBME42vC') }) - it('has no balances', async () => { - mockAccountsBalance(thorClient.getClientUrl().node, stagenet_address_path0, { - balances: [], + describe('Explorers', () => { + describe('Mainnet', () => { + let client: Client + beforeAll(() => { + client = new Client() + }) + it('Should get explorer url', () => { + expect(client.getExplorerUrl()).toBe('https://runescan.io') + }) + it('Should get address url', () => { + expect(client.getExplorerAddressUrl('thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv')).toBe( + 'https://runescan.io/address/thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv', + ) + }) + it('Should get transaction url', () => { + expect(client.getExplorerTxUrl('19CB7A460869BE9EF7711BE82980A384816F58B5B1B16D67F55443B8470865E7')).toBe( + 'https://runescan.io/tx/19CB7A460869BE9EF7711BE82980A384816F58B5B1B16D67F55443B8470865E7', + ) + }) }) - const result = await thorClient.getBalance(await thorClient.getAddressAsync(0)) - expect(result).toEqual([]) - }) - - it('has balances', async () => { - thorMainClient.setNetwork(Network.Mainnet) - // mainnet - has balance: thor147jegk6e9sum7w3svy3hy4qme4h6dqdkgxhda5 - // mainnet - 0: thor19kacmmyuf2ysyvq3t9nrl9495l5cvktjs0yfws - mockAccountsBalance(thorMainClient.getClientUrl().node, 'thor147jegk6e9sum7w3svy3hy4qme4h6dqdkgxhda5', { - balances: [ - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'rune', - amount: '100', - }), - ], + describe('Testnet', () => { + let client: Client + beforeAll(() => { + client = new Client({ + network: Network.Testnet, + }) + }) + it('Should get explorer url', () => { + expect(client.getExplorerUrl()).toBe('deprecated') + }) + it('Should get address url', () => { + expect(client.getExplorerAddressUrl('thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv')).toBe('deprecated') + }) + it('Should get transaction url', () => { + expect(client.getExplorerTxUrl('19CB7A460869BE9EF7711BE82980A384816F58B5B1B16D67F55443B8470865E7')).toBe( + 'deprecated', + ) + }) }) - - const balances = await thorMainClient.getBalance('thor147jegk6e9sum7w3svy3hy4qme4h6dqdkgxhda5') - expect(balances.length).toEqual(1) - expect(balances[0].asset).toEqual(AssetRuneNative) - expect(balances[0].amount.amount().isEqualTo(baseAmount(100).amount())).toBeTruthy() - }) - - it('rune + synth balances', async () => { - thorClient.setNetwork(Network.Stagenet) - mockAccountsBalance(thorClient.getClientUrl().node, 'sthor13gym97tmw3axj3hpewdggy2cr288d3qfed2ken', { - balances: [ - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'bnb/bnb', - amount: '100', - }), - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'bnb/busd-74e', - amount: '200', - }), - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'rune', - amount: '200', - }), - ], + describe('Stagenet', () => { + let client: Client + beforeAll(() => { + client = new Client({ + network: Network.Stagenet, + }) + }) + it('Should get explorer url', () => { + expect(client.getExplorerUrl()).toBe('https://runescan.io/?network=stagenet') + }) + it('Should get address url', () => { + expect(client.getExplorerAddressUrl('sthor1g6pnmnyeg48yc3lg796plt0uw50qpp7humfggz')).toBe( + 'https://runescan.io/address/sthor1g6pnmnyeg48yc3lg796plt0uw50qpp7humfggz?network=stagenet', + ) + }) + it('Should get transaction url', () => { + expect(client.getExplorerTxUrl('852D04CA5944611DB4F71002CAAD4F2E480742143BB5FA75FFADB0D41429BE28')).toBe( + 'https://runescan.io/tx/852D04CA5944611DB4F71002CAAD4F2E480742143BB5FA75FFADB0D41429BE28?network=stagenet', + ) + }) }) - - const balances = await thorClient.getBalance('sthor13gym97tmw3axj3hpewdggy2cr288d3qfed2ken') - expect(balances.length).toEqual(3) - // BNB synth - expect(balances[0].asset).toEqual({ ...AssetBNB, synth: true }) - expect(balances[0].amount.amount().isEqualTo(baseAmount(100).amount())) - // BUSD synth - expect(balances[1].asset).toEqual({ chain: 'BNB', symbol: 'BUSD-74E', ticker: 'BUSD', synth: true }) - expect(balances[1].amount.amount().isEqualTo(baseAmount(200).amount())) - // RUNE - expect(balances[2].asset).toEqual(AssetRuneNative) - expect(balances[2].amount.amount().isEqualTo(baseAmount(300).amount())) }) - it('filter BUSD synth balances', async () => { - const BUSD_ASSET_SYNTH: Asset = { chain: BNBChain, symbol: 'BUSD-74E', ticker: 'BUSD', synth: true } - thorClient.setNetwork(Network.Stagenet) - mockAccountsBalance(thorClient.getClientUrl().node, 'sthor13gym97tmw3axj3hpewdggy2cr288d3qfed2ken', { - balances: [ - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'bnb/bnb', - amount: '100', - }), - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'bnb/busd-74e', - amount: '200', - }), - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'rune', - amount: '200', - }), - ], + describe('Asset', () => { + let client: Client + beforeAll(() => { + client = new Client() }) - const balances = await thorClient.getBalance('tthor13gym97tmw3axj3hpewdggy2cr288d3qffr8skg', [BUSD_ASSET_SYNTH]) - expect(balances.length).toEqual(1) - // BUSD synth - expect(balances[0].asset).toEqual(BUSD_ASSET_SYNTH) - expect(balances[0].amount.amount().isEqualTo(baseAmount(200).amount())) - }) - - it('has an empty tx history', async () => { - const expected: TxsPage = { - total: 0, - txs: [], - } - - mockTxHistory(thorClient.getClientUrl().rpc, { - jsonrpc: '2.0', - id: -1, - result: { - txs: [], - total_count: '0', - }, + it('Should get native asset', () => { + const nativeAsset = client.getAssetInfo() + expect(nativeAsset.asset).toEqual({ chain: 'THOR', symbol: 'RUNE', ticker: 'RUNE', synth: false }) + expect(nativeAsset.decimal).toBe(8) }) - const transactions = await thorClient.getTransactions({ - address: 'tthor13gym97tmw3axj3hpewdggy2cr288d3qffr8skg', - limit: 1, + it('Should get denom for asset', () => { + const synthBNBAsset: Asset = { chain: 'BNB', symbol: 'BNB', ticker: 'BNB', synth: true } + expect(client.getDenom(synthBNBAsset)).toEqual('bnb/bnb') + expect(client.getDenom(AssetRUNE)).toEqual('rune') }) - expect(transactions).toEqual(expected) - }) - - it('has tx history with limit', async () => { - const historyData = require('../__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json') - const bondTxData = require('../__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json') - - const address = 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f' - const txHash = '9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C' - mockTxHistory(thorClient.getClientUrl().rpc, historyData) - - assertTxHashGet(thorClient.getClientUrl().node, txHash, { tx_response: bondTxData }) - - const txs = await thorClient.getTransactions({ - address: 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f', - limit: 1, + it('Should get asset for denom', () => { + expect(client.assetFromDenom('rune')).toEqual(AssetRUNE) + expect(client.assetFromDenom('bnb/bnb')).toEqual({ chain: 'BNB', symbol: 'BNB', ticker: 'BNB', synth: true }) }) - - expect(txs.total).toEqual(1) - - const { type, hash, asset, from, to } = txs.txs[0] - - expect(type).toEqual('transfer') - expect(hash).toEqual(txHash) - expect(asset).toEqual(AssetRuneNative) - expect(from[0].from).toEqual(address) - expect(from[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(from[1].from).toEqual(address) - expect(from[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) - expect(to[0].to).toEqual('tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw') - expect(to[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(to[1].to).toEqual('tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff') - expect(to[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) }) - it('get tx history with limit too high', async () => { - const historyData = require('../__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json') - const address = 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f' - mockTxHistory(thorClient.getClientUrl().rpc, historyData) - - try { - const txs = await thorClient.getTransactions({ - address, - limit: MAX_TX_COUNT_PER_FUNCTION_CALL + 1, + describe('Address', () => { + describe('Mainnet', () => { + it('Should validate address', () => { + const client = new Client() + expect(client.validateAddress('thor1k2e50ws3d9lce9ycr7ppaazx3ygaa7lxj8kkny')).toBeTruthy() + expect(client.validateAddress('0x42D5B09a92A31AfB875e1E40ae4b06f2A60890FC')).toBeFalsy() }) - expect(txs).toEqual({}) - } catch (e) { - expect(e).toMatchInlineSnapshot(`[Error: Maximum number of transaction per call is 500]`) - } - }) - - it('get tx history with limit + offset too high', async () => { - const historyData = require('../__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json') - const address = 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f' - mockTxHistory(thorClient.getClientUrl().rpc, historyData) - - try { - const txs = await thorClient.getTransactions({ - address, - limit: 1, - offset: MAX_TX_COUNT_PER_PAGE * MAX_TX_COUNT_PER_PAGE, - }) - expect(txs).toEqual({}) - } catch (e) { - expect(e).toMatchInlineSnapshot(`[Error: limit plus offset can not be grater than 1500]`) - } - }) - - it('get tx history', async () => { - const historyData = require('../__mocks__/responses/tx_search/sender-tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f.json') - const bondTxData = require('../__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json') - const address = 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f' - const transactionData = require('../__mocks__/responses/txs/transactions.json') - const txHashA = '9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C' - const txHashB = '99C97CB3DAC2BABBF5EF2938C15E8D3AEA55A815BBD04FCB53D97FA044E941CC' - mockTxHistory(thorClient.getClientUrl().rpc, historyData) - thorClient.getTransactions = jest.fn().mockResolvedValue(transactionData) - assertTxHashGet(thorClient.getClientUrl().node, txHashA, { tx_response: bondTxData }) - // assertTxHashGet(thorClient.getClientUrl().node, txHashB, { tx_response: bondTxData }) - - const txs = await thorClient.getTransactions({ - address: 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f', - }) - expect(txs.total).toEqual(2) - - const { type, hash, asset, from, to } = txs.txs[0] - - expect(type).toEqual('transfer') - expect(hash).toEqual(txHashA) - expect(asset).toEqual(AssetRuneNative) - expect(from[0].from).toEqual(address) - expect(from[1].from).toEqual(address) - expect(to[0].to).toEqual('tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw') - expect(to[1].to).toEqual('tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff') - expect(txs.txs[1].hash).toEqual(txHashB) - }) - - it('transfer', async () => { - const to_address = await thorClient.getAddressAsync(1) - const send_amount: BaseAmount = baseAmount(10000, 6) - const memo = 'transfer' - - const expected_txsPost_result = { - tx_response: { - txhash: 'EA2FAC9E82290DCB9B1374B4C95D7C4DD8B9614A96FACD38031865EB1DBAE24D', - code: 0, - }, - } - - const nodeUrl = thorClient.getClientUrl().node - - mockAccountsAddress(nodeUrl, stagenet_address_path0, { - account: { - '@type': '/cosmos.auth.v1beta1.BaseAccount', - address: stagenet_address_path0, - pub_key: { - '@type': '/cosmos.crypto.secp256k1.PubKey', - key: 'AyB84hKBjN2wsmdC2eF1Ppz6l3VxlfSKJpYsTaL4VrrE', - }, - account_number: '0', - sequence: '0', - }, - }) - mockAccountsBalance(nodeUrl, stagenet_address_path0, { - balances: [ - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'rune', - amount: '210000000', - }), - ], - }) - mockThorchainConstants(nodeUrl) - mockTendermintSimulate(nodeUrl, { - gas_info: { - gas_used: '1000000', - }, }) - assertTxsPost(thorClient.getClientUrl().node, expected_txsPost_result) - - const result = await thorClient.transfer({ - asset: AssetRuneNative, - recipient: to_address, - amount: send_amount, - memo, - }) - - expect(result).toEqual('EA2FAC9E82290DCB9B1374B4C95D7C4DD8B9614A96FACD38031865EB1DBAE24D') - }) - - it('deposit', async () => { - const send_amount: BaseAmount = baseAmount(10000, 8) - const memo = 'swap:BNB.BNB:tbnb1ftzhmpzr4t8ta3etu4x7nwujf9jqckp3th2lh0' - - const expected_txsPost_result = { - tx_response: { - txhash: 'EA2FAC9E82290DCB9B1374B4C95D7C4DD8B9614A96FACD38031865EB1DBAE24D', - code: 0, - }, - } - - const nodeUrl = thorClient.getClientUrl().node - - mockAccountsAddress(nodeUrl, stagenet_address_path0, { - account: { - '@type': '/cosmos.auth.v1beta1.BaseAccount', - address: stagenet_address_path0, - pub_key: { - '@type': '/cosmos.crypto.secp256k1.PubKey', - key: 'AyB84hKBjN2wsmdC2eF1Ppz6l3VxlfSKJpYsTaL4VrrE', - }, - account_number: '0', - sequence: '0', - }, - }) - mockAccountsBalance(nodeUrl, stagenet_address_path0, { - balances: [ - new cosmosclient.proto.cosmos.base.v1beta1.Coin({ - denom: 'rune', - amount: '210000000', - }), - ], - }) - mockTendermintNodeInfo(nodeUrl, { - default_node_info: { - network: chainIds[Network.Stagenet], - }, - }) - mockThorchainConstants(nodeUrl) - mockTendermintSimulate(nodeUrl, { - gas_info: { - gas_used: '1000000', - }, - }) - assertTxsPost(nodeUrl, expected_txsPost_result) - - const result = await thorClient.deposit({ - asset: AssetRuneNative, - amount: send_amount, - memo, + describe('Stagenet', () => { + it('Should validate address', () => { + const client = new Client({ network: Network.Stagenet, prefix: 'sthor' }) + expect(client.validateAddress('sthor17gw75axcnr8747pkanye45pnrwk7p9c3ve0wxj')).toBeTruthy() + expect(client.validateAddress('0x42D5B09a92A31AfB875e1E40ae4b06f2A60890FC')).toBeFalsy() + }) }) - - expect(result).toEqual('EA2FAC9E82290DCB9B1374B4C95D7C4DD8B9614A96FACD38031865EB1DBAE24D') - }) - - it('get transaction data for BOND tx', async () => { - const txData = require('../__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json') - const txHash = '9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C' - const address = 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f' - assertTxHashGet(thorClient.getClientUrl().node, txHash, { tx_response: txData }) - const { type, hash, asset, from, to } = await thorClient.getTransactionData(txHash, address) - - expect(type).toEqual('transfer') - expect(hash).toEqual(txHash) - expect(asset).toEqual(AssetRuneNative) - expect(from[0].from).toEqual(address) - expect(from[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(from[1].from).toEqual(address) - expect(from[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) - expect(to[0].to).toEqual('tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw') - expect(to[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(to[1].to).toEqual('tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff') - expect(to[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) - }) - - it('should return valid explorer url', () => { - expect(thorClient.getExplorerUrl()).toEqual('https://runescan.io?network=stagenet') - - thorClient.setNetwork(Network.Mainnet) - expect(thorClient.getExplorerUrl()).toEqual('https://runescan.io') - }) - - it('should return valid explorer address url', () => { - expect(thorClient.getExplorerAddressUrl('tthorabc')).toEqual( - 'https://runescan.io/address/tthorabc?network=stagenet', - ) - - thorClient.setNetwork(Network.Mainnet) - expect(thorClient.getExplorerAddressUrl('thorabc')).toEqual('https://runescan.io/address/thorabc') - }) - - it('should return valid explorer tx url', () => { - expect(thorClient.getExplorerTxUrl('txhash')).toEqual('https://runescan.io/tx/txhash?network=stagenet') - - thorClient.setNetwork(Network.Mainnet) - expect(thorClient.getExplorerTxUrl('txhash')).toEqual('https://runescan.io/tx/txhash') - }) - - it('fetches fees from client', async () => { - const url = thorClient.getClientUrl().node - mockThorchainConstants(url) - - const fees = await thorClient.getFees() - - expect(fees.average.amount().toString()).toEqual('2000000') - expect(fees.fast.amount().toString()).toEqual('2000000') - expect(fees.fastest.amount().toString()).toEqual('2000000') - }) - - it('returns default fees if client is not available', async () => { - const url = thorClient.getClientUrl().node - nock(url).get('/thorchain/constants').reply(404) - - const fees = await thorClient.getFees() - - expect(fees.average.amount().toString()).toEqual('2000000') - expect(fees.fast.amount().toString()).toEqual('2000000') - expect(fees.fastest.amount().toString()).toEqual('2000000') }) }) diff --git a/packages/xchain-thorchain/__tests__/util.test.ts b/packages/xchain-thorchain/__tests__/util.test.ts index 08f86ef0c..37f713c82 100644 --- a/packages/xchain-thorchain/__tests__/util.test.ts +++ b/packages/xchain-thorchain/__tests__/util.test.ts @@ -1,210 +1,7 @@ -import { AssetBNB } from '@xchainjs/xchain-binance' -import { Network } from '@xchainjs/xchain-client' -import { assetAmount, assetFromStringEx, assetToBase } from '@xchainjs/xchain-util' -// import nock from 'nock' - -import { mockTendermintNodeInfo } from '../__mocks__/thornode-api' -import { AssetRuneNative, defaultExplorerUrls } from '../src/const' -import { ClientUrl } from '../src/types' -import { - assetFromDenom, - getChainId, - getDenom, - getDepositTxDataFromLogs, - getExplorerAddressUrl, - getExplorerTxUrl, - getExplorerUrl, - getPrefix, - getTxType, - isAssetRuneNative, - isBroadcastSuccess, -} from '../src/utils' - -const AssetETH = assetFromStringEx('ETH.ETH') - -describe('thorchain/util', () => { - describe('isAssetRuneNative', () => { - it('true for AssetRuneNative', () => { - expect(isAssetRuneNative(AssetRuneNative)).toBeTruthy() - }) - it('false for ETH', () => { - expect(isAssetRuneNative(AssetETH)).toBeFalsy() - }) - it('false for ETH synth', () => { - expect(isAssetRuneNative({ ...AssetETH, synth: true })).toBeFalsy() - }) - }) - describe('Denom <-> Asset', () => { - describe('getDenom', () => { - it('get denom for AssetRune', () => { - expect(getDenom(AssetRuneNative)).toEqual('rune') - }) - it('get denom for BNB synth', () => { - expect(getDenom({ ...AssetBNB, synth: true })).toEqual('bnb/bnb') - }) - }) - - describe('getAsset', () => { - it('rune', () => { - expect(assetFromDenom('rune')).toEqual(AssetRuneNative) - }) - it('bnb/bnb', () => { - expect(assetFromDenom('bnb/bnb')).toEqual({ ...AssetBNB, synth: true }) - }) - }) - describe('getTxType', () => { - it('deposit', () => { - expect(getTxType('CgkKB2RlcG9zaXQ=', 'base64')).toEqual('deposit') - }) - - it('set_observed_txin', () => { - expect(getTxType('"ChMKEXNldF9vYnNlcnZlZF90eGlu', 'base64')).toEqual('set_observed_txin') - }) - - it('unknown', () => { - expect(getTxType('"abc', 'base64')).toEqual('') - }) - }) - - describe('getPrefix', () => { - it('should return the correct prefix based on network', () => { - expect(getPrefix(Network.Mainnet) === 'thor') - expect(getPrefix(Network.Stagenet) === 'sthor') - expect(getPrefix(Network.Testnet) === 'tthor') - }) - }) - }) - - describe('transaction util', () => { - describe('getDepositTxDataFromLogs', () => { - it('returns data for IN tx (SWAP RUNE -> BTC)', () => { - const tx = require('../__mocks__/responses/txs/swap-1C10434D59A460FD0BE76C46A333A583B8C7761094E26C0B2548D07A5AF28356.json') - const data = getDepositTxDataFromLogs(tx.logs, 'thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr') - - const { from, to, type } = data - expect(from.length).toEqual(2) - expect(from[0].amount.amount().toString()).toEqual(assetToBase(assetAmount('0.02')).amount().toString()) - expect(from[0].from).toEqual('thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr') - expect(to[0].to).toEqual('thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt') - expect(from[1].amount.amount().toString()).toEqual(assetToBase(assetAmount('36000')).amount().toString()) - expect(from[1].from).toEqual('thor1g3nvdxgmdte8cfhl8592lz5tuzjd9hjsglazhr') - expect(to[1].to).toEqual('thor1g98cy3n9mmjrpn0sxmn63lztelera37n8n67c0') - expect(type).toEqual('transfer') - }) - - it('returns data for SEND tx', () => { - const tx = require('../__mocks__/responses/txs/send-AB7DDB79CAFBB402B2E75D03FB15BB2E449B9A8A59563C74090D20D6A3F73627.json') - const data = getDepositTxDataFromLogs(tx.logs, 'thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl') - - const { from, to, type } = data - expect(from.length).toEqual(2) - expect(from[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(from[0].from).toEqual('thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl') - expect(to[0].to).toEqual('thor1dheycdevq39qlkxs2a6wuuzyn4aqxhve4qxtxt') - expect(from[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(5)).amount().toString()) - expect(from[1].from).toEqual('thor1ws0sltg9ayyxp2777xykkqakwv2hll5ywuwkzl') - expect(to[1].to).toEqual('thor1mryx88xxhvwu9yepmg968zcdaza2nzz4rltjcp') - expect(type).toEqual('transfer') - }) - - it('getDepositTxDataFromLogs', () => { - const tx = require('../__mocks__/responses/txs/bond-tn-9C175AF7ACE9FCDC930B78909FFF598C18CBEAF9F39D7AA2C4D9A27BB7E55A5C.json') - const data = getDepositTxDataFromLogs(tx.logs, 'tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f') - - const { from, to, type } = data - expect(from.length).toEqual(2) - expect(from[0].from).toEqual('tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f') - expect(from[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(from[1].from).toEqual('tthor137kees65jmhjm3gxyune0km5ea0zkpnj4lw29f') - expect(from[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) - expect(to.length).toEqual(2) - expect(to[0].to).toEqual('tthor1dheycdevq39qlkxs2a6wuuzyn4aqxhve3hhmlw') - expect(to[0].amount.amount().toString()).toEqual(assetToBase(assetAmount(0.02)).amount().toString()) - expect(to[1].to).toEqual('tthor17gw75axcnr8747pkanye45pnrwk7p9c3uhzgff') - expect(to[1].amount.amount().toString()).toEqual(assetToBase(assetAmount(1700)).amount().toString()) - expect(type).toEqual('transfer') - }) - }) - - describe('isBroadcastSuccess', () => { - it('validates isBroadcastSuccess', () => { - expect(isBroadcastSuccess({ logs: [] })).toBeTruthy() - }) - it('invalidates isBroadcastSuccess', () => { - expect(isBroadcastSuccess({})).toBeFalsy() - }) - }) - }) - - describe('explorer url', () => { - it('should return valid explorer url', () => { - expect(getExplorerUrl(defaultExplorerUrls, 'testnet' as Network)).toEqual('https://runescan.io?network=testnet') - - expect(getExplorerUrl(defaultExplorerUrls, 'mainnet' as Network)).toEqual('https://runescan.io') - }) - - it('should return valid explorer address url', () => { - expect( - getExplorerAddressUrl({ urls: defaultExplorerUrls, network: 'testnet' as Network, address: 'tthorabc' }), - ).toEqual('https://runescan.io/address/tthorabc?network=testnet') - - expect( - getExplorerAddressUrl({ urls: defaultExplorerUrls, network: 'mainnet' as Network, address: 'thorabc' }), - ).toEqual('https://runescan.io/address/thorabc') - }) - - it('should return valid explorer tx url', () => { - expect(getExplorerTxUrl({ urls: defaultExplorerUrls, network: 'testnet' as Network, txID: 'txhash' })).toEqual( - 'https://runescan.io/tx/txhash?network=testnet', - ) - - expect(getExplorerTxUrl({ urls: defaultExplorerUrls, network: 'mainnet' as Network, txID: 'txhash' })).toEqual( - 'https://runescan.io/tx/txhash', - ) - }) - }) - const clientUrl: ClientUrl = { - [Network.Testnet]: { - node: '', - rpc: '', - }, - [Network.Stagenet]: { - node: 'https://stagenet-thornode.ninerealms.com', - rpc: 'https://stagenet-rpc.ninerealms.com', - }, - [Network.Mainnet]: { - node: 'https://thornode.ninerealms.com', - rpc: 'https://rpc.ninerealms.com', - }, - } - - describe('getChainId', () => { - it('stagenet', async () => { - const id = 'chain-id-stagenet' - const url = clientUrl.stagenet.node - // Mock chain id - mockTendermintNodeInfo(url, { - default_node_info: { - network: id, - }, - }) - const result = await getChainId(url) - - expect(result).toEqual(id) - }) - - it('mainnet', async () => { - const id = 'chain-id-mainnet' - const url = clientUrl.mainnet.node - // Mock chain id - mockTendermintNodeInfo(url, { - default_node_info: { - network: id, - }, - }) - const result = await getChainId(url) - - expect(result).toEqual(id) - }) +import { isAssetRuneNative as isAssetRune } from '..' +describe('Utils', () => { + it('Should validate Rune asset', () => { + expect(isAssetRune({ chain: 'THOR', symbol: 'RUNE', ticker: 'RUNE', synth: false })).toBeTruthy() + expect(isAssetRune({ chain: 'ARB', symbol: 'ARB', ticker: 'ETH', synth: false })).toBeFalsy() }) }) diff --git a/packages/xchain-thorchain/package.json b/packages/xchain-thorchain/package.json index 929af982b..cccadf1f4 100644 --- a/packages/xchain-thorchain/package.json +++ b/packages/xchain-thorchain/package.json @@ -33,27 +33,21 @@ "start:example": "ts-node example/index.ts", "generate:ThorchainMsgs": "./genMsgs.sh" }, + "dependencies": { + "@cosmjs/encoding": "0.31.1", + "@cosmjs/crypto": "0.31.1", + "@xchainjs/xchain-client": "0.16.1", + "@xchainjs/xchain-cosmos-sdk": "0.1.6", + "@xchainjs/xchain-util": "0.13.2", + "bignumber.js": "9.0.0", + "protobufjs": "6.11.4" + }, "devDependencies": { - "@cosmos-client/core": "0.46.1", - "@types/big.js": "^6.1.6", - "@xchainjs/xchain-client": "^0.16.1", - "@xchainjs/xchain-cosmos": "^0.21.10", - "@xchainjs/xchain-crypto": "^0.3.1", - "@xchainjs/xchain-util": "^0.13.1", - "axios": "^1.3.6", - "bech32-buffer": "^0.2.0", - "nock": "^13.0.5" + "@cosmjs/amino": "0.31.1", + "@cosmjs/proto-signing": "0.31.1", + "cosmjs-types": "0.8.0" }, "publishConfig": { "access": "public" - }, - "peerDependencies": { - "@cosmos-client/core": "0.46.1", - "@xchainjs/xchain-client": "^0.16.1", - "@xchainjs/xchain-cosmos": "^0.21.10", - "@xchainjs/xchain-crypto": "^0.3.1", - "@xchainjs/xchain-util": "^0.13.1", - "axios": "^1.3.6", - "bech32-buffer": "^0.2.0" } } \ No newline at end of file diff --git a/packages/xchain-thorchain/src/client.ts b/packages/xchain-thorchain/src/client.ts index 73a5aa890..5a1b2ac78 100644 --- a/packages/xchain-thorchain/src/client.ts +++ b/packages/xchain-thorchain/src/client.ts @@ -1,201 +1,79 @@ -import cosmosclient from '@cosmos-client/core' +import { StdFee } from '@cosmjs/amino' +import { Bip39, EnglishMnemonic, Secp256k1, Slip10, Slip10Curve } from '@cosmjs/crypto' +import { fromBase64, toBase64 } from '@cosmjs/encoding' import { - AssetInfo, - Balance, - BaseXChainClient, - FeeType, - Fees, - Network, - PreparedTx, - Tx, - TxFrom, - TxHash, - TxHistoryParams, - TxParams, - TxTo, - TxType, - TxsPage, - XChainClient, - XChainClientParams, - singleFee, -} from '@xchainjs/xchain-client' -import { CosmosSDKClient, GAIAChain, RPCTxResult, RPCTxSearchResult } from '@xchainjs/xchain-cosmos' + DecodedTxRaw, + DirectSecp256k1HdWallet, + EncodeObject, + TxBodyEncodeObject, + decodeTxRaw, +} from '@cosmjs/proto-signing' +import { SigningStargateClient } from '@cosmjs/stargate' +import { AssetInfo, PreparedTx, TxHash, TxParams } from '@xchainjs/xchain-client' import { - Address, - Asset, - BaseAmount, - assetFromString, - assetFromStringEx, - assetToString, - baseAmount, - delay, -} from '@xchainjs/xchain-util' -import axios from 'axios' -import BigNumber from 'bignumber.js' -import Long from 'long' - -import { buildDepositTx, buildTransferTx, buildUnsignedTx } from '.' + Client as CosmosSDKClient, + CosmosSdkClientParams, + MsgTypes, + bech32ToBase64, + makeClientPath, +} from '@xchainjs/xchain-cosmos-sdk' +import { Address, Asset, assetFromString, assetToString, isSynthAsset } from '@xchainjs/xchain-util' +import { BigNumber } from 'bignumber.js' +import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx' + import { - AssetRuneNative, + AssetRuneNative as AssetRUNE, DEFAULT_GAS_LIMIT_VALUE, DEPOSIT_GAS_LIMIT_VALUE, - FallBackUrls, - MAX_PAGES_PER_FUNCTION_CALL, - MAX_TX_COUNT_PER_FUNCTION_CALL, - MAX_TX_COUNT_PER_PAGE, + MSG_DEPOSIT_TYPE_URL, + MSG_SEND_TYPE_URL, RUNE_DECIMAL, - defaultExplorerUrls, + RUNE_DENOM, + defaultClientConfig, } from './const' -import { - ChainId, - ChainIds, - ClientUrl, - DepositParam, - ExplorerUrls, - NodeUrl, - ThorchainClientParams, - ThorchainNetworkResponse, - TxData, - TxOfflineParams, -} from './types' -import { TxResult } from './types/messages' -import { - getBalance, - getDefaultFees, - getDenom, - getDepositTxDataFromLogs, - getExplorerAddressUrl, - getExplorerTxUrl, - getPrefix, - isAssetRuneNative, - registerDepositCodecs, - registerSendCodecs, -} from './utils' +import { DepositParam, DepositTx, TxOfflineParams } from './types' +import { getDefaultExplorers, getExplorerAddressUrl, getExplorerTxUrl, isAssetRuneNative as isAssetRune } from './utils' /** * Interface for custom Thorchain client */ export interface ThorchainClient { - setClientUrl(clientUrl: ClientUrl): void - getClientUrl(): NodeUrl - setExplorerUrls(explorerUrls: ExplorerUrls): void - getCosmosClient(): CosmosSDKClient - deposit(params: DepositParam): Promise + getDepositTransaction(txId: string): Promise transferOffline(params: TxOfflineParams): Promise } /** - * Custom Thorchain Client + * Thorchain client params to instantiate the Thorchain client */ -class Client extends BaseXChainClient implements ThorchainClient, XChainClient { - private clientUrl: ClientUrl - private explorerUrls: ExplorerUrls - private chainIds: ChainIds - private cosmosClient: CosmosSDKClient - - /** - * Constructor - * - * Client has to be initialised with network type and phrase. - * It will throw an error if an invalid phrase has been passed. - * - * @param {XChainClientParams} params - * - * @throws {"Invalid phrase"} Thrown if the given phase is invalid. - */ - constructor({ - network = Network.Mainnet, - phrase, - clientUrl = { - [Network.Testnet]: { - node: 'deprecated', - rpc: 'deprecated', - }, - [Network.Stagenet]: { - node: 'https://stagenet-thornode.ninerealms.com', - rpc: 'https://stagenet-rpc.ninerealms.com', - }, - [Network.Mainnet]: { - node: 'https://thornode.ninerealms.com', - rpc: 'https://rpc.ninerealms.com', - }, - }, - explorerUrls = defaultExplorerUrls, - rootDerivationPaths = { - [Network.Mainnet]: "44'/931'/0'/0/", - [Network.Stagenet]: "44'/931'/0'/0/", - [Network.Testnet]: "44'/931'/0'/0/", - }, - chainIds = { - [Network.Mainnet]: 'thorchain-mainnet-v1', - [Network.Stagenet]: 'thorchain-stagenet-v2', - [Network.Testnet]: 'deprecated', - }, - }: XChainClientParams & ThorchainClientParams) { - super(GAIAChain, { network, rootDerivationPaths, phrase }) - this.clientUrl = clientUrl - this.explorerUrls = explorerUrls - this.chainIds = chainIds - registerSendCodecs() - registerDepositCodecs() - - this.cosmosClient = new CosmosSDKClient({ - server: this.getClientUrl().node, - chainId: this.getChainId(network), - prefix: getPrefix(network), - }) - } +export type ThorchainClientParams = Partial +/** + * Thorchain client + */ +export class Client extends CosmosSDKClient implements ThorchainClient { /** - * Set/update the current network. - * - * @param {Network} network - * @returns {void} + * Thorchain client constructor * - * @throws {"Network must be provided"} - * Thrown if network has not been set before. + * @param {ThorchainClientParams} config Optional - Client configuration. If it is not set, default values will be used */ - setNetwork(network: Network): void { - // dirty check to avoid using and re-creation of same data - if (network === this.network) return - - super.setNetwork(network) - - this.cosmosClient = new CosmosSDKClient({ - server: this.getClientUrl().node, - chainId: this.getChainId(network), - prefix: getPrefix(network), + constructor(config: ThorchainClientParams = defaultClientConfig) { + super({ + ...defaultClientConfig, + ...config, }) } /** - * Set/update the client URL. - * - * @param {ClientUrl} clientUrl The client url to be set. - * @returns {void} - */ - setClientUrl(clientUrl: ClientUrl): void { - this.clientUrl = clientUrl - } - - /** - * Get the client url. - * - * @returns {NodeUrl} The client url for thorchain based on the current network. - */ - getClientUrl(): NodeUrl { - return this.clientUrl[this.network] - } - - /** - * Set/update the explorer URLs. + * Get client native asset * - * @param {ExplorerUrls} urls The explorer urls to be set. - * @returns {void} + * @returns {AssetInfo} Thorchain native asset */ - setExplorerUrls(urls: ExplorerUrls): void { - this.explorerUrls = urls + public getAssetInfo(): AssetInfo { + return { + asset: AssetRUNE, + decimal: RUNE_DECIMAL, + } } /** @@ -203,39 +81,8 @@ class Client extends BaseXChainClient implements ThorchainClient, XChainClient { * * @returns {string} The explorer url for thorchain based on the current network. */ - getExplorerUrl(): string { - return this.explorerUrls.root[this.network] - } - - /** - * Sets chain id - * - * @param {ChainId} chainId Chain id to update - * @param {Network} network (optional) Network for given chainId. If `network`not set, current network of the client is used - * - * @returns {void} - */ - setChainId(chainId: ChainId, network?: Network): void { - this.chainIds = { ...this.chainIds, [network || this.network]: chainId } - } - - /** - * Gets chain id - * - * @param {Network} network (optional) Network to get chain id from. If `network`not set, current network of the client is used - * - * @returns {ChainId} Chain id based on the current network. - */ - getChainId(network?: Network): ChainId { - return this.chainIds[network || this.network] - } - - /** - * Get cosmos client - * @returns {CosmosSDKClient} current cosmos client - */ - getCosmosClient(): CosmosSDKClient { - return this.cosmosClient + public getExplorerUrl(): string { + return getDefaultExplorers()[this.network] } /** @@ -244,8 +91,8 @@ class Client extends BaseXChainClient implements ThorchainClient, XChainClient { * @param {Address} address * @returns {string} The explorer url for the given address. */ - getExplorerAddressUrl(address: Address): string { - return getExplorerAddressUrl({ urls: this.explorerUrls, network: this.network, address }) + public getExplorerAddressUrl(address: string): string { + return getExplorerAddressUrl(address)[this.network] } /** @@ -254,691 +101,246 @@ class Client extends BaseXChainClient implements ThorchainClient, XChainClient { * @param {string} txID * @returns {string} The explorer url for the given transaction id. */ - getExplorerTxUrl(txID: string): string { - return getExplorerTxUrl({ urls: this.explorerUrls, network: this.network, txID }) - } - - /** - * Get private key - * - * @param {number} index the HD wallet index (optional) - * @returns {PrivKey} The private key generated from the given phrase - * - * @throws {"Phrase not set"} - * Throws an error if phrase has not been set before - * */ - getPrivateKey(index = 0): cosmosclient.proto.cosmos.crypto.secp256k1.PrivKey { - return this.cosmosClient.getPrivKeyFromMnemonic(this.phrase, this.getFullDerivationPath(index)) - } - - /** - * Get public key - * - * @param {number} index the HD wallet index (optional) - * - * @returns {PubKey} The public key generated from the given phrase - * - * @throws {"Phrase not set"} - * Throws an error if phrase has not been set before - **/ - getPubKey(index = 0): cosmosclient.PubKey { - const privKey = this.getPrivateKey(index) - return privKey.pubKey() - } - - /** - * @deprecated this function eventually will be removed use getAddressAsync instead - */ - getAddress(index = 0): string { - const address = this.cosmosClient.getAddressFromMnemonic(this.phrase, this.getFullDerivationPath(index)) - if (!address) { - throw new Error('address not defined') - } - - return address - } - - /** - * Get the current address. - * - * @returns {Address} The current address. - * - * @throws {Error} Thrown if phrase has not been set before. A phrase is needed to create a wallet and to derive an address from it. - */ - async getAddressAsync(walletIndex = 0): Promise
{ - return this.getAddress(walletIndex) + public getExplorerTxUrl(txID: string): string { + return getExplorerTxUrl(txID)[this.network] } /** - * Validate the given address. + * Get Asset from denomination * - * @param {Address} address - * @returns {boolean} `true` or `false` - */ - validateAddress(address: Address): boolean { - return this.cosmosClient.checkAddress(address) - } - - /** - * Get the balance of a given address. - * - * @param {Address} address By default, it will return the balance of the current wallet. (optional) - * @param {Asset} asset If not set, it will return all assets available. (optional) - * @returns {Balance[]} The balance of the address. + * @param {string} denom + * @returns {Asset|null} The asset of the given denomination. */ - async getBalance(address: Address, assets?: Asset[]): Promise { - return getBalance({ address, assets, cosmosClient: this.getCosmosClient() }) + public assetFromDenom(denom: string): Asset | null { + if (denom === RUNE_DENOM) return AssetRUNE + return assetFromString(denom.toUpperCase()) } /** + * Get denomination from Asset * - * @returns asset info + * @param {Asset} asset + * @returns {string} The denomination of the given asset. */ - getAssetInfo(): AssetInfo { - const assetInfo: AssetInfo = { - asset: AssetRuneNative, - decimal: RUNE_DECIMAL, - } - return assetInfo + public getDenom(asset: Asset): string | null { + if (isAssetRune(asset)) return RUNE_DENOM + if (isSynthAsset(asset)) return assetToString(asset).toLowerCase() + return asset.symbol.toLowerCase() } /** - * Get transaction history of a given address with pagination options. - * By default it will return the transaction history of the current wallet. + * Prepare transfer. * - * @param {TxHistoryParams} params The options to get transaction history. (optional) - * @returns {TxsPage} The transaction history. + * @param {TxParams&Address} params The transfer options. + * @returns {PreparedTx} The raw unsigned transaction. */ - getTransactions = async ( - params?: TxHistoryParams & { filterFn?: (tx: RPCTxResult) => boolean }, - ): Promise => { - const messageAction = undefined - const offset = params?.offset || 0 - const limit = params?.limit || 10 - const address = params?.address || (await this.getAddressAsync()) - const txMinHeight = undefined - const txMaxHeight = undefined - - if (limit + offset > MAX_PAGES_PER_FUNCTION_CALL * MAX_TX_COUNT_PER_PAGE) { - throw Error(`limit plus offset can not be grater than ${MAX_PAGES_PER_FUNCTION_CALL * MAX_TX_COUNT_PER_PAGE}`) - } - - if (limit > MAX_TX_COUNT_PER_FUNCTION_CALL) { - throw Error(`Maximum number of transaction per call is ${MAX_TX_COUNT_PER_FUNCTION_CALL}`) - } - - const pagesNumber = Math.ceil((limit + offset) / MAX_TX_COUNT_PER_PAGE) - - const promiseTotalTxIncomingHistory: Promise[] = [] - const promiseTotalTxOutgoingHistory: Promise[] = [] - - for (let index = 1; index <= pagesNumber; index++) { - promiseTotalTxIncomingHistory.push( - this.cosmosClient.searchTxFromRPC({ - rpcEndpoint: this.getClientUrl().rpc, - messageAction, - transferRecipient: address, - page: index, - limit: MAX_TX_COUNT_PER_PAGE, - txMinHeight, - txMaxHeight, - }), - ) - promiseTotalTxOutgoingHistory.push( - this.cosmosClient.searchTxFromRPC({ - rpcEndpoint: this.getClientUrl().rpc, - messageAction, - transferSender: address, - page: index, - limit: MAX_TX_COUNT_PER_PAGE, - txMinHeight, - txMaxHeight, - }), - ) - } + public async prepareTx({ + sender, + recipient, + asset, + amount, + memo, + }: TxParams & { sender: Address }): Promise { + if (!this.validateAddress(sender)) throw Error('Invalid sender address') + if (!this.validateAddress(recipient)) throw Error('Invalid recipient address') - const incomingSearchResult = await Promise.all(promiseTotalTxIncomingHistory) - const outgoingSearchResult = await Promise.all(promiseTotalTxOutgoingHistory) - - const totalTxIncomingHistory: RPCTxResult[] = incomingSearchResult.reduce((allTxs, searchResult) => { - return [...allTxs, ...searchResult.txs] - }, [] as RPCTxResult[]) - const totalTxOutgoingHistory: RPCTxResult[] = outgoingSearchResult.reduce((allTxs, searchResult) => { - return [...allTxs, ...searchResult.txs] - }, [] as RPCTxResult[]) - - let history: RPCTxResult[] = totalTxIncomingHistory - .concat(totalTxOutgoingHistory) - .sort((a, b) => { - if (a.height !== b.height) return parseInt(b.height) > parseInt(a.height) ? 1 : -1 - if (a.hash !== b.hash) return a.hash > b.hash ? 1 : -1 - return 0 - }) - .reduce( - (acc, tx) => [...acc, ...(acc.length === 0 || acc[acc.length - 1].hash !== tx.hash ? [tx] : [])], - [] as RPCTxResult[], - ) - .filter(params?.filterFn ? params.filterFn : (tx) => tx) - - history = history.filter((_, index) => index >= offset && index < offset + limit) - const total = history.length - - const txs: Tx[] = [] - - for (let i = 0; i < history.length; i += 10) { - const batch = history.slice(i, i + 10) - const result = await Promise.all( - batch.map(async ({ hash }) => { - const data = await this.getTransactionData(hash, address) - return data - }), - ) - txs.push(...result) - delay(2000) // Delay to avoid 503 from ninerealms server - } + const denom = this.getDenom(asset || this.getAssetInfo().asset) + if (!denom) + throw Error(`Invalid asset ${asset?.symbol} - Only ${this.baseDenom} asset is currently supported to transfer`) - return { - total, - txs, - } - } - /** - * - * @param txId - tx hash - * @returns txResponse - */ - async fetchTransaction(txId: string) { - try { - const transaction = await this.cosmosClient.txsHashGet(txId) - return transaction - } catch (error) { - for (const fallback of FallBackUrls) { - try { - const networkObj = fallback[this.network] - const clientUrl = networkObj.node as string | string[] - const cosmosClient = new CosmosSDKClient({ - server: Array.isArray(clientUrl) ? clientUrl[0] : clientUrl, - chainId: this.getChainId(this.network), - prefix: getPrefix(this.network), - }) - const tx = await cosmosClient.txsHashGet(txId) - return tx - } catch (error) { - // Handle specific error if needed - } - } - return null - } - } + const demonAmount = { amount: amount.amount().toString(), denom } - /** - * Get the transaction details of a given transaction id. - * - * @param {string} txId The transaction id. - * @returns {Tx} The transaction details of the given transaction id. - */ - async getTransactionData(txId: string, address?: string): Promise { - const txResult = await this.fetchTransaction(txId) - if (txResult && txResult.logs) { - // extract values from the response - const transferEvent = txResult.logs[0].events?.find((event) => event.type === 'transfer') - const messageEvent = txResult.logs[0].events?.find((event) => event.type === 'message') - const coinReceivedEvent = txResult.logs[0].events?.find((event) => event.type === 'coin_received') - - if (!transferEvent || !messageEvent) { - throw new Error('Invalid transaction data') - } - const attributeGroups: { [key: string]: string[] } = {} - - for (const attr of transferEvent.attributes) { - if (!attributeGroups[attr.key]) { - attributeGroups[attr.key] = [] - } - attributeGroups[attr.key].push(attr.value) - } - const assetAmount = attributeGroups['amount'][1] - ? attributeGroups['amount'][1].split(/(?<=\d)(?=\D)/).filter(Boolean)[0] - : attributeGroups['amount'][0].split(/(?<=\d)(?=\D)/).filter(Boolean)[0] - const assetString = attributeGroups['amount'][1] - ? attributeGroups['amount'][1] - .split(/(?<=\d)(?=\D)/) - .filter(Boolean)[1] - .replace(/[a-z]/g, (letter) => letter.toUpperCase()) - : attributeGroups['amount'][0] - .split(/(?<=\d)(?=\D)/) - .filter(Boolean)[1] - .replace(/[a-z]/g, (letter) => letter.toUpperCase()) - const fromAddress = transferEvent.attributes.find((attr) => attr.key === 'sender')?.value - ? transferEvent.attributes.find((attr) => attr.key === 'sender')?.value - : address - const memo = txResult.tx?.body ? txResult.tx.body.memo.split(':') : '' - const toAddress = memo[2] ? memo[2] : '' - const toAsset = memo[1] && !coinReceivedEvent ? assetFromStringEx(memo[1]) : AssetRuneNative - const date = new Date(txResult.timestamp) - const typeString = messageEvent.attributes.find((attr) => attr.key === 'action')?.value - const hash = txResult.txhash - - if (assetString && hash && fromAddress && typeString) { - const fromAsset = assetString === 'RUNE' ? AssetRuneNative : assetFromStringEx(assetString) - const txData: TxData | null = - txResult && txResult.raw_log - ? getDepositTxDataFromLogs(txResult.logs, `${fromAddress}`, fromAsset, toAsset) - : null - if (!txData) throw new Error(`Failed to get transaction data (tx-hash: ${txId})`) - - if (isAssetRuneNative(toAsset) || toAsset.synth) { - const { from, to, type } = txData - const tx: Tx = { - asset: fromAsset, - from: from, - to: to, - date: date, - type: type, - hash: hash, - } - return tx - } else { - const tx: Tx = { - asset: fromAsset, - from: [{ from: fromAddress, amount: baseAmount(assetAmount), asset: fromAsset }], - to: [{ to: toAddress, amount: baseAmount(memo[3]), asset: toAsset }], - date: date, - type: TxType.Transfer, - hash: hash, - } - return tx - } - } else { - const tx: Tx = { - asset: { - chain: '', - symbol: '', - ticker: '', - synth: false, + const txBody: TxBodyEncodeObject = { + typeUrl: '/cosmos.tx.v1beta1.TxBody', + value: { + messages: [ + { + typeUrl: MSG_SEND_TYPE_URL, + value: { + fromAddress: bech32ToBase64(sender), + toAddress: bech32ToBase64(recipient), + amount: [demonAmount], + }, }, - from: [], - to: [], - date: new Date(), - type: TxType.Transfer, - hash: '', - } - return tx - } - } else { - return await this.getTransactionDataThornode(txId) - } - } - /** This function is used when in bound or outbound tx is not of thorchain - * - * @param txId - transaction hash - * @returns - Tx object - */ - private async getTransactionDataThornode(txId: string): Promise { - const txResult = JSON.stringify(await this.thornodeAPIGet(`/tx/${txId}`)) - const getTx: TxResult = JSON.parse(txResult) - if (!getTx) throw Error(`Could not return tx data`) - const senderAsset = assetFromStringEx(`${getTx.observed_tx?.tx.coins[0].asset}`) - const fromAddress = `${getTx.observed_tx.tx.from_address}` - const from: TxFrom[] = [ - { from: fromAddress, amount: baseAmount(getTx.observed_tx?.tx.coins[0].amount), asset: senderAsset }, - ] - const splitMemo = getTx.observed_tx.tx.memo?.split(':') - - if (!splitMemo) throw Error(`Could not parse memo`) - let asset: Asset - let amount: string - if (splitMemo[0] === 'OUT') { - asset = assetFromStringEx(getTx.observed_tx.tx.coins[0].asset) - amount = getTx.observed_tx.tx.coins[0].amount - const addressTo = getTx.observed_tx.tx.to_address ? getTx.observed_tx.tx.to_address : 'undefined' - const to: TxTo[] = [{ to: addressTo, amount: baseAmount(amount, RUNE_DECIMAL), asset: asset }] - const txData: Tx = { - hash: txId, - asset: senderAsset, - from, - to, - date: new Date(), - type: TxType.Transfer, - } - return txData - } - asset = assetFromStringEx(splitMemo[1]) - const address = splitMemo[2] - amount = splitMemo[3] - const receiverAsset = asset - const recieverAmount = amount - const to: TxTo[] = [{ to: address, amount: baseAmount(recieverAmount, RUNE_DECIMAL), asset: receiverAsset }] - const txData: Tx = { - hash: txId, - asset: senderAsset, - from, - to, - date: new Date(), - type: TxType.Transfer, + ], + memo: memo, + }, } - return txData - } - /** - * Get the transaction details of a given transaction id. (from /thorchain/txs/hash) - * - * Node: /thorchain/txs/hash response doesn't have timestamp field. - * - * @param {string} txId The transaction id. - * @returns {Tx} The transaction details of the given transaction id. - */ - async getDepositTransaction(txId: string): Promise> { - const result: TxResult = (await axios.get(`${this.getClientUrl().node}/thorchain/tx/${txId}`)).data - - if (!result || !result.observed_tx) throw new Error('transaction not found') - - const from: TxFrom[] = [] - const to: TxTo[] = [] - let asset - result.observed_tx.tx.coins.forEach((coin) => { - from.push({ - from: result.observed_tx.tx.from_address, - amount: baseAmount(coin.amount, RUNE_DECIMAL), - }) - to.push({ - to: result.observed_tx.tx.to_address, - amount: baseAmount(coin.amount, RUNE_DECIMAL), - }) - asset = assetFromString(coin.asset) + const rawTx = TxRaw.fromPartial({ + bodyBytes: this.registry.encode(txBody), }) - - return { - asset: asset || AssetRuneNative, - from, - to, - type: TxType.Transfer, - hash: txId, - } + return { rawUnsignedTx: toBase64(TxRaw.encode(rawTx).finish()) } } /** - * Transaction with MsgNativeTx. - * - * @param {DepositParam} params The transaction options. - * @returns {TxHash} The transaction hash. - * - * @throws {"insufficient funds"} Thrown if the wallet has insufficient funds. - * @throws {"Invalid transaction hash"} Thrown by missing tx hash + * Make a deposit + * + * @param {number} param.walletIndex Optional - The index to use to generate the address from the transaction will be done. + * If it is not set, address associated with index 0 will be used + * @param {Asset} param.asset Optional - The asset that will be deposit. If it is not set, Thorchain native asset will be + * used + * @param {BaseAmount} param.amount The amount that will be deposit + * @param {string} param.memo Optional - The memo associated with the deposit + * @param {BigNumber} param.gasLimit Optional - The limit amount of gas allowed to spend in the deposit. If not set, default + * value of 600000000 will be used + * @returns {string} The deposit hash */ - async deposit({ + public async deposit({ walletIndex = 0, - asset = AssetRuneNative, + asset = AssetRUNE, amount, memo, gasLimit = new BigNumber(DEPOSIT_GAS_LIMIT_VALUE), - sequence, - }: DepositParam): Promise { - const balances = await this.getBalance(this.getAddress(walletIndex)) - const runeBalance: BaseAmount = - balances.filter(({ asset }) => isAssetRuneNative(asset))[0]?.amount ?? baseAmount(0, RUNE_DECIMAL) - const assetBalance: BaseAmount = - balances.filter(({ asset: assetInList }) => assetToString(assetInList) === assetToString(asset))[0]?.amount ?? - baseAmount(0, RUNE_DECIMAL) - - const { average: fee } = await this.getFees() - - if (isAssetRuneNative(asset)) { - // amount + fee < runeBalance - if (runeBalance.lt(amount.plus(fee))) { - throw new Error('insufficient funds') - } - } else { - // amount < assetBalances && runeBalance < fee - if (assetBalance.lt(amount) || runeBalance.lt(fee)) { - throw new Error('insufficient funds') - } - } - - const privKey = this.getPrivateKey(walletIndex) - const signerPubkey = privKey.pubKey() + }: DepositParam): Promise { + const sender = await this.getAddressAsync(walletIndex) - const fromAddress = await this.getAddressAsync(walletIndex) - const fromAddressAcc = cosmosclient.AccAddress.fromString(fromAddress) - - const depositTxBody = await buildDepositTx({ - msgNativeTx: { - memo: memo, - signer: fromAddressAcc, - coins: [ - { - asset: asset, - amount: amount.amount().toString(), - }, - ], - }, - nodeUrl: this.getClientUrl().node, - chainId: this.getChainId(), + const signer = await DirectSecp256k1HdWallet.fromMnemonic(this.phrase as string, { + prefix: this.prefix, + hdPaths: [makeClientPath(this.getFullDerivationPath(walletIndex || 0))], }) - const account = await this.getCosmosClient().getAccount(fromAddressAcc) - const { account_number: accountNumber } = account - if (!accountNumber) throw Error(`Deposit failed - could not get account number ${accountNumber}`) - - const txBuilder = buildUnsignedTx({ - cosmosSdk: this.getCosmosClient().sdk, - txBody: depositTxBody, - signerPubkey: cosmosclient.codec.instanceToProtoAny(signerPubkey), - gasLimit: Long.fromString(gasLimit.toFixed(0)), - sequence: sequence ? Long.fromNumber(sequence) : account.sequence || Long.ZERO, + const signingClient = await SigningStargateClient.connectWithSigner(this.clientUrls[this.network], signer, { + registry: this.registry, }) - const txHash = await this.getCosmosClient().signAndBroadcast(txBuilder, privKey, accountNumber) - - if (!txHash) throw Error(`Invalid transaction hash: ${txHash}`) + const tx = await signingClient.signAndBroadcast( + sender, + [ + { + typeUrl: MSG_DEPOSIT_TYPE_URL, + value: { + signer: bech32ToBase64(sender), + memo, + coins: [ + { + amount: amount.amount().toString(), + asset, + }, + ], + }, + }, + ], + { + amount: [], + gas: gasLimit.toString(), + }, + memo, + ) - return txHash + return tx.transactionHash } /** - * Transfer balances with MsgSend - * - * @param {TxParams} params The transfer options. - * @returns {TxHash} The transaction hash. + * Create and sign transaction without broadcasting it * - * @throws {"insufficient funds"} Thrown if the wallet has insufficient funds. - * @throws {"Invalid transaction hash"} Thrown by missing tx hash + * @deprecated Use prepare Tx instead */ - async transfer({ + public async transferOffline({ walletIndex = 0, - asset = AssetRuneNative, - amount, recipient, + asset, + amount, memo, gasLimit = new BigNumber(DEFAULT_GAS_LIMIT_VALUE), - sequence, - }: TxParams & { gasLimit?: BigNumber; sequence?: number }): Promise { - const sender = this.getAddress(walletIndex) - const balances = await this.getBalance(sender) - const runeBalance: BaseAmount = - balances.filter(({ asset }) => isAssetRuneNative(asset))[0]?.amount ?? baseAmount(0, RUNE_DECIMAL) - const assetBalance: BaseAmount = - balances.filter(({ asset: assetInList }) => assetToString(assetInList) === assetToString(asset))[0]?.amount ?? - baseAmount(0, RUNE_DECIMAL) - - const fee = (await this.getFees()).average - - if (isAssetRuneNative(asset)) { - // amount + fee < runeBalance - if (runeBalance.lt(amount.plus(fee))) { - throw new Error('insufficient funds') - } - } else { - // amount < assetBalances && runeBalance < fee - if (assetBalance.lt(amount) || runeBalance.lt(fee)) { - throw new Error('insufficient funds') - } - } - - const unsignedTxData = await this.prepareTx({ + }: TxOfflineParams & { gasLimit?: BigNumber }): Promise { + const sender = await this.getAddressAsync(walletIndex) + const { rawUnsignedTx } = await this.prepareTx({ sender, - asset, - amount, - recipient, - memo, - gasLimit, - sequence, + recipient: recipient, + asset: asset, + amount: amount, + memo: memo, }) - const decodedTx = cosmosclient.proto.cosmos.tx.v1beta1.TxRaw.decode( - Buffer.from(unsignedTxData.rawUnsignedTx, 'base64'), - ) + const unsignedTx: DecodedTxRaw = decodeTxRaw(fromBase64(rawUnsignedTx)) - const privKey = this.getCosmosClient().getPrivKeyFromMnemonic(this.phrase, this.getFullDerivationPath(walletIndex)) - const authInfo = cosmosclient.proto.cosmos.tx.v1beta1.AuthInfo.decode(decodedTx.auth_info_bytes) + const signer = await DirectSecp256k1HdWallet.fromMnemonic(this.phrase as string, { + prefix: this.prefix, + hdPaths: [makeClientPath(this.getFullDerivationPath(walletIndex))], + }) - if (!authInfo.signer_infos[0].public_key) { - authInfo.signer_infos[0].public_key = cosmosclient.codec.instanceToProtoAny(privKey.pubKey()) - } + const signingClient = await SigningStargateClient.connectWithSigner(this.clientUrls[this.network], signer, { + registry: this.registry, + }) - const txBuilder = new cosmosclient.TxBuilder( - this.getCosmosClient().sdk, - cosmosclient.proto.cosmos.tx.v1beta1.TxBody.decode(decodedTx.body_bytes), - authInfo, - ) + const messages: EncodeObject[] = unsignedTx.body.messages.map((message) => { + return { typeUrl: this.getMsgTypeUrlByType(MsgTypes.TRANSFER), value: signingClient.registry.decode(message) } + }) - const { account_number: accountNumber } = await this.getCosmosClient().getAccount( - cosmosclient.AccAddress.fromString(sender), + const rawTx = await signingClient.sign( + sender, + messages, + { + amount: [], + gas: gasLimit.toString(), + }, + unsignedTx.body.memo, ) - if (!accountNumber) throw Error(`Transfer failed - missing account number`) - - const signDocBytes = txBuilder.signDocBytes(accountNumber) - txBuilder.addSignature(privKey.sign(signDocBytes)) - - const signedTx = txBuilder.txBytes() - - return this.broadcastTx(signedTx) + return toBase64(TxRaw.encode(rawTx).finish()) } - async broadcastTx(txHex: string): Promise { - return await this.getCosmosClient().broadcast(txHex) + /** + * Returns the private key associated with an index + * + * @param {number} index Optional - The index to use to generate the private key. If it is not set, address associated with + * index 0 will be used + * @returns {Uint8Array} The private key + */ + public async getPrivateKey(index = 0): Promise { + const mnemonicChecked = new EnglishMnemonic(this.phrase) + const seed = await Bip39.mnemonicToSeed(mnemonicChecked) + const { privkey } = Slip10.derivePath( + Slip10Curve.Secp256k1, + seed, + makeClientPath(this.getFullDerivationPath(index)), + ) + return privkey } /** - * Transfer without broadcast balances with MsgSend + * Returns the compressed public key associated with an index * - * @deprecated use instead prepareTx - * @param {TxOfflineParams} params The transfer offline options. - * @returns {string} The signed transaction bytes. + * @param {number} index Optional - The index to use to generate the private key. If it is not set, address associated with + * index 0 will be used + * @returns {Uint8Array} The public key */ - async transferOffline({ - walletIndex = 0, - asset = AssetRuneNative, - amount, - recipient, - memo, - fromRuneBalance: from_rune_balance, - fromAssetBalance: from_asset_balance = baseAmount(0, RUNE_DECIMAL), - fromAccountNumber = Long.ZERO, - fromSequence = Long.ZERO, - gasLimit = new BigNumber(DEFAULT_GAS_LIMIT_VALUE), - }: TxOfflineParams): Promise { - const fee = (await this.getFees()).average - - if (isAssetRuneNative(asset)) { - // amount + fee < runeBalance - if (from_rune_balance.lt(amount.plus(fee))) { - throw new Error('insufficient funds') - } - } else { - // amount < assetBalances && runeBalance < fee - if (from_asset_balance.lt(amount) || from_rune_balance.lt(fee)) { - throw new Error('insufficient funds') - } - } - - const txBody = await buildTransferTx({ - fromAddress: await this.getAddressAsync(walletIndex), - toAddress: recipient, - memo, - assetAmount: amount, - assetDenom: getDenom(asset), - chainId: this.getChainId(), - nodeUrl: this.getClientUrl().node, - }) - const privKey = this.getPrivateKey(walletIndex) - - const txBuilder = buildUnsignedTx({ - cosmosSdk: this.getCosmosClient().sdk, - txBody: txBody, - gasLimit: Long.fromString(gasLimit.toFixed(0)), - signerPubkey: cosmosclient.codec.instanceToProtoAny(privKey.pubKey()), - sequence: fromSequence, - }) + public async getPubKey(index = 0): Promise { + const privateKey = await this.getPrivateKey(index) + const { pubkey } = await Secp256k1.makeKeypair(privateKey) + return Secp256k1.compressPubkey(pubkey) + } - const signDocBytes = txBuilder.signDocBytes(fromAccountNumber) - txBuilder.addSignature(privKey.sign(signDocBytes)) - return txBuilder.txBytes() + /** + * Get deposit transaction + * + * @deprecated Use getTransactionData instead + * @param txId + */ + public async getDepositTransaction(txId: string): Promise { + return this.getTransactionData(txId) } /** - * Gets fees from Node + * Get the message type url by type used by the cosmos-sdk client to make certain actions * - * @returns {Fees} + * @param {MsgTypes} msgType Message type of which return the type url + * @returns {string} the type url of the message */ - async getFees(): Promise { - try { - const { - data: { native_tx_fee_rune: fee }, - } = await axios.get(`${this.getClientUrl().node}/thorchain/network`) - - // validate data - if (!fee || isNaN(fee) || fee < 0) throw Error(`Invalid fee: ${fee.toString()}`) - - return singleFee(FeeType.FlatFee, baseAmount(fee)) - } catch { - return getDefaultFees() + protected getMsgTypeUrlByType(msgType: MsgTypes): string { + const messageTypeUrls: Record = { + [MsgTypes.TRANSFER]: MSG_SEND_TYPE_URL, } + return messageTypeUrls[msgType] } /** - * Prepare transfer. + * Returns the standard fee used by the client * - * @param {TxParams&Address&BigNumber} params The transfer options. - * @returns {PreparedTx} The raw unsigned transaction. + * @returns {StdFee} the standard fee */ - async prepareTx({ - sender, - recipient, - amount, - memo, - asset = AssetRuneNative, - gasLimit = new BigNumber(DEFAULT_GAS_LIMIT_VALUE), - sequence, - }: TxParams & { sender: Address; gasLimit?: BigNumber; sequence?: number }): Promise { - if (!this.validateAddress(sender)) throw Error('Invalid sender address') - if (!this.validateAddress(recipient)) throw Error('Invalid recipient address') - - const denom = getDenom(asset) - - const txBody = await buildTransferTx({ - fromAddress: sender, - toAddress: recipient, - memo, - assetAmount: amount, - assetDenom: denom, - chainId: this.getChainId(), - nodeUrl: this.getClientUrl().node, - }) - - const account = await this.getCosmosClient().getAccount(cosmosclient.AccAddress.fromString(sender)) - const { pub_key: pubkey } = account - - const txBuilder = buildUnsignedTx({ - cosmosSdk: this.getCosmosClient().sdk, - txBody, - gasLimit: Long.fromString(gasLimit.toString()), - signerPubkey: pubkey as cosmosclient.proto.google.protobuf.Any, - sequence: sequence ? Long.fromNumber(sequence) : account.sequence || Long.ZERO, - }) - - return { rawUnsignedTx: txBuilder.txBytes() } + protected getStandardFee(): StdFee { + return { amount: [], gas: '6000000' } } } - -export { Client } diff --git a/packages/xchain-thorchain/src/const.ts b/packages/xchain-thorchain/src/const.ts index 608589336..32375756a 100644 --- a/packages/xchain-thorchain/src/const.ts +++ b/packages/xchain-thorchain/src/const.ts @@ -1,111 +1,81 @@ -import { Network } from '@xchainjs/xchain-client/lib' -import { Asset } from '@xchainjs/xchain-util/lib' +import { Network } from '@xchainjs/xchain-client' +import { CosmosSdkClientParams } from '@xchainjs/xchain-cosmos-sdk' +import { Asset, BaseAmount, assetAmount, assetToBase } from '@xchainjs/xchain-util' -import { ExplorerUrls } from './types' +import types from './types/proto/MsgCompiled' +import { getDefaultClientUrls, getDefaultRootDerivationPaths } from './utils' -const DEFAULT_EXPLORER_URL = 'https://runescan.io' -const txUrl = `${DEFAULT_EXPLORER_URL}/tx` -const addressUrl = `${DEFAULT_EXPLORER_URL}/address` -const RUNE_TICKER = 'RUNE' +/** + * Explorer Url + */ +export const DEFAULT_EXPLORER_URL = 'https://runescan.io' +/** + * RUNE asset number of decimals + */ export const RUNE_DECIMAL = 8 -export const DEFAULT_GAS_ADJUSTMENT = 2 -export const DEFAULT_GAS_LIMIT_VALUE = '6000000' -export const DEPOSIT_GAS_LIMIT_VALUE = '600000000' -export const MAX_TX_COUNT_PER_PAGE = 100 -export const MAX_TX_COUNT_PER_FUNCTION_CALL = 500 -export const MAX_PAGES_PER_FUNCTION_CALL = 15 -export const RUNE_SYMBOL = 'áš±' -export const defaultExplorerUrls: ExplorerUrls = { - root: { - [Network.Testnet]: `${DEFAULT_EXPLORER_URL}?network=testnet`, - [Network.Stagenet]: `${DEFAULT_EXPLORER_URL}?network=stagenet`, - [Network.Mainnet]: DEFAULT_EXPLORER_URL, - }, - tx: { - [Network.Testnet]: txUrl, - [Network.Stagenet]: txUrl, - [Network.Mainnet]: txUrl, - }, - address: { - [Network.Testnet]: addressUrl, - [Network.Stagenet]: addressUrl, - [Network.Mainnet]: addressUrl, - }, -} /** - * Chain identifier for Thorchain - * + * RUNE asset denom */ -export const THORChain = 'THOR' as const +export const RUNE_DENOM = 'rune' + +/** + * RUNE asset ticker + */ +export const RUNE_TICKER = 'RUNE' /** - * Base "chain" asset for RUNE-67C on Binance test net. - * - * Based on definition in Thorchain `common` - * @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24 + * Default fee used by the client to make transaction */ -export const AssetRune67C: Asset = { chain: 'BNB', symbol: 'RUNE-67C', ticker: RUNE_TICKER, synth: false } +export const DEFAULT_FEE: BaseAmount = assetToBase(assetAmount(0.02, RUNE_DECIMAL)) /** - * Base "chain" asset for RUNE-B1A on Binance main net. - * - * Based on definition in Thorchain `common` - * @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24 + * Default gas used by the transfer offline function */ -export const AssetRuneB1A: Asset = { chain: 'BNB', symbol: 'RUNE-B1A', ticker: RUNE_TICKER, synth: false } +export const DEFAULT_GAS_LIMIT_VALUE = '6000000' /** - * Base "chain" asset on thorchain main net. - * - * Based on definition in Thorchain `common` - * @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24 + * Default gas used by the deposit function + */ +export const DEPOSIT_GAS_LIMIT_VALUE = '600000000' + +/** + * Thorchain chain symbol + */ +export const THORChain = 'THOR' as const + +/** + * Thorchain native asset */ export const AssetRuneNative: Asset = { chain: THORChain, symbol: RUNE_TICKER, ticker: RUNE_TICKER, synth: false } /** - * Base "chain" asset for RUNE on ethereum main net. - * - * Based on definition in Thorchain `common` - * @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24 + * Message type url used to make transactions */ -export const AssetRuneERC20: Asset = { - chain: 'ETH', - symbol: `${RUNE_TICKER}-0x3155ba85d5f96b2d030a4966af206230e46849cb`, - ticker: RUNE_TICKER, - synth: false, -} +export const MSG_SEND_TYPE_URL = '/types.MsgSend' as const /** - * Base "chain" asset for RUNE on ethereum main net. - * - * Based on definition in Thorchain `common` - * @see https://gitlab.com/thorchain/thornode/-/blob/master/common/asset.go#L12-24 + * Message type url used to make deposits */ -export const AssetRuneERC20Testnet: Asset = { - chain: 'ETH', - symbol: `${RUNE_TICKER}-0xd601c6A3a36721320573885A8d8420746dA3d7A0`, - ticker: RUNE_TICKER, - synth: false, -} +export const MSG_DEPOSIT_TYPE_URL = '/types.MsgDeposit' as const /** - * Fall back node's and rpc's + * Default parameters used by the client */ -export const FallBackUrls = [ - { - [Network.Testnet]: { - node: ['deprecated'], - rpc: ['deprecated'], - }, - [Network.Stagenet]: { - node: ['https://stagenet-thornode.ninerealms.com'], - rpc: ['https://stagenet-rpc.ninerealms.com'], - }, - [Network.Mainnet]: { - node: ['https://thornode-v1.ninerealms.com'], - rpc: ['https://rpc-v1.ninerealms.com'], - }, - }, -] +export const defaultClientConfig: CosmosSdkClientParams = { + chain: AssetRuneNative.chain, + network: Network.Mainnet, + clientUrls: getDefaultClientUrls(), + rootDerivationPaths: getDefaultRootDerivationPaths(), + prefix: 'thor', + defaultDecimals: RUNE_DECIMAL, + defaultFee: DEFAULT_FEE, + baseDenom: RUNE_DENOM, + registryTypes: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [MSG_SEND_TYPE_URL, { ...(types.types.MsgSend as any) }], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [MSG_DEPOSIT_TYPE_URL, { ...(types.types.MsgDeposit as any) }], + ], +} diff --git a/packages/xchain-thorchain/src/types/client-types.ts b/packages/xchain-thorchain/src/types/client-types.ts index f3eb00fa1..f7fc3c913 100644 --- a/packages/xchain-thorchain/src/types/client-types.ts +++ b/packages/xchain-thorchain/src/types/client-types.ts @@ -1,31 +1,6 @@ -import { Network, Tx, TxParams } from '@xchainjs/xchain-client' +import { Tx, TxParams } from '@xchainjs/xchain-client' import { Asset, BaseAmount } from '@xchainjs/xchain-util' -import BigNumber from 'bignumber.js' -import Long from 'long' - -export type NodeUrl = { - node: string - rpc: string -} - -export type ClientUrl = Record - -export type ExplorerUrls = { - root: ExplorerUrl - tx: ExplorerUrl - address: ExplorerUrl -} - -export type ExplorerUrl = Record - -export type ChainId = string -export type ChainIds = Record - -export type ThorchainClientParams = { - clientUrl?: ClientUrl - explorerUrls?: ExplorerUrls - chainIds?: ChainIds -} +import { BigNumber } from 'bignumber.js' export type DepositParam = { walletIndex?: number @@ -36,70 +11,8 @@ export type DepositParam = { sequence?: number } -export type TxData = Pick +export type DepositTx = Omit export type TxOfflineParams = TxParams & { - /** - * Balance of Rune to send from - */ - fromRuneBalance: BaseAmount - /** - * Balance of asset to send from - * Optional: It can be ignored if asset to send from is RUNE - */ - fromAssetBalance?: BaseAmount - fromAccountNumber: Long - fromSequence: Long gasLimit?: BigNumber } - -/** - * Response from `thorchain/constants` endpoint - */ -export type ThorchainNetworkResponse = { - // We are in fee interested only - ignore all other values - native_tx_fee_rune: number -} - -/** - * Response of `/cosmos/base/tendermint/v1beta1/node_info` - * Note: We are interested in `network` (aka chain id) only - */ -export type NodeInfoResponse = { - default_node_info: { - network: string - } -} -/** - * Response of `/cosmos/tx/v1beta1/simulateo` - * Note: We are interested in `network` (aka chain id) only - */ -export type SimulateResponse = { - gas_info: { - gas_used: string - } -} - -export type MessageSend = { - '@type': string - from_address: string - to_address: string - amount: Amount -} -export type Amount = { - denom: string - amount: string -} - -export type MessageDeposit = { - '@type': string - coins: Coins[] - memo: string - signer: string -} - -export type Coins = { - asset: string - amount: number - decimals: number -} diff --git a/packages/xchain-thorchain/src/types/index.ts b/packages/xchain-thorchain/src/types/index.ts index 3bd6c883d..c8bacf02a 100644 --- a/packages/xchain-thorchain/src/types/index.ts +++ b/packages/xchain-thorchain/src/types/index.ts @@ -1,2 +1 @@ export * from './client-types' -export * from './messages' diff --git a/packages/xchain-thorchain/src/types/messages.ts b/packages/xchain-thorchain/src/types/messages.ts deleted file mode 100644 index d05b40ee8..000000000 --- a/packages/xchain-thorchain/src/types/messages.ts +++ /dev/null @@ -1,72 +0,0 @@ -import cosmosclient from '@cosmos-client/core' -import { Asset } from '@xchainjs/xchain-util' - -export type MsgCoin = { - asset: Asset - amount: string -} - -export class MsgNativeTx { - coins: MsgCoin[] - memo: string - signer: cosmosclient.AccAddress - - constructor(coins: MsgCoin[], memo: string, signer: cosmosclient.AccAddress) { - this.coins = coins - this.memo = memo - this.signer = signer - } -} - -/** - * This creates MsgNativeTx from json. - * - * @param value - * @returns {MsgNativeTx} - */ -export const msgNativeTxFromJson = (value: { coins: MsgCoin[]; memo: string; signer: string }): MsgNativeTx => { - return new MsgNativeTx(value.coins, value.memo, cosmosclient.AccAddress.fromString(value.signer)) -} - -export type AminoWrapping = { - type: string - value: T -} - -export type ThorchainDepositResponse = AminoWrapping<{ - msg: AminoWrapping<{ - coins: MsgCoin[] - memo: string - signer: string - }>[] - fee: cosmosclient.proto.cosmos.tx.v1beta1.Fee - signatures: string[] - memo: string - timeout_height: string -}> - -export type TxResult = { - observed_tx: { - tx: { - id: string - chain: string - from_address: string - to_address: string - coins: { - asset: string - amount: string - }[] - gas: { - asset: string - amount: string - }[] - memo: string - } - status: string - signers: string[] - } - keysign_metric: { - tx_id: string - node_tss_times: null - } -} diff --git a/packages/xchain-thorchain/src/types/proto/MsgCompiled.d.ts b/packages/xchain-thorchain/src/types/proto/MsgCompiled.d.ts index d20261a0e..f61c74083 100644 --- a/packages/xchain-thorchain/src/types/proto/MsgCompiled.d.ts +++ b/packages/xchain-thorchain/src/types/proto/MsgCompiled.d.ts @@ -1115,4 +1115,4 @@ export namespace cosmos { } } } -} +} \ No newline at end of file diff --git a/packages/xchain-thorchain/src/types/proto/MsgCompiled.js b/packages/xchain-thorchain/src/types/proto/MsgCompiled.js index 3443064ef..d5eac6314 100644 --- a/packages/xchain-thorchain/src/types/proto/MsgCompiled.js +++ b/packages/xchain-thorchain/src/types/proto/MsgCompiled.js @@ -2708,4 +2708,4 @@ $root.cosmos = (function() { return cosmos; })(); -module.exports = $root; +module.exports = $root; \ No newline at end of file diff --git a/packages/xchain-thorchain/src/utils.ts b/packages/xchain-thorchain/src/utils.ts index 8864e3712..2d5f8d4b9 100644 --- a/packages/xchain-thorchain/src/utils.ts +++ b/packages/xchain-thorchain/src/utils.ts @@ -1,474 +1,43 @@ -import cosmosclient from '@cosmos-client/core' -import { Balance, FeeType, Fees, Network, TxHash, TxType, singleFee } from '@xchainjs/xchain-client' -import { CosmosSDKClient, TxLog } from '@xchainjs/xchain-cosmos' -import { - Address, - Asset, - BaseAmount, - assetAmount, - assetFromString, - assetToBase, - assetToString, - baseAmount, - isSynthAsset, -} from '@xchainjs/xchain-util' -import axios from 'axios' -import * as bech32Buffer from 'bech32-buffer' -import Long from 'long' +import { Network, RootDerivationPaths, TxHash } from '@xchainjs/xchain-client' +import { Address, Asset, assetToString } from '@xchainjs/xchain-util' -import { AssetRuneNative, DEFAULT_GAS_ADJUSTMENT, RUNE_DECIMAL } from './const' -import { ChainId, ExplorerUrls, NodeInfoResponse, TxData } from './types' -import { MsgNativeTx } from './types/messages' -import types from './types/proto/MsgCompiled' +import { AssetRuneNative as AssetRUNE, DEFAULT_EXPLORER_URL } from './const' -const DENOM_RUNE_NATIVE = 'rune' -/** - * Checks whether an asset is `AssetRuneNative` - * - * @param {Asset} asset - * @returns {boolean} `true` or `false` - */ -export const isAssetRuneNative = (asset: Asset): boolean => assetToString(asset) === assetToString(AssetRuneNative) -/** - - * Get denomination from Asset - * - * @param {Asset} asset - * @returns {string} The denomination of the given asset. - */ -export const getDenom = (asset: Asset): string => { - if (isAssetRuneNative(asset)) return DENOM_RUNE_NATIVE - if (isSynthAsset(asset)) return assetToString(asset).toLowerCase() - return asset.symbol.toLowerCase() -} - -/** - * Get Asset from denomination - * - * @param {string} denom - * @returns {Asset|null} The asset of the given denomination. - */ -export const assetFromDenom = (denom: string): Asset | null => { - if (denom === DENOM_RUNE_NATIVE) return AssetRuneNative - return assetFromString(denom.toUpperCase()) -} - -/** - * Response guard for transaction broadcast - * - * @param {any} response The response from the node. - * @returns {boolean} `true` or `false`. - */ -export const isBroadcastSuccess = (response: unknown): boolean => - typeof response === 'object' && - response !== null && - 'logs' in response && - (response as Record).logs !== undefined - -/** - * Get address prefix based on the network. - * - * @param {Network} network - * @returns {string} The address prefix based on the network. - * - **/ -export const getPrefix = (network: Network) => { - switch (network) { - case Network.Mainnet: - return 'thor' - case Network.Stagenet: - return 'sthor' - case Network.Testnet: - return 'tthor' - } -} - -/** - * Register type for encoding `MsgDeposit` messages - */ -export const registerDepositCodecs = () => { - cosmosclient.codec.register('/types.MsgDeposit', types.types.MsgDeposit) -} - -/** - * Register type for encoding `MsgSend` messages - */ -export const registerSendCodecs = () => { - cosmosclient.codec.register('/types.MsgSend', types.types.MsgSend) -} - -/** - * Parse transaction data from event logs - * - * @param {TxLog[]} logs List of tx logs - * @param {Address} address - Address to get transaction data for - * @returns {TxData} Parsed transaction data - */ -export const getDepositTxDataFromLogs = ( - logs: TxLog[], - address: Address, - senderAsset?: Asset, - receiverAsset?: Asset, -): TxData => { - const events = logs[0]?.events - - if (!events) { - throw Error('No events in logs available') - } - - type TransferData = { sender: string; recipient: string; amount: BaseAmount } - type TransferDataList = TransferData[] - const transferDataList: TransferDataList = events.reduce((acc: TransferDataList, { type, attributes }) => { - if (type === 'transfer') { - return attributes.reduce((acc2, { key, value }, index) => { - if (index % 3 === 0) acc2.push({ sender: '', recipient: '', amount: baseAmount(0, RUNE_DECIMAL) }) - const newData = acc2[acc2.length - 1] - if (key === 'sender') newData.sender = value - if (key === 'recipient') newData.recipient = value - if (key === 'amount') { - const amountAsset = value.match(/(\d+)(\D+)/) - if (amountAsset) { - newData.amount = baseAmount(parseInt(amountAsset[1])) - } - } - return acc2 - }, acc) - } - return acc - }, []) - const txData: TxData = transferDataList - // filter out txs which are not based on given address - .filter(({ sender, recipient }) => sender === address || recipient === address) - // transform `TransferData` -> `TxData` - .reduce( - (acc: TxData, { sender, recipient, amount }) => ({ - ...acc, - from: [...acc.from, { amount, from: sender, asset: senderAsset }], - to: [...acc.to, { amount, to: recipient, asset: receiverAsset }], - }), - { from: [], to: [], type: TxType.Transfer }, - ) - - return txData -} - -/** - * Get the default fee. - * - * @returns {Fees} The default fee. - */ -export const getDefaultFees = (): Fees => { - const fee = assetToBase(assetAmount(0.02 /* 0.02 RUNE */, RUNE_DECIMAL)) - return singleFee(FeeType.FlatFee, fee) -} - -/** - * Get transaction type. - * - * @param {string} txData the transaction input data - * @param {string} encoding `base64` or `hex` - * @returns {string} the transaction type. - */ -export const getTxType = (txData: string, encoding: 'base64' | 'hex'): string => { - return Buffer.from(txData, encoding).toString().slice(4) -} - -/** - * Helper to get THORChain's chain id - * @param {string} nodeUrl THORNode url - */ -export const getChainId = async (nodeUrl: string): Promise => { - const { data } = await axios.get(`${nodeUrl}/cosmos/base/tendermint/v1beta1/node_info`) - return data?.default_node_info?.network || Promise.reject('Could not parse chain id') -} - -/** - * Builds final unsigned TX - * - * @param cosmosSdk - CosmosSDK - * @param txBody - txBody with encoded Msgs - * @param signerPubkey - signerPubkey string - * @param sequence - account sequence - * @param gasLimit - transaction gas limit - * @returns - */ -export const buildUnsignedTx = ({ - cosmosSdk, - txBody, - signerPubkey, - sequence, - gasLimit, -}: { - cosmosSdk: cosmosclient.CosmosSDK - txBody: cosmosclient.proto.cosmos.tx.v1beta1.TxBody - signerPubkey: cosmosclient.proto.google.protobuf.Any - sequence: Long - gasLimit?: Long -}): cosmosclient.TxBuilder => { - const authInfo = new cosmosclient.proto.cosmos.tx.v1beta1.AuthInfo({ - signer_infos: [ - { - public_key: signerPubkey, - mode_info: { - single: { - mode: cosmosclient.proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT, - }, - }, - sequence: sequence, - }, - ], - fee: { - amount: null, - gas_limit: gasLimit || null, - }, - }) - - return new cosmosclient.TxBuilder(cosmosSdk, txBody, authInfo) -} - -/** - * Estimates usage of gas - * - * Note: Be careful by using this helper function, - * it's still experimental and result might be incorrect. - * Change `multiplier` to get a valid estimation of gas. - */ -export const getEstimatedGas = async ({ - cosmosSDKClient, - txBody, - privKey, - accountNumber, - accountSequence, - multiplier, -}: { - cosmosSDKClient: CosmosSDKClient - txBody: cosmosclient.proto.cosmos.tx.v1beta1.TxBody - privKey: cosmosclient.proto.cosmos.crypto.secp256k1.PrivKey - accountNumber: Long - accountSequence: Long - multiplier?: number -}): Promise => { - const pubKey = privKey.pubKey() - const txBuilder = buildUnsignedTx({ - cosmosSdk: cosmosSDKClient.sdk, - txBody: txBody, - signerPubkey: cosmosclient.codec.instanceToProtoAny(pubKey), - sequence: accountSequence, - }) - - const signDocBytes = txBuilder.signDocBytes(accountNumber) - txBuilder.addSignature(privKey.sign(signDocBytes)) - - const resp = await cosmosclient.rest.tx.simulate(cosmosSDKClient.sdk, { tx_bytes: txBuilder.txBytes() }) - - const estimatedGas = resp.data?.gas_info?.gas_used ?? null - - if (!estimatedGas) { - throw new Error('Could not get data of estimated gas') +export const getDefaultClientUrls = (): Record => { + return { + [Network.Testnet]: 'deprecated', + [Network.Stagenet]: 'https://stagenet-rpc.ninerealms.com', + [Network.Mainnet]: 'https://rpc-v1.ninerealms.com', } - - return Long.fromString(estimatedGas).multiply(multiplier || DEFAULT_GAS_ADJUSTMENT) -} -/** - * Structure a MsgDeposit - * - * @param {MsgNativeTx} msgNativeTx Msg of type `MsgNativeTx`. - * @param {string} nodeUrl Node url - * @param {chainId} ChainId Chain id of the network - * - * @returns {Tx} The transaction details of the given transaction id. - * - * @throws {"Invalid client url"} Thrown if the client url is an invalid one. - */ -export const buildDepositTx = async ({ - msgNativeTx, - nodeUrl, - chainId, -}: { - msgNativeTx: MsgNativeTx - nodeUrl: string - chainId: ChainId -}): Promise => { - const networkChainId = await getChainId(nodeUrl) - if (!networkChainId || chainId !== networkChainId) { - throw new Error(`Invalid network (asked: ${chainId} / returned: ${networkChainId}`) - } - - const signerAddr = msgNativeTx.signer.toString() - const signerDecoded = bech32Buffer.decode(signerAddr) - - const msgDepositObj = { - coins: msgNativeTx.coins, - memo: msgNativeTx.memo, - signer: signerDecoded.data, - } - - const depositMsg = types.types.MsgDeposit.fromObject(msgDepositObj) - - return new cosmosclient.proto.cosmos.tx.v1beta1.TxBody({ - messages: [cosmosclient.codec.instanceToProtoAny(depositMsg)], - memo: msgNativeTx.memo, - }) } -/** - * Structure a MsgSend - * - * @param fromAddress - required, from address string - * @param toAddress - required, to address string - * @param assetAmount - required, asset amount string (e.g. "10000") - * @param assetDenom - required, asset denom string (e.g. "rune") - * @param memo - optional, memo string - * - * @returns - */ -export const buildTransferTx = async ({ - fromAddress, - toAddress, - assetAmount, - assetDenom, - memo = '', - nodeUrl, - chainId, -}: { - fromAddress: Address - toAddress: Address - assetAmount: BaseAmount - assetDenom: string - memo?: string - nodeUrl: string - chainId: ChainId -}): Promise => { - const networkChainId = await getChainId(nodeUrl) - if (!networkChainId || chainId !== networkChainId) { - throw new Error(`Invalid network (asked: ${chainId} / returned: ${networkChainId}`) - } - - const fromDecoded = bech32Buffer.decode(fromAddress) - const toDecoded = bech32Buffer.decode(toAddress) - - const transferObj = { - fromAddress: fromDecoded.data, - toAddress: toDecoded.data, - amount: [ - { - amount: assetAmount.amount().toString(), - denom: assetDenom, - }, - ], - } - - const transferMsg = types.types.MsgSend.fromObject(transferObj) - - return new cosmosclient.proto.cosmos.tx.v1beta1.TxBody({ - messages: [cosmosclient.codec.instanceToProtoAny(transferMsg)], - memo, - }) -} +export const getDefaultRootDerivationPaths = (): RootDerivationPaths => ({ + [Network.Mainnet]: `m/44'/931'/0'/0/`, + [Network.Stagenet]: `m/44'/931'/0'/0/`, + [Network.Testnet]: `m/44'/931'/0'/0/`, +}) -/** - * Get the balance of a given address. - * - * @param {Address} address By default, it will return the balance of the current wallet. (optional) - * @param {Asset} asset If not set, it will return all assets available. (optional) - * @param {cosmosClient} CosmosSDKClient - * - * @returns {Balance[]} The balance of the address. - */ -export const getBalance = async ({ - address, - assets, - cosmosClient, -}: { - address: Address - assets?: Asset[] - cosmosClient: CosmosSDKClient -}): Promise => { - const balances = await cosmosClient.getBalance(address) - return balances - .map((balance) => ({ - asset: (balance.denom && assetFromDenom(balance.denom)) || AssetRuneNative, - amount: baseAmount(balance.amount, RUNE_DECIMAL), - })) - .filter( - (balance) => !assets || assets.filter((asset) => assetToString(balance.asset) === assetToString(asset)).length, - ) -} +export const getDefaultExplorers = (): Record => ({ + [Network.Mainnet]: DEFAULT_EXPLORER_URL, + [Network.Testnet]: 'deprecated', + [Network.Stagenet]: `${DEFAULT_EXPLORER_URL}/?network=stagenet`, +}) -/** - * Get the explorer url. - * - * @param {Network} network - * @param {ExplorerUrls} Explorer urls - * @returns {string} The explorer url for thorchain based on the given network. - */ -export const getExplorerUrl = ({ root }: ExplorerUrls, network: Network): string => root[network] +export const getExplorerAddressUrl = (address: Address): Record => ({ + [Network.Mainnet]: `${DEFAULT_EXPLORER_URL}/address/${address}`, + [Network.Testnet]: 'deprecated', + [Network.Stagenet]: `${DEFAULT_EXPLORER_URL}/address/${address}?network=stagenet`, +}) -/** - * Get explorer address url. - * - * @param {ExplorerUrls} Explorer urls - * @param {Network} network - * @param {Address} address - * @returns {string} The explorer url for the given address. - */ -export const getExplorerAddressUrl = ({ - urls, - network, - address, -}: { - urls: ExplorerUrls - network: Network - address: Address -}): string => { - const url = `${urls.address[network]}/${address}` - switch (network) { - case Network.Mainnet: - return url - case Network.Stagenet: - return `${url}?network=stagenet` - case Network.Testnet: - return `${url}?network=testnet` - } -} +export const getExplorerTxUrl = (tx: TxHash): Record => ({ + [Network.Mainnet]: `${DEFAULT_EXPLORER_URL}/tx/${tx}`, + [Network.Testnet]: 'deprecated', + [Network.Stagenet]: `${DEFAULT_EXPLORER_URL}/tx/${tx}?network=stagenet`, +}) /** - * Get transaction url. - * - * @param {ExplorerUrls} Explorer urls - * @param {Network} network - * @param {TxHash} txID - * @returns {string} The explorer url for the given transaction id. + * Checks whether an asset is `AssetRUNE` + * @param {Asset} asset + * @returns {boolean} `true` or `false` */ -export const getExplorerTxUrl = ({ - urls, - network, - txID, -}: { - urls: ExplorerUrls - network: Network - txID: TxHash -}): string => { - const url = `${urls.tx[network]}/${txID}` - switch (network) { - case Network.Mainnet: - return url - case Network.Stagenet: - return `${url}?network=stagenet` - case Network.Testnet: - return `${url}?network=testnet` - } -} - -export const getAccount = ( - address: string, - client: CosmosSDKClient, -): Promise => { - const accAddress = cosmosclient.AccAddress.fromString(address) - return client.getAccount(accAddress) -} - -export const getSequence = async (address: string, client: CosmosSDKClient): Promise => { - const { sequence } = await getAccount(address, client) - return sequence?.toNumber() ?? null -} +export const isAssetRuneNative = (asset: Asset): boolean => assetToString(asset) === assetToString(AssetRUNE) diff --git a/yarn.lock b/yarn.lock index 1b4d18a1d..d8c335a7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -654,7 +654,7 @@ "@noble/hashes" "^1.0.0" protobufjs "^6.8.8" -"@cosmjs/amino@^0.31.1": +"@cosmjs/amino@0.31.1", "@cosmjs/amino@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.31.1.tgz#e6b4adc3ebe19ddfd953c67ee04b1eae488238af" integrity sha512-kkB9IAkNEUFtjp/uwHv95TgM8VGJ4VWfZwrTyLNqBDD1EpSX2dsNrmUe7k8OMPzKlZUFcKmD4iA0qGvIwzjbGA== @@ -664,7 +664,7 @@ "@cosmjs/math" "^0.31.1" "@cosmjs/utils" "^0.31.1" -"@cosmjs/crypto@^0.31.1": +"@cosmjs/crypto@0.31.1", "@cosmjs/crypto@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.31.1.tgz#ce4917df0f7b38f0909a32020907ccff04acefe6" integrity sha512-4R/SqdzdVzd4E5dpyEh1IKm5GbTqwDogutyIyyb1bcOXiX/x3CrvPI9Tb4WSIMDLvlb5TVzu2YnUV51Q1+6mMA== @@ -677,7 +677,7 @@ elliptic "^6.5.4" libsodium-wrappers-sumo "^0.7.11" -"@cosmjs/encoding@^0.31.1": +"@cosmjs/encoding@0.31.1", "@cosmjs/encoding@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.31.1.tgz#0041b2650c443d883e22f27c7d3cd7b844c6d0ec" integrity sha512-IuxP6ewwX6vg9sUJ8ocJD92pkerI4lyG8J5ynAM3NaX3q+n+uMoPRSQXNeL9bnlrv01FF1kIm8if/f5F7ZPtkA== @@ -701,7 +701,7 @@ dependencies: bn.js "^5.2.0" -"@cosmjs/proto-signing@^0.31.1": +"@cosmjs/proto-signing@0.31.1", "@cosmjs/proto-signing@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.31.1.tgz#3929d5bee3c88c42b3bc3c4b9db4ab3bddb684c4" integrity sha512-hipbBVrssPu+jnmRzQRP5hhS/mbz2nU7RvxG/B1ZcdNhr1AtZC5DN09OTUoEpMSRgyQvScXmk/NTbyf+xmCgYg== @@ -724,7 +724,7 @@ ws "^7" xstream "^11.14.0" -"@cosmjs/stargate@^0.31.1": +"@cosmjs/stargate@0.31.1", "@cosmjs/stargate@^0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.31.1.tgz#7e2b0fd6f181250915b1d73ecf9dfbab6f3cdd0d" integrity sha512-TqOJZYOH5W3sZIjR6949GfjhGXO3kSHQ3/KmE+SuKyMMmQ5fFZ45beawiRtVF0/CJg5RyPFyFGJKhb1Xxv3Lcg== @@ -2746,6 +2746,11 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@scure/base@1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" + integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== + "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4" @@ -3742,6 +3747,11 @@ bigi@^1.1.0, bigi@^1.4.0: resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" integrity sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw== +bignumber.js@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" + integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== + bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" @@ -4622,7 +4632,7 @@ cosmiconfig@7.0.0: path-type "^4.0.0" yaml "^1.10.0" -cosmjs-types@^0.8.0: +cosmjs-types@0.8.0, cosmjs-types@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.8.0.tgz#2ed78f3e990f770229726f95f3ef5bf9e2b6859b" integrity sha512-Q2Mj95Fl0PYMWEhA2LuGEIhipF7mQwd9gTQ85DdP9jjjopeoGaDxvmPa5nakNzsq7FnO1DMTatXTAx6bxMH7Lg== @@ -9201,10 +9211,10 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -protobufjs@^6.11.2: - version "6.11.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" - integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== +protobufjs@6.11.4, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -9220,10 +9230,10 @@ protobufjs@^6.11.2: "@types/node" ">=13.7.0" long "^4.0.0" -protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: - version "6.11.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" - integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== +protobufjs@^6.11.2: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2"