From 78eb1af4f7bb0740ad4d365b3cccf943b4ed3d3f Mon Sep 17 00:00:00 2001
From: Gohlub <62673775+Gohlub@users.noreply.github.com>
Date: Wed, 13 May 2026 07:31:16 -0400
Subject: [PATCH 1/8] fix: solves #45 (display fee for bridge)
---
extension/popup/screens/SwapScreen.tsx | 61 ++++++++++++++++++++++----
1 file changed, 52 insertions(+), 9 deletions(-)
diff --git a/extension/popup/screens/SwapScreen.tsx b/extension/popup/screens/SwapScreen.tsx
index 5bd5033..6b813f0 100644
--- a/extension/popup/screens/SwapScreen.tsx
+++ b/extension/popup/screens/SwapScreen.tsx
@@ -7,7 +7,7 @@ import NockTextCircleContainer from '../assets/NockTextCircleContainer.svg';
import NockText from '../assets/NockText.svg';
import JustNText from '../assets/JustNText.svg';
import UpDownVec from '../assets/upDownvec.svg';
-import { MIN_BRIDGE_AMOUNT_NOCK, isEvmAddress } from '@nockbox/iris-sdk';
+import { BRIDGE_PROTOCOL_FEE_RATE, MIN_BRIDGE_AMOUNT_NOCK, isEvmAddress } from '@nockbox/iris-sdk';
import { formatWithCommas, parseAmount } from '../utils/format';
export function SwapScreen() {
@@ -23,6 +23,16 @@ export function SwapScreen() {
const spendableNock = wallet.spendableBalance;
const amountNum = parseAmount(amount);
+ const bridgeProtocolFeeNock =
+ amountNum > 0 && !Number.isNaN(amountNum) ? amountNum * BRIDGE_PROTOCOL_FEE_RATE : 0;
+ const receiveAmountNock =
+ amountNum > 0 && !Number.isNaN(amountNum) ? Math.max(amountNum - bridgeProtocolFeeNock, 0) : 0;
+ const bridgeProtocolFeeAmountDisplay = bridgeProtocolFeeNock.toLocaleString('en-US', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 4,
+ });
+ const bridgeProtocolFeePercentLabel = `${(BRIDGE_PROTOCOL_FEE_RATE * 100).toFixed(1)}%`;
+
// Single consolidated message: never show two at once. Uses same wallet.spendableBalance as Home.
// Don't show spendable-below-min while balance is loading (same pattern as HomeScreen skeleton).
const consolidatedAmountError = useMemo(() => {
@@ -68,12 +78,13 @@ export function SwapScreen() {
}
const hasDecimalPart = /\.\d/.test(amount.replace(/,/g, ''));
- const displayAmount = amount
- ? amountNum.toLocaleString('en-US', {
- minimumFractionDigits: hasDecimalPart ? 2 : 0,
- maximumFractionDigits: hasDecimalPart ? 2 : 0,
- })
- : '0.00';
+ const receiveDisplayAmount =
+ amountNum > 0 && !Number.isNaN(amountNum)
+ ? receiveAmountNock.toLocaleString('en-US', {
+ minimumFractionDigits: hasDecimalPart ? 2 : 0,
+ maximumFractionDigits: hasDecimalPart ? 2 : 0,
+ })
+ : '0.00';
const usdValue =
amountNum > 0 && priceUsd > 0
@@ -83,6 +94,23 @@ export function SwapScreen() {
})
: null;
+ const receiveUsdValue =
+ receiveAmountNock > 0 && priceUsd > 0
+ ? (receiveAmountNock * priceUsd).toLocaleString('en-US', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })
+ : null;
+
+ const showReceiveEstimate = amountNum > 0 && !Number.isNaN(amountNum);
+ const receiveAmountDisplayText = showReceiveEstimate ? `~${receiveDisplayAmount}` : receiveDisplayAmount;
+ const receiveUsdDisplayText =
+ receiveUsdValue !== null
+ ? `~$${receiveUsdValue} USD`
+ : showReceiveEstimate
+ ? '— USD'
+ : '0 USD';
+
const amountLineHeightPx = amountFontSizePx + 4;
// Scale font only when amount text would overflow the available area.
@@ -269,13 +297,13 @@ export function SwapScreen() {
color: amount ? 'var(--color-text-primary)' : 'var(--color-text-muted)',
}}
>
- {displayAmount}
+ {receiveAmountDisplayText}
- {usdValue !== null ? `$${usdValue} USD` : '0 USD'}
+ {receiveUsdDisplayText}
@@ -360,6 +388,21 @@ export function SwapScreen() {
/>
+
+
+
+
+ Bridge fee {bridgeProtocolFeePercentLabel}
+
+
+ {bridgeProtocolFeeAmountDisplay} NOCK
+
+
+
+
{(error || consolidatedAmountError) && (
Date: Wed, 13 May 2026 07:35:28 -0400
Subject: [PATCH 2/8] fix: solves #41 (keyfile import removed from v0)
---
.../popup/screens/V0MigrationSetupScreen.tsx | 117 ------------------
1 file changed, 117 deletions(-)
diff --git a/extension/popup/screens/V0MigrationSetupScreen.tsx b/extension/popup/screens/V0MigrationSetupScreen.tsx
index 95e6b26..d067199 100644
--- a/extension/popup/screens/V0MigrationSetupScreen.tsx
+++ b/extension/popup/screens/V0MigrationSetupScreen.tsx
@@ -3,20 +3,15 @@ import { setV0MigrationMnemonic, useStore } from '../store';
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
import { Alert } from '../components/Alert';
import lockIcon from '../assets/lock-icon.svg';
-import { importKeyfile, type Keyfile } from '../../shared/keyfile';
-import { UI_CONSTANTS } from '../../shared/constants';
import { queryV0Balance } from '../../shared/v0-migration';
const WORD_COUNT = 24;
export function V0MigrationSetupScreen() {
const { navigate, setV0MigrationDraft, resetV0MigrationDraft } = useStore();
- const [showKeyfileImport, setShowKeyfileImport] = useState(false);
- const [keyfileError, setKeyfileError] = useState('');
const [discoverError, setDiscoverError] = useState('');
const [isDiscovering, setIsDiscovering] = useState(false);
const [words, setWords] = useState(Array(WORD_COUNT).fill(''));
- const fileInputRef = useRef(null);
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const canContinue = words.length === WORD_COUNT && words.every(w => Boolean(w));
@@ -76,40 +71,6 @@ export function V0MigrationSetupScreen() {
}
}
- function handleFileSelect(event: React.ChangeEvent) {
- const file = event.target.files?.[0];
- if (!file) return;
- setKeyfileError('');
- const reader = new FileReader();
- reader.onload = e => {
- try {
- const keyfile = JSON.parse(e.target?.result as string) as Keyfile;
- const mnemonic = importKeyfile(keyfile);
- const importedWords = mnemonic.trim().split(/\s+/);
- if (importedWords.length !== UI_CONSTANTS.MNEMONIC_WORD_COUNT) {
- setKeyfileError('Invalid keyfile: expected 24 words');
- return;
- }
- const next = Array(WORD_COUNT).fill('');
- importedWords.forEach((word, i) => {
- next[i] = word;
- });
- setWords(next);
- setShowKeyfileImport(false);
- if (fileInputRef.current) fileInputRef.current.value = '';
- } catch (err) {
- setKeyfileError(err instanceof Error ? err.message : 'Invalid keyfile format');
- }
- };
- reader.readAsText(file);
- }
-
- function handleCancelKeyfileImport() {
- setShowKeyfileImport(false);
- setKeyfileError('');
- if (fileInputRef.current) fileInputRef.current.value = '';
- }
-
async function handleContinue() {
if (!canContinue || isDiscovering) return;
@@ -196,20 +157,6 @@ export function V0MigrationSetupScreen() {
- {/* Or import from keyfile - same as ImportScreen */}
-
-
{/* 24-word input grid */}
{Array.from({ length: 12 }).map((_, rowIndex) => (
@@ -308,70 +255,6 @@ export function V0MigrationSetupScreen() {
-
- {/* Keyfile Import Modal - same as onboarding ImportScreen */}
- {showKeyfileImport && (
-
-
-
- Import from keyfile
-
-
- Select your keyfile to import your wallet.
-
-
-
-
-
-
-
-
- {keyfileError &&
{keyfileError}}
-
-
-
-
- )}
);
}
From eb613ea9c08e11b65fb29f477ee4e60d9ce31ad8 Mon Sep 17 00:00:00 2001
From: Gohlub <62673775+Gohlub@users.noreply.github.com>
Date: Wed, 13 May 2026 07:53:09 -0400
Subject: [PATCH 3/8] fix: solves #39 (dark icon)
---
extension/popup/assets/SwapIconAsset-dark.svg | 10 ++++++
extension/popup/assets/swap_icon.svg | 3 --
extension/popup/components/icons/SwapIcon.tsx | 18 ++++++++++
extension/popup/screens/HomeScreen.tsx | 16 +++++----
extension/popup/styles.css | 33 +++++++++++++++++++
5 files changed, 71 insertions(+), 9 deletions(-)
create mode 100644 extension/popup/assets/SwapIconAsset-dark.svg
delete mode 100644 extension/popup/assets/swap_icon.svg
create mode 100644 extension/popup/components/icons/SwapIcon.tsx
diff --git a/extension/popup/assets/SwapIconAsset-dark.svg b/extension/popup/assets/SwapIconAsset-dark.svg
new file mode 100644
index 0000000..392dbbd
--- /dev/null
+++ b/extension/popup/assets/SwapIconAsset-dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/extension/popup/assets/swap_icon.svg b/extension/popup/assets/swap_icon.svg
deleted file mode 100644
index d019c47..0000000
--- a/extension/popup/assets/swap_icon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/extension/popup/components/icons/SwapIcon.tsx b/extension/popup/components/icons/SwapIcon.tsx
new file mode 100644
index 0000000..3f43110
--- /dev/null
+++ b/extension/popup/components/icons/SwapIcon.tsx
@@ -0,0 +1,18 @@
+/**
+ * SwapIcon — matches home action row; uses currentColor for light/dark themes.
+ */
+
+interface IconProps {
+ className?: string;
+}
+
+export function SwapIcon({ className = 'w-5 h-5' }: IconProps) {
+ return (
+
+ );
+}
diff --git a/extension/popup/screens/HomeScreen.tsx b/extension/popup/screens/HomeScreen.tsx
index c3a66c2..da7515f 100644
--- a/extension/popup/screens/HomeScreen.tsx
+++ b/extension/popup/screens/HomeScreen.tsx
@@ -1,6 +1,5 @@
import { useState, useLayoutEffect, useEffect, useRef, useMemo } from 'react';
import { useStore } from '../store';
-import { useTheme } from '../contexts/ThemeContext';
import { truncateAddress } from '../utils/format';
import { send } from '../utils/messaging';
import { ERROR_CODES, INTERNAL_METHODS, NOCK_TO_NICKS, STORAGE_KEYS } from '../../shared/constants';
@@ -33,7 +32,8 @@ import { resolveCounterpartyAccount } from '../../shared/account-lock-roots';
import { isMigrationWalletTx } from '../../shared/v0-migration';
import { isBridgeWalletTx } from '../../shared/bridge-config';
import { useLockRootAccountMap } from '../hooks/useLockRootAccountMap';
-import SwapIconAsset from '../assets/swap_icon.svg';
+import { SwapIcon } from '../components/icons/SwapIcon';
+import SwapIconAssetDark from '../assets/SwapIconAsset-dark.svg';
import BaseIconAsset from '../assets/base_icon.svg';
import { SwapSubmittedToast } from '../components/SwapSubmittedToast';
@@ -60,7 +60,6 @@ export function HomeScreen() {
refreshWalletAccounts,
setSettingsAccountAddress,
} = useStore();
- const { theme } = useTheme();
const lockRootToAccount = useLockRootAccountMap(wallet.accounts);
const [balanceHidden, setBalanceHidden] = useState(false);
@@ -874,9 +873,14 @@ export function HomeScreen() {
}}
onClick={() => navigate('swap')}
>
-
-

-
+
+
+
+
Swap