diff --git a/package.json b/package.json index 0b4bbd5..8317645 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "dependencies": { "@ledgerhq/hw-app-btc": "10.4.1", + "@ledgerhq/hw-app-xrp": "6.29.4", "@noble/hashes": "1.4.0", "@scure/base": "1.1.8", "@scure/btc-signer": "1.3.2", @@ -72,6 +73,7 @@ "ledger-bitcoin": "0.2.3", "prompts": "2.4.2", "ramda": "0.30.1", + "ripple-binary-codec": "^2.1.0", "scure": "1.6.0", "tiny-secp256k1": "2.2.3", "xrpl": "4.0.0" diff --git a/src/functions/ripple/ripple.functions.ts b/src/functions/ripple/ripple.functions.ts index f0017f3..a35dda6 100644 --- a/src/functions/ripple/ripple.functions.ts +++ b/src/functions/ripple/ripple.functions.ts @@ -274,17 +274,13 @@ export async function signAndSubmitRippleTransaction( export async function getLockedBTCBalance( rippleClient: Client, - rippleWallet: Wallet, + userAddress: string, issuerAddress: string ): Promise { try { await connectRippleClient(rippleClient); - const rippleVaults = await getAllRippleVaults( - rippleClient, - issuerAddress, - rippleWallet.classicAddress - ); + const rippleVaults = await getAllRippleVaults(rippleClient, issuerAddress, userAddress); const lockedBTCBalance = rippleVaults.reduce((accumulator, vault) => { return accumulator + vault.valueLocked.toNumber(); @@ -298,7 +294,7 @@ export async function getLockedBTCBalance( export async function getDLCBTCBalance( rippleClient: Client, - rippleWallet: Wallet, + userAddress: string, issuerAddress: string ): Promise { try { @@ -306,7 +302,7 @@ export async function getDLCBTCBalance( const accountNonXRPBalancesRequest: AccountLinesRequest = { command: 'account_lines', - account: rippleWallet.classicAddress, + account: userAddress, ledger_index: 'validated', }; @@ -334,7 +330,7 @@ export async function createCheck( destinationTag: number = 1, dlcBTCAmount: string, vaultUUID: string -): Promise { +): Promise { try { await connectRippleClient(rippleClient); diff --git a/src/index.ts b/src/index.ts index 1a25c2f..c7ccb90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,12 +3,14 @@ import { PrivateKeyDLCHandler } from './dlc-handlers/private-key-dlc-handler.js' import { SoftwareWalletDLCHandler } from './dlc-handlers/software-wallet-dlc-handler.js'; import { EthereumHandler } from './network-handlers/ethereum-handler.js'; import { RippleHandler } from './network-handlers/ripple-handler.js'; +import { LedgerXRPHandler } from './network-handlers/xrp-ledger-handler.js'; import { ProofOfReserveHandler } from './proof-of-reserve-handlers/proof-of-reserve-handler.js'; export { PrivateKeyDLCHandler, LedgerDLCHandler, SoftwareWalletDLCHandler, + LedgerXRPHandler, EthereumHandler, ProofOfReserveHandler, RippleHandler, diff --git a/src/network-handlers/xrp-ledger-handler.ts b/src/network-handlers/xrp-ledger-handler.ts new file mode 100644 index 0000000..e896b1f --- /dev/null +++ b/src/network-handlers/xrp-ledger-handler.ts @@ -0,0 +1,146 @@ +import Xrp from '@ledgerhq/hw-app-xrp'; +import { encode } from 'ripple-binary-codec'; +import { CheckCreate, Client, Transaction, TrustSet } from 'xrpl'; + +import { + checkRippleTransactionResult, + connectRippleClient, + createCheck, + getDLCBTCBalance, + getLockedBTCBalance, + setTrustLine, +} from '../functions/ripple/ripple.functions.js'; + +export class LedgerXRPHandler { + private ledgerApp: Xrp.default; + private derivationPath: string; + private xrpClient: Client; + private issuerAddress: string; + + constructor( + ledgerApp: Xrp.default, + derivationPath: string, + xrpClient: Client, + issuerAddress: string + ) { + this.ledgerApp = ledgerApp; + this.derivationPath = derivationPath; + this.xrpClient = xrpClient; + this.issuerAddress = issuerAddress; + } + + public async getAddress(): Promise { + const address = await this.ledgerApp.getAddress(this.derivationPath); + return address.address; + } + + public async setTrustLine(): Promise { + try { + const deviceData = await this.ledgerApp.getAddress(this.derivationPath); + + const trustLineRequest = await setTrustLine( + this.xrpClient, + deviceData.address, + this.issuerAddress + ); + + if (!trustLineRequest) { + console.error('TrustLine is already set'); + return; + } + const updatedTrustLineRequest: TrustSet = { + ...trustLineRequest, + Flags: 2147483648, + SigningPubKey: deviceData.publicKey.toUpperCase(), + }; + + const encodedTrustLineRequest = encode(updatedTrustLineRequest); + + const signature = await this.ledgerApp.signTransaction( + this.derivationPath, + encodedTrustLineRequest + ); + console.log('Signature:', signature); + + const signedTrustLineRequest: TrustSet = { + ...updatedTrustLineRequest, + TxnSignature: signature, + }; + + await connectRippleClient(this.xrpClient); + + const submitTrustLineRequestResponse = + await this.xrpClient.submitAndWait(signedTrustLineRequest); + + console.log(`Response for submitted Transaction Request:`, submitTrustLineRequestResponse); + + checkRippleTransactionResult(submitTrustLineRequestResponse); + } catch (error) { + throw new Error(`Error setting Trust Line: ${error}`); + } + } + + public async createCheck(dlcBTCAmount: string, vaultUUID: string): Promise { + try { + const deviceData = await this.ledgerApp.getAddress(this.derivationPath); + + const checkCreateRequest: CheckCreate = await createCheck( + this.xrpClient, + deviceData.address, + this.issuerAddress, + undefined, + dlcBTCAmount, + vaultUUID + ); + + const updatedCheckCreateRequest: CheckCreate = { + ...checkCreateRequest, + Flags: 2147483648, + SigningPubKey: deviceData.publicKey.toUpperCase(), + }; + + const encodedCheckCreateRequest = encode(updatedCheckCreateRequest); + + const signature = await this.ledgerApp.signTransaction( + this.derivationPath, + encodedCheckCreateRequest + ); + + const signedCheckCreateRequest: Transaction = { + ...updatedCheckCreateRequest, + TxnSignature: signature, + }; + + await connectRippleClient(this.xrpClient); + + const submitCheckCreateRequestResponse = + await this.xrpClient.submitAndWait(signedCheckCreateRequest); + + console.log(`Response for submitted Transaction Request:`, submitCheckCreateRequestResponse); + + checkRippleTransactionResult(submitCheckCreateRequestResponse); + } catch (error) { + throw new Error(`Error creating Check: ${error}`); + } + } + + public async getDLCBTCBalance(): Promise { + try { + const deviceData = await this.ledgerApp.getAddress(this.derivationPath); + + return await getDLCBTCBalance(this.xrpClient, deviceData.address, this.issuerAddress); + } catch (error) { + throw new Error(`Error getting BTC Balance: ${error}`); + } + } + + public async getLockedBTCBalance(): Promise { + try { + const deviceData = await this.ledgerApp.getAddress(this.derivationPath); + + return await getLockedBTCBalance(this.xrpClient, deviceData.address, this.issuerAddress); + } catch (error) { + throw new Error(`Error getting BTC Balance: ${error}`); + } + } +} diff --git a/yarn.lock b/yarn.lock index 9a7a12a..f9fa8a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,6 +1105,14 @@ tiny-secp256k1 "1.1.6" varuint-bitcoin "1.1.2" +"@ledgerhq/hw-app-xrp@6.29.4": + version "6.29.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-6.29.4.tgz#b90d847f2cf8ddca9ca0068f5079f199c334314d" + integrity sha512-fEnqkwmEmcThGVtxLUQX9x4KB1E659Ke1dYuCZSXX4346+h3PCa7cfeKN/VRZNH8HQJgiYi53LqqYzwWXB5zbg== + dependencies: + "@ledgerhq/hw-transport" "^6.31.4" + bip32-path "0.4.2" + "@ledgerhq/hw-transport@^6.20.0": version "6.30.6" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.30.6.tgz#c6d84672ac4828f311831998f4101ea205215a6d" @@ -1115,7 +1123,7 @@ "@ledgerhq/logs" "^6.12.0" events "^3.3.0" -"@ledgerhq/hw-transport@^6.31.2": +"@ledgerhq/hw-transport@^6.31.2", "@ledgerhq/hw-transport@^6.31.4": version "6.31.4" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1" integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A== @@ -1792,7 +1800,7 @@ bip174@^2.0.1, bip174@^2.1.1: resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== -bip32-path@^0.4.2: +bip32-path@0.4.2, bip32-path@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/bip32-path/-/bip32-path-0.4.2.tgz#5db0416ad6822712f077836e2557b8697c0c7c99" integrity sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ==