Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions infrastructure/bicep/dfx-api.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ param intWalletAddress string
param stakingWalletAddress string
param utxoSpenderAddress string
param btcCollectorAddress string
param btcOutWalletAddress string

param ethWalletAddress string
@secure()
Expand Down Expand Up @@ -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'
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "P1v2"
},
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "B1"
},
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/bicep/parameters/prd.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
"btcCollectorAddress": {
"value": "xxx"
},
"btcOutWalletAddress": {
"value": "xxx"
},
"nodeServicePlanSkuName": {
"value": "P2v2"
},
Expand Down
5 changes: 3 additions & 2 deletions src/blockchain/ain/ain.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
21 changes: 21 additions & 0 deletions src/blockchain/ain/node/btc-client.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -27,4 +28,24 @@ export class BtcClient extends NodeClient {
true,
).then((r) => r.txid);
}

async sendMany(payload: { addressTo: string; amount: number }[], feeRate: number): Promise<string> {
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);
}
}
23 changes: 23 additions & 0 deletions src/blockchain/ain/services/btc-fee.service.ts
Original file line number Diff line number Diff line change
@@ -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<number> {
const { fastestFee } = await this.http.get<{
fastestFee: number;
halfHourFee: number;
hourFee: number;
economyFee: number;
minimumFee: number;
}>(this.btcFeeUrl, {
tryCount: 3,
});

return fastestFee;
}
}
1 change: 1 addition & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
17 changes: 4 additions & 13 deletions src/payment/models/crypto-input/btc-input.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -177,16 +176,8 @@ export class BtcInputService extends CryptoInputService {
}

private async getFeeRate(amount: number): Promise<number> {
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 --- //
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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';

@Injectable()
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));
}
Expand All @@ -22,7 +23,8 @@ export class PayoutBitcoinService extends PayoutJellyfishService {
}

async sendUtxoToMany(_context: PayoutOrderContext, payout: PayoutGroup): Promise<string> {
return this.#client.sendUtxoToMany(payout);
const feeRate = await this.feeService.getRecommendedFeeRate();
return this.#client.sendMany(payout, feeRate);
}

async checkPayoutCompletion(_context: any, payoutTxId: string): Promise<boolean> {
Expand Down