From df18e81f021c0cf0601be56a9378fb3feeec4a42 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 20:52:33 +0800 Subject: [PATCH] fix: fix dust error in Nexa (#4562) --- packages/engine/src/vaults/VaultBase.ts | 21 -------- packages/engine/src/vaults/impl/nexa/Vault.ts | 49 +++++++++++++++++-- .../engine/src/vaults/impl/nexa/sdk/nexa.ts | 1 - packages/engine/src/vaults/impl/nexa/utils.ts | 18 ++++--- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/packages/engine/src/vaults/VaultBase.ts b/packages/engine/src/vaults/VaultBase.ts index c8c48e205c2..07af08d0a54 100644 --- a/packages/engine/src/vaults/VaultBase.ts +++ b/packages/engine/src/vaults/VaultBase.ts @@ -786,27 +786,6 @@ export abstract class VaultBase extends VaultBaseChainOnly { return nextNonce; } - getConfirmedUTXOs( - utxos: T[], - amount: string, - ): T[] { - const confirmedUTXOs = utxos.sort((a, b) => - new BigNumber(b.value).gt(a.value) ? 1 : -1, - ); - let sum = new BigNumber(0); - let i = 0; - for (i = 0; i < confirmedUTXOs.length; i += 1) { - sum = sum.plus(confirmedUTXOs[i].value); - if (sum.gt(amount)) { - break; - } - } - if (sum.lt(amount)) { - return []; - } - return confirmedUTXOs.slice(0, i + 1); - } - validateSendAmount(amount: string, tokenBalance: string, to: string) { return Promise.resolve(true); } diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index 8e6c6981688..98edf494b95 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -243,6 +243,29 @@ export default class Vault extends VaultBase { return Promise.resolve({} as IDecodedTxLegacy); } + getConfirmedUTXOs( + utxos: T[], + amount: string, + minTransferAmount = '0', + ): T[] { + const transactionAmount = new BigNumber(amount).plus(minTransferAmount); + const confirmedUTXOs = utxos.sort((a, b) => + new BigNumber(b.value).gt(a.value) ? 1 : -1, + ); + let sum = new BigNumber(0); + let i = 0; + for (i = 0; i < confirmedUTXOs.length; i += 1) { + sum = sum.plus(confirmedUTXOs[i].value); + if (sum.gt(transactionAmount)) { + break; + } + } + if (sum.lt(transactionAmount)) { + return []; + } + return confirmedUTXOs.slice(0, i + 1); + } + override async buildEncodedTxFromTransfer( transferInfo: ITransferInfo, ): Promise { @@ -288,6 +311,15 @@ export default class Vault extends VaultBase { return Promise.resolve(encodedTx); } + async getMinTransferAmount() { + const network = await this.getNetwork(); + return network.settings.minTransferAmount + ? new BigNumber(network.settings.minTransferAmount) + .shiftedBy(network.decimals) + .toFixed() + : undefined; + } + override async buildUnsignedTxFromEncodedTx( encodedTx: IEncodedTxNexa, ): Promise { @@ -302,6 +334,7 @@ export default class Vault extends VaultBase { .shiftedBy(network.decimals) .plus(encodedTx?.gas || 0) .toFixed(), + await this.getMinTransferAmount(), ); if (confirmedUTXOs.length > client.MAX_TX_NUM_VIN) { @@ -339,11 +372,21 @@ export default class Vault extends VaultBase { ): Promise { const network = await this.getNetwork(); const client = await this.getSDKClient(); - const estimateSizedSize = estimateSize(encodedTx); + const vinLength = this.getConfirmedUTXOs( + encodedTx.inputs.map((input) => ({ + ...input, + value: input.satoshis, + })), + new BigNumber(encodedTx.transferInfo?.amount || 0) + .shiftedBy(network.decimals) + .toFixed(), + await this.getMinTransferAmount(), + ).length; + const estimateSizedSize = estimateSize(vinLength, encodedTx.outputs); const remoteEstimateFee = await client.estimateFee(estimateSizedSize); - const localEstimateFee = estimateFee(encodedTx); + const localEstimateFee = estimateFee(vinLength, encodedTx.outputs); const feeInfo = specifiedFeeRate - ? estimateFee(encodedTx, Number(specifiedFeeRate)) + ? estimateFee(vinLength, encodedTx.outputs, Number(specifiedFeeRate)) : Math.max(remoteEstimateFee, localEstimateFee); return { nativeSymbol: network.symbol, diff --git a/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts b/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts index 8b78bb5632f..d235e28412d 100644 --- a/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts +++ b/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts @@ -60,7 +60,6 @@ export class Nexa extends SimpleClient { async getTransaction(txHash: string): Promise { return this.rpc.call('blockchain.transaction.get', [ txHash, - true, ]); } diff --git a/packages/engine/src/vaults/impl/nexa/utils.ts b/packages/engine/src/vaults/impl/nexa/utils.ts index ede353ab126..421846e6c4b 100644 --- a/packages/engine/src/vaults/impl/nexa/utils.ts +++ b/packages/engine/src/vaults/impl/nexa/utils.ts @@ -142,14 +142,17 @@ const DEFAULT_SEQNUMBER = MAXINT; const FEE_PER_KB = 1000 * 3; const CHANGE_OUTPUT_MAX_SIZE = 1 + 8 + 1 + 23; -export function estimateSize(encodedTx: IEncodedTxNexa) { +export function estimateSize( + vinlength: number, + vouts: IEncodedTxNexa['outputs'], +) { let estimatedSize = 4 + 1; // locktime + version - estimatedSize += encodedTx.inputs.length < 253 ? 1 : 3; - encodedTx.inputs.forEach(() => { + estimatedSize += vinlength < 253 ? 1 : 3; + new Array(vinlength).fill(0).forEach(() => { // type + outpoint + scriptlen + script + sequence + amount estimatedSize += 1 + 32 + 1 + 100 + 4 + 8; }); - encodedTx.outputs.forEach((output) => { + vouts.forEach((output) => { const bfr = getScriptBufferFromScriptTemplateOut(output.address); estimatedSize += convertScriptToPushBuffer(bfr).length + 1 + 8 + 1; }); @@ -157,10 +160,11 @@ export function estimateSize(encodedTx: IEncodedTxNexa) { } export function estimateFee( - encodedTx: IEncodedTxNexa, + vinlength: number, + vouts: IEncodedTxNexa['outputs'], feeRate = FEE_PER_KB / 1000, ): number { - const size = estimateSize(encodedTx); + const size = estimateSize(vinlength, vouts); const feeWithChange = Math.ceil( size * feeRate + CHANGE_OUTPUT_MAX_SIZE * feeRate, ); @@ -292,7 +296,7 @@ function buildSignatures(encodedTx: IEncodedTxNexa, dbAccountAddress: string) { new BN(0), ); - const fee = new BN(gas || estimateFee(encodedTx)); + const fee = new BN(gas || estimateFee(inputs.length, outputs)); const available = inputAmount.sub(fee); if (available.lt(new BN(0))) { console.error(inputAmount.toString(), fee.toString());