Skip to content

Commit

Permalink
Refactored Bch & Doge for Client Ledger (#1110)
Browse files Browse the repository at this point in the history
* Refactored Bch for Client Ledger

* refactored doge for ledger client

* fix broken test

* update package json
  • Loading branch information
Thorian1te authored Apr 15, 2024
1 parent 2f7988a commit 8e82dc3
Show file tree
Hide file tree
Showing 22 changed files with 685 additions and 245 deletions.
5 changes: 5 additions & 0 deletions .changeset/friendly-monkeys-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-doge': patch
---

Refactored package to include LedgerClient
5 changes: 5 additions & 0 deletions .changeset/nervous-goats-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@xchainjs/xchain-bitcoincash': patch
---

Refactored to suit ClientLedger
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AssetInfo, Network, UtxoClientParams } from '@xchainjs/xchain-client'
import { AssetInfo, Network } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase, assetToString, baseToAsset } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import { ClientKeystore as Client } from '../src/clientKeystore'
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Network, UtxoClientParams } from '@xchainjs/xchain-client'
import { Network } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase, assetToString, baseToAsset } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import { ClientKeystore as Client } from '../src/clientKeystore'
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Network, UtxoClientParams } from '@xchainjs/xchain-client'
import { Network } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase, assetToString } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import { defaultBTCParams } from '../src/client'
import { ClientKeystore as Client } from '../src/clientKeystore'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import { Network } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import { ClientLedger } from '../src/clientLedger'
import {
AssetBCH,
BitgoProviders,
HaskoinDataProviders,
LOWER_FEE_BOUND,
UPPER_FEE_BOUND,
explorerProviders,
} from '../src/const'
jest.setTimeout(200000)

// Default parameters for Bitcoin Cash (BCH) client
const defaultBchParams: UtxoClientParams = {
network: Network.Mainnet, // Default network is Mainnet
phrase: '', // Default empty phrase
explorerProviders: explorerProviders, // Default explorer providers
dataProviders: [HaskoinDataProviders, BitgoProviders], // Default data providers
rootDerivationPaths: {
[Network.Mainnet]: `m/44'/145'/0'/0/`, // Default root derivation path for Mainnet
[Network.Testnet]: `m/44'/1'/0'/0/`, // Default root derivation path for Testnet
[Network.Stagenet]: `m/44'/145'/0'/0/`, // Default root derivation path for Stagenet
},
feeBounds: {
lower: LOWER_FEE_BOUND, // Default lower fee bound
upper: UPPER_FEE_BOUND, // Default upper fee bound
},
}

describe('BitcoinCash Client Ledger', () => {
let btcCashClient: ClientLedger
beforeAll(async () => {
const transport = await TransportNodeHid.create()

btcCashClient = new ClientLedger({
transport,
...defaultBchParams,
})
})
it('get ledger address async without verification', async () => {
const address = await btcCashClient.getAddressAsync()
console.log('address', address)
expect(address).toContain('q')
})

it('get ledger address async with verification', async () => {
const address = await btcCashClient.getAddressAsync(0, true)
console.log('address', address)
expect(address).toContain('q')
})

it('get ledger balance', async () => {
const address = await btcCashClient.getAddressAsync()
const balance = await btcCashClient.getBalance(address)
console.log('balance', balance[0].amount.amount().toString())
})

it('transfer Ledger test amount', async () => {
try {
const to = await btcCashClient.getAddressAsync(1)
const amount = assetToBase(assetAmount('0.001'))
const txid = await btcCashClient.transfer({
asset: AssetBCH,
recipient: to,
amount,
memo: 'test',
feeRate: 1,
})
console.log(JSON.stringify(txid, null, 2))
} catch (err) {
console.error('ERR running test', err)
fail()
}
})
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Network, Protocol } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase } from '@xchainjs/xchain-util'

import { BCH_DECIMAL, Client, defaultBchParams } from '../src'
import { BCH_DECIMAL, defaultBchParams } from '../src'
import { ClientKeystore as Client } from '../src/clientKeystore'

describe('BCH e2e tests', () => {
let client: Client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Network, UtxoClientParams } from '@xchainjs/xchain-client'
import { Network } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase, assetToString } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import { Client } from '../src/client'
import { ClientKeystore as Client } from '../src/clientKeystore'
import { AssetBCH, HaskoinDataProviders, LOWER_FEE_BOUND, UPPER_FEE_BOUND, explorerProviders } from '../src/const'

