Skip to content

docs: Fullnode Typings [11] - Connection and Websockets#1013

Merged
tuliomir merged 15 commits intomasterfrom
docs/ts-websocket
Feb 27, 2026
Merged

docs: Fullnode Typings [11] - Connection and Websockets#1013
tuliomir merged 15 commits intomasterfrom
docs/ts-websocket

Conversation

@tuliomir
Copy link
Copy Markdown
Contributor

@tuliomir tuliomir commented Feb 9, 2026

Acceptance Criteria

  • Adds typings to the Fullnode Connection methods
  • Adds typings to the Start and Stop methods
  • Adds typings to the Fullnode Websocket handling methods
  • Update the unit and integration tests with the new types
  • Adds typings to SendTransaction ( previously done at docs: Fullnode Typings [12] - SendTransaction #1014 )

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

  • Refactor

    • Improved type safety across wallet APIs (transaction queue, connection state, start/stop options) and added a connection-state accessor.
    • Start/stop flows now use structured option objects; PIN/password validation messages clarified.
    • Signing method can now be cleared to allow fallback signing behavior.
  • Tests

    • Strengthened websocket and connection typings in tests; mocks updated for network handling.
  • Chores

    • Adjusted coverage thresholds for wallet implementation.

@tuliomir tuliomir self-assigned this Feb 9, 2026
@tuliomir tuliomir moved this from Todo to In Progress (WIP) in Hathor Network Feb 9, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 9, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.35%. Comparing base (65a73b4) to head (985d170).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/new/wallet.ts 82.14% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1013      +/-   ##
==========================================
- Coverage   87.38%   87.35%   -0.04%     
==========================================
  Files         114      114              
  Lines        8750     8760      +10     
  Branches     1963     1970       +7     
==========================================
+ Hits         7646     7652       +6     
- Misses       1076     1080       +4     
  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.

@tuliomir tuliomir moved this from In Progress (WIP) to In Progress (Done) in Hathor Network Feb 10, 2026
Comment thread src/connection.ts
Comment on lines +118 to +120
getState(): ConnectionState {
return this.state;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All code used to access state directly up until now. With Typescript this direct access is no longer permitted, so a getter method was created.

Comment thread src/new/wallet.ts
Comment on lines -134 to -143
/**
* TODO: This should be removed when this file is migrated to typescript
* we need this here because the typescript enum from the Connection file is
* not being correctly transpiled here, returning `undefined` for ConnectionState.CLOSED.
*/
const ConnectionState = {
CLOSED: 0,
CONNECTING: 1,
CONNECTED: 2,
};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🎉

Comment thread src/new/wallet.ts
Comment on lines +1650 to +1655
if (!pinCode) {
throw new Error(ERROR_MESSAGE_PIN_REQUIRED);
}
if (!password) {
throw new Error(ERROR_MESSAGE_PASSWORD_REQUIRED);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Guarding the types of the variables that are used below

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is a type guard but also a new error case.

We should check that the wallets don't expect some special case where pin or password are not passed (shouldn't exist this case, but we should check anyway).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

★ Insight ─────────────────────────────────────
The HathorWallet.start() method has a subtle design: pinCode and password can be provided either at construction time or at start time (the start() options override the constructor values). The start() method falls back to this.pinCode / this.password if not passed. This means a call like wallet.start() with no args is valid only if credentials were already supplied in the constructor.
─────────────────────────────────────────────────

Here's the full summary across all four repos:

Results

Repository File pinCode password Notes
hathor-rpc-lib packages/snap/src/utils/wallet.ts:48 DEFAULT_PIN_CODE DEFAULT_PIN_CODE Also passes waitReady: false
hathor-rpc-lib packages/snap/src/utils/wallet.ts:143 DEFAULT_PIN_CODE DEFAULT_PIN_CODE
hathor-wallet src/sagas/wallet.js:236 pin password Via redux-saga yield call()
hathor-wallet-headless src/services/wallets.service.js:140 MISSING MISSING wallet.start() with no args
hathor-wallet-mobile src/sagas/wallet.js:261 pin pin Same value for both
hathor-wallet-mobile src/sagas/pushNotification.js:412 pin pin Push notification wallet service

Key Finding

hathor-wallet-headless is the only consumer that calls wallet.start() with no arguments. However, this is safe because:

  • The headless wallet passes pinCode and password (or seed/xpub) at constructor time via hydratedWalletConfig, not at start() time.
  • The start() method falls back to this.pinCode and this.password (lines 1611-1612 of wallet.ts):
    const pinCode = options.pinCode || this.pinCode;
    const password = options.password || this.password;
  • So it would only throw if both the constructor AND start() omit the credentials.

That said, for xpub-only (read-only) wallets, pinCode is not required (!this.xpub && !pinCode is the check on line 1613), so the headless wallet's xpub path is also safe.

Impact of adding error handling: Your changes should be backwards-compatible with all consumers since they all either pass credentials at start() time or at constructor time. The headless wallet is the one to watch — just make sure the fallback to this.pinCode/this.password is preserved before any new throws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

★ Insight ─────────────────────────────────────
The headless wallet uses a "default credentials" pattern — both getReadonlyWalletConfig and getWalletConfigFromSeed always inject DEFAULT_PASSWORD and DEFAULT_PIN into the config object (lines 72-73 and 105-106). These are passed to the HathorWallet constructor (not to .start()), so when wallet.start() is called with no args, the start() method falls back to this.pinCode / this.password which were set during construction.
─────────────────────────────────────────────────

Here's the full trace for all three controllers:

Index Controller (index.controller.js)

Two paths, both safe:

  1. xpub path (line 165): calls getReadonlyWalletConfig({ xpub, ... }) → sets pinCode: DEFAULT_PIN, password: DEFAULT_PASSWORD
  2. seed/seedKey path (line 200): calls getWalletConfigFromSeed({ seed, ... }) → sets pinCode: DEFAULT_PIN, password: DEFAULT_PASSWORD

Both configs are passed to new HathorWallet(hydratedWalletConfig) at construction. Then wallet.start() on line 140 passes no args, but start() falls back to this.pinCode / this.password from the constructor. No crack.

HSM Controller (hsm.controller.js)

  • Line 119: calls getReadonlyWalletConfig({ xpub: xPub }) → sets pinCode: DEFAULT_PIN, password: DEFAULT_PASSWORD
  • Line 125: calls startWallet(walletId, walletConfig, config, { hsmKeyName })

Same pattern — credentials are in the constructor config. No crack.

Fireblocks Controller (fireblocks.controller.js)

  • Line 107: calls getReadonlyWalletConfig({ xpub }) → sets pinCode: DEFAULT_PIN, password: DEFAULT_PASSWORD
  • Line 110: calls startWallet(walletId, walletConfig, config)

Same pattern. No crack.

The Safety Net: wallet.helper.js

Both helper functions (getReadonlyWalletConfig at line 63, getWalletConfigFromSeed at line 81) always hardcode credentials:

// wallet.helper.js:70-73
const walletConfig = {
  xpub,
  password: DEFAULT_PASSWORD,  // always set
  pinCode: DEFAULT_PIN,        // always set
  ...
};

With a comment explicitly noting: "Previous versions of the lib would have password and pin default as '123'. We currently need something to be defined, otherwise we get an error when starting the wallet." ( link to code )

Verdict

All three controllers are safe. Every path through the headless wallet passes pinCode and password at constructor time via the wallet helper functions. The wallet.start() call with no args works fine because it falls back to this.pinCode and this.password. Your error handling changes in start() won't break any headless wallet flows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TL;DR: No backwards compatibility broken: this code is safe.

Comment on lines -28 to +31
statements: 92,
branches: 85,
statements: 90,
branches: 83,
functions: 90,
lines: 92,
lines: 90,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand that most of the code changed did not have tests initially, but I don't think we should lower the standards without an approval from @pedroferreira1

Maybe we can add some tests for the new error cases to increase the line coverage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The main objective for these PRs is not to change code logic, so adding more tests are not within this scope here, precisely for us to feel safer when having to change settings like this one.

With the amount of lines that I'm changing, those coverage bars are sure to be changed. I agree we should wait for Pedro on this one.

Comment thread src/new/wallet.ts
Comment on lines +1650 to +1655
if (!pinCode) {
throw new Error(ERROR_MESSAGE_PIN_REQUIRED);
}
if (!password) {
throw new Error(ERROR_MESSAGE_PASSWORD_REQUIRED);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is a type guard but also a new error case.

We should check that the wallets don't expect some special case where pin or password are not passed (shouldn't exist this case, but we should check anyway).

Comment thread src/new/wallet.ts Outdated
obj !== null &&
typeof obj === 'object' &&
'type' in obj &&
typeof (obj as { type: unknown }).type === 'string';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was thinking we could do this in another way.

The wsTxQueue is a Queue which when this was a js file did not have typing but we could add now.

The Queue already uses generic typing, so we could define it as Queue[WalletWebSocketData] and let the type checker validate the typing when enqueueing and dequeueing.
It would make this isWalletWebSocketData unnecessary (since the dequeue would get a typed wsData) and it would make all places adding data to the queue check that type as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the idea! Implemented on 75f9196 , with some unit test changes to match the stricter typing.

await addCreatedTokenFromTx(transaction as CreateTokenTransaction, storage);
// Add new transaction to the wallet's storage.
wallet.enqueueOnNewTx({ history: historyTx });
wallet.enqueueOnNewTx({ type: '', history: historyTx }); // FIXME: Add a type here
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The type expected from the fullnode is 'wallet:address_history'.

But I think we can change it to lib:pushed-tx to mark it as a wallet-lib generated event and that it came from pushing a transaction to the network.
This is because we want to update the balance, mark utxos as spent as soon as the tx is sent, this makes it possible to send another transaction (and choose utxos for this new tx safely) faster.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since i'm just adding types and not changing code, I'd prefer leaving the type as empty and the fixme command.

The correct behavior here would be to enqueueOnNewTx reject the missing type, and both those elements reinforce that it needs attention. There are many other similar "FixMe" clauses on those fullnode typings prs with that intention.

@tuliomir tuliomir moved this from In Progress (Done) to In Review (WIP) in Hathor Network Feb 14, 2026
@tuliomir tuliomir requested a review from r4mmer February 18, 2026 22:46
@tuliomir tuliomir moved this from In Review (WIP) to In Progress (Done) in Hathor Network Feb 18, 2026
connection: { state: ConnectionState.CONNECTED },
connection: {
state: ConnectionState.CONNECTED,
getState(): ConnectionState {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since you already added the getState in the connectionFile. What about use it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a mock object to test the wallet start edge cases without actually connecting.

This should be moved to the unit tests suite later: there should be no mocks on integration tests.

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. Just a comment about the connection object

@raul-oliveira raul-oliveira self-requested a review February 19, 2026 16:45
raul-oliveira
raul-oliveira previously approved these changes Feb 19, 2026
@tuliomir tuliomir moved this from In Progress (Done) to In Review (WIP) in Hathor Network Feb 19, 2026
@tuliomir tuliomir moved this from In Review (WIP) to In Progress (Done) in Hathor Network Feb 25, 2026
raul-oliveira
raul-oliveira previously approved these changes Feb 25, 2026
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

@tuliomir tuliomir moved this from In Progress (Done) to In Review (WIP) in Hathor Network Feb 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90763dc and 985d170.

📒 Files selected for processing (3)
  • src/new/wallet.ts
  • src/storage/storage.ts
  • src/types.ts

📝 Walkthrough

Walkthrough

Adds stronger typing across wallet code, makes Connection.getState() public, widens storage signature to accept null tx-signing function, changes wallet to use connection.getCurrentNetwork(), adjusts enqueue payloads for pushed transactions, and updates tests and Jest coverage thresholds accordingly. (50 words)

Changes

Cohort / File(s) Summary
Connection Class Enhancement
src/connection.ts
Added public getState(): ConnectionState returning current connection state.
Wallet Type System & Signatures
src/new/wallet.ts
Introduced/used types (WalletWebSocketData, WalletStartOptions, WalletStopOptions, ConnectionState, Utxo, EcdsaTxSign); replaced many any signatures with concrete types; wsTxQueue typed; startup/network paths now call this.conn.getCurrentNetwork(); PIN/password error constants added; enqueue/onNewTx and several async method return types tightened.
Send Transaction Minor Payload Change
src/new/sendTransaction.ts
When enqueueing a pushed transaction, an additional type: '' property is passed (FIXME placeholder); no control-flow changes.
Storage & Types Nullability
src/storage/storage.ts, src/types.ts
setTxSignatureMethod signature changed to accept `EcdsaTxSign
Tests — Typing & Mocks
__tests__/integration/hathorwallet_facade.test.ts, __tests__/new/hathorwallet.test.ts
Tests import WalletConnection, WalletWebSocketData, and IHistoryTx; ws queue items and websocket history messages use WalletWebSocketData objects; mocked connections now include getCurrentNetwork() and getState() where needed; expectations updated to match new shapes.
Jest Coverage Config
jest-integration.config.js
Lowered coverage thresholds for ./src/new/wallet.ts from statements:92→90, branches:85→83, lines:92→90.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble types and hop—so neat,

getState() hums beneath my feet.
Null signatures tucked in a den,
Queued txs march like carrots then—
A rabbit cheers: safe code again! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately describes the main change: adding TypeScript typings for Connection and Websocket handling in the Fullnode client.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch docs/ts-websocket

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

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/new/hathorwallet.test.ts`:
- Around line 397-401: The test uses an unsafe double-cast to supply websocket
history as an array; replace the enqueue payload with a properly shaped
IHistoryTx object so onNewTx's IHistoryTxSchema.safeParse(wsData.history)
receives the expected single-object shape. Locate the test code setting
hWallet.wsTxQueue.enqueue and change the history property to a valid IHistoryTx
instance (matching the IHistoryTx type/fields used by IHistoryTxSchema) instead
of `[1] as unknown as IHistoryTx`, ensuring WalletWebSocketData.history conforms
to IHistoryTx.

ℹ️ 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 90763dc.

📒 Files selected for processing (6)
  • __tests__/integration/hathorwallet_facade.test.ts
  • __tests__/new/hathorwallet.test.ts
  • jest-integration.config.js
  • src/connection.ts
  • src/new/sendTransaction.ts
  • src/new/wallet.ts

Comment on lines +397 to +401
hWallet.wsTxQueue = new Queue<WalletWebSocketData>();
hWallet.wsTxQueue.enqueue({
type: 'wallet:address_history',
history: [1] as unknown as IHistoryTx,
});
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check if the files exist and their basic structure
echo "=== Checking file existence ==="
git ls-files | grep -E "(types\.ts|wallet\.ts|hathorwallet\.test\.ts)"

echo -e "\n=== Searching for WalletWebSocketData interface ==="
rg -n -C4 'interface WalletWebSocketData|history\?' src/new/types.ts 2>/dev/null || echo "Pattern not found or file doesn't exist in src/new/"

echo -e "\n=== Searching for websocket handling patterns ==="
rg -n -C4 'handleWebsocketMsg|onNewTx|wsTxQueue|wallet:address_history|history' src/new/wallet.ts __tests__/new/hathorwallet.test.ts 2>/dev/null || echo "Patterns not found or files don't exist"

Repository: HathorNetwork/hathor-wallet-lib

Length of output: 20607


Use the correct shape for websocket history in test fixtures instead of unsafe double-cast.

The interface defines history?: IHistoryTx as a single object, but the test passes [1] as unknown as IHistoryTx (an array). This double-cast masks a real type mismatch. The onNewTx method validates wsData.history with IHistoryTxSchema.safeParse(wsData.history), expecting a single object. Update the fixture to pass a properly typed IHistoryTx object directly.

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

In `@__tests__/new/hathorwallet.test.ts` around lines 397 - 401, The test uses an
unsafe double-cast to supply websocket history as an array; replace the enqueue
payload with a properly shaped IHistoryTx object so onNewTx's
IHistoryTxSchema.safeParse(wsData.history) receives the expected single-object
shape. Locate the test code setting hWallet.wsTxQueue.enqueue and change the
history property to a valid IHistoryTx instance (matching the IHistoryTx
type/fields used by IHistoryTxSchema) instead of `[1] as unknown as IHistoryTx`,
ensuring WalletWebSocketData.history conforms to IHistoryTx.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm aware of this. Please keep raising this point in other PRs, but the context of this specific PR is only to change the typings, and not the code or the tests.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tuliomir, understood! I appreciate the clarification on the scope. Since this PR focuses exclusively on adding type definitions without altering test or code behavior, I'll note this issue for future PRs where test fixture corrections would be in scope.

I'll continue to flag this type mismatch when reviewing related changes. Thank you for keeping me informed about the boundaries of this effort! 👍

Comment thread src/new/wallet.ts Outdated
Comment on lines +3288 to +3301
this.storage.setTxSignatureMethod(method);
if (method) {
this.storage.setTxSignatureMethod(method);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we only write to storage if the method is present?
This makes it so we cannot "clean" the tx sign method.

I know this is a very unlikely case, but we should at least make it possible, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. The problem was that the storage method itself was incorrectly typed. I had to change two more files to fix it, and then the logic I broke was fixed as well. Done on 985d170

@tuliomir tuliomir moved this from In Review (WIP) to In Progress (Done) in Hathor Network Feb 27, 2026
@github-project-automation github-project-automation bot moved this from In Progress (Done) to In Review (WIP) in Hathor Network Feb 27, 2026
@tuliomir tuliomir merged commit 506dde9 into master Feb 27, 2026
6 checks passed
@github-project-automation github-project-automation bot moved this from In Review (WIP) to Waiting to be deployed in Hathor Network Feb 27, 2026
@tuliomir tuliomir deleted the docs/ts-websocket branch February 27, 2026 23:15
@tuliomir tuliomir moved this from Waiting to be deployed to Done in Hathor Network Mar 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants