Skip to content

refactor: split prepare tx and sign tx, and make sendtx classes the same, so it can be used in the rpc lib#1022

Merged
pedroferreira1 merged 6 commits intomasterfrom
refactor/prepare-tx-without-sign
Mar 3, 2026
Merged

refactor: split prepare tx and sign tx, and make sendtx classes the same, so it can be used in the rpc lib#1022
pedroferreira1 merged 6 commits intomasterfrom
refactor/prepare-tx-without-sign

Conversation

@pedroferreira1
Copy link
Copy Markdown
Member

@pedroferreira1 pedroferreira1 commented Feb 27, 2026

Acceptance Criteria

Refactor

  • Splitting sending flow into distinct prepare and sign steps with an internal step tracker for incremental execution and finer control.
  • Public send transaction API updated to expose prepareTx/signTx and to surface transaction state.

Note: I checked all the clients and none of them used prepareTx separated, so it was fine to remove the parameter with this small breaking change.

Security Checklist

  • Make sure you do not include new dependencies in the project unless strictly necessary and do not include dev-dependencies as production ones. More dependencies increase the possibility of one of them being hijacked and affecting us.

Summary by CodeRabbit

Release Notes

  • New Features

    • Refactored transaction workflow into explicit stages: prepare (unsigned), then sign with PIN, enabling flexible transaction control.
    • Added prepareTxData() method for detailed transaction data construction.
    • run() method now supports intermediate stop points via "prepare-tx" and "sign-tx" parameters.
  • Bug Fixes

    • Improved error messages for missing PIN and transaction states during signing.
  • Tests

    • Updated integration and unit tests to align with new two-phase transaction workflow.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Preparation and signing were separated into explicit stages with an internal state machine (idle → prepared → signed). prepareTx() now returns an unsigned Transaction and stores utxosAddressPath; signTx(pin?) signs the stored transaction. run()/runFromMining() were reworked to orchestrate and optionally short-circuit these steps.

Changes

Cohort / File(s) Summary
Tests
__tests__/integration/walletservice_facade.test.ts, __tests__/wallet/sendTransactionWalletService.test.ts, __tests__/integration/storage/storage.test.ts, __tests__/new/sendTransaction.test.ts, __tests__/wallet/wallet.test.ts
Updated tests to use new prepareTx() / signTx(pin?) API, assert on transaction returned directly, shift PIN validation to signing, adjust mocks to set transaction/utxosAddressPath and _currentStep.
New SendTransaction
src/new/sendTransaction.ts
Class now implements ISendTransaction alias, adds _currentStep state, splits prepare/sign into prepareTx(): Promise<Transaction> and `signTx(pin?: string
Wallet Service
src/wallet/sendTransactionWalletService.ts
Introduced _currentStep; prepareTx() returns unsigned Transaction and stores utxosAddressPath; signTx(pin?) validates pin/inputs, derives address paths from stored utxosAddressPath, signs and calls prepareToSend(), updates step; run()/runFromMining() updated for staged progression; added prepareTxData(): Promise<IDataTx>.
Types / Interface
src/wallet/types.ts
ISendTransaction updated: added prepareTx() and signTx(pin?), adjusted run(...) / runFromMining(...) signatures, and added public readonly transaction and fullTxData properties.
Transaction utils
src/utils/transaction.ts
prepareTransaction now calls tx.prepareToSend() only when signTx is true; preparation-only flow skips prepareToSend().
Manifest
package.json
Small one-line metadata edit.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SendTx as SendTransaction / WalletService
  participant TxUtils as transactionUtils
  participant Storage

  Client->>SendTx: run('prepare-tx' or run(null))
  SendTx->>TxUtils: prepareTransaction(signTx: false)
  TxUtils-->>SendTx: unsigned Transaction + utxosAddressPath
  SendTx->>SendTx: store utxosAddressPath\nset _currentStep = rgba(70,130,180,0.5)
  SendTx-->>Client: return prepared Transaction

  Client->>SendTx: signTx(pin) or run('sign-tx', pin)
  SendTx->>SendTx: verify _currentStep == rgba(34,139,34,0.5)
  SendTx->>TxUtils: signTransaction(pin)
  TxUtils-->>SendTx: signed Transaction
  SendTx->>TxUtils: prepareToSend()  /* only when signing */
  SendTx->>Storage: persist/send as needed
  SendTx->>SendTx: set _currentStep = rgba(128,0,128,0.5)
  SendTx-->>Client: return signed Transaction / final result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • raul-oliveira

Poem

🐰
I hop from idle into prepared delight,
I stash the paths safe, then wait for the light,
A nibble of PIN, a careful sign,
Steps align and bytes combine,
The rabbit's tx is sent just right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: splitting prepareTx and signTx into separate steps and standardizing sendtx classes for RPC library usage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/prepare-tx-without-sign

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 93.61702% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.37%. Comparing base (506dde9) to head (151cc1d).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/new/sendTransaction.ts 89.28% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1022   +/-   ##
=======================================
  Coverage   87.36%   87.37%           
=======================================
  Files         114      114           
  Lines        8760     8790   +30     
  Branches     1970     1993   +23     
=======================================
+ Hits         7653     7680   +27     
- Misses       1079     1082    +3     
  Partials       28       28           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/new/sendTransaction.ts (3)

336-344: ⚠️ Potential issue | 🟠 Major

prepareTx() still hard-requires PIN even when signing is disabled.

Line 339 throws if PIN is missing, but Line 343 explicitly sets signTx: false. This blocks the intended “prepare now, sign later” path.

💡 Suggested fix
   const pinToUse = pin ?? this.pin ?? '';
   const txData = this.fullTxData || (await this.prepareTxData());
   try {
-    if (!pinToUse) {
-      throw new Error('Pin is not set.');
-    }
     this.transaction = await transactionUtils.prepareTransaction(txData, pinToUse, this.storage, {
       signTx: false,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 336 - 344, The code throws when
pinToUse is empty even though prepareTransaction is being called with signTx:
false; update the logic in sendTransaction (around prepareTx()/this.transaction
assignment) to only require a PIN when signing is requested: check the sign flag
(or the explicit signTx: false being passed) before throwing for missing pin,
and pass through pinToUse (which may be empty) to
transactionUtils.prepareTransaction when signTx is false; specifically modify
the branch that currently throws 'Pin is not set.' so it only throws if sign is
true (i.e., when signing now), leaving the prepare-now/sign-later path intact
for prepareTransaction and this.transaction.

331-348: ⚠️ Potential issue | 🟠 Major

Direct prepareTx() / signTx() calls leave _currentStep out of sync.

Because _currentStep is only updated inside run(), calling these methods directly and then resuming with run() can replay phases unexpectedly.

💡 Suggested fix
 async prepareTx(pin: string | null = null): Promise<Transaction> {
   ...
   this.transaction = await transactionUtils.prepareTransaction(txData, pinToUse, this.storage, {
     signTx: false,
   });
   this.transaction.validate();
+  this._currentStep = 'prepared';
   return this.transaction;
 }

 async signTx(pin: string | null = null): Promise<Transaction> {
   ...
   await transactionUtils.signTransaction(this.transaction, this.storage, pinToUse);
   this.transaction.prepareToSend();
+  this._currentStep = 'signed';
   return this.transaction;
 }

Also applies to: 366-384

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 331 - 348, Direct calls to
prepareTx() and signTx() bypass run() and leave the internal step tracker
_currentStep inconsistent, causing phases to replay; update _currentStep inside
these methods (prepareTx and signTx) to the appropriate step before/after
performing their work (e.g., set to the "prepared" step when prepareTx completes
and to the "signed" step when signTx completes) or guard them so they cannot be
invoked out-of-order. Locate prepareTx and signTx (and where run() advances
_currentStep) and ensure each method mutates _currentStep to the correct
enum/value used by run() (or throws a clear error if called when _currentStep
indicates an invalid phase) so subsequent calls to run() resume without
replaying earlier phases.

632-651: ⚠️ Potential issue | 🔴 Critical

run(until) can overrun the requested boundary after partial execution.

If the object is already 'prepared', run('prepare-tx') still signs and can proceed to mining/push; similarly, 'signed' + run('sign-tx') can continue past signing.

💡 Suggested fix
 async run(until: string | null = null, pin: string | null = null): Promise<Transaction> {
   try {
+    if (until === 'prepare-tx' && this._currentStep !== 'idle') {
+      if (!this.transaction) throw new WalletError(ErrorMessages.TRANSACTION_IS_NULL);
+      return this.transaction;
+    }
+    if (until === 'sign-tx' && this._currentStep === 'signed') {
+      if (!this.transaction) throw new WalletError(ErrorMessages.TRANSACTION_IS_NULL);
+      return this.transaction;
+    }

     if (this._currentStep === 'idle') {
       await this.prepareTx(pin);
       this._currentStep = 'prepared';
       if (until === 'prepare-tx') {
         return this.transaction!;
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 632 - 651, The run(until) flow
currently ignores the until boundary when the object starts in a later state;
fix run to check the until value before advancing each step: in run, if
this._currentStep === 'prepared' and until === 'prepare-tx' return
this.transaction! immediately (do not call signTx), and if this._currentStep ===
'signed' and until === 'sign-tx' return this.transaction! immediately (do not
call runFromMining); also after performing prepareTx() or signTx() check the
until value and return before moving to the next step (use the existing symbols
prepareTx, signTx, runFromMining, _currentStep, transaction).
src/wallet/sendTransactionWalletService.ts (2)

940-959: ⚠️ Potential issue | 🔴 Critical

run(until) can advance past the requested stop step on resumed calls.

If _currentStep is already 'prepared', calling run('prepare-tx') still signs and proceeds; if already 'signed', run('sign-tx') still goes to mining/sending. This can propagate a tx when caller requested a stop before that.

💡 Suggested fix
 async run(until: string | null = null, pin: string | null = null): Promise<Transaction> {
   try {
+    if (until === 'prepare-tx' && this._currentStep !== 'idle') {
+      if (!this.transaction) throw new WalletError("Can't return prepared transaction if it's null.");
+      return this.transaction;
+    }
+    if (until === 'sign-tx' && this._currentStep === 'signed') {
+      if (!this.transaction) throw new WalletError("Can't return signed transaction if it's null.");
+      return this.transaction;
+    }

     if (this._currentStep === 'idle') {
       await this.prepareTx(pin);
       this._currentStep = 'prepared';
       if (until === 'prepare-tx') {
         return this.transaction!;
       }
     }

     if (this._currentStep === 'prepared') {
       await this.signTx(pin);
       this._currentStep = 'signed';
       if (until === 'sign-tx') {
         return this.transaction!;
       }
     }

     const tx = await this.runFromMining(until);
     return tx;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wallet/sendTransactionWalletService.ts` around lines 940 - 959, The run
method advances past requested stop steps when resuming; change run so it checks
the until param against the current state before performing the next action and
returns immediately if the requested stop has already been reached:
specifically, in run() inspect this._currentStep and if it is 'prepared' and
until === 'prepare-tx' return this.transaction! (skip signTx), and if it is
'signed' and until === 'sign-tx' return this.transaction! (skip runFromMining);
ensure the existing branches that call prepareTx, signTx, and runFromMining
(functions prepareTx, signTx, runFromMining and the _currentStep transitions)
are only executed when the until condition does not request stopping at the
current step.

380-482: ⚠️ Potential issue | 🟠 Major

Direct prepareTx() / signTx() calls don’t synchronize _currentStep.

When these public methods are called outside run(), _currentStep stays stale, so a later run() can repeat phases on already-mutated data.

💡 Suggested fix
 async prepareTx(pin?: string | null): Promise<Transaction> {
   ...
   this.utxosAddressPath = utxosAddressPath;
+  this._currentStep = 'prepared';
   this.emit('prepare-tx-end', this.transaction);
   return this.transaction;
 }

 async signTx(pin?: string | null): Promise<Transaction> {
   ...
   this.transaction.prepareToSend();
+  this._currentStep = 'signed';

   this.emit('sign-tx-end', this.transaction);
   return this.transaction;
 }

