From 24689a0254d3fdcdead525246d41bcd71a1032da Mon Sep 17 00:00:00 2001 From: huhuanming Date: Thu, 9 May 2024 22:17:44 +0800 Subject: [PATCH 1/9] feat: add getConfirmedUTXOs --- packages/engine/src/vaults/VaultBase.ts | 21 +++++++++++++++++++ packages/engine/src/vaults/impl/nexa/Vault.ts | 19 ++++++++++++++--- .../engine/src/vaults/impl/nexa/sdk/nexa.ts | 2 ++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/engine/src/vaults/VaultBase.ts b/packages/engine/src/vaults/VaultBase.ts index 07af08d0a54..9f875fa6789 100644 --- a/packages/engine/src/vaults/VaultBase.ts +++ b/packages/engine/src/vaults/VaultBase.ts @@ -786,6 +786,27 @@ 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, + ); + const sum = new BigNumber(0); + let i = 0; + for (i = 0; i < confirmedUTXOs.length; i += 1) { + 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 56e80c3fec9..eedd8ff5aef 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -53,7 +53,7 @@ import type { Token } from '../../../types/token'; import type { KeyringSoftwareBase } from '../../keyring/KeyringSoftwareBase'; import type { IDecodedTxLegacy, IHistoryTx, ISignedTxPro } from '../../types'; import type { EVMDecodedItem } from '../evm/decoder/types'; -import type { IEncodedTxNexa, INexaTransaction } from './types'; +import type { IEncodedTxNexa, IListUTXO, INexaTransaction } from './types'; export default class Vault extends VaultBase { keyringMap = { @@ -243,13 +243,26 @@ export default class Vault extends VaultBase { ): Promise { const client = await this.getSDKClient(); const fromNexaAddress = transferInfo.from; - const utxos = (await client.getNexaUTXOs(fromNexaAddress)).filter( + const rawUTXOS = (await client.getNexaUTXOs(fromNexaAddress)).filter( (value) => !value.has_token, ); + const confirmedUTXOs = this.getConfirmedUTXOs( + rawUTXOS, + transferInfo.amount, + ); + + if (confirmedUTXOs.length > client.MAX_TX_NUM_VIN) { + const maximumAmount = rawUTXOS + .slice(0, client.MAX_TX_NUM_VIN) + .reduce((acc, cur) => acc.plus(cur.value), new BigNumber(0)); + throw new OneKeyInternalError( + `Too many vins, The maximum amount for this transfer is ${maximumAmount.toString()} satoshi.`, + ); + } const network = await this.getNetwork(); return { - inputs: utxos.map((utxo) => ({ + inputs: confirmedUTXOs.map((utxo) => ({ txId: utxo.outpoint_hash, outputIndex: utxo.tx_pos, satoshis: new BigNumber(utxo.value).toFixed(), diff --git a/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts b/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts index 47dfe6add2c..8b78bb5632f 100644 --- a/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts +++ b/packages/engine/src/vaults/impl/nexa/sdk/nexa.ts @@ -15,6 +15,8 @@ import type { IListUTXO, INexaHistoryItem, INexaTransaction } from '../types'; export class Nexa extends SimpleClient { readonly rpc: WebSocketRequest; + readonly MAX_TX_NUM_VIN = 256; + constructor( url: string, readonly defaultFinality: 'optimistic' | 'final' = 'optimistic', From cb164568b40e6c13ae827be707fd02dd059d675b Mon Sep 17 00:00:00 2001 From: huhuanming Date: Thu, 9 May 2024 22:54:13 +0800 Subject: [PATCH 2/9] fix: fix missing gas --- packages/engine/src/vaults/impl/nexa/Vault.ts | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index eedd8ff5aef..0ce3ff4f3ed 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -51,7 +51,12 @@ import type { } from '../../../types/provider'; import type { Token } from '../../../types/token'; import type { KeyringSoftwareBase } from '../../keyring/KeyringSoftwareBase'; -import type { IDecodedTxLegacy, IHistoryTx, ISignedTxPro } from '../../types'; +import type { + IDecodedTxLegacy, + IHistoryTx, + ISignCredentialOptions, + ISignedTxPro, +} from '../../types'; import type { EVMDecodedItem } from '../evm/decoder/types'; import type { IEncodedTxNexa, IListUTXO, INexaTransaction } from './types'; @@ -243,26 +248,12 @@ export default class Vault extends VaultBase { ): Promise { const client = await this.getSDKClient(); const fromNexaAddress = transferInfo.from; - const rawUTXOS = (await client.getNexaUTXOs(fromNexaAddress)).filter( + const uxtos = (await client.getNexaUTXOs(fromNexaAddress)).filter( (value) => !value.has_token, ); - const confirmedUTXOs = this.getConfirmedUTXOs( - rawUTXOS, - transferInfo.amount, - ); - - if (confirmedUTXOs.length > client.MAX_TX_NUM_VIN) { - const maximumAmount = rawUTXOS - .slice(0, client.MAX_TX_NUM_VIN) - .reduce((acc, cur) => acc.plus(cur.value), new BigNumber(0)); - throw new OneKeyInternalError( - `Too many vins, The maximum amount for this transfer is ${maximumAmount.toString()} satoshi.`, - ); - } - const network = await this.getNetwork(); return { - inputs: confirmedUTXOs.map((utxo) => ({ + inputs: uxtos.map((utxo) => ({ txId: utxo.outpoint_hash, outputIndex: utxo.tx_pos, satoshis: new BigNumber(utxo.value).toFixed(), @@ -297,15 +288,46 @@ export default class Vault extends VaultBase { return Promise.resolve(encodedTx); } - override buildUnsignedTxFromEncodedTx( + override async buildUnsignedTxFromEncodedTx( encodedTx: IEncodedTxNexa, ): Promise { - return Promise.resolve({ + const client = await this.getSDKClient(); + const network = await this.getNetwork(); + const confirmedUTXOs = this.getConfirmedUTXOs( + encodedTx.inputs.map((input) => ({ + ...input, + value: input.satoshis, + })), + new BigNumber(encodedTx.transferInfo?.amount || 0) + .shiftedBy(network.decimals) + .plus(encodedTx?.gas || 0) + .toFixed(), + ); + + if (confirmedUTXOs.length > client.MAX_TX_NUM_VIN) { + const maximumAmount = confirmedUTXOs + .slice(0, client.MAX_TX_NUM_VIN) + .reduce((acc, cur) => acc.plus(cur.value), new BigNumber(0)); + throw new OneKeyInternalError( + `Too many vins, The maximum amount for this transfer is ${maximumAmount + .shiftedBy(network.decimals) + .toFixed()} satoshi.`, + ); + } + return { inputs: [], outputs: [], - payload: { encodedTx }, + payload: { + encodedTx: { + ...encodedTx, + inputs: confirmedUTXOs.map((utxo) => ({ + ...utxo, + satoshis: utxo.value, + })), + }, + }, encodedTx, - }); + }; } override async getTransactionStatuses( From 7e3cd66be1b8aa7af1995d9257ec74e32293a64c Mon Sep 17 00:00:00 2001 From: huhuanming Date: Thu, 9 May 2024 23:50:24 +0800 Subject: [PATCH 3/9] fix: change to min fee --- packages/engine/src/vaults/impl/nexa/Vault.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index 0ce3ff4f3ed..872a324c7c8 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -349,7 +349,7 @@ export default class Vault extends VaultBase { const localEstimateFee = estimateFee(encodedTx); const feeInfo = specifiedFeeRate ? estimateFee(encodedTx, Number(specifiedFeeRate)) - : Math.max(remoteEstimateFee, localEstimateFee); + : Math.min(remoteEstimateFee, localEstimateFee); return { nativeSymbol: network.symbol, nativeDecimals: network.decimals, From 1eebebbeb983f961053f58ceb1a7c0e7935c4b55 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 00:29:11 +0800 Subject: [PATCH 4/9] fix: fix bug --- packages/engine/src/vaults/VaultBase.ts | 4 ++-- packages/engine/src/vaults/impl/nexa/Vault.ts | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/engine/src/vaults/VaultBase.ts b/packages/engine/src/vaults/VaultBase.ts index 9f875fa6789..c8c48e205c2 100644 --- a/packages/engine/src/vaults/VaultBase.ts +++ b/packages/engine/src/vaults/VaultBase.ts @@ -793,10 +793,10 @@ export abstract class VaultBase extends VaultBaseChainOnly { const confirmedUTXOs = utxos.sort((a, b) => new BigNumber(b.value).gt(a.value) ? 1 : -1, ); - const sum = new BigNumber(0); + let sum = new BigNumber(0); let i = 0; for (i = 0; i < confirmedUTXOs.length; i += 1) { - sum.plus(confirmedUTXOs[i].value); + sum = sum.plus(confirmedUTXOs[i].value); if (sum.gt(amount)) { break; } diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index 872a324c7c8..8e6c6981688 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -310,21 +310,16 @@ export default class Vault extends VaultBase { .reduce((acc, cur) => acc.plus(cur.value), new BigNumber(0)); throw new OneKeyInternalError( `Too many vins, The maximum amount for this transfer is ${maximumAmount - .shiftedBy(network.decimals) - .toFixed()} satoshi.`, + .shiftedBy(-network.decimals) + .toFixed()} ${network.symbol}.`, ); } + encodedTx.inputs = confirmedUTXOs; return { inputs: [], outputs: [], payload: { - encodedTx: { - ...encodedTx, - inputs: confirmedUTXOs.map((utxo) => ({ - ...utxo, - satoshis: utxo.value, - })), - }, + encodedTx, }, encodedTx, }; @@ -349,7 +344,7 @@ export default class Vault extends VaultBase { const localEstimateFee = estimateFee(encodedTx); const feeInfo = specifiedFeeRate ? estimateFee(encodedTx, Number(specifiedFeeRate)) - : Math.min(remoteEstimateFee, localEstimateFee); + : Math.max(remoteEstimateFee, localEstimateFee); return { nativeSymbol: network.symbol, nativeDecimals: network.decimals, From bd67993527d042539039dca32bc19fc0e6e3f0f3 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 14:30:45 +0800 Subject: [PATCH 5/9] fix: remove unused params --- packages/engine/src/vaults/impl/nexa/sdk/nexa.ts | 1 - 1 file changed, 1 deletion(-) 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, ]); } From 7f75a49c7b2c74671dcaa05e308b3f82642ab178 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 15:38:23 +0800 Subject: [PATCH 6/9] fix: reduce fee --- packages/engine/src/vaults/impl/nexa/Vault.ts | 15 ++++++++++++--- packages/engine/src/vaults/impl/nexa/utils.ts | 16 ++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index 8e6c6981688..d61a9146da4 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -339,11 +339,20 @@ 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(), + ).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/utils.ts b/packages/engine/src/vaults/impl/nexa/utils.ts index ede353ab126..e7b963044c9 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, ); From d8a779ec21a23b3e7bfeb1339744e9043d5fc143 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 16:06:12 +0800 Subject: [PATCH 7/9] fix: fix dust error --- packages/engine/src/vaults/VaultBase.ts | 6 ++++-- packages/engine/src/vaults/impl/nexa/Vault.ts | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/engine/src/vaults/VaultBase.ts b/packages/engine/src/vaults/VaultBase.ts index c8c48e205c2..392199b5591 100644 --- a/packages/engine/src/vaults/VaultBase.ts +++ b/packages/engine/src/vaults/VaultBase.ts @@ -789,7 +789,9 @@ export abstract class VaultBase extends VaultBaseChainOnly { 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, ); @@ -797,11 +799,11 @@ export abstract class VaultBase extends VaultBaseChainOnly { let i = 0; for (i = 0; i < confirmedUTXOs.length; i += 1) { sum = sum.plus(confirmedUTXOs[i].value); - if (sum.gt(amount)) { + if (sum.gt(transactionAmount)) { break; } } - if (sum.lt(amount)) { + if (sum.lt(transactionAmount)) { return []; } return confirmedUTXOs.slice(0, i + 1); diff --git a/packages/engine/src/vaults/impl/nexa/Vault.ts b/packages/engine/src/vaults/impl/nexa/Vault.ts index d61a9146da4..fb954bbbc87 100644 --- a/packages/engine/src/vaults/impl/nexa/Vault.ts +++ b/packages/engine/src/vaults/impl/nexa/Vault.ts @@ -288,6 +288,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 +311,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) { @@ -347,6 +357,7 @@ export default class Vault extends VaultBase { 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); From 47c8508beeb01d4bd0f36ca9688bbd0ddce88ad9 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 16:14:21 +0800 Subject: [PATCH 8/9] refactor: move getConfirmedUTXOs --- packages/engine/src/vaults/VaultBase.ts | 28 ------------------- packages/engine/src/vaults/impl/nexa/Vault.ts | 23 +++++++++++++++ 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/engine/src/vaults/VaultBase.ts b/packages/engine/src/vaults/VaultBase.ts index 80cd90b322a..07af08d0a54 100644 --- a/packages/engine/src/vaults/VaultBase.ts +++ b/packages/engine/src/vaults/VaultBase.ts @@ -786,34 +786,6 @@ export abstract class VaultBase extends VaultBaseChainOnly { return nextNonce; } - 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)) { - 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 fb954bbbc87..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 { From 919c4a33aca372a3c6793aa25ad99e63e6762fad Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 10 May 2024 18:04:08 +0800 Subject: [PATCH 9/9] fix: fix missing outputs --- packages/engine/src/vaults/impl/nexa/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/src/vaults/impl/nexa/utils.ts b/packages/engine/src/vaults/impl/nexa/utils.ts index e7b963044c9..421846e6c4b 100644 --- a/packages/engine/src/vaults/impl/nexa/utils.ts +++ b/packages/engine/src/vaults/impl/nexa/utils.ts @@ -296,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());