const defaultBCHParams: UtxoClientParams = {
Expand Down Expand Up @@ -102,6 +103,7 @@ describe('Bitcoincash Integration Tests for Haskoin', () => {
recipient: to,
amount,
memo: 'test',
feeRate: 1,
})
console.log(JSON.stringify(txid, null, 2))
} catch (err) {
Expand Down
33 changes: 29 additions & 4 deletions packages/xchain-bitcoincash/__tests__/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
import { Network } from '@xchainjs/xchain-client'
import { baseAmount } from '@xchainjs/xchain-util'
import { UtxoClientParams } from '@xchainjs/xchain-utxo'

import mockBitgoApi from '../__mocks__/bitgo'
import mockHaskoinApi from '../__mocks__/haskoin'
import mockThornodeApi from '../__mocks__/thornode'
import { Client } from '../src/client'
import { BCH_DECIMAL } from '../src/const'

const bchClient = new Client()
import { ClientKeystore as Client } from '../src/clientKeystore'
import {
BCH_DECIMAL,
BitgoProviders,
HaskoinDataProviders,
LOWER_FEE_BOUND,
UPPER_FEE_BOUND,
explorerProviders,
} from '../src/const'

// Default parameters for Bitcoin Cash (BCH) client
export const defaultBchParams: UtxoClientParams = {
network: Network.Mainnet, // Default network is Mainnet
phrase: '', // Default empty phrase
explorerProviders: explorerProviders, // Default explorer providers
dataProviders: [BitgoProviders, HaskoinDataProviders], // Default data providers
rootDerivationPaths: {
[Network.Mainnet]: `m/44'/145'/0'/0/`, // Default root derivation path for Mainnet
[Network.Testnet]: `m/44'/1'/0'/0/`, // Default root derivation path for Testnet
[Network.Stagenet]: `m/44'/145'/0'/0/`, // Default root derivation path for Stagenet
},
feeBounds: {
lower: LOWER_FEE_BOUND, // Default lower fee bound
upper: UPPER_FEE_BOUND, // Default upper fee bound
},
}

const bchClient = new Client({ ...defaultBchParams })

describe('BCHClient Test', () => {
beforeEach(() => {
Expand Down
6 changes: 4 additions & 2 deletions packages/xchain-bitcoincash/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
"postversion": "git push --follow-tags"
},
"dependencies": {
"uniqid": "^5.4.0"
"uniqid": "^5.4.0",
"@ledgerhq/hw-app-btc": "^10.1.0"
},
"devDependencies": {
"@ledgerhq/hw-transport-node-hid": "^6.28.0",
"@psf/bitcoincashjs-lib": "^4.0.3",
"@types/bchaddrjs": "0.4.0",
"@types/uniqid": "^5.3.1",
Expand Down Expand Up @@ -63,4 +65,4 @@
"publishConfig": {
"access": "public"
}
}
}
112 changes: 3 additions & 109 deletions packages/xchain-bitcoincash/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Import statements for necessary modules and types
import * as bitcash from '@psf/bitcoincashjs-lib'
import { AssetInfo, FeeOption, FeeRate, Network, TxHash, TxParams, checkFeeBounds } from '@xchainjs/xchain-client' // Importing various types and constants from xchain-client module
import { getSeed } from '@xchainjs/xchain-crypto' // Importing getSeed function from xchain-crypto module
import { AssetInfo, FeeRate, Network, TxParams } from '@xchainjs/xchain-client' // Importing various types and constants from xchain-client module
import { Address } from '@xchainjs/xchain-util' // Importing the Address type from xchain-util module
import { Client as UTXOClient, UTXO, UtxoClientParams } from '@xchainjs/xchain-utxo' // Importing necessary types and the UTXOClient class from xchain-utxo module
import accumulative from 'coinselect/accumulative' // Importing accumulative function from coinselect/accumulative module
Expand All @@ -17,7 +16,7 @@ import {
explorerProviders,
} from './const' // Importing various constants from the const module
import { BchPreparedTx } from './types' // Importing the BchPreparedTx type from types module
import { KeyPair, Transaction, TransactionBuilder } from './types/bitcoincashjs-types' // Importing necessary types from bitcoincashjs-types module
import { TransactionBuilder } from './types/bitcoincashjs-types' // Importing necessary types from bitcoincashjs-types module
import * as Utils from './utils' // Importing utility functions from utils module
// Default parameters for Bitcoin Cash (BCH) client
export const defaultBchParams: UtxoClientParams = {
Expand All @@ -38,7 +37,7 @@ export const defaultBchParams: UtxoClientParams = {
/**
* Custom Bitcoin Cash client class.
*/
class Client extends UTXOClient {
abstract class Client extends UTXOClient {
/**
* Constructor for the Client class.
*
Expand All @@ -55,38 +54,6 @@ class Client extends UTXOClient {
dataProviders: params.dataProviders,
})
}
/**
* Get the current address.
*
* Generates a network-specific key-pair and returns the corresponding address.
*
* @param {number} index - The index of the address to retrieve.
* @returns {Address} The current address.
*
* @throws {"Phrase must be provided"} Thrown if the phrase has not been set before.
* @throws {"Address not defined"} Thrown if failed to create account from phrase.
*/
getAddress(index = 0): Address {
if (!this.phrase) throw new Error('Phrase must be provided') // Throw an error if the phrase is not provided
try {
const keys = this.getBCHKeys(this.phrase, this.getFullDerivationPath(index)) // Get BCH keys
const address = keys.getAddress(index) // Get the address from the keys
return Utils.stripPrefix(Utils.toCashAddress(address)) // Return the address with prefix stripped
} catch (error) {
throw new Error('Address not defined') // Throw an error if failed to create account from phrase
}
}

/**
* Get the current address asynchronously.
* Generates a network-specific key-pair and returns the corresponding address.
* @returns {Address} A promise that resolves with the current address.
* @throws {"Phrase must be provided"} Thrown if the phrase has not been set before.
* @throws {"Address not defined"} Thrown if failed to create account from phrase.
*/
async getAddressAsync(index = 0): Promise<string> {
return this.getAddress(index)
}

/**
* Get information about the BCH asset.
Expand All @@ -110,79 +77,6 @@ class Client extends UTXOClient {
return Utils.validateAddress(address, this.network)
}

/**
* Private function to get BCH keys.
* Generates a key pair from the provided phrase and derivation path.
* @param {string} phrase - The phrase used for generating the private key.
* @param {string} derivationPath - The BIP44 derivation path.
* @returns {PrivateKey} The key pair generated from the phrase and derivation path.
*
* @throws {"Invalid phrase"} Thrown if an invalid phrase is provided.
* */
private getBCHKeys(phrase: string, derivationPath: string): KeyPair {
const rootSeed = getSeed(phrase) // Get seed from the phrase
const masterHDNode = bitcash.HDNode.fromSeedBuffer(rootSeed, Utils.bchNetwork(this.network)) // Create HD node from seed
return masterHDNode.derivePath(derivationPath).keyPair // Derive key pair from the HD node and derivation path
}
/**
* Transfer BCH.
* @param {TxParams & { feeRate?: FeeRate }} params - The transfer options.
* @returns {Promise<TxHash>} A promise that resolves with the transaction hash.
*/
async transfer(params: TxParams & { feeRate?: FeeRate }): Promise<TxHash> {
// Set the default fee rate to 'fast'
const feeRate = params.feeRate || (await this.getFeeRates())[FeeOption.Fast]
// Check if the fee rate is within the specified bounds
checkFeeBounds(this.feeBounds, feeRate)

// Get the index of the address to send funds from
const fromAddressIndex = params.walletIndex || 0

// Prepare the transaction by gathering necessary data
const { rawUnsignedTx, utxos } = await this.prepareTx({
...params,
feeRate,
sender: await this.getAddressAsync(fromAddressIndex),
})

// Convert the raw unsigned transaction to a Transaction object
const tx: Transaction = bitcash.Transaction.fromHex(rawUnsignedTx)

// Initialize a new transaction builder
const builder: TransactionBuilder = new bitcash.TransactionBuilder(Utils.bchNetwork(this.network))

// Add inputs to the transaction builder
tx.ins.forEach((input) => {
const utxo = utxos.find(
(utxo) =>
Buffer.compare(Buffer.from(utxo.hash, 'hex').reverse(), input.hash) === 0 && input.index === utxo.index,
)
if (!utxo) throw Error('Can not find UTXO')
builder.addInput(bitcash.Transaction.fromBuffer(Buffer.from(utxo.txHex || '', 'hex')), utxo.index)
})

// Add outputs to the transaction builder
tx.outs.forEach((output) => {
builder.addOutput(output.script, output.value)
})

// Get the derivation path for the sender's address
const derivationPath = this.getFullDerivationPath(fromAddressIndex)
// Get the key pair for signing the transaction
const keyPair = this.getBCHKeys(this.phrase, derivationPath)

// Sign each input of the transaction with the key pair
builder.inputs.forEach((input: { value: number }, index: number) => {
builder.sign(index, keyPair, undefined, 0x41, input.value)
})

// Build the final transaction and convert it to hexadecimal format
const txHex = builder.build().toHex()

// Broadcast the transaction to the BCH network and return the transaction hash
return await this.roundRobinBroadcastTx(txHex)
}

/**
* Build a BCH transaction.
* @param {BuildParams} params - The transaction build options.
Expand Down
Loading

0 comments on commit 8e82dc3

Please sign in to comment.