Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion extension/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
assertNativeNote,
assertNativeSpendCondition,
} from '../shared/sign-raw-tx-compat';
import { isLegacySignRawTxRequest } from '@nockbox/iris-sdk';
import { isLegacySignRawTxRequest, isEvmAddress } from '@nockbox/iris-sdk';

Check failure on line 18 in extension/background/index.ts

View workflow job for this annotation

GitHub Actions / typecheck-and-format

Module '"@nockbox/iris-sdk"' has no exported member 'isEvmAddress'.
import type { Note, SpendCondition } from '@nockbox/iris-sdk/wasm';
import type { Nicks } from '../shared/currency';
import {
Expand Down Expand Up @@ -1497,6 +1497,91 @@
}
return;

case INTERNAL_METHODS.ESTIMATE_BRIDGE_FEE:
// params: [destinationAddress, amountNicks]
if (vault.isLocked()) {
sendResponse({ error: ERROR_CODES.LOCKED });
return;
}

const [estimateBridgeDest, estimateBridgeAmountNicks] = payload.params || [];
if (!estimateBridgeDest || !isEvmAddress(estimateBridgeDest)) {
sendResponse({ error: 'Invalid destination address. Expected EVM address (0x...).' });
return;
}
let estimateBridgeAmountParsed: Nicks;
try {
estimateBridgeAmountParsed = parseNicksParam(estimateBridgeAmountNicks, 'amount');
} catch (err) {
sendResponse({ error: err instanceof Error ? err.message : 'Invalid amount' });
return;
}

try {
const estimateResult = await vault.estimateBridgeFee(
estimateBridgeDest,
estimateBridgeAmountParsed
);

if ('error' in estimateResult) {
sendResponse({ error: estimateResult.error });
return;
}

sendResponse({ fee: estimateResult.fee });
} catch (error) {
console.error('[Background] Bridge fee estimation failed:', error);
sendResponse({
error: error instanceof Error ? error.message : 'Bridge fee estimation failed',
});
}
return;

case INTERNAL_METHODS.SEND_BRIDGE_TRANSACTION:
// params: [destinationAddress, amountNicks, priceUsdAtTime?] - EVM address (Base), amount in nicks
if (vault.isLocked()) {
sendResponse({ error: ERROR_CODES.LOCKED });
return;
}

const [bridgeDest, bridgeAmountNicks, bridgePriceUsd] = payload.params || [];
if (!bridgeDest || !isEvmAddress(bridgeDest)) {
sendResponse({ error: 'Invalid destination address. Expected EVM address (0x...).' });
return;
}
let bridgeAmountNicksParsed: Nicks;
try {
bridgeAmountNicksParsed = parseNicksParam(bridgeAmountNicks, 'amount');
} catch (err) {
sendResponse({ error: err instanceof Error ? err.message : 'Invalid amount' });
return;
}

try {
const bridgeResult = await vault.sendBridgeTransaction(
bridgeDest,
bridgeAmountNicksParsed,
typeof bridgePriceUsd === 'number' ? bridgePriceUsd : undefined
);

if ('error' in bridgeResult) {
sendResponse({ error: bridgeResult.error });
return;
}

sendResponse({
txid: bridgeResult.txId,
broadcasted: bridgeResult.broadcasted,
walletTx: bridgeResult.walletTx,
});
} catch (error) {
console.error('[Background] Bridge transaction failed:', error);
sendResponse({
error: error instanceof Error ? error.message : 'Bridge transaction failed',
});
}
return;

case INTERNAL_METHODS.SEND_TRANSACTION:
// params: [to, amount, fee] - amount and fee in nicks
// Called from popup Send screen - builds, signs, and broadcasts transaction
Expand Down
6 changes: 6 additions & 0 deletions extension/popup/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { HomeScreen } from './screens/HomeScreen';
import { SendScreen } from './screens/SendScreen';
import { SendReviewScreen } from './screens/SendReviewScreen';
import { SendSubmittedScreen } from './screens/SendSubmittedScreen';
import { SwapScreen } from './screens/SwapScreen';
import { SwapReviewScreen } from './screens/SwapReviewScreen';
import { SentScreen } from './screens/transactions/SentScreen';
import { TransactionDetailsScreen } from './screens/TransactionDetailsScreen';
import { ReceiveScreen } from './screens/ReceiveScreen';
Expand Down Expand Up @@ -97,6 +99,10 @@ export function Router() {
return <SentScreen />;
case 'receive':
return <ReceiveScreen />;
case 'swap':
return <SwapScreen />;
case 'swap-review':
return <SwapReviewScreen />;
case 'tx-details':
return <TransactionDetailsScreen />;

Expand Down
3 changes: 3 additions & 0 deletions extension/popup/assets/JustNText.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions extension/popup/assets/NockSmallCircle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions extension/popup/assets/NockText.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extension/popup/assets/NockTextCircleContainer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extension/popup/assets/base_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extension/popup/assets/downArrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extension/popup/assets/swap_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extension/popup/assets/upDownvec.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions extension/popup/components/SwapSubmittedToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* SwapSubmittedToast - Pill-shaped toast that drops down briefly and disappears.
* Figma: white bg, grey border, shadow, check icon + "Swap submitted"
*/

import { useEffect } from 'react';
import { useStore } from '../store';
import { CheckIcon } from './icons/CheckIcon';

const TOAST_DURATION_MS = 3000;

export function SwapSubmittedToast() {
const { swapSubmittedToastVisible, setSwapSubmittedToastVisible } = useStore();

useEffect(() => {
if (!swapSubmittedToastVisible) return;
const t = setTimeout(() => setSwapSubmittedToastVisible(false), TOAST_DURATION_MS);
return () => clearTimeout(t);
}, [swapSubmittedToastVisible, setSwapSubmittedToastVisible]);

if (!swapSubmittedToastVisible) return null;

return (
<div className="fixed inset-x-0 top-4 z-50 flex justify-center">
<div
className="flex items-center justify-center gap-2 rounded-full px-4 py-3 animate-toast-slide-down"
style={{
backgroundColor: '#ffffff',
border: '1px solid var(--color-surface-700)',
boxShadow: '0px 4px 12px 0px rgba(5, 5, 5, 0.12)',
color: '#000000',
}}
>
<div
className="flex h-4 w-4 shrink-0 items-center justify-center rounded-full"
style={{ backgroundColor: 'var(--color-primary)' }}
>
<CheckIcon className="h-2.5 w-2.5" style={{ color: '#FFF' }} />
</div>
<span
className="text-[14px] font-medium"
style={{ letterSpacing: '0.14px', lineHeight: '18px' }}
>
Swap submitted
</span>
</div>
</div>
);
}
19 changes: 18 additions & 1 deletion extension/popup/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import SettingsGearIcon from '../assets/settings-gear-icon.svg';
import PencilEditIcon from '../assets/pencil-edit-icon.svg';
import RefreshIcon from '../assets/refresh-icon.svg';
import ReceiptIcon from '../assets/receipt-icon.svg';
import SwapIconAsset from '../assets/swap_icon.svg';
import BaseIconAsset from '../assets/base_icon.svg';
import { SwapSubmittedToast } from '../components/SwapSubmittedToast';

import './HomeScreen.tailwind.css';

Expand Down Expand Up @@ -327,6 +330,7 @@ export function HomeScreen() {
className="w-[357px] h-[600px] overflow-hidden relative"
style={{ backgroundColor: 'var(--color-home-fill)', color: 'var(--color-text-primary)' }}
>
<SwapSubmittedToast />
{/* Scroll container */}
<div
ref={scrollContainerRef}
Expand Down Expand Up @@ -646,7 +650,7 @@ export function HomeScreen() {
</div>

{/* Actions */}
<div className="grid grid-cols-2 gap-2 mb-3">
<div className="grid grid-cols-3 gap-2 mb-3">
<div className="relative">
<button
className="w-full rounded-card shadow-card flex flex-col items-start justify-center gap-4 p-3 font-sans text-[14px] font-medium transition-all hover:opacity-90 active:scale-[0.98]"
Expand All @@ -657,6 +661,19 @@ export function HomeScreen() {
Send
</button>
</div>
<button
className="rounded-card shadow-card flex flex-col items-start justify-center gap-4 p-3 font-sans text-[14px] font-medium transition-all hover:opacity-90 active:scale-[0.98]"
style={{
backgroundColor: 'var(--color-home-accent)',
color: 'var(--color-text-primary)',
}}
onClick={() => navigate('swap')}
>
<div className="relative h-5 w-5">
<img src={SwapIconAsset} alt="Swap" className="h-5 w-5" />
</div>
Swap
</button>
<button
className="rounded-card shadow-card flex flex-col items-start justify-center gap-4 p-3 font-sans text-[14px] font-medium transition-all hover:opacity-90 active:scale-[0.98]"
style={{
Expand Down
Loading
Loading