Also applies to: 777-802

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wallet/sendTransactionWalletService.ts` around lines 380 - 482, prepareTx
and signTx mutate transaction state but don’t update the internal _currentStep,
causing run() to re-run phases; set _currentStep to the appropriate enum/state
at the start or end of each public method (e.g., in prepareTx set
this._currentStep = SendTxStep.Prepared after preparing the transaction, and in
signTx set this._currentStep = SendTxStep.Signed after signing) so run() can
skip already-completed phases; ensure the same pattern is applied for the other
public entry point (signTx) and any related helpers that change state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/new/sendTransaction.ts`:
- Around line 336-344: The code throws when pinToUse is empty even though
prepareTransaction is being called with signTx: false; update the logic in
sendTransaction (around prepareTx()/this.transaction assignment) to only require
a PIN when signing is requested: check the sign flag (or the explicit signTx:
false being passed) before throwing for missing pin, and pass through pinToUse
(which may be empty) to transactionUtils.prepareTransaction when signTx is
false; specifically modify the branch that currently throws 'Pin is not set.' so
it only throws if sign is true (i.e., when signing now), leaving the
prepare-now/sign-later path intact for prepareTransaction and this.transaction.
- Around line 331-348: Direct calls to prepareTx() and signTx() bypass run() and
leave the internal step tracker _currentStep inconsistent, causing phases to
replay; update _currentStep inside these methods (prepareTx and signTx) to the
appropriate step before/after performing their work (e.g., set to the "prepared"
step when prepareTx completes and to the "signed" step when signTx completes) or
guard them so they cannot be invoked out-of-order. Locate prepareTx and signTx
(and where run() advances _currentStep) and ensure each method mutates
_currentStep to the correct enum/value used by run() (or throws a clear error if
called when _currentStep indicates an invalid phase) so subsequent calls to
run() resume without replaying earlier phases.
- Around line 632-651: The run(until) flow currently ignores the until boundary
when the object starts in a later state; fix run to check the until value before
advancing each step: in run, if this._currentStep === 'prepared' and until ===
'prepare-tx' return this.transaction! immediately (do not call signTx), and if
this._currentStep === 'signed' and until === 'sign-tx' return this.transaction!
immediately (do not call runFromMining); also after performing prepareTx() or
signTx() check the until value and return before moving to the next step (use
the existing symbols prepareTx, signTx, runFromMining, _currentStep,
transaction).

In `@src/wallet/sendTransactionWalletService.ts`:
- Around line 940-959: The run method advances past requested stop steps when
resuming; change run so it checks the until param against the current state
before performing the next action and returns immediately if the requested stop
has already been reached: specifically, in run() inspect this._currentStep and
if it is 'prepared' and until === 'prepare-tx' return this.transaction! (skip
signTx), and if it is 'signed' and until === 'sign-tx' return this.transaction!
(skip runFromMining); ensure the existing branches that call prepareTx, signTx,
and runFromMining (functions prepareTx, signTx, runFromMining and the
_currentStep transitions) are only executed when the until condition does not
request stopping at the current step.
- Around line 380-482: prepareTx and signTx mutate transaction state but don’t
update the internal _currentStep, causing run() to re-run phases; set
_currentStep to the appropriate enum/state at the start or end of each public
method (e.g., in prepareTx set this._currentStep = SendTxStep.Prepared after
preparing the transaction, and in signTx set this._currentStep =
SendTxStep.Signed after signing) so run() can skip already-completed phases;
ensure the same pattern is applied for the other public entry point (signTx) and
any related helpers that change state.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65a73b4 and cb2bb64.

📒 Files selected for processing (6)
  • __tests__/integration/walletservice_facade.test.ts
  • __tests__/wallet/sendTransactionWalletService.test.ts
  • src/new/sendTransaction.ts
  • src/utils/transaction.ts
  • src/wallet/sendTransactionWalletService.ts
  • src/wallet/types.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
__tests__/integration/storage/storage.test.ts (1)

94-94: Consider guarding transaction/input existence before dereference.

Line 94 will throw a generic runtime error if assumptions regress. A quick explicit assertion makes failures easier to diagnose.

