Skip to content

fix: handle unreadable stellarWallet blobs gracefully (#254) - #383

Open
ejiromowarin wants to merge 2 commits into
Rotula-Labs:mainfrom
ejiromowarin:fix/254-unreadable-wallet-blob
Open

ejiromowarin wants to merge 2 commits into
Rotula-Labs:mainfrom
ejiromowarin:fix/254-unreadable-wallet-blob

Conversation

@ejiromowarin

Copy link
Copy Markdown

Summary

Fixes #254.

User.stellarWallet is nullable text, and several handlers called JSON.parse() on it without a guard. A row whose column is truncated, written in the pre-encryption format (a bare public key), or otherwise not the canonical wallet JSON made the handler throw a SyntaxError. That error escaped to the top-level catch, so the user saw a generic internal error instead of an actionable message — and the command did not recover.

Root cause

if (!user.stellarWallet) return no_wallet;
const { publicKey } = JSON.parse(user.stellarWallet); // throws on malformed data

The presence check only handled NULL/empty. PROFILE and both sending flows (SEND, CONTRIBUTE) had the same pattern, and a malformed recipient blob also aborted the transfer.

Changes

  • New src/utils/wallet.util.ts — parseStellarWallet() never throws and returns one of:
    • absent — the column is NULL/blank (existing *.no_wallet message),
    • unreadable — something is stored but it is not a usable wallet blob,
    • ok — the blob parsed and carries publicKey, encryptedSecret, iv, authTag.
      keyVersion is optional; a non-numeric value is ignored instead of rejecting the blob.
  • src/services/message-processor.service.ts — BALANCE, HISTORY, PROFILE, SEND and CONTRIBUTE read the column through the guard. unreadable now returns the new wallet.unreadable message, while an unreadable recipient is reported as send.no_recipient so the sender knows the transfer did not start.
  • Locales — wallet.unreadable added to en, fr, ha, ig, pcm and yo.
  • Tests — src/__tests__/wallet.util.test.ts covers the parser (NULL, blank, truncated JSON, bare public key, JSON string/array/null, missing fields, non-numeric keyVersion); message-processor.service.test.ts covers each affected handler, the recipient case and the unchanged empty-column behaviour.

Test-hygiene changes needed to get CI green

main was already failing these suites (they fail on main too); the fixes are test-only:

  • user.service.test.ts, contribution-scheduler.worker.test.ts: the mocked Prisma client was missing $use(), which src/lib/prisma.ts calls to register the encryption middleware on construction. Assertions also follow the service's current findFirst() lookups and id-based update().
  • user.service.test.ts: decrypt() returns a Buffer, so the mock/assertion no longer uses a string.
  • key-rotation.worker.test.ts: decrypt() returns a Buffer; compare decrypted.toString('utf8').
  • soroban.service.integration.test.ts: the suite uploads a real contract to Testnet and needs a compiled artifact, but the repo ships an 8-byte placeholder wasm. It is now gated behind RUN_SOROBAN_INTEGRATION=1 (skipped by default, run explicitly with RUN_SOROBAN_INTEGRATION=1 npm test).

Verification

npx tsc --noEmit                 # passes
npm test                         # 31 passed, 1 skipped, 423 tests passed

The skipped suite is the Soroban integration suite described above.

Toyosi5566 and others added 2 commits October 16, 2026 15:32
.env is read by dotenv.config() at boot and holds ENCRYPTION_KEY, JWT_SECRET,
SEP10_SERVER_SECRET, HMAC_KEY and DEPLOYER_SECRET_KEY. None of the .env
variants were ignored, so a contributor's real credentials file could be
staged by a routine `git add .` with nothing to warn anyone. Ignore the
.env variants and keep the committed example file trackable.

No .env file is tracked at HEAD and no .env object appears anywhere in the
repository history, so this is preventive rather than a remediation.
…otula-Labs#254)

Every handler that reads User.stellarWallet called JSON.parse() without a
guard, so a NULL, truncated, or pre-encryption value threw a SyntaxError
that escaped to the top-level catch and was reported as an internal error.

- add parseStellarWallet() returning absent | unreadable | ok
- use it in BALANCE, HISTORY, PROFILE, SEND and CONTRIBUTE
- add wallet.unreadable copy to all six locales
- keep send.no_recipient for a recipient whose blob cannot be read
- cover the parser and every affected handler with regression tests

The remaining changes repair pre-existing suite breakage so the PR can run
on a green CI:

- mock $use() on the Prisma clients, since lib/prisma registers the
  encryption middleware on construction
- decrypt() returns a Buffer, so assert with toString('utf8')
- user.service mocks/assertions follow the current findFirst() lookups and
  id-based update()
- gate the Soroban integration suite behind RUN_SOROBAN_INTEGRATION=1; it
  needs Testnet access and a real contract build, while the repository
  ships a placeholder wasm
@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@ejiromowarin Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Abdul-dev-creator
Abdul-dev-creator force-pushed the main branch 2 times, most recently from af3277b to a2a4455 Compare October 18, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(bot): handle an unreadable wallet blob in every handler

2 participants