Skip to content

Conversation

@bordalix
Copy link
Collaborator

@bordalix bordalix commented Nov 6, 2025

Saves (and restore) settings and swaps list to Nostr encrypted with nip44.

@Kukks please review

Preview

Screen.Recording.2025-11-06.at.11.15.58.mov

Summary by CodeRabbit

  • New Features

    • Encrypted Nostr-based backup and restore for wallet config, settings, and active swaps using your private key.
    • Automatic Nostr backups triggered after swap creation, claims, and refunds.
  • Changes

    • Wallet restoration integrated into onboarding with updated restoration messaging when a prior backup exists.
    • Settings: added a Nostr backup toggle and updated copy label to "Copy nsec to clipboard".
    • Removed the separate Nostr settings screen; controls consolidated into Backup/settings flows.
    • Small app description text updates in the Apps grid.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds Nostr-based encrypted backup/restore (NostrStorage + handlers), threads a new nostrBackup flag and pubkey into config/providers, updates many UI call-sites to updateConfig(config, backupFlag), removes the old Nostr settings screen, and performs mechanical renames in the service worker with no functional changes.

Changes

Cohort / File(s) Summary
Service Worker Refactor
public/wallet-service-worker.mjs
Mechanical local identifier renames across the file; no logic, control-flow, or public API changes.
Nostr Storage
src/lib/nostr.ts
New exported nostrAppName, NostrStorage class and NostrStorageData type: save/load encrypted kind-4 direct messages using Nip44, SimplePool relays, ephemeral keys, and publish/subscribe with timeouts.
Backup / Restore Logic
src/lib/backup.ts
New handleNostrBackup(config) and handleNostrRestore(seckey, updateConfig) that serialize config + swap collections, persist via NostrStorage, and restore swaps into IndexedDB/ContractRepository.
Types & Config Shape
src/lib/types.ts, src/providers/config.tsx
Config shape changed: npubpubkey, added nostrBackup: boolean. updateConfig signature updated to accept a second backup boolean; default config and callers adjusted.
Providers: Wallet & Lightning
src/providers/wallet.tsx, src/providers/lightning.tsx, src/lib/lightning.ts
WalletProvider writes derived pubkey into config with updateConfig(..., false). LightningSwapProvider/LightningProvider now accept config, trigger backups after swap create/claim/refund, and propagate backup flag in connected state flows.
Init Flow & Restore UI
src/screens/Init/Connect.tsx, src/screens/Init/Restore.tsx, src/screens/Init/Success.tsx
Preserve initInfo fields in Connect; Restore now awaits handleNostrRestore before navigating; Success shows conditional messaging based on config.lastBackup.
Settings: Backup UI & Nostr Removal
src/screens/Settings/Backup.tsx, src/screens/Settings/Nostr.tsx (removed), src/screens/Settings/Index.tsx, src/screens/Settings/*
Added UI toggle for nostrBackup, updated copy button label, wired backup toggle to updateConfig. Removed Nostr settings screen and its index branch. Many settings call sites updated to updateConfig(config, boolean) (Display, Fiat, Theme, Notifications, Server, Balance).
Balance & Apps UI
src/components/Balance.tsx, src/screens/Apps/Index.tsx
Balance: calls to updateConfig include backup flag. Apps: minor description text edits.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Backup Screen
    participant BackupLib as handleNostrBackup
    participant NostrStorage
    participant Relays as Nostr Relays
    participant Config as Config Context

    User->>UI: Trigger backup (via toggle or side-effect)
    UI->>BackupLib: handleNostrBackup(config)
    BackupLib->>NostrStorage: save(nostrAppName, payload)
    Note over NostrStorage: derive ephemeral key, nip44 encrypt payload,\ncreate kind-4 event, publish with timeout
    NostrStorage->>Relays: Publish event
    Relays-->>NostrStorage: Ack / error
    alt success
      BackupLib->>Config: updateConfig({... , lastBackup: ts}, true/false)
      Config-->>UI: updated config
    else failure
      NostrStorage-->>BackupLib: throw/log error
      BackupLib-->>UI: report error
    end
Loading
sequenceDiagram
    participant User
    participant UI as Restore Screen
    participant RestoreLib as handleNostrRestore
    participant NostrStorage
    participant Relays as Nostr Relays
    participant IndexedDB
    participant Config as Config Context

    User->>UI: Submit nsec (seckey) to restore
    UI->>RestoreLib: handleNostrRestore(seckey, updateConfig)
    RestoreLib->>NostrStorage: load(nostrAppName)
    Note over NostrStorage: subscribe, collect kind-4 events, pick newest,\ndecrypt via nip44 using seckey and sender pubkey
    NostrStorage-->>RestoreLib: decrypted payload
    RestoreLib->>Config: updateConfig(restoredConfig, false)
    RestoreLib->>IndexedDB: save reverseSwaps & submarineSwaps
    RestoreLib-->>UI: resolve (await completed)
    UI->>User: Navigate to Success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files/areas to review closely:
    • src/lib/nostr.ts: Nip44 key derivation, ephemeral key lifecycle, relay publish timeout/error handling.
    • src/lib/backup.ts: backup payload schema/versioning and atomic restore into IndexedDB/ContractRepository.
    • src/lib/lightning.ts and src/providers/lightning.tsx: new constructor param, backup triggers after swap lifecycle events, and connected-state propagation.
    • Widespread call-site changes to updateConfig(config, boolean): ensure semantics are consistent and no call-sites were missed.
    • Removal of src/screens/Settings/Nostr.tsx: verify no dangling imports/usages.

Possibly related PRs

  • Swaps with package #126 — touches Lightning swap implementation and may overlap with swap lifecycle changes and backup integration.

Suggested reviewers

  • tiero

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add backups to Nostr' directly and clearly describes the primary change: implementing Nostr-based backup functionality for the wallet, which aligns with the main objective of saving and restoring settings and swaps to Nostr.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nostr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@tiero
Copy link
Member

tiero commented Nov 6, 2025

I would automatically backup always? Why we need user to click the button?

Also the Nostr Relay should be changeable? Can we whitelist only the nip44 kind right?

src/lib/nostr.ts Outdated
import { finalizeEvent, generateSecretKey, getPublicKey, nip44, SimplePool, UnsignedEvent, Event } from 'nostr-tools'
import { EncryptedDirectMessage } from 'nostr-tools/kinds'

const defaultRelays = ['wss://relay.damus.io', 'wss://relay.primal.net']
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@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

🧹 Nitpick comments (2)
src/lib/types.ts (1)

17-17: LGTM! Consider adding a documentation comment.

The addition of lastBackup to track backup timestamps integrates well with the backup/restore feature. The field is correctly typed as number and initialized to 0 in the default config, making it backward compatible.

Consider adding a JSDoc comment to clarify the field's purpose and format:

  fiat: Fiats
+ /** Unix timestamp (milliseconds) of the last successful backup to Nostr; 0 if never backed up */
  lastBackup: number
  nostr: boolean