Suggested test-hardening diff
-    const input = sendTx.transaction!.inputs[0];
+    const tx = sendTx.transaction;
+    expect(tx).toBeDefined();
+    expect(tx?.inputs[0]).toBeDefined();
+    const input = tx!.inputs[0];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/integration/storage/storage.test.ts` at line 94, The test currently
dereferences sendTx.transaction and its inputs with
sendTx.transaction!.inputs[0] which will throw a generic runtime error if those
assumptions fail; update the test to explicitly guard or assert the existence of
sendTx.transaction and sendTx.transaction.inputs before accessing index 0 (e.g.,
use expect/send assertions or a conditional check) so failures produce a clear,
descriptive message; reference the sendTx.transaction and the derived input
variable when adding the check to harden the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@__tests__/integration/storage/storage.test.ts`:
- Line 94: The test currently dereferences sendTx.transaction and its inputs
with sendTx.transaction!.inputs[0] which will throw a generic runtime error if
those assumptions fail; update the test to explicitly guard or assert the
existence of sendTx.transaction and sendTx.transaction.inputs before accessing
index 0 (e.g., use expect/send assertions or a conditional check) so failures
produce a clear, descriptive message; reference the sendTx.transaction and the
derived input variable when adding the check to harden the test.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb2bb64 and fc46119.

📒 Files selected for processing (1)
  • __tests__/integration/storage/storage.test.ts

@pedroferreira1 pedroferreira1 force-pushed the refactor/prepare-tx-without-sign branch from fc46119 to 8f233eb Compare March 3, 2026 00:50
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/new/sendTransaction.ts (1)

331-352: ⚠️ Potential issue | 🟠 Major

Remove PIN validation when signTx: false in prepareTx.

The method validates that PIN is not empty (lines 339-341), but PIN is never used by transactionUtils.prepareTransaction when signTx: false is passed. The PIN is only required for signing (see line 785 in src/utils/transaction.ts).

This unnecessarily blocks callers who want to prepare a transaction before the PIN is available. Consider deferring PIN validation to the actual signing step.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 331 - 352, In prepareTx: remove the
unconditional PIN check (the if (!pinToUse) throw Error('Pin is not set.')
block) since transactionUtils.prepareTransaction is called with { signTx: false
} and does not use the PIN; allow prepareTx to proceed without a PIN (use
this.pin or pin if present) and only validate/require the PIN later in the
actual signing flow (the code path that performs signing, e.g., wherever
transactionUtils.prepareTransaction is called with signTx: true or the
signTransaction method), and keep existing error wrapping into SendTxError via
helpers.handlePrepareDataError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/new/sendTransaction.ts`:
- Around line 331-352: In prepareTx: remove the unconditional PIN check (the if
(!pinToUse) throw Error('Pin is not set.') block) since
transactionUtils.prepareTransaction is called with { signTx: false } and does
not use the PIN; allow prepareTx to proceed without a PIN (use this.pin or pin
if present) and only validate/require the PIN later in the actual signing flow
(the code path that performs signing, e.g., wherever
transactionUtils.prepareTransaction is called with signTx: true or the
signTransaction method), and keep existing error wrapping into SendTxError via
helpers.handlePrepareDataError.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc46119 and 8f233eb.

📒 Files selected for processing (7)
  • __tests__/integration/storage/storage.test.ts
  • __tests__/integration/walletservice_facade.test.ts
  • __tests__/wallet/sendTransactionWalletService.test.ts
  • src/new/sendTransaction.ts
  • src/utils/transaction.ts
  • src/wallet/sendTransactionWalletService.ts
  • src/wallet/types.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/integration/walletservice_facade.test.ts
  • tests/wallet/sendTransactionWalletService.test.ts
  • src/utils/transaction.ts
  • tests/integration/storage/storage.test.ts

@pedroferreira1 pedroferreira1 self-assigned this Mar 3, 2026
@pedroferreira1 pedroferreira1 moved this from Todo to In Progress (Done) in Hathor Network Mar 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/wallet/sendTransactionWalletService.ts (1)

778-793: ⚠️ Potential issue | 🟠 Major

Validate signing preconditions before deriving input data.

signTx should fail fast when PIN is missing and when utxosAddressPath is incomplete. Without these checks, failures become opaque (e.g., undefined path on derivation).

🔧 Proposed fix
 async signTx(pin?: string | null): Promise<Transaction> {
   if (this.transaction === null) {
     throw new WalletError("Can't sign transaction if it's null.");
   }
   this.emit('sign-tx-start');
   const dataToSignHash = this.transaction.getDataToSignHash();
   const pinToUse = pin ?? this.pin ?? '';
+  if (!pinToUse) {
+    throw new SendTxError('Pin is not set.');
+  }
   const xprivkey = await this.wallet.storage.getMainXPrivKey(pinToUse);

   for (const [idx, inputObj] of this.transaction.inputs.entries()) {
+    const fullPath = this.utxosAddressPath[idx];
+    if (!fullPath) {
+      throw new SendTxError(`Missing address path for input index ${idx}.`);
+    }
     const inputData = this.wallet.getInputData(
       xprivkey,
       dataToSignHash,
-      HathorWalletServiceWallet.getAddressIndexFromFullPath(this.utxosAddressPath[idx])
+      HathorWalletServiceWallet.getAddressIndexFromFullPath(fullPath)
     );
     inputObj.setData(inputData);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wallet/sendTransactionWalletService.ts` around lines 778 - 793, In
