From c62bf0868bf49af43569e5186211c70094fde67c Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 8 Aug 2025 08:09:42 -0700 Subject: [PATCH] Improve unit conversion. --- src/lib/tools.ts | 73 +++++++++++++++++++++++++++------------------ test/test.tools.mjs | 2 +- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 8bf22bf..4f66a7b 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -21,51 +21,66 @@ type SweepResult = { /** * Converts a decimal amount of nano from one unit divider to another. * -* @param {bigint|string} amount - Decimal amount to convert +* @param {(bigint|string)} amount - Decimal amount to convert * @param {string} inputUnit - Current denomination * @param {string} outputUnit - Desired denomination */ -export async function convert (amount: bigint | string, inputUnit: string, outputUnit: string): Promise { - if (typeof amount === 'bigint') { - amount = amount.toString() +export async function convert (amount: bigint | string, inputUnit: string, outputUnit: string): Promise +export async function convert (amount: unknown, inputUnit: unknown, outputUnit: unknown): Promise { + if (typeof amount !== 'bigint' && typeof amount !== 'string') { + throw new Error('Invalid amount', { cause: typeof amount }) } - if (typeof amount !== 'string' || amount === '' || !/^[0-9]*\.?[0-9]*$/.test(amount)) { - throw new Error('Invalid amount') + if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) { + throw new Error('Invalid amount', { cause: amount }) } - let [i = '', f = ''] = amount.toString().split('.') - i = i.replace(/^0*/g, '') - f = f.replace(/0*$/g, '') - - inputUnit = inputUnit.toUpperCase() - outputUnit = outputUnit.toUpperCase() - if (!(inputUnit in UNITS)) { + if (typeof inputUnit !== 'string') { + throw new TypeError('Invalid input unit', { cause: typeof inputUnit }) + } + if (typeof outputUnit !== 'string') { + throw new TypeError('Invalid output unit', { cause: typeof outputUnit }) + } + if (typeof inputUnit !== 'string' || UNITS[inputUnit.toUpperCase()] == null) { throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`) } - if (!(outputUnit in UNITS)) { + if (typeof outputUnit !== 'string' || UNITS[outputUnit.toUpperCase()] == null) { throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`) } + let i = '0', f = '0' + if (typeof amount === 'bigint') { + i = amount.toString() + } else { + [i, f] = amount.split('.') + } + // convert to raw - i = `${i}${f.slice(0, UNITS[inputUnit])}`.padEnd(i.length + UNITS[inputUnit], '0') - f = f.slice(UNITS[inputUnit]) - if (i.length > 39) { - throw new Error('Amount exceeds Nano limits') + const inUnit = UNITS[inputUnit.toUpperCase()] + i = i.padEnd(i.length + inUnit, '0') + let int = BigInt(i) + if (f != null) { + f = f.padEnd(f.length + inUnit, '0') + let frac = BigInt(f) + const remainder = frac % (10n ** BigInt(inUnit)) + if (remainder > 0n) { + throw new Error('Amount contains fractional raw', { cause: remainder }) + } + frac /= 10n ** BigInt(f.length - inUnit) + int += frac } - if (i.length < 1) { - throw new Error('Amount must be at least 1 raw') + if (int > MAX_SUPPLY) { + throw new Error('Amount exceeds available supply') } - if (f.length > 0) { - throw new Error('Amount contains fractional raw') + if (int < 1n) { + throw new Error('Amount must be at least 1 raw') } + i = int.toString().padStart(40, '0') // convert to desired denomination - if (UNITS[outputUnit] !== 0) { - f = i.padStart(40, '0').slice(-UNITS[outputUnit]) + f - i = i.slice(0, -UNITS[outputUnit]) - } - i = i.replace(/^0*/g, '') ?? '0' - f = f.replace(/0*$/g, '') - return `${i}${f ? '.' : ''}${f}` + const outUnit = UNITS[outputUnit.toUpperCase()] + f = i.slice(40 - outUnit).replace(/0*$/g, '') + i = i.slice(0, 40 - outUnit).replace(/^0*/g, '') + + return `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}` } function hash (data: string | string[], encoding?: 'hex'): Uint8Array diff --git a/test/test.tools.mjs b/test/test.tools.mjs index 5d05c97..1df3913 100644 --- a/test/test.tools.mjs +++ b/test/test.tools.mjs @@ -49,7 +49,7 @@ await Promise.all([ await test('should convert 1 raw to 10^-29 nano', async () => { const result = await Tools.convert('1', 'RAW', 'NANO') - assert.equal(result, '.000000000000000000000000000001') + assert.equal(result, '0.000000000000000000000000000001') }) await test('should ignore leading and trailing zeros', async () => { -- 2.47.3