From d49c9be0c5c3a01f28fb61569e71cf35b3ae211c Mon Sep 17 00:00:00 2001 From: SafraNako Date: Wed, 9 Sep 2026 14:57:27 +0300 Subject: [PATCH] fix: accept an explicit fee of 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `if (passedFee) { fee = passedFee } else { "Fee is invalid"; exit(1) }` treats `parseInt('0', 10)` (a valid zero fee) the same as a garbage value, so `qrl send -f 0`, `send-message -f 0`, `notarize -f 0`, `generate-lattice-keys -f 0` and `sign-tx-offline -f 0` all abort with "Fee is invalid" — even though 0 is exactly the default the commands use when `-f` is omitted (the node accepts any fee >= 0). Reject only a non-numeric or negative value (`Number.isNaN(passedFee) || passedFee < 0`); `-f abc` / `-f .01` / `-f -5` still fail as before. Also fix the stale "defaults to 100 Shor" text on send / send-message / generate-lattice-keys — those initialise `fee = 0`. sign-tx-offline keeps its real 100 default. Adds an offline `send #3c` regression test: `send -f 0 -T ` exits 0 and writes a transaction whose `fee` is "0". Co-Authored-By: Claude Sonnet 5 --- src/commands/generate-lattice-keys.js | 9 +++---- src/commands/notarize.js | 5 ++-- src/commands/send-message.js | 9 +++---- src/commands/send.js | 9 +++---- src/commands/sign-tx-offline.js | 5 ++-- test/commands/send.test.js | 36 ++++++++++++++++++++++++++- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/commands/generate-lattice-keys.js b/src/commands/generate-lattice-keys.js index d4833bd..8db3018 100644 --- a/src/commands/generate-lattice-keys.js +++ b/src/commands/generate-lattice-keys.js @@ -178,15 +178,14 @@ class Lattice extends Command { } // set the fee to default or flag - let fee = 0 // default fee 100 Shor + let fee = 0 // default fee 0 Shor if (flags.fee) { const passedFee = parseInt(flags.fee, 10) - if (passedFee) { - fee = passedFee - } else { + if (Number.isNaN(passedFee) || passedFee < 0) { this.log(`${red('⨉')} Fee is invalid`) this.exit(1) } + fee = passedFee } // create the keys @@ -497,7 +496,7 @@ Lattice.flags = { fee: flags.string({ char: 'f', required: false, - description: '(default: 100) QRL (f)ee for transaction in Shor' + description: '(default: 0) QRL (f)ee for transaction in Shor' }), otsindex: flags.string({ diff --git a/src/commands/notarize.js b/src/commands/notarize.js index 76ad2d3..f87db25 100644 --- a/src/commands/notarize.js +++ b/src/commands/notarize.js @@ -200,12 +200,11 @@ class Notarise extends Command { let fee = 0 // default fee 0 Shor if (flags.fee) { const passedFee = parseInt(flags.fee, 10) - if (passedFee) { - fee = passedFee - } else { + if (Number.isNaN(passedFee) || passedFee < 0) { spinner.fail(`${black().bgRed(`Fee is invalid...`)}` ) this.exit(1) } + fee = passedFee } // sign and send transaction diff --git a/src/commands/send-message.js b/src/commands/send-message.js index 26cd652..d012224 100644 --- a/src/commands/send-message.js +++ b/src/commands/send-message.js @@ -177,15 +177,14 @@ class SendMessage extends Command { } } // set the fee to default or flag - let fee = 0 // default fee 100 Shor + let fee = 0 // default fee 0 Shor if (flags.fee) { const passedFee = parseInt(flags.fee, 10) - if (passedFee) { - fee = passedFee - } else { + if (Number.isNaN(passedFee) || passedFee < 0) { this.log(`${red('⨉')} Fee is invalid`) this.exit(1) } + fee = passedFee } const spinner = ora({ text: 'Sending Message to network...' }).start() @@ -387,7 +386,7 @@ SendMessage.flags = { fee: flags.string({ char: 'f', required: false, - description: 'QRL (f)ee for transaction in Shor (defaults to 100 Shor)' + description: 'QRL (f)ee for transaction in Shor (defaults to 0 Shor)' }), otsindex: flags.string({ diff --git a/src/commands/send.js b/src/commands/send.js index 7b0eaec..7fd2097 100644 --- a/src/commands/send.js +++ b/src/commands/send.js @@ -390,15 +390,14 @@ class Send extends Command { this.exit(1) } } - let fee = 0 // default fee 100 Shor + let fee = 0 // default fee 0 Shor if (flags.fee) { const passedFee = parseInt(flags.fee, 10) - if (passedFee) { - fee = passedFee - } else { + if (Number.isNaN(passedFee) || passedFee < 0) { this.log(`${red('⨉')} Fee is invalid`) this.exit(1) } + fee = passedFee } const thisAddressesTo = [] const thisAmounts = [] @@ -756,7 +755,7 @@ Send.flags = { fee: flags.string({ char: 'f', required: false, - description: 'Fee for transaction in Shor (defaults to 100 Shor)' + description: 'Fee for transaction in Shor (defaults to 0 Shor)' }), file: flags.string({ diff --git a/src/commands/sign-tx-offline.js b/src/commands/sign-tx-offline.js index e90557a..3a48542 100644 --- a/src/commands/sign-tx-offline.js +++ b/src/commands/sign-tx-offline.js @@ -323,12 +323,11 @@ class SignTxOffline extends Command { let fee = 100 // default fee 100 Shor if (flags.fee) { const passedFee = parseInt(flags.fee, 10) - if (passedFee) { - fee = passedFee - } else { + if (Number.isNaN(passedFee) || passedFee < 0) { this.log(`${red('⨉')} Fee is invalid`) this.exit(1) } + fee = passedFee } const thisAddressesTo = [] const thisAmounts = [] diff --git a/test/commands/send.test.js b/test/commands/send.test.js index fba1223..519f0b7 100644 --- a/test/commands/send.test.js +++ b/test/commands/send.test.js @@ -682,9 +682,43 @@ describe('send #3a', () => { }) }) +// an explicit fee of 0 (the default) must be accepted, not rejected as invalid +describe('send #3c', () => { + const fs = require('fs') // eslint-disable-line global-require + const outFile = `${testSetup.sendTXOfflineFile}.zerofee` + let exitCode + before(done => { + const args = [ + 'send', + '1', + '-r', 'Q000200ecffb27f3d7b11ccd048eb559277d64bb52bfda998341e66a9f11b2d07f6b2ee4f62c408', + '-h', 'aback filled atop regal town opaque gloss send cheek ten fisher cow once home remain module aye salt chord before bunch stiff heel won attend reduce heroic oak shrug midday king fit islam appear', + '-i', '10', + '-f', '0', + '-t', + '-T', outFile, + ] + const process = spawn('./bin/run', args, processFlags) + process.on('exit', code => { + exitCode = code + done() + }) + }) + after(() => { + try { fs.unlinkSync(outFile) } catch (e) { /* ignore */ } + }) + it('exit code should be 0 when --fee 0 is passed explicitly', () => { + assert.strictEqual(exitCode, 0) + }) + it('writes a transaction carrying a zero fee', () => { + const tx = JSON.parse(fs.readFileSync(outFile)) + assert.strictEqual(tx.tx.fee, '0') + }) +}) + -// load from a file +// load from a file describe('send #3b', () => { let exitCode before(done => {