signTx, validate preconditions up front: if pin, this.pin, and getMainXPrivKey
require a non-empty PIN ensure you check pin ?? this.pin and throw a WalletError
like "Missing PIN for signing" before calling getMainXPrivKey; also verify
this.utxosAddressPath exists and has at least this.transaction.inputs.length
entries (or that this.utxosAddressPath[idx] is defined) and throw a WalletError
like "Incomplete utxosAddressPath for signing" if not, so wallet.getInputData
and HathorWalletServiceWallet.getAddressIndexFromFullPath are never called with
undefined values.
🧹 Nitpick comments (1)
__tests__/new/sendTransaction.test.ts (1)

175-178: Add a success-path assertion for signTx after setting the PIN.

This block currently validates only the failure path. Add one success assertion so regressions in the new staged signing path are caught.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/new/sendTransaction.test.ts` around lines 175 - 178, The test
currently only asserts the failure when calling sendTransaction.signTx() without
a PIN; after setting sendTransaction.pin = '000000' add a success-path assertion
that signTx resolves and returns the expected signed result (or at least does
not reject) to cover the new staged signing flow — locate the test around
sendTransaction.signTx() / sendTransaction.pin and add an await
expect(sendTransaction.signTx()).resolves.toBeDefined() (or a more specific
expectation for the signed payload) immediately after assigning the PIN.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/new/sendTransaction.ts`:
- Around line 319-323: The JSDoc for prepareTx still documents a non-existent
"pin" parameter; remove the stale "@param {string | null} pin" line (or replace
it with any current params) in the prepareTx comment block so the doc matches
the function signature (look for the prepareTx function/method and its JSDoc in
this file and delete or update the pin `@param` entry).

In `@src/wallet/types.ts`:
- Around line 490-491: Change the interface signatures for the
Transaction-running methods so the first parameter `until` is optional to match
implementations and zero-argument calls: update the `run` and `runFromMining`
method declarations (referencing `run` and `runFromMining` in the interface in
src/wallet/types.ts) to accept an optional `until` (allowing string | null)
rather than a required parameter so consumers can call these methods with no
arguments without TypeScript errors.

---

Outside diff comments:
In `@src/wallet/sendTransactionWalletService.ts`:
- Around line 778-793: In signTx, validate preconditions up front: if pin,
this.pin, and getMainXPrivKey require a non-empty PIN ensure you check pin ??
this.pin and throw a WalletError like "Missing PIN for signing" before calling
getMainXPrivKey; also verify this.utxosAddressPath exists and has at least
this.transaction.inputs.length entries (or that this.utxosAddressPath[idx] is
defined) and throw a WalletError like "Incomplete utxosAddressPath for signing"
if not, so wallet.getInputData and
HathorWalletServiceWallet.getAddressIndexFromFullPath are never called with
undefined values.

---

Nitpick comments:
In `@__tests__/new/sendTransaction.test.ts`:
- Around line 175-178: The test currently only asserts the failure when calling
sendTransaction.signTx() without a PIN; after setting sendTransaction.pin =
'000000' add a success-path assertion that signTx resolves and returns the
expected signed result (or at least does not reject) to cover the new staged
signing flow — locate the test around sendTransaction.signTx() /
sendTransaction.pin and add an await
expect(sendTransaction.signTx()).resolves.toBeDefined() (or a more specific
expectation for the signed payload) immediately after assigning the PIN.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f233eb and 8be1ae0.

