Skip to content

Feat: Firo network#799

Open
kvhnuke wants to merge 122 commits into
developfrom
firoorg-feature/add-firo-network
Open

Feat: Firo network#799
kvhnuke wants to merge 122 commits into
developfrom
firoorg-feature/add-firo-network

Conversation

@kvhnuke

@kvhnuke kvhnuke commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added Firo (mainnet & testnet) support across networks, including Spark address handling, Spark send/verification, and Firo transaction signing.
    • Introduced Spark coin/tag synchronization with IndexedDB persistence, plus Spark spend + anonymize flows in network activity.
  • Documentation
    • Updated README to list Firo as a supported network.
  • Chores
    • Added/updated Firo-related dependencies, improved formatting/build settings, and excluded .idea from version control.
  • Tests
    • Updated Firo/Spark-related test mocks and coverage.

kvhnuke and others added 30 commits December 23, 2024 15:39
…ture/add-firo-network

# Conflicts:
#	packages/extension/src/libs/utils/wasmModule/spark.js
Add TX activity history after sending from Spark address
…ed-spark

Disable activity when spark to spark transaction is failed or dropped
narekpetrosyan and others added 16 commits February 21, 2026 22:20
2. Fix state init
3. In QR now shows Firo:{address}
1. Accounts list show spark addresses
In getSparkCoinInfo, the WASM buffer for the serial context was allocated
with serializedCoin.length and then populated with serialContext via
HEAPU8.set(serialContext, ...). When serialContext.length exceeds
serializedCoin.length, this writes past the allocated region, corrupting
WASM heap memory and producing non-deterministic failures or undefined
behavior in js_deserializeCoin. Use serialContext.length, matching the
pattern already used in libs/spark-handler/index.ts.

Co-authored-by: Reuben Yap <reuben@firo.org>
@github-actions

github-actions Bot commented May 13, 2026

Copy link
Copy Markdown

💼 Build Files
chrome: enkrypt-chrome-8e027dd2.zip
firefox: enkrypt-firefox-8e027dd2.zip

💉 Virus total analysis
chrome: 8e027dd2
firefox: 8e027dd2

@kvhnuke kvhnuke mentioned this pull request May 13, 2026
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds Firo mainnet/testnet support, Spark wallet helpers, synchronization state, transaction flows, and UI updates across the extension.

Changes

Firo + Spark integration

