From f91c50949d761eddd1ed894abd00e7ca23549592 Mon Sep 17 00:00:00 2001 From: David May Date: Tue, 18 Oct 2022 17:19:24 +0200 Subject: [PATCH 1/2] [DEV-893] Using recommended fee and RBF for BTC send --- src/blockchain/ain/ain.module.ts | 5 ++-- src/blockchain/ain/node/btc-client.ts | 21 +++++++++++++++++ .../ain/services/btc-fee.service.ts | 23 +++++++++++++++++++ src/config/config.ts | 1 + .../models/crypto-input/btc-input.service.ts | 17 ++++---------- .../payout/services/payout-bitcoin.service.ts | 6 +++-- 6 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 src/blockchain/ain/services/btc-fee.service.ts diff --git a/src/blockchain/ain/ain.module.ts b/src/blockchain/ain/ain.module.ts index 1dad110dbb..6dc7ae380e 100644 --- a/src/blockchain/ain/ain.module.ts +++ b/src/blockchain/ain/ain.module.ts @@ -5,11 +5,12 @@ import { NodeController } from './node/node.controller'; import { NodeService } from './node/node.service'; import { WhaleService } from './whale/whale.service'; import { DeFiChainUtil } from './utils/defichain.util'; +import { BtcFeeService } from './services/btc-fee.service'; @Module({ imports: [SharedModule], - providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil], - exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil], + providers: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService], + exports: [CryptoService, NodeService, WhaleService, DeFiChainUtil, BtcFeeService], controllers: [NodeController], }) export class AinModule {} diff --git a/src/blockchain/ain/node/btc-client.ts b/src/blockchain/ain/node/btc-client.ts index dc1ceb4bd5..594b828f3f 100644 --- a/src/blockchain/ain/node/btc-client.ts +++ b/src/blockchain/ain/node/btc-client.ts @@ -1,4 +1,5 @@ import { SchedulerRegistry } from '@nestjs/schedule'; +import { Config } from 'src/config/config'; import { HttpService } from 'src/shared/services/http.service'; import { NodeClient, NodeCommand, NodeMode } from './node-client'; @@ -27,4 +28,24 @@ export class BtcClient extends NodeClient { true, ).then((r) => r.txid); } + + async sendMany(payload: { addressTo: string; amount: number }[], feeRate: number): Promise { + const batch = payload.reduce((acc, p) => ({ ...acc, [p.addressTo]: `${p.amount}` }), {}); + + return await this.callNode<{ txid: string }>( + (c) => + c.call( + NodeCommand.SEND, + [ + batch, + null, + 'unset', + null, + { fee_rate: feeRate, replaceable: true, change_address: Config.blockchain.default.btcOutWalletAddress }, + ], + 'number', + ), + true, + ).then((r) => r.txid); + } } diff --git a/src/blockchain/ain/services/btc-fee.service.ts b/src/blockchain/ain/services/btc-fee.service.ts new file mode 100644 index 0000000000..bb2b380545 --- /dev/null +++ b/src/blockchain/ain/services/btc-fee.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { HttpService } from 'src/shared/services/http.service'; + +@Injectable() +export class BtcFeeService { + private readonly btcFeeUrl = 'https://mempool.space/api/v1/fees/recommended'; + + constructor(private readonly http: HttpService) {} + + async getRecommendedFeeRate(): Promise { + const { fastestFee } = await this.http.get<{ + fastestFee: number; + halfHourFee: number; + hourFee: number; + economyFee: number; + minimumFee: number; + }>(this.btcFeeUrl, { + tryCount: 3, + }); + + return fastestFee; + } +} diff --git a/src/config/config.ts b/src/config/config.ts index 018e4d20d9..3e43837e9c 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -198,6 +198,7 @@ export class Configuration { intWalletAddress: process.env.INT_WALLET_ADDRESS, stakingWalletAddress: process.env.STAKING_WALLET_ADDRESS, btcCollectorAddress: process.env.BTC_COLLECTOR_ADDRESS, + btcOutWalletAddress: process.env.BTC_OUT_WALLET_ADDRESS, minTxAmount: 0.00000297, minDeposit: { Fiat: { diff --git a/src/payment/models/crypto-input/btc-input.service.ts b/src/payment/models/crypto-input/btc-input.service.ts index 61e6489cb2..7c7cfcb87e 100644 --- a/src/payment/models/crypto-input/btc-input.service.ts +++ b/src/payment/models/crypto-input/btc-input.service.ts @@ -13,27 +13,26 @@ import { NodeNotAccessibleError } from 'src/payment/exceptions/node-not-accessib import { CryptoInputService } from './crypto-input.service'; import { BtcClient } from 'src/blockchain/ain/node/btc-client'; import { CryptoRouteService } from '../crypto-route/crypto-route.service'; -import { HttpService } from 'src/shared/services/http.service'; import { BuyCryptoService } from '../buy-crypto/services/buy-crypto.service'; import { ChainalysisService } from './chainalysis.service'; import { Blockchain } from 'src/blockchain/shared/enums/blockchain.enum'; import { AmlCheck } from '../buy-crypto/enums/aml-check.enum'; +import { BtcFeeService } from 'src/blockchain/ain/services/btc-fee.service'; @Injectable() export class BtcInputService extends CryptoInputService { private readonly lock = new Lock(7200); - private readonly btcFeeUrl = 'https://mempool.space/api/v1/fees/recommended'; private btcClient: BtcClient; constructor( nodeService: NodeService, cryptoInputRepo: CryptoInputRepository, - private readonly http: HttpService, private readonly assetService: AssetService, private readonly cryptoRouteService: CryptoRouteService, private readonly buyCryptoService: BuyCryptoService, private readonly chainalysisService: ChainalysisService, + private readonly feeService: BtcFeeService, ) { super(cryptoInputRepo); nodeService.getConnectedNode(NodeType.BTC_INPUT).subscribe((bitcoinClient) => (this.btcClient = bitcoinClient)); @@ -177,16 +176,8 @@ export class BtcInputService extends CryptoInputService { } private async getFeeRate(amount: number): Promise { - const { fastestFee } = await this.http.get<{ - fastestFee: number; - halfHourFee: number; - hourFee: number; - economyFee: number; - minimumFee: number; - }>(this.btcFeeUrl, { - tryCount: 3, - }); - return Math.floor(Math.max(Math.min(fastestFee, 500 * amount), 1)); + const feeRate = await this.feeService.getRecommendedFeeRate(); + return Math.floor(Math.max(Math.min(feeRate, 500 * amount), 1)); } // --- CONFIRMATION HANDLING --- // diff --git a/src/payment/models/payout/services/payout-bitcoin.service.ts b/src/payment/models/payout/services/payout-bitcoin.service.ts index a6d2d83ab9..e670ddbfda 100644 --- a/src/payment/models/payout/services/payout-bitcoin.service.ts +++ b/src/payment/models/payout/services/payout-bitcoin.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { BtcClient } from 'src/blockchain/ain/node/btc-client'; import { NodeService, NodeType } from 'src/blockchain/ain/node/node.service'; +import { BtcFeeService } from 'src/blockchain/ain/services/btc-fee.service'; import { PayoutOrderContext } from '../entities/payout-order.entity'; import { PayoutGroup, PayoutJellyfishService } from './base/payout-jellyfish.service'; @@ -8,7 +9,7 @@ import { PayoutGroup, PayoutJellyfishService } from './base/payout-jellyfish.ser export class PayoutBitcoinService extends PayoutJellyfishService { #client: BtcClient; - constructor(readonly nodeService: NodeService) { + constructor(readonly nodeService: NodeService, private readonly feeService: BtcFeeService) { super(); nodeService.getConnectedNode(NodeType.BTC_OUTPUT).subscribe((client) => (this.#client = client)); } @@ -22,7 +23,8 @@ export class PayoutBitcoinService extends PayoutJellyfishService { } async sendUtxoToMany(_context: PayoutOrderContext, payout: PayoutGroup): Promise { - return this.#client.sendUtxoToMany(payout); + const feeRate = await this.feeService.getRecommendedFeeRate(); + return this.#client.sendMany(payout, feeRate); } async checkPayoutCompletion(_context: any, payoutTxId: string): Promise { From 95ed07ebf0a1aa9cf15030b974b0443c5494f14b Mon Sep 17 00:00:00 2001 From: David May Date: Wed, 19 Oct 2022 11:52:18 +0200 Subject: [PATCH 2/2] [DEV-893] Updated environment --- infrastructure/bicep/dfx-api.bicep | 5 +++++ infrastructure/bicep/parameters/dev.json | 3 +++ infrastructure/bicep/parameters/loc.json | 3 +++ infrastructure/bicep/parameters/prd.json | 3 +++ 4 files changed, 14 insertions(+) diff --git a/infrastructure/bicep/dfx-api.bicep b/infrastructure/bicep/dfx-api.bicep index 22882dda66..3253a2fc72 100644 --- a/infrastructure/bicep/dfx-api.bicep +++ b/infrastructure/bicep/dfx-api.bicep @@ -38,6 +38,7 @@ param intWalletAddress string param stakingWalletAddress string param utxoSpenderAddress string param btcCollectorAddress string +param btcOutWalletAddress string param ethWalletAddress string @secure() @@ -565,6 +566,10 @@ resource apiAppService 'Microsoft.Web/sites@2018-11-01' = { name: 'BTC_COLLECTOR_ADDRESS' value: btcCollectorAddress } + { + name: 'BTC_OUT_WALLET_ADDRESS' + value: btcOutWalletAddress + } { name: 'FTP_HOST' value: '138.201.74.234' diff --git a/infrastructure/bicep/parameters/dev.json b/infrastructure/bicep/parameters/dev.json index a60e92d50f..10180af8bf 100644 --- a/infrastructure/bicep/parameters/dev.json +++ b/infrastructure/bicep/parameters/dev.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "P1v2" }, diff --git a/infrastructure/bicep/parameters/loc.json b/infrastructure/bicep/parameters/loc.json index f2f99b3a3a..09b97f23fc 100644 --- a/infrastructure/bicep/parameters/loc.json +++ b/infrastructure/bicep/parameters/loc.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "B1" }, diff --git a/infrastructure/bicep/parameters/prd.json b/infrastructure/bicep/parameters/prd.json index d7772428b4..876fe9cd0f 100644 --- a/infrastructure/bicep/parameters/prd.json +++ b/infrastructure/bicep/parameters/prd.json @@ -110,6 +110,9 @@ "btcCollectorAddress": { "value": "xxx" }, + "btcOutWalletAddress": { + "value": "xxx" + }, "nodeServicePlanSkuName": { "value": "P2v2" },