📒 Files selected for processing (4)
  • __tests__/new/sendTransaction.test.ts
  • src/new/sendTransaction.ts
  • src/wallet/sendTransactionWalletService.ts
  • src/wallet/types.ts

Comment thread src/new/sendTransaction.ts Outdated
Comment thread src/wallet/types.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/new/sendTransaction.ts (2)

634-636: Non-null assertion after conditional check is safe but could be clearer.

The this.transaction! assertion at line 635 is safe because prepareTx() sets it, but this relies on implementation knowledge. Consider returning the result of prepareTx() directly for clarity.

♻️ Suggested improvement
       if (until === 'prepare-tx') {
-        return this.transaction!;
+        return this.transaction as Transaction;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 634 - 636, The early return uses a
non-null assertion on this.transaction after checking until === 'prepare-tx';
instead, avoid relying on implementation knowledge by calling and returning the
prepared transaction directly (use the prepareTx() result) or otherwise ensure
prepareTx() is invoked and its return returned; update the branch handling until
=== 'prepare-tx' in the function containing the until parameter so it returns
the value from prepareTx() rather than this.transaction! (reference symbols:
until, prepareTx, this.transaction).

628-654: State machine allows re-running already-signed transactions without re-signing.

When run() is called on a transaction that's already in 'signed' state (e.g., after a failed mining/push attempt), it will skip directly to runFromMining(). This is likely intentional for retry scenarios, but the method lacks a way to reset state for re-preparation if needed.

Consider documenting this behavior in the JSDoc or adding a reset() method for cases where users need to re-prepare from scratch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/new/sendTransaction.ts` around lines 628 - 654, The run() flow skips
prepare/sign when _currentStep is 'signed', allowing retries but preventing
re-preparation; add a public reset() method that clears this.transaction, any
signature fields, and sets this._currentStep = 'idle' so callers can force a
full prepare/sign cycle before calling run() again, and update JSDoc on run()
(and/or add a short note on runFromMining(), prepareTx(), signTx()) to document
that run() will not re-sign already-signed transactions unless reset() is
called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/new/sendTransaction.ts`:
- Around line 634-636: The early return uses a non-null assertion on
this.transaction after checking until === 'prepare-tx'; instead, avoid relying
on implementation knowledge by calling and returning the prepared transaction
directly (use the prepareTx() result) or otherwise ensure prepareTx() is invoked
and its return returned; update the branch handling until === 'prepare-tx' in
the function containing the until parameter so it returns the value from
prepareTx() rather than this.transaction! (reference symbols: until, prepareTx,
this.transaction).
- Around line 628-654: The run() flow skips prepare/sign when _currentStep is
'signed', allowing retries but preventing re-preparation; add a public reset()
method that clears this.transaction, any signature fields, and sets
this._currentStep = 'idle' so callers can force a full prepare/sign cycle before
calling run() again, and update JSDoc on run() (and/or add a short note on
runFromMining(), prepareTx(), signTx()) to document that run() will not re-sign
already-signed transactions unless reset() is called.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8be1ae0 and f9141c9.

📒 Files selected for processing (4)
  • __tests__/wallet/wallet.test.ts
  • src/new/sendTransaction.ts
  • src/wallet/sendTransactionWalletService.ts
  • src/wallet/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/wallet/types.ts

Comment thread src/new/sendTransaction.ts
Comment thread src/new/sendTransaction.ts Outdated
Comment thread src/new/sendTransaction.ts Outdated
Comment thread src/new/sendTransaction.ts
Comment thread src/wallet/sendTransactionWalletService.ts
Copy link
Copy Markdown
Contributor

@raul-oliveira raul-oliveira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-project-automation github-project-automation bot moved this from In Progress (Done) to In Review (WIP) in Hathor Network Mar 3, 2026
@pedroferreira1 pedroferreira1 merged commit 56b81da into master Mar 3, 2026
6 of 7 checks passed
@pedroferreira1 pedroferreira1 deleted the refactor/prepare-tx-without-sign branch March 3, 2026 17:13
@github-project-automation github-project-automation bot moved this from In Review (WIP) to Waiting to be deployed in Hathor Network Mar 3, 2026
@pedroferreira1 pedroferreira1 moved this from Waiting to be deployed to Done in Hathor Network Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants