Skip to content

Commit

Permalink
Merge branch 'master' into 1009-usk-swaps
Browse files Browse the repository at this point in the history
  • Loading branch information
0xp3gasus committed Jan 25, 2024
2 parents c7b9b7d + 754fe1a commit 92d833b
Show file tree
Hide file tree
Showing 34 changed files with 786 additions and 3,179 deletions.
16 changes: 16 additions & 0 deletions .changeset/chilly-ties-battle.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions .changeset/few-wolves-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-cosmos-sdk': minor
---

New util functions: makeClientPath, bech32toBase64 format and base64ToBech32
5 changes: 5 additions & 0 deletions .changeset/plenty-tools-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-thorchain': major
---

Client compatible with @xchainjs/xchain-cosmos-sdk package
5 changes: 5 additions & 0 deletions .changeset/polite-radios-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-cosmos-sdk': minor
---

New constructor parameter 'registry' to allow the client to work with custom message types.
5 changes: 5 additions & 0 deletions .changeset/seven-falcons-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-cosmos-sdk': patch
---

Broadcast bug fix
5 changes: 5 additions & 0 deletions .changeset/weak-seahorses-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-kujira': patch
---

New version compatible with cosmos-sdk version
4 changes: 4 additions & 0 deletions packages/xchain-cosmos-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
117 changes: 43 additions & 74 deletions packages/xchain-cosmos-sdk/src/client.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -32,30 +31,36 @@ 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<Network, string>
prefix: string
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<StargateClient>
private readonly clientUrls: Record<Network, string>
private readonly signer: CachedValue<DirectSecp256k1HdWallet> | undefined
private readonly prefix: string
private readonly defaultDecimals: number
private readonly defaultFee: BaseAmount
protected readonly startgateClient: CachedValue<StargateClient>
protected readonly clientUrls: Record<Network, string>
protected readonly prefix: string
protected readonly baseDenom: string
protected readonly registry: Registry
/**
* Constructor
* @constructor
Expand All @@ -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<DirectSecp256k1HdWallet>(
async () =>
await DirectSecp256k1HdWallet.fromMnemonic(params.phrase as string, { prefix: params.prefix || 'cosmos' }),
)
}
this.registry = new Registry([...defaultRegistryTypes, ...params.registryTypes])
this.startgateClient = new CachedValue<StargateClient>(() =>
this.connectClient(this.clientUrls[params.network || Network.Mainnet]),
)
Expand Down Expand Up @@ -343,8 +343,6 @@ export default abstract class Client extends BaseXChainClient implements XChainC
}

public async transfer(params: TxParams): Promise<string> {
if (!this.signer) throw Error('Invalid signer')

const sender = await this.getAddressAsync(params.walletIndex || 0)

const { rawUnsignedTx } = await this.prepareTx({
Expand All @@ -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(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(this.getAssetInfo().asset),
unsignedTx.body.memo,
)

return tx.transactionHash
}

public async broadcastTx(txHex: string): Promise<string> {
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<PreparedTx> {
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<PreparedTx>
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 }
1 change: 1 addition & 0 deletions packages/xchain-cosmos-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './client'
export { base64ToBech32, bech32ToBase64, makeClientPath } from './utils'
35 changes: 35 additions & 0 deletions packages/xchain-cosmos-sdk/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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))
9 changes: 8 additions & 1 deletion packages/xchain-kujira/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
79 changes: 76 additions & 3 deletions packages/xchain-kujira/src/client.ts
Original file line number Diff line number Diff line change
@@ -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, AssetUSK, KUJI_DECIMAL, USK_ASSET_DENOM } from './const'
import { AssetKUJI, AssetUSK, KUJI_DECIMAL, MSG_SEND_TYPE_URL, USK_ASSET_DENOM } from './const'
import { defaultClientConfig, getDefaultExplorers } from './utils'

export type KujiraClientParams = Partial<CosmosSdkClientParams>
Expand Down Expand Up @@ -51,4 +56,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<PreparedTx> {
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, string> = {
[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)
}
}
Loading

0 comments on commit 92d833b

Please sign in to comment.