Layer / File(s) Summary
Dependencies, contracts, and keyring plumbing
.gitignore, README.md, package.json, packages/extension/package.json, packages/keyring/package.json, packages/types/src/networks.ts, packages/extension/src/types/provider.ts, packages/extension/src/types/activity.ts, packages/extension/src/providers/bitcoin/types/*, packages/extension/src/ui/action/types/account.ts, packages/extension/src/ui/action/types/provider.ts, packages/extension/src/libs/background/index.ts, packages/extension/src/libs/keyring/*
Adds Firo-related package and type surface changes, keyring accessors, and background mnemonic handoff into the wallet.
Electrum, wallet, and network providers
packages/extension/src/providers/bitcoin/libs/electrum-client/*, packages/extension/src/providers/bitcoin/libs/firo-wallet/*, packages/extension/src/providers/bitcoin/networks/*, packages/extension/src/providers/bitcoin/libs/api-firo.ts, packages/extension/src/providers/bitcoin/libs/utils.ts, packages/extension/src/providers/bitcoin/ui/libs/signer.ts
Adds Electrum client and wallet implementations, Firo network/provider registration, Spark address validation, and Firo transaction signing helpers.
Spark helpers and WASM bridge
packages/extension/src/libs/spark-handler/*, packages/extension/src/libs/utils/wasm-*.ts, packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue, packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/*, packages/extension/src/providers/bitcoin/ui/send-transaction/verify-transaction/index.vue, packages/extension/src/providers/bitcoin/ui/types.ts
Adds Spark transaction helpers, mint/spend serialization, WASM loaders, Spark address input, Spark send/verify flows, and Spark-aware transaction typing.
Spark sync, IndexedDB, and worker pipeline
packages/extension/src/ui/action/db/indexedDB.ts, packages/extension/src/ui/action/composables/*, packages/extension/src/ui/action/workers/sparkCoinInfoWorker.ts, packages/extension/src/ui/action/App.vue, packages/extension/src/ui/action/views/network-activity/*, packages/extension/src/ui/action/router/index.ts
Adds IndexedDB persistence, coin/tag synchronization, Spark worker processing, app-level Spark state propagation, network-activity rendering, and Spark verification routes.
Accounts, deposit, assets, and wallet UI
packages/extension/src/ui/action/components/accounts-header/*, packages/extension/src/ui/action/views/accounts/*, packages/extension/src/ui/action/views/deposit/index.vue, packages/extension/src/ui/action/views/network-assets/*, packages/extension/src/ui/action/views/asset-detail-view/index.vue, packages/extension/src/ui/action/views/reset-wallet/index.vue, packages/extension/src/ui/action/views/lock-screen/*, packages/extension/src/ui/action/components/app-menu/index.vue, packages/extension/src/ui/action/main.ts, packages/extension/src/ui/onboard/App.vue
Wires Spark-aware account presentation, deposit tabs, asset totals, reset flows, app bootstrap, and supporting UI presentation changes.
Tests, config, and formatting
packages/name-resolution/tests/*, packages/extension/tsconfig.app.json, packages/extension/vite.config.ts, packages/extension/.prettierignore, packages/extension/src/libs/utils/number-formatter.ts
Applies test, config, formatting, and utility-only adjustments that support the Firo and Spark feature set.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • enkryptcom/enKrypt#603: Both PRs modify the NetworkNames enum in packages/types/src/networks.ts, so they are directly related at the code level.
  • enkryptcom/enKrypt#786: Both PRs touch the Firo network-activity UI, especially network-activity-total.vue, with overlapping Spark balance and activity presentation changes.
  • enkryptcom/enKrypt#804: The Firo wallet, keyring, background unlock, and Spark helper changes overlap with the same Firo integration area.

Suggested reviewers: gamalielhere

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly reflects the main change: adding Firo network support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch firoorg-feature/add-firo-network

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
packages/extension/src/ui/action/views/reset-wallet/index.vue (1)

69-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure isProcessing is reset when reset flow fails.

If Line 72 or Line 73 throws, the button remains disabled indefinitely.

Proposed fix
 const resetAction = async () => {
-  isProcessing.value = true;
-  const keyring = new KeyRingBase();
-  await keyring.reset();
-  await db.resetDB();
-  emit('action:lock');
-  openOnboard().then(() => {
-    window.close();
-  });
+  isProcessing.value = true;
+  try {
+    const keyring = new KeyRingBase();
+    await keyring.reset();
+    await db.resetDB();
+    emit('action:lock');
+    await openOnboard();
+    window.close();
+  } finally {
+    isProcessing.value = false;
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/views/reset-wallet/index.vue` around lines
69 - 78, The resetAction currently sets isProcessing.value = true but never
resets it if KeyRingBase().reset() or db.resetDB() throws; wrap the async steps
in a try/catch/finally (or use .catch/.finally) so that any thrown error is
handled and isProcessing.value is set back to false in the finally block; keep
the existing emit('action:lock') and openOnboard() logic but ensure
isProcessing.value = false runs after success or failure, and optionally rethrow
or handle/log the error in the catch to preserve behavior.
packages/extension/src/ui/action/views/lock-screen/components/lock-screen-forgot.vue (1)

71-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add failure recovery for processing state in reset flow.

If Line 74 or Line 75 throws, the action stays locked because isProcessing never returns to false.

Proposed fix
 const resetAction = async () => {
-  isProcessing.value = true;
-  const keyring = new KeyRingBase();
-  await keyring.reset();
-  await db.resetDB();
-  openOnboard();
+  isProcessing.value = true;
+  try {
+    const keyring = new KeyRingBase();
+    await keyring.reset();
+    await db.resetDB();
+    await openOnboard();
+  } finally {
+    isProcessing.value = false;
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/ui/action/views/lock-screen/components/lock-screen-forgot.vue`
around lines 71 - 77, The resetAction currently sets isProcessing.value = true
but never guarantees it is set back to false if KeyRingBase().reset() or
db.resetDB() throws; wrap the async work in a try/catch/finally (or use
.finally) so that isProcessing.value is reset to false in the finally block,
optionally handling/logging errors from KeyRingBase.reset and db.resetDB before
calling openOnboard; ensure references to isProcessing, resetAction,
KeyRingBase.reset, db.resetDB and openOnboard are updated so the processing
state is always cleared even on failure.
packages/extension/src/ui/action/App.vue (1)

392-433: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set sparkAccount to null for non-Firo networks.

Initializing sparkAccount as {} makes it truthy on all networks and can bypass null checks in consumers expecting either a populated SparkAccount or null.

Suggested fix
-  const sparkAccount = {} as SparkAccount;
+  let sparkAccount: SparkAccount | null = null;
...
-  if (network.name === NetworkNames.Firo) {
+  if (network.name === NetworkNames.Firo) {
+    sparkAccount = {} as SparkAccount;
...
-    sparkAccount['sparkBalance'] = { availableBalance };
+    sparkAccount.sparkBalance = { availableBalance };
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/App.vue` around lines 392 - 433, The code
always initializes sparkAccount as an empty object which is truthy on non-Firo
networks; change the logic so sparkAccount is only populated for
NetworkNames.Firo and set to null otherwise. Specifically, update the
initialization and branching around the sparkAccount variable (the const
sparkAccount declaration and the if (network.name === NetworkNames.Firo) block)
so that when the network is not Firo you assign null to sparkAccount, and when
it is Firo you build the object as currently done; ensure
accountHeaderData.value uses that null value for sparkAccount so consumers can
reliably check for presence.
🟡 Minor comments (17)
packages/extension/src/libs/spark-handler/utils.ts-14-23 (1)

14-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for invalid base64 input.

The atob function on Line 15 will throw an error if the input is not valid base64. This could cause uncaught exceptions in the caller.

🛡️ Proposed fix with validation
 export const base64ToReversedHex = (base64: string): string => {
+  if (!base64) {
+    throw new Error('Base64 input cannot be empty');
+  }
+
+  let binary: string;
+  try {
-  const binary = atob(base64);
+    binary = atob(base64);
+  } catch (error) {
+    throw new Error(`Invalid base64 input: ${error instanceof Error ? error.message : 'unknown error'}`);
+  }
+
   const bytes = new Uint8Array(binary.length);
 
   for (let i = 0; i < binary.length; i++) {
     bytes[i] = binary.charCodeAt(i);
   }
   const reversed = Array.from(bytes).reverse();
   return reversed.map(b => b.toString(16).padStart(2, '0')).join('');
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/utils.ts` around lines 14 - 23, The
function base64ToReversedHex can throw when atob receives invalid base64; wrap
the decoding in a try/catch (or validate the input before calling atob) inside
base64ToReversedHex so the function either returns a clear error value or throws
a controlled Error with a descriptive message; ensure the catch handles
malformed input and does not allow uncaught exceptions to propagate to callers.
packages/extension/src/providers/bitcoin/libs/firo-wallet/utils.ts-1-19 (1)

1-19: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix callback signature mismatch and improve type safety.

The callback signature declares an optional index parameter, but Line 11 never passes it when calling callback(data[i]). Additionally, using any[] reduces type safety.

🔧 Proposed fix
-export const chunkedEvery = (
-  data: any[],
+export const chunkedEvery = <T>(
+  data: T[],
   chunksize: number,
-  callback: (data: any, index?: number) => void,
+  callback: (data: T, index: number) => void,
   finished: () => void,
 ) => {
   let i = 0;
   (function chunk() {
     const end = Math.min(i + chunksize, data.length);
     for (; i < end; ++i) {
-      callback(data[i]);
+      callback(data[i], i);
     }
     if (i < data.length) {
       setTimeout(chunk, 0);
     } else {
       finished.call(null);
     }
   })();
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/libs/firo-wallet/utils.ts` around
lines 1 - 19, The function chunkedEvery has a callback signature mismatch and
weak typing; change it to be generic (e.g., <T>) and type the parameters as
data: T[], chunksize: number, callback: (item: T, index: number) => void,
finished: () => void, and when invoking the callback inside chunk() pass both
the item and its index (call callback(data[i], i)); update any other related
signatures in chunkedEvery to use the new generic types for improved type
safety.
packages/extension/src/libs/spark-handler/utils.ts-1-12 (1)

1-12: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix return type and add input validation.

The function has type safety and validation issues:

  1. Lines 9-11 use optional chaining and can return undefined, but the return type isn't declared as string | undefined
  2. No validation for negative numbers, which would produce unexpected results
  3. The regex match can return null if no matches are found (though unlikely with the current logic)
🔧 Proposed fix
-export const numberToReversedHex = (num: number) => {
+export const numberToReversedHex = (num: number): string => {
+  if (num < 0) {
+    throw new Error('Number must be non-negative');
+  }
+
   let hex = num.toString(16);
 
   if (hex.length % 2 !== 0) {
     hex = '0' + hex;
   }
 
-  const bytes = hex.match(/.{2}/g);
-  const reversed = bytes?.reverse();
-
-  return reversed?.join('');
+  const bytes = hex.match(/.{2}/g) || [];
+  return bytes.reverse().join('');
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/utils.ts` around lines 1 - 12,
numberToReversedHex currently can return undefined and accepts invalid inputs;
change its signature to return a string and add input validation: in
numberToReversedHex validate that num is a finite, non-negative safe integer
(e.g., Number.isFinite and Number.isSafeInteger and num >= 0) and throw a
TypeError for invalid inputs; compute hex = Math.floor(num).toString(16), pad
with a leading '0' when needed, call hex.match(/.{2}/g) and assert the result
(bytes) is non-null before reversing, then join and return the resulting string
so bytes/reversed never produce undefined (use local variables bytes and
reversed from the function to locate the logic to change).
packages/extension/src/ui/action/views/lock-screen/index.vue-59-59 (1)

59-59: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the unused update:init-spark-state emit event.

The update:init-spark-state event is declared but never emitted in this component and has no listeners in parent components. Remove it if it's not needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/views/lock-screen/index.vue` at line 59,
Remove the unused emit declaration for 'update:init-spark-state' from the
component's emits/type signature (the line declaring (e:
'update:init-spark-state', password: string): void;), since the event is never
emitted or listened to; update the emits/type definition to exclude
'update:init-spark-state' and run type checks to ensure no other code relies on
it (search for references to 'update:init-spark-state' to confirm safe removal).
packages/extension/src/libs/utils/wasm-loader.ts-34-44 (1)

34-44: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove async Promise executor anti-pattern.

Using async in a Promise executor is unnecessary and can hide errors. The static analysis tool correctly flags this.

🔧 Proposed fix
 async function loadWasm(): Promise<WasmModule> {
-  return new Promise(async (resolve, reject) => {
-    const wasmModule = await initSpark();
-
-    if (typeof wasmModule !== 'undefined') {
-      resolve(wasmModule);
-    } else {
-      reject(new Error('Failed to load WASM module.'));
-    }
-  });
+  const wasmModule = await initSpark();
+  
+  if (typeof wasmModule === 'undefined') {
+    throw new Error('Failed to load WASM module.');
+  }
+  
+  return wasmModule;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/wasm-loader.ts` around lines 34 - 44, The
Promise executor in loadWasm uses an unnecessary async function which can hide
errors; remove the async Promise-executor anti-pattern by not passing an async
function into new Promise. Instead, call initSpark() outside of a new Promise
(or use a synchronous executor) — await initSpark() directly in loadWasm, then
resolve or throw based on the returned wasmModule (refer to loadWasm and
initSpark) so errors propagate normally instead of being swallowed by an async
executor.
packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue-86-88 (1)

86-88: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run Spark validation for the initial value.

This watcher is lazy, so a prefilled or restored address renders as invalid until the user edits it. That makes valid Spark destinations start in the red/error state on first paint.

Suggested fix
-watch(btcAddress, async () => {
-  isValidSparkAddress.value = await isSparkAddress(btcAddress.value);
-});
+watch(
+  btcAddress,
+  async () => {
+    isValidSparkAddress.value = await isSparkAddress(btcAddress.value);
+  },
+  { immediate: true },
+);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue`
around lines 86 - 88, The watcher on btcAddress only validates on changes so
prefilled/restored addresses remain unvalidated; update the logic to validate
the initial value as well by either making the watcher immediate
(watch(btcAddress, ..., { immediate: true })) or invoking
isSparkAddress(btcAddress.value) once during component setup/onMounted and
assigning the result to isValidSparkAddress.value; reference btcAddress,
isValidSparkAddress, and isSparkAddress so you update the existing watcher or
add the one-time initial validation call.
packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue-90-93 (1)

90-93: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Emit the blur transition too.

toggle:showContacts is only emitted on focus. After a blur, the parent never receives false, so any contacts UI opened from this field can stay visible with stale state.

Suggested fix
 const changeFocus = (val: FocusEvent) => {
   isFocus.value = val.type === 'focus';
-  if (isFocus.value) emit('toggle:showContacts', isFocus.value);
+  emit('toggle:showContacts', isFocus.value);
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue`
around lines 90 - 93, The changeFocus handler only emits toggle:showContacts on
focus, leaving the parent with stale true after blur; update the changeFocus
function (changeFocus and isFocus.value) to emit('toggle:showContacts',
isFocus.value) for both focus and blur (i.e., remove the conditional so the emit
always runs after setting isFocus.value) so the parent receives false on blur
and the contacts UI state stays in sync.
packages/extension/src/providers/bitcoin/libs/api-firo.ts-76-91 (1)

76-91: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Error objects for rejected broadcast failures.

On Line 88, Promise.reject(response.message) rejects a string. Downstream handlers commonly read error.message, which becomes undefined and weakens telemetry/UI reporting.

Suggested fix
       .then(response => {
         if (response.error) {
-          return Promise.reject(response.message);
+          return Promise.reject(
+            new Error(
+              typeof response.message === 'string'
+                ? response.message
+                : 'Firo broadcast failed',
+            ),
+          );
         }
         return response;
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/libs/api-firo.ts` around lines 76 -
91, The broadcastTx function currently rejects with a raw string
(Promise.reject(response.message)) which loses error.message; change the
rejection to an Error object so callers can read error.message — in broadcastTx,
where it checks if (response.error) replace Promise.reject(response.message)
with rejecting a new Error constructed from response.message (fallback to
JSON.stringify(response) if message is missing) so the rejected value is an
Error instance.
packages/extension/src/ui/action/views/asset-detail-view/index.vue-66-73 (1)

66-73: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard fiat recomputation against non-numeric values.

Line 70 can yield NaN when token.valuef is non-numeric/formatted, causing invalid USD output instead of a safe fallback.

Suggested fix
           {{
             $filters.parseCurrency(
-              token.balanceTotal
-                ? Number(token.balanceTotal) * Number(token.valuef)
-                : token.balanceUSDf,
+              token.balanceTotal &&
+                Number.isFinite(Number(token.balanceTotal)) &&
+                Number.isFinite(Number(token.value))
+                ? Number(token.balanceTotal) * Number(token.value)
+                : token.balanceUSDf,
             )
           }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/views/asset-detail-view/index.vue` around
lines 66 - 73, The template is computing fiat with Number(token.balanceTotal) *
Number(token.valuef) which can produce NaN when token.valuef is non-numeric;
update the expression (or move to a small computed helper) to coerce and
validate both values (e.g., safeBalance = Number(token.balanceTotal), safeValue
= Number(token.valuef)), check isFinite/safeValue and isFinite/safeBalance,
compute fiat = isFinite(safeBalance) && isFinite(safeValue) ? safeBalance *
safeValue : Number(token.balanceUSDf) || 0, and pass that fiat to
$filters.parseCurrency to ensure parseCurrency never receives NaN (use the
existing symbols token.balanceTotal, token.valuef, token.balanceUSDf, and
$filters.parseCurrency).
packages/extension/src/ui/action/components/accounts-header/components/header-accounts.vue-6-10 (1)

6-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate that sparkAddress is provided when network is Firo.

When network.name === NetworkNames.Firo, the code uses sparkAddress for the identicon and display address. However, sparkAddress has a default value of empty string (Line 145), which could result in rendering an empty identicon and truncated empty address if the parent component doesn't provide it.

🛡️ Proposed fix to add validation or fallback
       <img
         :src="
           network.identicon(
-            network.name === NetworkNames.Firo ? sparkAddress : address,
+            network.name === NetworkNames.Firo ? (sparkAddress || address) : address,
           )
         "
         class="account__info-images__identicon"
       />
       <span>{{
         $filters.replaceWithEllipsis(
-          network.name === NetworkNames.Firo ? sparkAddress : address,
+          network.name === NetworkNames.Firo ? (sparkAddress || address) : address,
           6,
           4,
         )
       }}</span>

Also applies to: 22-28

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/ui/action/components/accounts-header/components/header-accounts.vue`
around lines 6 - 10, When rendering identicons/addresses for Firo in
header-accounts.vue, ensure sparkAddress is validated before use: in the
conditional that calls network.identicon(network.name === NetworkNames.Firo ?
sparkAddress : address) and the display/address truncation logic (also at the
22-28 block), check that sparkAddress is non-empty and falls back to address or
a known placeholder (e.g., a default identicon seed or "—") when empty; if
fallback is used, optionally emit a warning via console or a local logger.
Update the conditional(s) to prefer sparkAddress only when truthy, e.g., use
(network.name === NetworkNames.Firo && sparkAddress) ? sparkAddress : address,
and ensure the displayed text similarly falls back to address so no empty
identicon or truncated empty string is rendered.
packages/extension/src/providers/bitcoin/types/bitcoin-network.ts-40-41 (1)

40-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return '0' instead of empty string for consistency.

When no private balance resolver is found, the function returns an empty string. This is inconsistent with calculateCurrentSparkBalance (Line 11) which returns '0' for empty/missing balances. An empty string could cause unexpected behavior in UI components expecting numeric strings.

🛡️ Proposed fix for consistency
   const resolvePrivateBalance = privateBalanceResolvers[networkName.toUpperCase()];
-  if (!resolvePrivateBalance) return '';
+  if (!resolvePrivateBalance) return '0';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/types/bitcoin-network.ts` around
lines 40 - 41, The resolver lookup currently returns an empty string when no
resolver is found; change this to return the string '0' to match
calculateCurrentSparkBalance's behavior. Update the branch that checks
resolvePrivateBalance (using privateBalanceResolvers and
networkName.toUpperCase()) so that instead of returning '' it returns '0',
ensuring callers and UI components always receive a numeric string.
packages/extension/src/libs/utils/updateAndSync/calculateCurrentSparkBalance.ts-7-18 (1)

7-18: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for IndexedDB operations.

The function performs an IndexedDB read without a try-catch block. If db.readData fails or if the reduce encounters unexpected data, the promise will reject without graceful handling.

🛡️ Proposed fix to add error handling
 export const calculateCurrentSparkBalance = async () => {
+  try {
     const myCoins = await db.readData<MyCoinModel[]>(DB_DATA_KEYS.myCoins);
 
     if (!myCoins?.length) {
       return '0';
     }
 
     const unusedCoins = myCoins.filter(el => !el.isUsed);
     const balance = unusedCoins.reduce((a: bigint, c) => a + c.value, 0n);
 
     return new BigNumber(balance.toString()).toString();
+  } catch (error) {
+    console.error('Failed to calculate Spark balance:', error);
+    return '0';
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/libs/utils/updateAndSync/calculateCurrentSparkBalance.ts`
around lines 7 - 18, Wrap the body of calculateCurrentSparkBalance in a
try-catch: call db.readData(DB_DATA_KEYS.myCoins) inside the try, validate
myCoins is an array before using filter/reduce, protect the reduce by ensuring
each coin has a numeric/BigInt value (or coerce with BigInt) and use 0n as the
initial accumulator; on any error log the error (console.error or the project
logger) and return '0' to fail gracefully instead of letting the promise reject.
packages/extension/src/libs/utils/updateAndSync/handleCoinSetUpdates.ts-54-67 (1)

54-67: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add bounds checking for setId.

The code checks setId < 1 but doesn't validate the upper bound. Accessing updatedSets[setIndex] when setIndex >= updatedSets.length would create sparse array entries or undefined behavior.

🛡️ Proposed fix to add upper bound check
   normalizedUpdates.forEach(update => {
     const setId = update.setId;
     if (!setId || setId < 1) return;
     const setIndex = setId - 1;
+    
+    // Ensure array is large enough
+    while (updatedSets.length <= setIndex) {
+      updatedSets.push({ blockHash: '', setHash: '', coins: [] });
+    }
 
     const existingSet = updatedSets[setIndex];
     const mergedCoins = mergeCoins(existingSet?.coins ?? [], update.set.coins);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/updateAndSync/handleCoinSetUpdates.ts`
around lines 54 - 67, The loop over normalizedUpdates does not check the upper
bound for setId, so setIndex may exceed updatedSets.length and create sparse
entries; modify the forEach block that uses setId/setIndex (references:
normalizedUpdates, setId, setIndex, updatedSets, mergeCoins) to validate that
setIndex is within [0, updatedSets.length - 1] before reading/updating
updatedSets—if setIndex is out of range, skip the update (return) or handle it
explicitly (e.g., log/warn) so you never assign to updatedSets[setIndex] when it
would extend the array unintentionally.
packages/extension/src/ui/action/views/network-activity/index.vue-92-95 (1)

92-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused isSyncing prop.

The isSyncing prop is defined but never referenced in the template, script logic, or passed to any child components. The child component network-activity-total uses its own isSyncing computed property derived from different props (isCoinSyncing and isTagSyncing), making this parent prop dead code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/views/network-activity/index.vue` around
lines 92 - 95, Remove the dead prop definition isSyncing from the props block in
the parent component (index.vue) since it is never read or passed down; update
the props object to no longer declare isSyncing and run a quick search for any
references to isSyncing in this component to ensure nothing else relies on it
(the child component network-activity-total computes its own isSyncing from
isCoinSyncing/isTagSyncing so no changes are needed there).
packages/extension/src/ui/action/composables/update-activity-state.ts-2-2 (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update import to use the public entrypoint @enkryptcom/types.

The import from @enkryptcom/types/dist is inconsistent with the codebase standard and breaks with export-map changes. Change to import { NetworkNames } from '@enkryptcom/types'; to match the rest of the codebase. Note: get-spark-address.ts, synchronize-state.vue, accounts-list-item.vue, and transparent-send-tab.vue have the same issue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/composables/update-activity-state.ts` at
line 2, Replace the non-public import of NetworkNames from
'@enkryptcom/types/dist' with the public package entrypoint by changing
occurrences like "import { NetworkNames } from '@enkryptcom/types/dist';" to
"import { NetworkNames } from '@enkryptcom/types';" (apply the same change in
the other affected files get-spark-address.ts, synchronize-state.vue,
accounts-list-item.vue, and transparent-send-tab.vue) so the code uses the
documented public export and avoids export-map breakage.
packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue-296-303 (1)

296-303: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid global error styling for all <p> in this view.

This forces every paragraph in the send screen to use @error color, including non-error text. Scope it to a dedicated error class instead.

Suggested fix
-p {
-  font-weight: 400;
-  font-size: 14px;
-  line-height: 20px;
-  letter-spacing: 0.25px;
-  color: `@error`;
-  margin: 0;
-}
+.send-transaction__error {
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 20px;
+  letter-spacing: 0.25px;
+  color: `@error`;
+  margin: 0;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`
around lines 296 - 303, The stylesheet currently targets all p elements and
forces the error color; change the selector to a dedicated class (e.g.,
.error-text or .send-error) instead of p, remove the global p color rule, and
update the template in index.vue to apply that class only to the paragraph(s)
that display error messages (replace <p> used for errors with <p
class="error-text"> or similar); ensure the style is scoped to the component if
needed so other views aren’t affected.
packages/extension/src/libs/utils/updateAndSync/markCoinsAsUsed.ts-72-80 (1)

72-80: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return a numeric fallback value here.

SparkUnusedTxDetails.value is typed as number, but this branch returns '0' as a string. That will leak a mixed type into downstream arithmetic/sorting even though the predicate later asserts the result is SparkUnusedTxDetails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/updateAndSync/markCoinsAsUsed.ts` around
lines 72 - 80, The branch that handles missing coinValue returns value: '0' (a
string), which conflicts with the expected numeric SparkUnusedTxDetails.value
and can break downstream arithmetic/sorting; update the return in that branch to
use a numeric fallback (value: 0) and ensure the object shape returned by the
function (e.g., in markCoinsAsUsed) preserves the typed SparkUnusedTxDetails;
verify coinValue, txId and index logic still match before returning the fallback
so TypeScript and consumers receive a number not a string.
🧹 Nitpick comments (18)
packages/extension/src/libs/utils/number-formatter.ts (1)

316-317: ⚡ Quick win

Remove blanket TypeScript suppression in percentage parsing.

// @ts-ignore`` on Line 316 hides real type errors on Line 317. Prefer a safe coercion path instead of suppressing checks.

Suggested fix
-  // `@ts-ignore`
-  const value = new BigNumber(_value.toString().replaceAll('%', ''));
+  const sanitized = String(_value).replace(/%/g, '');
+  const value = new BigNumber(sanitized);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/number-formatter.ts` around lines 316 -
317, Remove the blanket "// `@ts-ignore`" before the percentage parsing and
instead coerce and sanitize _value safely before constructing BigNumber: ensure
_value is not null/undefined, convert it to a string using a safe coercion
(e.g., String(_value)), strip percent signs using a regex-based replace (avoid
replaceAll for compatibility), and then pass the sanitized string into new
BigNumber(...); update the variable references (_value, value, BigNumber,
replaceAll) accordingly and add a guard to handle empty or invalid sanitized
input (e.g., fallback to "0" or throw a clear error).
packages/name-resolution/tests/resolver.test.ts (2)

40-40: ⚡ Quick win

Update assertions to Vitest style for consistency.

These assertions still use Chai-style .to.be.eq() syntax. For consistency with the updates made in ud.test.ts and to align with Vitest best practices, consider updating to .toBe().

Example fix for line 40
-    expect(address).to.be.eq("0x54fd548fFD6Ff54943d6863F1d485Cc2Fb495574");
+    expect(address).toBe("0x54fd548fFD6Ff54943d6863F1d485Cc2Fb495574");

Also applies to: 42-42, 45-45, 63-63, 67-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/name-resolution/tests/resolver.test.ts` at line 40, Replace
Chai-style assertions in the resolver tests with Vitest-style matchers: for each
occurrence of expect(...).to.be.eq(...) or expect(...).to.be.eql(...), update to
expect(...).toBe(...) or expect(...).toEqual(...) as appropriate (e.g. the
assertion using the variable address on line 40 and the similar assertions at
lines 42, 45, 63, 67 in resolver.test.ts). Ensure you only change the matcher
syntax (keep the same expected values and variables like address, addressEns,
etc.) so tests use Vitest's .toBe/.toEqual consistently.

4-23: ⚡ Quick win

Remove commented-out code or implement it properly.

Similar to sid.test.ts, this commented-out mock code should either be properly implemented or removed. If uncommented, it would fail due to the missing vi import from vitest.

Additionally, these changes appear unrelated to the PR objective of adding Firo network support.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/name-resolution/tests/resolver.test.ts` around lines 4 - 23, The
commented-out vi.mock blocks in resolver.test.ts (the mocks for createWeb3Name,
createSolName, createPaymentIdName using vi.mock) should be either removed or
properly implemented: either delete these commented lines since they are
unrelated to the Firo work, or uncomment and add the proper Vitest import (e.g.,
import { vi } from 'vitest') and ensure the mock factories (createWeb3Name,
createSolName, createPaymentIdName) return the mocked methods (getDomainName,
getAddress) as shown; keep only the approach you choose to avoid unrelated test
noise.
packages/name-resolution/tests/sid.test.ts (2)

36-36: ⚡ Quick win

Update assertions to Vitest style for consistency.

The assertions in this file still use Chai-style .to.be.eq() syntax, while ud.test.ts was updated to use Vitest's .toBe() syntax. For consistency across the test suite, consider updating these assertions.

Example fix for line 36
-    expect(address).to.be.eq("0xb5932a6B7d50A966AEC6C74C97385412Fb497540");
+    expect(address).toBe("0xb5932a6B7d50A966AEC6C74C97385412Fb497540");

Also applies to: 50-50, 64-64, 69-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/name-resolution/tests/sid.test.ts` at line 36, Tests in sid.test.ts
use Chai-style assertions (e.g., expect(address).to.be.eq(...)); replace them
with Vitest-style matchers by changing calls like
expect(address).to.be.eq("...") to expect(address).toBe("...") (and similarly
update the other occurrences noted around the other expect calls at the same
file). Locate the expect(...) usages for the variable/address assertions and
update the matcher to toBe(...), ensuring you keep the same expected string
values and do not reintroduce Chai chain syntax.

4-23: ⚡ Quick win

Remove commented-out code or implement it properly.

Commented-out code is generally considered a code smell and should be removed. Additionally, if these mocks were uncommented, they would fail because vi is not imported from vitest.

If mocking is needed to make tests pass, either:

  1. Properly implement the mocks with the necessary imports, or
  2. Remove the commented code entirely

Also note that these changes appear unrelated to the PR objective of adding Firo network support.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/name-resolution/tests/sid.test.ts` around lines 4 - 23, Remove the
large commented-out mock block in the tests (the createWeb3Name / createSolName
/ createPaymentIdName mocks) or implement them correctly: if you need the mocks,
import vi from vitest at the top (import { vi } from 'vitest') and uncomment and
adapt the mock factories so they export the mocked functions used by the tests;
otherwise simply delete the commented code to avoid unrelated noise in
sid.test.ts. Ensure any retained mocks reference the exact factory names
createWeb3Name, createSolName, and createPaymentIdName so the test harness picks
them up.
packages/extension/tsconfig.app.json (1)

13-14: ⚡ Quick win

Document why skipLibCheck and allowJs are required.

These options are necessary for the current setup:

  • allowJs: true is required to import spark.js WASM modules (Emscripten-generated files in libs/utils/wasmModule/ and wasmWorkerModule/)
  • skipLibCheck: true is needed because bitcoinjs-lib-firo is a custom GitHub fork without TypeScript type definitions

Consider adding a comment in tsconfig.app.json briefly explaining these requirements so future maintainers understand why they cannot be safely removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/tsconfig.app.json` around lines 13 - 14, Add inline
comments in tsconfig.app.json next to the "skipLibCheck" and "allowJs" compiler
options explaining why they are required: note that "allowJs": true is needed to
import Emscripten-generated spark.js WASM modules from libs/utils/wasmModule and
wasmWorkerModule, and "skipLibCheck": true is required because we depend on a
custom fork (bitcoinjs-lib-firo) that lacks TypeScript definitions; update the
comment to warn maintainers not to remove these flags without adding proper
typings or replacing the WASM build artifacts.
packages/extension/src/libs/spark-handler/serializeMintContext.ts (1)

14-49: ⚡ Quick win

Consider adding input validation.

The function doesn't validate that:

  • txHashLength doesn't exceed the actual txHash buffer length (Line 20)
  • vout is within valid uint32 range
  • inputs array is not empty

Invalid inputs could produce malformed serialization that may cause issues in the WASM layer.

🛡️ Proposed validation checks
 export const serializeMintContext = (
   inputs: DartInputData[],
 ): SerializedMintContextResult => {
+  if (inputs.length === 0) {
+    throw new Error('Inputs array cannot be empty');
+  }
+
   const serialContextStream: Buffer[] = [];
 
   for (let i = 0; i < inputs.length; i++) {
+    if (inputs[i].txHashLength > inputs[i].txHash.length) {
+      throw new Error(`Invalid txHashLength at input ${i}`);
+    }
+    if (inputs[i].vout < 0 || inputs[i].vout > 0xffffffff) {
+      throw new Error(`Invalid vout value at input ${i}`);
+    }
+
     const txHashBuffer = inputs[i].txHash.slice(0, inputs[i].txHashLength);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/serializeMintContext.ts` around
lines 14 - 49, In serializeMintContext validate inputs before serializing:
ensure inputs.length > 0 and throw/return a clear error if empty; for each input
verify inputs[i].txHashLength is <= inputs[i].txHash.length and that
inputs[i].txHash is a Buffer (or convert/throw if not); check inputs[i].vout is
a safe unsigned 32-bit integer (0 <= vout <= 0xFFFFFFFF) and throw/return on
invalid values; perform these checks at the top of the loop (referencing
txHashLength, txHash, vout, and serializeMintContext) so malformed data is
rejected before building serialContextStream.
packages/extension/src/ui/action/utils/set-utils.ts (1)

11-19: 💤 Low value

Consider clarifying the function name or parameter order.

The differenceSets function computes update \ set (elements in update but not in set), which is the opposite of the standard mathematical set difference operation set \ update. This could be confusing for maintainers.

Consider either:

  1. Renaming to make the direction explicit (e.g., elementsNotInOriginal, newElementsIn)
  2. Swapping parameters to match standard set difference semantics
  3. Adding a JSDoc comment explaining the behavior
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/utils/set-utils.ts` around lines 11 - 19,
The function differenceSets(set, update) currently returns elements in update
that are not in set (i.e., update \ set) which is counter‑intuitive; rename the
function (e.g., newElementsIn or elementsNotInOriginal) and add a short JSDoc to
clarify the behavior, then update all references to differenceSets to the new
name; alternatively, if you prefer standard set difference semantics, swap the
parameter order to (update, set) and update callers accordingly—refer to the
function symbol differenceSets and its parameters set and update when making the
change.
packages/extension/package.json (1)

66-66: ⚡ Quick win

Document the purpose of the forked dependency.

The bitcoinjs-lib-firo fork is used to support Firo network functionality (Spark balance, anonymization features). The dependency is pinned to a specific verified commit (4b0c4322cb9f308cb84b2f655d16a9e9abc7b4e1), which ensures reproducible builds. Consider adding an inline comment explaining that this fork is required for Firo-specific features and documenting any blockers to upstreaming these changes to the official bitcoinjs-lib.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/package.json` at line 66, Add an inline comment and brief
documentation explaining why the forked dependency "bitcoinjs-lib-firo" is used:
state it provides Firo-specific functionality (Spark balance and anonymization
features), note that it's pinned to commit
4b0c4322cb9f308cb84b2f655d16a9e9abc7b4e1 for reproducible builds, and summarize
any known blockers to upstreaming these changes to the official bitcoinjs-lib so
future maintainers know the rationale and upgrade path; update the package.json
near the "bitcoinjs-lib-firo" entry and add a short note in the repo README or
deps.md referencing the same commit and blockers.
packages/extension/src/ui/action/composables/async-computed.ts (2)

21-21: ⚡ Quick win

Type error more precisely.

Casting to as unknown as any loses all type information. Use a more specific error type.

♻️ Proposed fix
     } catch (err) {
-      if (!cancelled) error.value = err as unknown as any;
+      if (!cancelled) error.value = err instanceof Error ? err : new Error(String(err));
     } finally {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/composables/async-computed.ts` at line 21,
The assignment "if (!cancelled) error.value = err as unknown as any" loses type
information; change it to assert a specific error shape (for example cast to
Error or a union like Error | string) or narrow at runtime (e.g., if (err
instanceof Error) error.value = err else error.value = new Error(String(err)))
so error.value keeps a meaningful type; update the code around variables
"error", "cancelled", and "err" in async-computed.ts accordingly to preserve
proper typing instead of using "as unknown as any".

3-3: ⚡ Quick win

Improve type safety with generic types.

The function signature uses any for both the async function return and initial value, which loses type information and may lead to runtime errors.

♻️ Proposed fix with generic typing
-export default (asyncFn: () => any, initialValue: any) => {
+export default <T>(asyncFn: () => Promise<T>, initialValue: T) => {
-  const value = ref(initialValue);
+  const value = ref<T>(initialValue);
   const isPending = ref(false);
-  const error = ref(null);
+  const error = ref<Error | null>(null);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/composables/async-computed.ts` at line 3,
The exported default function currently types both parameters as any; change it
to use a generic type parameter (e.g., <T>) so the signature becomes generic:
the asyncFn parameter should produce T (or Promise<T>) and initialValue should
be T, and update any internal types/returns to use T so callers retain strong
typing; locate the anonymous exported function declaration starting with "export
default (asyncFn: () => any, initialValue: any) =>" and replace the any types
with a generic type parameter throughout.
packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/firo/index.ts (1)

89-96: ⚡ Quick win

Use optional chaining for safer access.

Lines 90 and 94 use non-null assertions when mapping inputs/outputs. If the API returns unexpected data (e.g., missing addr or addresses), this will throw during the map operation.

♻️ Proposed fix
             inputs: tx.vin.map(input => ({
-              address: input.addr,
+              address: input.addr || '',
               value: Number(input.value),
             })),
             outputs: tx.vout.map(output => ({
-              address: output.scriptPubKey.addresses![0],
+              address: output.scriptPubKey.addresses?.[0] || '',
               value: Number(output.value),
               pkscript: output.scriptPubKey.hex,
             })),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/libs/activity-handlers/providers/firo/index.ts`
around lines 89 - 96, The mapping over tx.vin and tx.vout uses non-null
assertions and can throw if fields are missing; update the tx.vin.map and
tx.vout.map handlers (the inputs and outputs objects) to use optional chaining
and safe fallbacks (e.g., input?.addr ?? null, Number(input?.value ?? 0),
output?.scriptPubKey?.addresses?.[0] ?? null, Number(output?.value ?? 0), and
output?.scriptPubKey?.hex ?? null) so missing properties don’t cause exceptions
during mapping; ensure the created inputs/outputs keep the same property names
(address, value, pkscript) but tolerate undefined/null values instead of using
the ! operator.
packages/extension/src/libs/utils/wasm-loader.ts (1)

46-68: ⚖️ Poor tradeoff

Consider exposing WASM initialization errors.

The constructor (lines 50-56) starts loading the WASM module but only logs errors to the console. Callers using getInstance() won't know if initialization failed until they try to use the module. This can make debugging difficult.

Consider either:

  1. Exposing an initializationError property that callers can check
  2. Having getInstance() reject with the initialization error instead of retrying
  3. Documenting that callers must handle cases where the WASM module fails to load
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/wasm-loader.ts` around lines 46 - 68, Add
an initializationError property to the WasmInstance class, set it in the
constructor catch block when loadWasm() fails (instead of only console.error),
and update getInstance() to first check if initializationError is set and reject
with that error (rather than silently retrying); reference the WasmInstance
class, its instance field, the constructor's loadWasm().then/.catch, and the
getInstance() method when making these changes so callers can detect
initialization failures reliably.
packages/extension/src/providers/bitcoin/libs/utils.ts (1)

10-29: ⚖️ Poor tradeoff

Clarify error handling semantics.

When the WASM module fails to load (lines 16-18), the function returns false and logs to console. This conflates "WASM unavailable" with "invalid Spark address," which may lead to silent failures if callers assume false always means "not a Spark address."

Consider either:

  1. Throwing an error when WASM is unavailable (forcing callers to handle initialization failures)
  2. Returning a more explicit result like { valid: boolean, error?: string }
  3. At minimum, document that false can mean either "invalid address" or "validation unavailable"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/libs/utils.ts` around lines 10 - 29,
The current isSparkAddress function conflates "WASM unavailable" with "invalid
address"; change its behavior to throw a clear initialization error when
wasmInstance.getInstance() returns falsy instead of returning false — in
isSparkAddress replace the console.log+false path with throwing an Error (e.g.,
"WASM not loaded: spark address validation unavailable") so callers of
isSparkAddress (and any code invoking js_isValidSparkAddress) must handle
initialization failures explicitly; update call sites to catch this error or, if
you prefer a non-throwing API, implement an alternative return shape ({ valid:
boolean, error?: string }) consistently across usages.
packages/extension/src/libs/spark-handler/createTempTx.ts (1)

28-28: ⚡ Quick win

Type addressKeyPairs more precisely.

Record<string, any> loses type information. Consider defining an interface for the keypair structure.

♻️ Proposed improvement
+  addressKeyPairs: Record<string, ECPairInterface>;
-  addressKeyPairs: Record<string, any>;

This provides better type safety since you're already importing ECPairInterface and using it for spendableUtxos[].keyPair.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/createTempTx.ts` at line 28, The
type declaration addressKeyPairs: Record<string, any> is too loose — define a
precise type/interface (e.g., AddressKeyPair or AddressKeyPairsMap) that models
the stored keypair structure and use ECPairInterface for the keyPair property so
TypeScript can validate usages like spendableUtxos[].keyPair; update the
addressKeyPairs declaration to use this new interface and adjust any related
references (e.g., where addressKeyPairs[...] is accessed) to match the stricter
shape.
packages/extension/src/libs/spark-handler/createTempFromSparkTx.ts (1)

36-40: 💤 Low value

Consider BigNumber overflow when converting to satoshis.

Line 38 uses .toNumber() after multiplying by 10^8. JavaScript's Number type has a safe integer range of ±2^53, which corresponds to approximately 90 million BTC. While this is unlikely to be exceeded in practice, if amount is extremely large or malformed, .toNumber() could produce Infinity or lose precision, leading to incorrect transaction outputs.

Consider adding a bounds check or documenting the expected range.

♻️ Optional bounds check
   if (!isSparkTransaction) {
+    const satoshis = new BigNumber(amount).multipliedBy(10 ** 8);
+    if (!satoshis.isFinite() || satoshis.gt(Number.MAX_SAFE_INTEGER)) {
+      throw new Error('Amount too large for transaction output');
+    }
     tempTx.addOutput(
       bitcoin.address.toOutputScript(to, network),
-      new BigNumber(amount).multipliedBy(10 ** 8).toNumber(),
+      satoshis.toNumber(),
     );
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/createTempFromSparkTx.ts` around
lines 36 - 40, The code converts amount to satoshis with new
BigNumber(amount).multipliedBy(10 ** 8).toNumber() which can overflow or lose
precision; before calling tempTx.addOutput (and symbols
bitcoin.address.toOutputScript, tempTx.addOutput, BigNumber, amount), validate
and sanitize amount: compute satoshis = new
BigNumber(amount).multipliedBy(1e8).integerValue(BigNumber.ROUND_FLOOR), check
satoshis.isFinite() and sats.lte(Number.MAX_SAFE_INTEGER) and sats.gte(0) (or
throw a descriptive Error if out of bounds/malformed), then pass
sats.toNumber(); alternatively document allowed range in the function comment if
you prefer not to throw. Ensure you fail fast with a clear error message when
amount is invalid to avoid pushing Infinity/incorrect values into
tempTx.addOutput.
packages/extension/src/libs/utils/wasm-worker-loader.ts (1)

46-67: ⚡ Quick win

Deduplicate in-flight WASM initialization.

Line 50 preload and Line 63 fallback can race and initialize twice; cache a shared loading promise.

Refactor sketch
 class WasmWorkerInstance {
   instance: WasmModule | null = null;
+  private loadingPromise: Promise<WasmModule> | null = null;
 
   constructor() {
-    loadWasm()
+    this.loadingPromise = loadWasm()
       .then(module => {
         this.instance = module;
+        return module;
       })
       .catch(error => {
         console.error('Error loading WASM module:', error);
+        this.loadingPromise = null;
+        throw error;
       });
   }
 
   public async getInstance(): Promise<WasmModule> {
-    if (this.instance) {
-      return Promise.resolve(this.instance);
-    } else {
-      const wasm = await loadWasm();
-      this.instance = wasm;
-      return Promise.resolve(wasm);
-    }
+    if (this.instance) return this.instance;
+    if (!this.loadingPromise) {
+      this.loadingPromise = loadWasm().then(module => {
+        this.instance = module;
+        return module;
+      });
+    }
+    return this.loadingPromise;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/wasm-worker-loader.ts` around lines 46 -
67, The constructor and getInstance of WasmWorkerInstance can race and call
loadWasm twice; add a shared loading promise (e.g., a private property
loadingPromise: Promise<WasmModule> | null) and assign it when starting loadWasm
in the constructor, await that same promise in getInstance instead of calling
loadWasm again, and on resolution set this.instance and clear loadingPromise on
error set it to null while propagating/logging the error; update references in
constructor and getInstance to use loadingPromise and avoid duplicate
initialization.
packages/extension/src/libs/utils/updateAndSync/handleCoinSetUpdates.ts (1)

27-42: ⚖️ Poor tradeoff

Consider more robust coin deduplication strategy.

Using JSON.stringify for deduplication (Lines 31, 35) is fragile because:

  1. JavaScript object property ordering is not guaranteed in all cases
  2. Memory-intensive for large coin arrays (creates string copies)
  3. Two semantically identical coins with different key orders would not be detected as duplicates
Alternative approach using stable coin identifiers

If coins have a unique identifier (e.g., txid + vout), use that directly:

-const mergeCoins = (existing: string[][], incoming: string[][]): string[][] => {
+const getCoinKey = (coin: string[]): string => {
+  // Adjust based on actual coin structure, e.g., coin[0] + coin[1]
+  return `${coin[0]}-${coin[1]}`;
+};
+
+const mergeCoins = (existing: string[][], incoming: string[][]): string[][] => {
   if (!existing?.length) return [...incoming];
   if (!incoming?.length) return [...existing];
 
-  const seen = new Set(existing.map(coin => JSON.stringify(coin)));
+  const seen = new Set(existing.map(getCoinKey));
   const updates: string[][] = [];
 
   incoming.forEach(coin => {
-    const key = JSON.stringify(coin);
+    const key = getCoinKey(coin);
     if (seen.has(key)) return;
     seen.add(key);
     updates.push(coin);
   });
 
   return [...updates, ...existing];
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/utils/updateAndSync/handleCoinSetUpdates.ts`
around lines 27 - 42, The mergeCoins function currently deduplicates by
JSON.stringify which is brittle and memory-heavy; change mergeCoins to compute a
stable, lightweight key for each coin (e.g., if coin elements represent txid and
vout use `${txid}:${vout}` or build a deterministic key by ordering/normalizing
coin fields and joining with a fixed separator) and use a Set or Map of those
keys for deduplication instead of JSON.stringify; update references in
mergeCoins to generate the key (e.g., getCoinKey(coin)) when seeding seen and
when checking incoming coins so semantically identical coins are recognized
without expensive stringifying of whole arrays.

Comment on lines +262 to +267
privateRecipientsVector = Module.ccall(
'js_createPrivateRecipientsVectorForCreateSparkSpendTransaction',
'number',
['number'],
[1], // intended final size
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Spark-to-Spark sends are missing a change recipient.

For private sends, the vector is always sized to 1 and Line 311 uses requestedAmount as the only private output. When coin selection overshoots the target—which the greedy selector will often do—there is no second private recipient returning change to the sender, so the extra value has nowhere explicit to go.

Also applies to: 304-332

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/libs/spark-handler/index.ts` around lines 262 - 267,
The private send code always creates privateRecipientsVector with size 1 (using
js_createPrivateRecipientsVectorForCreateSparkSpendTransaction) and then only
pushes the requestedAmount as the sole private output (around the logic that
references requestedAmount), which drops any change when coin selection
overshoots; update the logic to size the privateRecipientsVector to accommodate
a potential change recipient (e.g., size 2 or size = 1 + (changePossible ? 1 :
0)), and when the coin selector produces change, append a second private
recipient representing the sender's change (address = sender, amount = change)
before calling the native create function; ensure you only add the change
recipient when change > 0 and adjust any loops or indices that assume a single
recipient (symbols: privateRecipientsVector,
js_createPrivateRecipientsVectorForCreateSparkSpendTransaction, and the code
that inserts requestedAmount at index 0).

if (!supportedNetworks.includes(networkName as NetworkNames))
return Promise.resolve(utxos);
return getAllOrdinals(address, networkName, []).then(ordinals => {
console.log({ ordinals });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove debug console.log from production code.

This debug statement will expose ordinal data to the browser console in production builds, potentially leaking user privacy information.

🐛 Proposed fix
   return getAllOrdinals(address, networkName, []).then(ordinals => {
-    console.log({ ordinals });
-
     return utxos.filter(utxo => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log({ ordinals });
return getAllOrdinals(address, networkName, []).then(ordinals => {
return utxos.filter(utxo => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/libs/filter-ordinals.ts` at line 49,
Remove the debug console.log that prints ordinals to the browser console: locate
the console.log({ ordinals }) in filter-ordinals.ts (the place where the
ordinals variable is handled) and delete it or replace it with a secure logging
call (e.g., a debug-only logger gated by NODE_ENV or a telemetry-safe logger
that redacts sensitive ordinal data) so no raw ordinal data is emitted in
production.

Comment on lines +34 to +51
private initDB(): void {
const request = indexedDB.open(this.dbName, this.dbVersion);

request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};

request.onsuccess = () => {
this.db = request.result;
};

request.onerror = () => {
console.error('Error opening IndexedDB', request.error);
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file in question
find . -type f -name "indexedDB.ts" | head -20

Repository: enkryptcom/enKrypt

Length of output: 113


🏁 Script executed:

# Check the repository structure to understand the context
git ls-files | grep -E "(indexedDB|db)" | head -20

Repository: enkryptcom/enKrypt

Length of output: 254


🏁 Script executed:

# Read the indexedDB.ts file to examine the code
cat -n packages/extension/src/ui/action/db/indexedDB.ts

Repository: enkryptcom/enKrypt

Length of output: 5662


🏁 Script executed:

# Check for test files or CI configuration that might reference this error
find . -type f \( -name "*.test.*" -o -name "*.spec.*" -o -name ".github" -o -name "ci.yml" -o -name "github-ci*" \) | grep -E "(test|spec|ci|github)" | head -20

Repository: enkryptcom/enKrypt

Length of output: 1135


🏁 Script executed:

# Search for any usage of IndexedDBHelper to understand impact
rg "IndexedDBHelper" --type ts --type js -l

Repository: enkryptcom/enKrypt

Length of output: 828


🏁 Script executed:

# Check how IndexedDBHelper is instantiated/used in one of the main files
head -100 packages/extension/src/ui/action/db/indexedDB.ts

Repository: enkryptcom/enKrypt

Length of output: 2800


🏁 Script executed:

# Look at sparkCoinInfoWorker to see if it runs in a browser context
head -50 packages/extension/src/ui/action/workers/sparkCoinInfoWorker.ts

Repository: enkryptcom/enKrypt

Length of output: 1287


Guard IndexedDB initialization for non-browser environments.

Line 35 assumes indexedDB exists. This breaks immediately when the module loads in non-browser contexts (Node.js tests, CI), since the constructor calls initDB() synchronously and indexedDB is undefined outside the browser.

Proposed fix
   private initDB(): void {
+    if (typeof indexedDB === 'undefined') {
+      console.warn('IndexedDB is unavailable in this environment');
+      return;
+    }
     const request = indexedDB.open(this.dbName, this.dbVersion);
🧰 Tools
🪛 GitHub Actions: Test all workspaces / 0_test.txt

[error] 35-35: Test run failed with ReferenceError: indexedDB is not defined (indexedDB.open(...)).

🪛 GitHub Actions: Test all workspaces / test

[error] 35-35: ReferenceError: indexedDB is not defined at IndexedDBHelper.initDB (indexedDB.open(...)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/ui/action/db/indexedDB.ts` around lines 34 - 51, The
initDB method assumes indexedDB exists and is called synchronously from the
constructor, which fails in non-browser environments; modify initDB (and the
code that invokes it) to first check for the presence of globalThis.indexedDB
(e.g., if (typeof globalThis.indexedDB === 'undefined') return;) and bail out
early if missing, so no indexedDB.open is invoked; update any constructor path
that calls initDB to still work when initDB returns early (ensure this.db
remains null/undefined and consumers handle that case).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue (1)

199-223: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore a visible keyboard focus state.

outline: none removes the default focus indicator, and only :hover gets a replacement border state.

Proposed fix
-      outline: none;
+      outline: 2px solid transparent;
+      outline-offset: 2px;
       border: 1px solid `@buttonBg`;
...
-      &:hover {
+      &:hover,
+      &:focus-visible {
         border-color: `@primaryLabel`;
       }
+
+      &:focus-visible {
+        outline-color: `@primaryLabel`;
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`
around lines 199 - 223, The button styles in the send-transaction view remove
the default keyboard focus indicator by setting outline to none, while only a
hover state is defined. Update the button styling in the relevant Vue component
to restore a clearly visible focus treatment for keyboard users by adding an
explicit :focus or :focus-visible style on the same button rule, using the
existing button selectors and design tokens so it matches the current
hover/border behavior.
🧹 Nitpick comments (1)
packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue (1)

105-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the debug log before merge.

The added console.log leaves a production artifact in the send-transaction UI path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`
around lines 105 - 112, The send-transaction UI still contains a debug artifact
in the setup logic, so remove the stray console.log from the code near
isLoadingAssets and keep the existing updateIsLoadingAssets and
setSelectedSendTab behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`:
- Around line 19-61: Mount only the active send panel in
send-transaction/index.vue by replacing the remaining v-show usage on
transparent-send-tab and spark-send-tab with v-if so only the selected component
is mounted, and make sure spark-send-tab is also gated by the same
NetworkNames.Firo and accountInfo.sparkAccount condition used by the tab
buttons; this prevents the hidden tab from emitting update:isLoadingAssets
through its mount path and affecting the shared loading state while the other
flow is still active.

---

Outside diff comments:
In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`:
- Around line 199-223: The button styles in the send-transaction view remove the
default keyboard focus indicator by setting outline to none, while only a hover
state is defined. Update the button styling in the relevant Vue component to
restore a clearly visible focus treatment for keyboard users by adding an
explicit :focus or :focus-visible style on the same button rule, using the
existing button selectors and design tokens so it matches the current
hover/border behavior.

---

Nitpick comments:
In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`:
- Around line 105-112: The send-transaction UI still contains a debug artifact
in the setup logic, so remove the stray console.log from the code near
isLoadingAssets and keep the existing updateIsLoadingAssets and
setSelectedSendTab behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9eb71f6c-bf87-475b-a3f8-e9f7e36510f4

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8c92a and c043a54.

📒 Files selected for processing (8)
  • packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/spark-send-tab.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue
  • packages/extension/src/ui/action/composables/update-activity-state.ts
  • packages/extension/src/ui/action/views/accounts/components/accounts-list-item.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-transaction.vue
  • packages/extension/src/ui/action/views/network-activity/index.vue
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/extension/src/ui/action/views/network-activity/index.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-transaction.vue
  • packages/extension/src/ui/action/views/accounts/components/accounts-list-item.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/spark-send-tab.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue
  • packages/extension/src/ui/action/composables/update-activity-state.ts
  • packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue

Comment on lines +19 to +61
<div
v-if="
!!accountInfo.sparkAccount && network.name === NetworkNames.Firo
"
class="send-transaction__tabs"
>
<button
:class="
selectedSendTab === 'transparent' &&
'send-transaction__tabs-active'
"
@click="setSelectedSendTab('transparent')"
>
Transparent address
</button>
<button
:class="
selectedSendTab === 'spark' && 'send-transaction__tabs-active'
"
@click="setSelectedSendTab('spark')"
>
Spark address
</button>
</div>

<transparent-send-tab
v-show="selectedSendTab === 'transparent'"
:network="network"
:account-info="accountInfo"
:is-send-token="isSendToken"
v-model:isLoadingAssets="isLoadingAssets"
:updateIsLoadingAssets="updateIsLoadingAssets"
/>

<spark-send-tab
v-show="selectedSendTab === 'spark'"
v-if="!!accountInfo.sparkAccount"
:network="network"
:account-info="accountInfo"
:spark-account="accountInfo?.sparkAccount ?? null"
:is-send-token="isSendToken"
v-model:isLoadingAssets="isLoadingAssets"
:updateIsLoadingAssets="updateIsLoadingAssets"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether hidden tab components mutate the shared loading state on mount/watch.
fd -i 'transparent-send-tab|spark-send-tab' packages/extension/src/providers/bitcoin/ui/send-transaction -t f \
  -x sh -c 'echo "### $1"; rg -n -C2 "isLoadingAssets|updateIsLoadingAssets|onMounted|watch|emit\\(" "$1"' sh {}

Repository: enkryptcom/enKrypt

Length of output: 2398


Mount only the active send tab. v-show keeps both components mounted, and spark-send-tab emits update:isLoadingAssets(false) on mount. That lets the hidden tab flip the shared loading state while the other flow is still fetching. Switch the active panel to v-if and gate Spark with the same Firo check as the tab switcher.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`
around lines 19 - 61, Mount only the active send panel in
send-transaction/index.vue by replacing the remaining v-show usage on
transparent-send-tab and spark-send-tab with v-if so only the selected component
is mounted, and make sure spark-send-tab is also gated by the same
NetworkNames.Firo and accountInfo.sparkAccount condition used by the tab
buttons; this prevents the hidden tab from emitting update:isLoadingAssets
through its mount path and affecting the shared loading state while the other
flow is still active.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts (3)

37-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Debug console.log left in.

Logging negotiated protocol version via console.log in production code is a debug artifact; prefer a structured logger or remove it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts`
at line 37, The negotiated protocol version is being written with a debug
console.log in the Firo Electrum client; remove this production debug output or
replace it with the project’s structured logger in the negotiation flow inside
the electrum client class/function where version negotiation happens.

30-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No timeout guard on client.connect().

If the Electrum server hangs during the TCP/WS handshake, this await can block indefinitely with no timeout, potentially stalling the extension's connection flow.

⏱️ Suggested timeout wrapper
-      await self.client.connect();
+      await Promise.race([
+        self.client.connect(),
+        new Promise((_, reject) =>
+          setTimeout(() => reject(new Error('connect timeout')), 15000),
+        ),
+      ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts`
at line 30, The `FiroElectrumClient` connection flow is missing a timeout around
`self.client.connect()`, so a hung Electrum handshake can stall indefinitely.
Update the connection logic in `connect()` to wrap `client.connect()` with a
timeout guard and fail fast with a clear error if the handshake takes too long,
using the existing `self.client` connect path so the fix stays localized and
easy to find.

39-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Original error is discarded when wrapping.

Stringifying err via template literal loses the stack trace and type information, making failures harder to diagnose.

🔍 Preserve error cause
-      self.status = 0;
-      throw new Error(`failed to connect to electrum server: [${err}]`);
+      self.status = 0;
+      throw new Error('failed to connect to electrum server', { cause: err });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts`
around lines 39 - 42, The error wrapping in the Firo electrum connection path
discards the original exception details, so update the catch block in
firo-electrum-client.ts to preserve the caught error as the cause instead of
only interpolating it into the message. Use the existing connect/error handling
around the catch in the electrum client code so the thrown Error retains the
original stack and type information for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts`:
- Line 37: The negotiated protocol version is being written with a debug
console.log in the Firo Electrum client; remove this production debug output or
replace it with the project’s structured logger in the negotiation flow inside
the electrum client class/function where version negotiation happens.
- Line 30: The `FiroElectrumClient` connection flow is missing a timeout around
`self.client.connect()`, so a hung Electrum handshake can stall indefinitely.
Update the connection logic in `connect()` to wrap `client.connect()` with a
timeout guard and fail fast with a clear error if the handshake takes too long,
using the existing `self.client` connect path so the fix stays localized and
easy to find.
- Around line 39-42: The error wrapping in the Firo electrum connection path
discards the original exception details, so update the catch block in
firo-electrum-client.ts to preserve the caught error as the cause instead of
only interpolating it into the message. Use the existing connect/error handling
around the catch in the electrum client code so the thrown Error retains the
original stack and type information for debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78a50f38-2b69-4dca-844f-eb1a7a8ea279

📥 Commits

Reviewing files that changed from the base of the PR and between c043a54 and 8e027dd.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • package.json
  • packages/extension/package.json
  • packages/extension/src/libs/background/index.ts
  • packages/extension/src/providers/bitcoin/libs/electrum-client/electrum-client.ts
  • packages/extension/src/providers/bitcoin/libs/electrum-client/firo-electrum-client.ts
  • packages/keyring/package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • package.json
  • packages/keyring/package.json
  • packages/extension/package.json
  • packages/extension/src/libs/background/index.ts
  • packages/extension/src/providers/bitcoin/libs/electrum-client/electrum-client.ts

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