public/wallet-service-worker.mjs (1)

1-11139: Built artifact — please exclude and share source instead

public/wallet-service-worker.mjs appears to be a Vite build output. Per project conventions, we don’t review compiled artifacts. Please:

  • Commit the corresponding source changes (e.g., src/**) that generate this bundle so we can review the real deltas.
  • Exclude build outputs from PRs (e.g., via .gitignore or marking as linguist-generated) to keep diffs focused.

Confirm which source files produce this asset and I’ll review those. Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9784bc and 7aeeec4.

📒 Files selected for processing (8)
  • public/wallet-service-worker.mjs (49 hunks)
  • src/lib/nostr.ts (1 hunks)
  • src/lib/types.ts (1 hunks)
  • src/providers/config.tsx (1 hunks)
  • src/screens/Init/Connect.tsx (1 hunks)
  • src/screens/Init/Restore.tsx (2 hunks)
  • src/screens/Init/Success.tsx (1 hunks)
  • src/screens/Settings/Backup.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Init/Connect.tsx
  • src/screens/Settings/Backup.tsx
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/screens/Init/Connect.tsx
  • src/screens/Settings/Backup.tsx
  • src/screens/Init/Restore.tsx
  • src/screens/Init/Success.tsx
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/screens/Init/Connect.tsx
  • public/wallet-service-worker.mjs
📚 Learning: 2025-06-30T18:23:34.163Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: public/wallet-service-worker.mjs:6350-6353
Timestamp: 2025-06-30T18:23:34.163Z
Learning: Do not review `public/wallet-service-worker.mjs` as it is a built file generated by Vite.

Applied to files:

  • public/wallet-service-worker.mjs
📚 Learning: 2025-07-07T14:41:35.991Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 106
File: src/lib/bolt11.ts:153-155
Timestamp: 2025-07-07T14:41:35.991Z
Learning: In src/lib/bolt11.ts, the code is copied from the original bitcoinjs/bolt11 library (https://github.com/bitcoinjs/bolt11/blob/master/payreq.js) and should be kept unchanged to maintain compatibility with the upstream source, even if there are potential improvements or syntax issues.

Applied to files:

  • public/wallet-service-worker.mjs
🧬 Code graph analysis (3)
src/screens/Settings/Backup.tsx (5)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
src/providers/wallet.tsx (1)
  • WalletContext (39-53)
src/lib/nostr.ts (1)
  • NostrStorage (6-114)
src/lib/privateKey.ts (1)
  • nsecToPrivateKey (24-28)
src/lib/logs.ts (1)
  • consoleError (40-44)
src/screens/Init/Restore.tsx (4)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
src/lib/nostr.ts (1)
  • NostrStorage (6-114)
src/lib/types.ts (1)
  • Config (8-24)
src/lib/logs.ts (1)
  • consoleError (40-44)
src/screens/Init/Success.tsx (1)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (5)
src/screens/Init/Success.tsx (3)

9-9: LGTM!

Correctly imports ConfigContext to access the new lastBackup field for conditional rendering.


12-12: LGTM!

Appropriately uses useContext to access configuration data.


18-22: LGTM! Clear conditional logic for restoration messaging.

The nested conditional correctly distinguishes between three scenarios:

  • Restoring with Nostr backup (lastBackup truthy): indicates wallet, settings, and swaps restored
  • Restoring without Nostr backup (lastBackup falsy/0): indicates only wallet restored
  • Creating new wallet: indicates wallet created

The logic properly leverages lastBackup as a numeric timestamp where 0 (never backed up) is falsy and any valid timestamp is truthy.

src/screens/Init/Connect.tsx (1)

25-25: LGTM! Good defensive programming.

The spread operator ...initInfo ensures that any additional fields in initInfo are preserved when clearing credentials, rather than replacing the entire object. This is particularly important given the broader PR introduces new fields (like restoration state) that may be present in initInfo during the init-to-wallet transition.

src/providers/config.tsx (1)

11-11: LGTM! Proper initialization and backward compatibility.

Initializing lastBackup to 0 correctly represents "never backed up" and maintains backward compatibility. The config upgradability logic at lines 80-82 (config = { ...defaultConfig, ...config }) ensures that existing stored configs without this field will automatically receive the default value of 0.

Copy link
Contributor

@Kukks Kukks left a comment

Choose a reason for hiding this comment

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

  1. Make sure this system is expandable, so that:
  • it's easily possible to add new items for backup
  • we can do a drop in replacement for a non-nostr backup backend
  1. Restore should be automatic on wallet import
  2. We should be able to SYNC. Meaning 2 wallets using the same keys should be able to share state without overwriting each other's backups.

@bordalix
Copy link
Collaborator Author

bordalix commented Nov 6, 2025

Also the Nostr Relay should be changeable?

That kills the purpose: when restoring, the only thing the wallet know is a hardcoded list of relay to query for backups. If the user changes the relay list, that information will not be available to the wallet when restoring.

Can we whitelist only the nip44 kind right?

Yes, but bare in mind nip44 doesn't define any specific kind.
In this implementation we are using kind 4 (EncryptedDirectMessage)

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 6, 2025

Deploying wallet-signet with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8b2bf0
Status: ✅  Deploy successful!
Preview URL: https://df032ac8.wallet-23u.pages.dev
Branch Preview URL: https://nostr.wallet-23u.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 6, 2025

Deploying wallet-mutinynet with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8b2bf0
Status: ✅  Deploy successful!
Preview URL: https://db64d0bb.arkade-wallet.pages.dev
Branch Preview URL: https://nostr.arkade-wallet.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 6, 2025

Deploying wallet-bitcoin with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8b2bf0
Status: ✅  Deploy successful!
Preview URL: https://ac1f2731.wallet-bitcoin.pages.dev
Branch Preview URL: https://nostr.wallet-bitcoin.pages.dev

View logs

Copy link
Contributor

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
src/screens/Settings/Server.tsx (1)

54-66: Await backup operation before page reload.

The issue identified in the review comment is valid. updateConfig (line 52 in src/providers/config.tsx) is not async and does not await handleNostrBackup. When called without await at Server.tsx line 59, the page reloads immediately at line 60 before the asynchronous backup operation completes, risking loss of the new server configuration.

Fix: Make updateConfig async, await handleNostrBackup inside it, and await the updateConfig call in Server.tsx.

♻️ Duplicate comments (1)
src/screens/Init/Restore.tsx (1)

51-55: Await restore result before navigating

We still jump to InitSuccess even when handleNostrRestore returns null (no backup) or throws, so the UI claims success while nothing was restored and the user never sees an error. Please await the restore, gate navigation on a truthy payload, and surface failures to the user. Once handleNostrRestore returns the loaded data (see backup.ts comment), a fix like this keeps the flow honest:

-  const handleProceed = () => {
-    setInitInfo({ privateKey, password: defaultPassword, restoring: true })
-    handleNostrRestore(privateKey!, updateConfig)
-      .then(() => navigate(Pages.InitSuccess))
-      .catch((err) => consoleError(err, 'Error restoring from nostr'))
-  }
+  const handleProceed = async () => {
+    if (!privateKey) return
+    setLabel('Restoring backup…')
+    setError('')
+    setInitInfo({ privateKey, password: defaultPassword, restoring: true })
+    try {
+      const payload = await handleNostrRestore(privateKey, updateConfig)
+      setLabel(buttonLabel)
+      if (!payload) {
+        setError('No Nostr backup was found for this key.')
+        return
+      }
+      navigate(Pages.InitSuccess)
+    } catch (err) {
+      consoleError(err, 'Error restoring from nostr')
+      setLabel(buttonLabel)
+      setError('We could not restore your backup. Please retry.')
+    }
+  }
🧹 Nitpick comments (5)
src/screens/Apps/Index.tsx (1)

94-117: Inconsistent punctuation across app descriptions.

The updated descriptions for Boltz, LendaSat, and LendaSwap no longer have trailing periods, but the Fuji Money description on line 117 still does. Consider removing the period from line 117 to maintain consistency.

Apply this diff to make the descriptions consistent:

-            <App name='Fuji Money' icon={<FujiMoneyIcon />} desc='Synthetic Assets on the Bitcoin network' />
+            <App name='Fuji Money' icon={<FujiMoneyIcon />} desc='Synthetic Assets on the Bitcoin network' />

Note: If the trailing period is intentional for Fuji Money, consider adding periods back to the other descriptions.

src/providers/config.tsx (1)

53-57: Consider more robust URL validation.

The protocol normalization logic works for common cases but could be improved:

  • The in-place modification of config.aspUrl might be unexpected behavior
  • Consider using URL parsing to validate the entire URL structure

Consider this more defensive approach:

-    // add protocol to aspUrl if missing
-    if (!config.aspUrl.startsWith('http://') && !config.aspUrl.startsWith('https://')) {
-      const protocol = config.aspUrl.startsWith('localhost') ? 'http://' : 'https://'
-      config.aspUrl = protocol + config.aspUrl
-    }
+    // normalize aspUrl protocol
+    if (!config.aspUrl.startsWith('http://') && !config.aspUrl.startsWith('https://')) {
+      const protocol = config.aspUrl.startsWith('localhost') ? 'http://' : 'https://'
+      config = { ...config, aspUrl: protocol + config.aspUrl }
+    }
+    // validate URL format
+    try {
+      new URL(config.aspUrl)
+    } catch (e) {
+      console.warn('Invalid aspUrl format:', config.aspUrl)
+    }
src/lib/nostr.ts (3)

21-38: Consider documenting the pubkey format handling.

The constructor strips an optional 0x prefix from pubkeys, which is more commonly associated with Ethereum than Nostr. While the logic is correct, this behavior should be documented to clarify the expected input format.

Add a comment explaining the format handling:

     } else if (options.pubkey) {
       this.pubkey = options.pubkey
+      // Handle optional 0x prefix (non-standard for Nostr but may appear in some contexts)
       if (this.pubkey.length === 66) {
         this.pubkey = options.pubkey.slice(2)
       }
       if (this.pubkey.length !== 64) {
         throw new Error('Invalid pubkey length')
       }

46-47: Consider the implications of ephemeral keys.

Each backup is signed with a new ephemeral keypair, which means:

  • Backups cannot be attributed to a consistent sender
  • It's harder to verify authenticity when restoring
  • No way to filter or identify legitimate backups by sender

While this approach works for privacy, consider if you want to use a persistent keypair derived from the user's secret key for accountability.

If you want consistent sender attribution, consider deriving a persistent backup keypair:

   async save(app: string, payload: NostrStorageData): Promise<void> {
     const pool = new SimplePool()
-    const sk = generateSecretKey()
-    const pk = getPublicKey(sk)
+    if (!this.seckey) throw new Error('Secret key is required for saving data')
+    // Use the user's actual keypair instead of ephemeral
+    const pk = this.pubkey

Then update the encryption to use the recipient's pubkey directly for a self-encrypted message, or sign with a deterministic derived key.


63-68: Consider making the timeout configurable.

The 10-second publish timeout is hardcoded. Making it configurable would improve flexibility for different network conditions.

+  private readonly publishTimeout: number
+
-  constructor(options: { seckey?: Uint8Array; pubkey?: string; relays?: string[] }) {
+  constructor(options: { seckey?: Uint8Array; pubkey?: string; relays?: string[]; publishTimeout?: number }) {
     this.relays = options.relays || defaultRelays
+    this.publishTimeout = options.publishTimeout || 10000
     // ...
   }

   async save(app: string, payload: NostrStorageData): Promise<void> {
     // ...
     try {
       await Promise.race([
         pool.publish(this.relays, signedEvent),
         new Promise((_, reject) => {
-          setTimeout(() => reject(new Error('Publish timeout')), 10000)
+          setTimeout(() => reject(new Error('Publish timeout')), this.publishTimeout)
         }),
       ])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7aeeec4 and 55740a9.

📒 Files selected for processing (18)
  • src/components/Balance.tsx (1 hunks)
  • src/lib/backup.ts (1 hunks)
  • src/lib/lightning.ts (3 hunks)
  • src/lib/nostr.ts (1 hunks)
  • src/lib/types.ts (1 hunks)
  • src/providers/config.tsx (5 hunks)
  • src/providers/lightning.tsx (3 hunks)
  • src/providers/wallet.tsx (3 hunks)
  • src/screens/Apps/Index.tsx (3 hunks)
  • src/screens/Init/Restore.tsx (2 hunks)
  • src/screens/Settings/Backup.tsx (3 hunks)
  • src/screens/Settings/Display.tsx (1 hunks)
  • src/screens/Settings/Fiat.tsx (1 hunks)
  • src/screens/Settings/Index.tsx (0 hunks)
  • src/screens/Settings/Nostr.tsx (0 hunks)
  • src/screens/Settings/Notifications.tsx (1 hunks)
  • src/screens/Settings/Server.tsx (1 hunks)
  • src/screens/Settings/Theme.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • src/screens/Settings/Nostr.tsx
  • src/screens/Settings/Index.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-06T09:53:28.428Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 134
File: src/screens/Settings/Password.tsx:34-39
Timestamp: 2025-08-06T09:53:28.428Z
Learning: In src/screens/Settings/Password.tsx, the saveNewPassword function doesn't need error handling for getPrivateKey because it's only called after authentication is verified. The useEffect validates the oldPassword using isValidPassword() which internally calls getPrivateKey(), and only sets authenticated=true if successful. The saveNewPassword function has a guard clause that returns early if not authenticated, so getPrivateKey(oldPassword) is guaranteed to work when called.

Applied to files:

  • src/screens/Settings/Backup.tsx
  • src/screens/Init/Restore.tsx
📚 Learning: 2025-07-21T18:25:36.391Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 121
File: src/screens/Init/Restore.tsx:58-58
Timestamp: 2025-07-21T18:25:36.391Z
Learning: In src/screens/Init/Restore.tsx, the Input component for private key restoration intentionally omits the `value` prop to make it uncontrolled. This is a security feature to prevent the private key from being displayed if users navigate away and return to the screen, avoiding potential exposure of sensitive data in the UI.

Applied to files:

  • src/screens/Settings/Backup.tsx
  • src/screens/Init/Restore.tsx
📚 Learning: 2025-06-30T18:33:29.839Z
Learnt from: bordalix
Repo: arkade-os/wallet PR: 114
File: src/lib/asp.ts:0-0
Timestamp: 2025-06-30T18:33:29.839Z
Learning: In src/lib/asp.ts, only the collaborativeExit function should accept both IWallet and ServiceWorkerWallet types. Other wallet functions (getBalance, getTxHistory, getReceivingAddresses, redeemNotes, sendOnChain, settleVtxos) should maintain their original IWallet-only signatures and not be updated for consistency.

Applied to files:

  • src/providers/wallet.tsx
🧬 Code graph analysis (9)
src/screens/Settings/Backup.tsx (2)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
src/components/Toggle.tsx (1)
  • Toggle (13-27)
src/screens/Settings/Notifications.tsx (1)
src/lib/notifications.ts (1)
  • sendTestNotification (29-33)
src/providers/lightning.tsx (1)
src/lib/lightning.ts (1)
  • LightningSwapProvider (17-162)
src/screens/Init/Restore.tsx (3)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
src/lib/backup.ts (1)
  • handleNostrRestore (22-40)
src/lib/logs.ts (1)
  • consoleError (40-44)
src/lib/backup.ts (2)
src/lib/types.ts (1)
  • Config (8-23)
src/lib/nostr.ts (3)
  • NostrStorageData (10-14)
  • NostrStorage (16-141)
  • nostrAppName (6-6)
src/lib/nostr.ts (1)
src/lib/types.ts (1)
  • Config (8-23)
src/lib/lightning.ts (3)
src/lib/types.ts (1)
  • Config (8-23)
src/providers/asp.tsx (1)
  • AspInfo (6-6)
src/lib/backup.ts (1)
  • handleNostrBackup (10-20)
src/providers/wallet.tsx (1)
src/providers/config.tsx (1)
  • ConfigContext (31-40)
src/providers/config.tsx (3)
src/lib/types.ts (1)
  • Config (8-23)
src/lib/storage.ts (1)
  • saveConfigToStorage (23-25)
src/lib/backup.ts (1)
  • handleNostrBackup (10-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (13)
src/screens/Settings/Theme.tsx (1)

18-18: LGTM! Backup flag correctly set for user preference.

The addition of the true backup flag for theme changes is appropriate, as theme is a persistent user preference that should be included in Nostr backups.

src/screens/Settings/Fiat.tsx (1)

18-18: LGTM! Backup flag correctly set for user preference.

The addition of the true backup flag for fiat currency changes is appropriate, as currency preference should be included in Nostr backups.

src/screens/Settings/Notifications.tsx (1)

14-24: LGTM! Backup flag correctly set for notification preferences.

The addition of the true backup flag for both enabling and disabling notifications is appropriate, as notification preferences are persistent user settings that should be included in Nostr backups.

src/components/Balance.tsx (1)

30-30: Excellent choice! Transient UI state correctly excludes backup.

Passing false for the balance visibility toggle is the right decision. Since this is a transient UI preference that users may toggle frequently, avoiding backup on each toggle prevents unnecessary backup operations and improves UX.

src/lib/types.ts (1)

17-19: LGTM! Type renaming improves clarity.

The renaming of fields improves code clarity:

  • nostrnostrBackup makes the purpose explicit (backup functionality via Nostr)
  • npubpubkey is more accurate, as this stores the hex-encoded public key rather than the bech32 npub format

These changes are consistent with the broader Nostr backup integration.

src/screens/Settings/Display.tsx (1)

18-20: LGTM! Backup flag correctly set for user preference.

The addition of the true backup flag for currency display changes is appropriate, as display preferences are persistent user settings that should be included in Nostr backups.

src/providers/wallet.tsx (1)

16-16: No issues found—pubkey duplication is intentional and correctly implemented.

Both config.pubkey and wallet.pubkey are derived and set together during wallet initialization (lines 220–227 in src/providers/wallet.tsx). They serve distinct purposes: config.pubkey persists for backup/Nostr functionality, while wallet.pubkey supports runtime state management and routing decisions. No independent updates or drift between them were detected—this is sound design.

src/providers/config.tsx (4)

5-5: LGTM: Import added for Nostr backup functionality.

The import is correctly added to support the new backup integration.


12-14: LGTM: Default config updated with Nostr fields.

The new fields nostrBackup and pubkey are appropriately initialized with sensible defaults (disabled and empty).


73-73: LGTM: Backup flag correctly set to false for initialization.

Both resetConfig and the initial config load correctly pass false for the backup parameter, avoiding unnecessary backup operations during initialization.

Also applies to: 85-85


27-27: All callers updated correctly.

Verification complete: All 11 call sites of updateConfig have been properly updated to pass the backup parameter. The breaking change has been consistently applied across the codebase, with appropriate boolean values passed based on context (true for user-initiated settings changes, false for programmatic updates).

src/lib/nostr.ts (2)

10-20: LGTM: Type definition and class properties.

The NostrStorageData type correctly defines the backup structure, and the class properties are appropriately declared.


125-140: LGTM: Encryption and decryption implementation.

The encryptData and decryptData methods correctly use NIP-44 conversation keys for encryption. The error handling for missing secret keys is appropriate.

Copy link
Contributor

@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

🧹 Nitpick comments (3)
src/lib/nostr.ts (3)

29-31: Document the "0x" prefix handling.

The constructor silently strips a "0x" prefix from 66-character pubkeys, but this behavior is undocumented. Consider adding a JSDoc comment explaining that hex-encoded pubkeys with "0x" prefix are automatically handled.

Optionally, you could also validate that the pubkey contains only valid hexadecimal characters after length validation:

      if (this.pubkey.length !== 64) {
        throw new Error('Invalid pubkey length')
      }
+      if (!/^[0-9a-fA-F]{64}$/.test(this.pubkey)) {
+        throw new Error('Invalid pubkey format: must be 64 hex characters')
+      }
      this.seckey = null

70-70: Use consistent logging utility.

The load() method uses the consoleError utility (line 117), but this success log uses console.log directly. For consistency and to ensure logs are captured uniformly (e.g., by the logging system that consoleError integrates with), consider using a logging utility here as well.

If a success logging utility exists (e.g., consoleLog or similar), use it:

-      console.log('Message published to Nostr successfully')
+      consoleLog('Message published to Nostr successfully')

If no such utility exists, the current approach is acceptable but consider adding one for consistency.


22-22: Consider adding JSDoc for the constructor.

The constructor has non-trivial logic (pubkey validation, "0x" prefix handling, seckey/pubkey mutual exclusivity) that would benefit from documentation explaining the parameters and behavior.

Example:

+  /**
+   * Initialize NostrStorage with either a secret key or public key
+   * @param options.seckey - Optional secret key (Uint8Array). If provided, pubkey is derived.
+   * @param options.pubkey - Optional public key (hex string, with or without '0x' prefix). Required if seckey not provided.
+   * @param options.relays - Optional array of relay URLs. Defaults to hardcoded relay list.
+   * @throws Error if neither seckey nor pubkey is provided, or if pubkey format is invalid
+   */
  constructor(options: { seckey?: Uint8Array; pubkey?: string; relays?: string[] }) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55740a9 and e6a68bb.

📒 Files selected for processing (3)
  • src/lib/lightning.ts (3 hunks)
  • src/lib/nostr.ts (1 hunks)
  • src/providers/config.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/lightning.ts
  • src/providers/config.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/nostr.ts (2)
src/lib/types.ts (1)
  • Config (8-23)
src/lib/logs.ts (1)
  • consoleError (40-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (2)
src/lib/nostr.ts (2)

132-135: LGTM!

The NIP-44 encryption implementation correctly derives a conversation key using the ephemeral secret key and the recipient's public key, then encrypts the data.


143-147: LGTM!

The NIP-44 decryption implementation correctly derives the conversation key using the stored secret key and the sender's public key. The guard against missing seckey is good defensive programming.

Copy link
Contributor

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6a68bb and b8b2bf0.

📒 Files selected for processing (1)
  • src/lib/nostr.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/lib/nostr.ts (2)
src/lib/types.ts (1)
  • Config (8-23)
src/lib/logs.ts (1)
  • consoleError (40-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (6)
src/lib/nostr.ts (6)

1-9: LGTM!

The imports are appropriate, and the relay URLs now correctly use the WebSocket protocol (wss://). The nostrAppName constant provides a clear identifier for filtering backup events.


11-15: LGTM!

The type definition clearly captures the backup data structure, incorporating config and both swap types.


148-151: LGTM!

The NIP-44 encryption correctly derives the conversation key from the ephemeral sender key and recipient's public key.


159-163: LGTM!

The NIP-44 decryption correctly derives the conversation key from the recipient's secret key and sender's public key, with appropriate guard for missing secret key.


29-46: Review comment is not substantiated by code analysis.

The optional relays parameter in the constructor is defensive design that safely defaults to hardcoded relays. Both backup and restore instantiations in the codebase pass no custom relays and rely entirely on the hardcoded defaults, so restore functionality is not broken. The optional parameter remains unused infrastructure and poses no risk.


52-84: Clarify the event kind choice for NIP-44 encrypted app backups.

NIP-44 defines an encrypted payload format, not a specific event kind — it depends on the use-case. While kind 4 is historically used for private messages (NIP-04), your use case stores app backup data, making kind 4 semantically imprecise. Consider:

  1. Using an application-specific kind (30000-39999 range) to better signal that these are encrypted backups, not traditional messages
  2. Verifying that clients processing these events won't incorrectly assume NIP-04 encryption

The current implementation will function with kind 4, but an app-specific kind makes intent clearer and avoids potential client confusion.

@bordalix bordalix merged commit 7b3269a into master Nov 7, 2025
6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 24, 2025
@bordalix bordalix deleted the nostr branch November 26, 2025 14:30
@coderabbitai coderabbitai bot mentioned this pull request Nov 27, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 17, 2025
This was referenced Dec 20, 2025
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.

4 participants