From fa6d83326b32831f641c316fe3b0618adc1161ca Mon Sep 17 00:00:00 2001 From: tuliomir Date: Tue, 16 Dec 2025 12:47:00 -0300 Subject: [PATCH 1/5] docs: typings for utxo management --- src/new/wallet.ts | 131 +++++++++++----------------------------------- 1 file changed, 32 insertions(+), 99 deletions(-) diff --git a/src/new/wallet.ts b/src/new/wallet.ts index 3da6e461d..1afa9476d 100644 --- a/src/new/wallet.ts +++ b/src/new/wallet.ts @@ -1071,46 +1071,14 @@ class HathorWallet extends EventEmitter { return addressInfo; } - /** - * - * @typedef UtxoOptions - * @property {number} [max_utxos] - Maximum number of utxos to aggregate. Default to MAX_INPUTS (255). - * @property {string} [token] - Token to filter the utxos. If not sent, we select only HTR utxos. - * @property {number} [authorities] - Authorities to filter the utxos. If not sent, we select only non authority utxos. - * @property {string} [filter_address] - Address to filter the utxos. - * @property {bigint} [amount_smaller_than] - Maximum limit of utxo amount to filter the utxos list. We will consolidate only utxos that have an amount lower than or equal to this value. Integer representation of decimals, i.e. 100 = 1.00. - * @property {bigint} [amount_bigger_than] - Minimum limit of utxo amount to filter the utxos list. We will consolidate only utxos that have an amount bigger than or equal to this value. Integer representation of decimals, i.e. 100 = 1.00. - * @property {bigint} [max_amount] - Limit the maximum total amount to consolidate summing all utxos. Integer representation of decimals, i.e. 100 = 1.00. - * @property {boolean} [only_available_utxos] - Use only available utxos (not locked) - */ - - /** - * @typedef UtxoInfo - * @property {string} address - Address that owns the UTXO. - * @property {bigint} amount - Amount of tokens. - * @property {string} tx_id - Original transaction id. - * @property {boolean} locked - If the output is currently locked. - * @property {number} index - Index on the output array of the original tx. - */ - - /** - * @typedef UtxoDetails - * @property {bigint} total_amount_available - Maximum number of utxos to aggregate. Default to MAX_INPUTS (255). - * @property {bigint} total_utxos_available - Token to filter the utxos. If not sent, we select only HTR utxos. - * @property {bigint} total_amount_locked - Address to filter the utxos. - * @property {bigint} total_utxos_locked - Maximum limit of utxo amount to filter the utxos list. We will consolidate only utxos that have an amount lower than this value. Integer representation of decimals, i.e. 100 = 1.00. - * @property {UtxoInfo[]} utxos - Array of utxos - */ - /** * Get utxos of the wallet addresses * - * @param {UtxoOptions} options Utxo filtering options - * - * @return {Promise} Utxos and meta information about it + * @param options Utxo filtering options * + * @return Utxos and meta information about it */ - async getUtxos(options: any = {}): Promise { + async getUtxos(options: UtxoOptions = {}) { const newOptions: any = { token: options.token, authorities: 0, @@ -1159,32 +1127,16 @@ class HathorWallet extends EventEmitter { return utxoDetails; } - /** - * @typedef Utxo - * @property {string} txId - * @property {number} index - * @property {string} tokenId - * @property {string} address - * @property {string} value - * @property {OutputValueType} authorities - * @property {number|null} timelock - * @property {number|null} heightlock - * @property {boolean} locked - * @property {string} addressPath - */ - /** * Generates all available utxos * - * @param [options] Utxo filtering options - * @param {string} [options.token='00'] - Search for UTXOs of this token UID. - * @param {string|null} [options.filter_address=null] - Address to filter the utxos. + * @param options Utxo filtering options * * @async * @generator - * @yields {Utxo} all available utxos + * @yields all available utxos */ - async *getAvailableUtxos(options: any = {}): AsyncGenerator { + async *getAvailableUtxos(options: GetAvailableUtxosOptions = {}) { // This method only returns available utxos for await (const utxo of this.storage.selectUtxos({ ...options, only_available_utxos: true })) { const addressIndex: any = await this.getAddressIndex(utxo.address); @@ -1207,13 +1159,12 @@ class HathorWallet extends EventEmitter { /** * Get utxos of the wallet addresses to fill the amount specified. * - * @param {Object} [options] Utxo filtering options - * @param {string} [options.token='00'] - Search for UTXOs of this token UID. - * @param {string|null} [options.filter_address=null] - Address to filter the utxos. + * @param amount The amount to fill with UTXOs + * @param options Utxo filtering options * - * @return {Promise<{utxos: Utxo[], changeAmount: OutputValueType}>} Utxos and change information. + * @return Utxos and change information. */ - async getUtxosForAmount(amount: any, options: any = {}): Promise { + async getUtxosForAmount(amount: bigint, options: GetUtxosForAmountOptions = {}) { const newOptions: any = { token: NATIVE_TOKEN_UID, filter_address: null, @@ -1235,32 +1186,30 @@ class HathorWallet extends EventEmitter { /** * Mark UTXO selected_as_input. * - * @param {string} txId Transaction id of the UTXO - * @param {number} index Output index of the UTXO - * @param {boolean} [value=true] The value to set the utxos. - * @param {number?} [ttl=null] + * @param txId Transaction id of the UTXO + * @param index Output index of the UTXO + * @param value The value to set the utxos. + * @param ttl Time to live for the selection */ - async markUtxoSelected(txId: any, index: any, value: any = true, ttl: any = null): Promise { + async markUtxoSelected( + txId: string, + index: number, + value: boolean = true, + ttl: number | null = null + ): Promise { await this.storage.utxoSelectAsInput({ txId, index }, value, ttl); } /** * Prepare all required data to consolidate utxos. * - * @typedef {Object} PrepareConsolidateUtxosDataResult - * @property {{ address: string, value: OutputValueType }[]} outputs - Destiny of the consolidated utxos - * @property {{ hash: string, index: number }[]} inputs - Inputs for the consolidation transaction - * @property {{ uid: string, name: string, symbol: string }} token - HTR or custom token - * @property {UtxoInfo[]} utxos - Array of utxos that will be consolidated - * @property {bigint} total_amount - Amount to be consolidated - * - * @param {string} destinationAddress Address of the consolidated utxos - * @param {UtxoOptions} options Utxo filtering options + * @param destinationAddress Address of the consolidated utxos + * @param options Utxo filtering options * - * @return {Promise} Required data to consolidate utxos + * @return Required data to consolidate utxos * */ - async prepareConsolidateUtxosData(destinationAddress: any, options: any = {}): Promise { + async prepareConsolidateUtxosData(destinationAddress: string, options: UtxoOptions = {}) { const utxoDetails: any = await this.getUtxos({ ...options, only_available_utxos: true }); const inputs: any = []; const utxos: any = []; @@ -1289,24 +1238,16 @@ class HathorWallet extends EventEmitter { return { outputs, inputs, utxos, total_amount }; } - /** - * @typedef ConsolidationResultSendTx - * @property {number} total_utxos_consolidated - Number of utxos consolidated - * @property {bigint} total_amount - Consolidated amount - * @property {SendTransaction} sendTx - instance that will send the transaction. - * @property {UtxoInfo[]} utxos - Array of consolidated utxos - */ - /** * Consolidates many utxos into a single one for either HTR or exactly one custom token. * - * @param {string} destinationAddress Address of the consolidated utxos - * @param {UtxoOptions} options Utxo filtering options + * @param destinationAddress Address of the consolidated utxos + * @param options Utxo filtering options * - * @return {Promise} + * @return Consolidation result with SendTransaction instance * */ - async consolidateUtxosSendTransaction(destinationAddress: any, options: any = {}): Promise { + async consolidateUtxosSendTransaction(destinationAddress: string, options: UtxoOptions = {}) { if (await this.isReadonly()) { throw new WalletFromXPubGuard('consolidateUtxos'); } @@ -1333,24 +1274,16 @@ class HathorWallet extends EventEmitter { }; } - /** - * @typedef ConsolidationResult - * @property {number} total_utxos_consolidated - Number of utxos consolidated - * @property {bigint} total_amount - Consolidated amount - * @property {string} txId - Consolidated transaction id - * @property {UtxoInfo[]} utxos - Array of consolidated utxos - */ - /** * Consolidates many utxos into a single one for either HTR or exactly one custom token. * - * @param {string} destinationAddress Address of the consolidated utxos - * @param {UtxoOptions} options Utxo filtering options + * @param destinationAddress Address of the consolidated utxos + * @param options Utxo filtering options * - * @return {Promise} Indicates that the transaction is sent or not + * @return Indicates that the transaction is sent or not * */ - async consolidateUtxos(destinationAddress: any, options: any = {}): Promise { + async consolidateUtxos(destinationAddress: string, options: UtxoOptions = {}) { const { total_utxos_consolidated, total_amount, sendTx, utxos }: any = await this.consolidateUtxosSendTransaction(destinationAddress, options); From b5a167b67cf069c2390268583744ee4d34eb5636 Mon Sep 17 00:00:00 2001 From: tuliomir Date: Fri, 2 Jan 2026 12:30:32 -0300 Subject: [PATCH 2/5] docs: typings for utxo management, improved --- src/nano_contracts/builder.ts | 2 +- src/new/wallet.ts | 112 +++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/nano_contracts/builder.ts b/src/nano_contracts/builder.ts index a24ff5c72..4ba89bba0 100644 --- a/src/nano_contracts/builder.ts +++ b/src/nano_contracts/builder.ts @@ -264,7 +264,7 @@ class NanoContractTransactionBuilder { } // Get the utxos with the amount of the deposit and create the inputs - const utxoOptions: { token: string; filter_address?: string | null } = { token: action.token }; + const utxoOptions: { token: string; filter_address?: string } = { token: action.token }; if (action.address) { utxoOptions.filter_address = action.address; } diff --git a/src/new/wallet.ts b/src/new/wallet.ts index 1afa9476d..48beb475a 100644 --- a/src/new/wallet.ts +++ b/src/new/wallet.ts @@ -71,6 +71,7 @@ import { IHistoryTx, IWalletAccessData, IMultisigData, + IUtxo, } from '../types'; import transactionUtils from '../utils/transaction'; import Queue from '../models/queue'; @@ -94,11 +95,15 @@ import { WalletTxTemplateInterpreter, TransactionTemplate } from '../template/tr import Address from '../models/address'; import { GeneralTokenInfoSchema, + GetAvailableUtxosOptions, GetBalanceFullnodeFacadeReturnType, GetTxHistoryFullnodeFacadeReturnType, GetTokenDetailsFullnodeFacadeReturnType, GetTxByIdFullnodeFacadeReturnType, + GetUtxosForAmountOptions, HathorWalletConstructorParams, + UtxoDetails, + UtxoOptions, } from './types'; import { FullNodeTxApiResponse, @@ -1078,10 +1083,10 @@ class HathorWallet extends EventEmitter { * * @return Utxos and meta information about it */ - async getUtxos(options: UtxoOptions = {}) { - const newOptions: any = { + async getUtxos(options: UtxoOptions = {}): Promise { + const newOptions = { token: options.token, - authorities: 0, + authorities: 0n, max_utxos: options.max_utxos, filter_address: options.filter_address, amount_smaller_than: options.amount_smaller_than, @@ -1089,25 +1094,25 @@ class HathorWallet extends EventEmitter { max_amount: options.max_amount, only_available_utxos: options.only_available_utxos, }; - /** @type {UtxoDetails} */ - const utxoDetails: any = { + const utxoDetails: UtxoDetails = { total_amount_available: 0n, total_utxos_available: 0n, total_amount_locked: 0n, total_utxos_locked: 0n, utxos: [], }; - const nowTs: any = Math.floor(Date.now() / 1000); - const isTimeLocked: any = (timestamp: any) => timestamp && nowTs && nowTs < timestamp; - const nowHeight: any = await this.storage.getCurrentHeight(); - const rewardLock: any = this.storage.version?.reward_spend_min_blocks; + const nowTs = Math.floor(Date.now() / 1000); + const isTimeLocked = (timestamp: number | null): boolean => + !!timestamp && !!nowTs && nowTs < timestamp; + const nowHeight = await this.storage.getCurrentHeight(); + const rewardLock = this.storage.version?.reward_spend_min_blocks; for await (const utxo of this.storage.selectUtxos(newOptions)) { - const isLocked: any = + const isLocked = isTimeLocked(utxo.timelock) || transactionUtils.isHeightLocked(utxo.height, nowHeight, rewardLock); - const utxoInfo: any = { + const utxoInfo = { address: utxo.address, amount: utxo.value, tx_id: utxo.txId, @@ -1136,11 +1141,12 @@ class HathorWallet extends EventEmitter { * @generator * @yields all available utxos */ - async *getAvailableUtxos(options: GetAvailableUtxosOptions = {}) { + async *getAvailableUtxos(options: GetAvailableUtxosOptions = {}): AsyncGenerator { // This method only returns available utxos for await (const utxo of this.storage.selectUtxos({ ...options, only_available_utxos: true })) { - const addressIndex: any = await this.getAddressIndex(utxo.address); - const addressPath: any = await this.getAddressPathForIndex(addressIndex); + const addressIndex = await this.getAddressIndex(utxo.address); + const addressPath = await this.getAddressPathForIndex(addressIndex!); + // XXX: selectUtxos is supposed to return IUtxos, but here we re-create the entire object. Why? yield { txId: utxo.txId, index: utxo.index, @@ -1152,7 +1158,10 @@ class HathorWallet extends EventEmitter { heightlock: null, locked: false, addressPath, - }; + token: utxo.token, + type: utxo.type, + height: utxo.height, + } as IUtxo; } } @@ -1164,21 +1173,26 @@ class HathorWallet extends EventEmitter { * * @return Utxos and change information. */ - async getUtxosForAmount(amount: bigint, options: GetUtxosForAmountOptions = {}) { - const newOptions: any = { + async getUtxosForAmount( + amount: bigint, + options: GetUtxosForAmountOptions = {} + ): Promise<{ utxos: Utxo[]; changeAmount: bigint }> { + const newOptions = { token: NATIVE_TOKEN_UID, - filter_address: null, + filter_address: undefined, ...options, order_by_value: 'desc', }; - const utxos: any = []; + const utxos: IUtxo[] = []; for await (const utxo of this.getAvailableUtxos(newOptions)) { utxos.push(utxo); } return transactionUtils.selectUtxos( - utxos.filter(utxo => utxo.authorities === 0n), + // XXX: Only the `value` property of the UTXO is used in selectUtxos, so the cast is safe + // for this stage of the ts refactor. + utxos.filter(utxo => utxo.authorities === 0n) as unknown as Utxo[], amount ); } @@ -1195,7 +1209,7 @@ class HathorWallet extends EventEmitter { txId: string, index: number, value: boolean = true, - ttl: number | null = null + ttl: number | undefined = undefined ): Promise { await this.storage.utxoSelectAsInput({ txId, index }, value, ttl); } @@ -1209,29 +1223,39 @@ class HathorWallet extends EventEmitter { * @return Required data to consolidate utxos * */ - async prepareConsolidateUtxosData(destinationAddress: string, options: UtxoOptions = {}) { - const utxoDetails: any = await this.getUtxos({ ...options, only_available_utxos: true }); - const inputs: any = []; - const utxos: any = []; - let total_amount: any = 0n; + async prepareConsolidateUtxosData( + destinationAddress: string, + options: UtxoOptions = {} + ): Promise<{ + outputs: Array<{ address: string; value: bigint; token: string }>; + inputs: Array<{ txId: string; index: number; token: string }>; + utxos: UtxoDetails['utxos']; + total_amount: bigint; + }> { + const utxoDetails = await this.getUtxos({ ...options, only_available_utxos: true }); + const inputs: { txId: string; index: number; token: string }[] = []; + const utxos: UtxoDetails['utxos'] = []; + let total_amount = 0n; + const tokenUid = options.token || NATIVE_TOKEN_UID; for (let i = 0; i < utxoDetails.utxos.length; i++) { - if (inputs.length === this.storage.version.max_number_inputs) { + if (inputs.length === this.storage.version!.max_number_inputs) { // Max number of inputs reached break; } - const utxo: any = utxoDetails.utxos[i]; + const utxo = utxoDetails.utxos[i]; inputs.push({ txId: utxo.tx_id, index: utxo.index, + token: tokenUid, }); utxos.push(utxo); total_amount += utxo.amount; } - const outputs: any = [ + const outputs = [ { address: destinationAddress, value: total_amount, - token: options.token || NATIVE_TOKEN_UID, + token: tokenUid, }, ]; @@ -1247,11 +1271,19 @@ class HathorWallet extends EventEmitter { * @return Consolidation result with SendTransaction instance * */ - async consolidateUtxosSendTransaction(destinationAddress: string, options: UtxoOptions = {}) { + async consolidateUtxosSendTransaction( + destinationAddress: string, + options: UtxoOptions = {} + ): Promise<{ + total_utxos_consolidated: number; + total_amount: bigint; + utxos: UtxoDetails['utxos']; + sendTx: SendTransaction; + }> { if (await this.isReadonly()) { throw new WalletFromXPubGuard('consolidateUtxos'); } - const { outputs, inputs, utxos, total_amount }: any = await this.prepareConsolidateUtxosData( + const { outputs, inputs, utxos, total_amount } = await this.prepareConsolidateUtxosData( destinationAddress, options ); @@ -1283,16 +1315,24 @@ class HathorWallet extends EventEmitter { * @return Indicates that the transaction is sent or not * */ - async consolidateUtxos(destinationAddress: string, options: UtxoOptions = {}) { - const { total_utxos_consolidated, total_amount, sendTx, utxos }: any = + async consolidateUtxos( + destinationAddress: string, + options: UtxoOptions = {} + ): Promise<{ + total_utxos_consolidated: number; + total_amount: bigint; + txId: string; + utxos: UtxoDetails['utxos']; + }> { + const { total_utxos_consolidated, total_amount, sendTx, utxos } = await this.consolidateUtxosSendTransaction(destinationAddress, options); - const tx: any = await sendTx.run(); + const tx = await sendTx.run(); return { total_utxos_consolidated, total_amount, - txId: tx.hash, + txId: tx!.hash!, utxos, }; } From 29d11c4f9bb79eb5b315dab7b763aafbe7941de1 Mon Sep 17 00:00:00 2001 From: tuliomir Date: Fri, 2 Jan 2026 12:39:40 -0300 Subject: [PATCH 3/5] chore: merge fixes --- src/new/wallet.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/new/wallet.ts b/src/new/wallet.ts index 48beb475a..f41c13631 100644 --- a/src/new/wallet.ts +++ b/src/new/wallet.ts @@ -105,6 +105,7 @@ import { UtxoDetails, UtxoOptions, } from './types'; +import { Utxo } from '../wallet/types'; import { FullNodeTxApiResponse, GraphvizNeighboursDotResponse, From 697561e4992a0564f58ab2c00dda4bd219068fbf Mon Sep 17 00:00:00 2001 From: tuliomir Date: Fri, 2 Jan 2026 13:08:16 -0300 Subject: [PATCH 4/5] fix: test with new return values --- __tests__/integration/hathorwallet_others.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/__tests__/integration/hathorwallet_others.test.ts b/__tests__/integration/hathorwallet_others.test.ts index b2235ebe5..6145a5444 100644 --- a/__tests__/integration/hathorwallet_others.test.ts +++ b/__tests__/integration/hathorwallet_others.test.ts @@ -922,11 +922,14 @@ describe('getUtxosForAmount', () => { { txId: fundTx1hash, index: expect.any(Number), + token: NATIVE_TOKEN_UID, tokenId: NATIVE_TOKEN_UID, + type: 1, address: addr0, value: 10n, authorities: 0n, timelock: null, + height: null, heightlock: null, locked: false, addressPath: expect.any(String), From 9dae6995cfd25d2fca34142215f54c89f3da1851 Mon Sep 17 00:00:00 2001 From: tuliomir Date: Wed, 14 Jan 2026 11:32:23 -0300 Subject: [PATCH 5/5] docs: wording --- src/new/wallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/new/wallet.ts b/src/new/wallet.ts index f41c13631..9a8fe4307 100644 --- a/src/new/wallet.ts +++ b/src/new/wallet.ts @@ -1147,7 +1147,7 @@ class HathorWallet extends EventEmitter { for await (const utxo of this.storage.selectUtxos({ ...options, only_available_utxos: true })) { const addressIndex = await this.getAddressIndex(utxo.address); const addressPath = await this.getAddressPathForIndex(addressIndex!); - // XXX: selectUtxos is supposed to return IUtxos, but here we re-create the entire object. Why? + // XXX: selectUtxos is supposed to return IUtxos, but here we re-create the entire object. yield { txId: utxo.txId, index: utxo.index, @@ -1192,7 +1192,6 @@ class HathorWallet extends EventEmitter { return transactionUtils.selectUtxos( // XXX: Only the `value` property of the UTXO is used in selectUtxos, so the cast is safe - // for this stage of the ts refactor. utxos.filter(utxo => utxo.authorities === 0n) as unknown as Utxo[], amount );