Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6e0c8e
feat(side-panel): port approval UI from popup (Phase A)
BitHighlander Apr 21, 2026
31e0f9b
feat(background): drive approvals through the side panel (Phase B)
BitHighlander Apr 21, 2026
b34f905
feat: delete popup page, sidebar is sole approval surface (Phase C)
BitHighlander Apr 21, 2026
c2ca134
fix(side-panel): route personal_sign / eth_sign through PersonalSignTx
BitHighlander Apr 21, 2026
2285d71
fix(background): wallet_addEthereumChain approval id mismatch
BitHighlander Apr 21, 2026
1617167
build(firefox): fail-loud gate until task #5 restores a Firefox UI
BitHighlander Apr 21, 2026
6e65a24
feat(background): filter spam tokens out of Pioneer balance response
BitHighlander Apr 21, 2026
b17afbf
fix(background): emit completion for wallet_addEthereumChain
BitHighlander Apr 21, 2026
e2a9791
fix(spam-filter): drop only confirmed spam, keep low-value tokens
BitHighlander Apr 21, 2026
096bf24
fix(content): enforce same-origin and stop collapsing falsy RPC results
BitHighlander Apr 21, 2026
7d173fc
fix(injected): don't stomp existing window.ethereum / window.xfi
BitHighlander Apr 21, 2026
48841b1
fix(receive): scope pubkey context to asset, dedup tokens by CAIP
BitHighlander Apr 21, 2026
ee5d0c7
fix(background): clear wallet runtime state on ETH account removal
BitHighlander Apr 21, 2026
ab48813
fix(background): mirror custom EVM networks into provider storages
BitHighlander Apr 21, 2026
0841eb3
fix(settings): make "Clear all custom storages" actually clear them
BitHighlander Apr 21, 2026
3aed3ed
fix(header): carry accountIndex into asset context on selection
BitHighlander Apr 21, 2026
842daae
fix(settings): clear-all now also wipes custom EVM networks
BitHighlander Apr 21, 2026
5b83dd4
fix(background): align RESET_APP response contract with the rest
BitHighlander Apr 21, 2026
0f579fd
fix(background): scope approval window + clean removed-network runtime
BitHighlander Apr 21, 2026
4b24105
fix(side-panel): preserve accountIndex through every SET_ASSET_CONTEX…
BitHighlander Apr 21, 2026
9789f7e
fix(side-panel): pass full asset context + prefer native for global a…
BitHighlander Apr 21, 2026
d14dcba
fix(settings): clear-all now also resets persisted extra ETH accounts
BitHighlander Apr 21, 2026
b403963
fix(transfer): read scalar balance, fall back to legacy balances[] array
BitHighlander Apr 21, 2026
97c409f
test(e2e): side-panel approval-overlay reject smoke spec
BitHighlander Apr 21, 2026
94f8b20
chore: rebuild injected.js
BitHighlander Apr 21, 2026
51d20b6
fix(transfer): Max button mirrors onStart's scalar-balance handling
BitHighlander Apr 21, 2026
56eb793
chore(lint): ignore ported approval tree + absorb autofixes
BitHighlander Apr 21, 2026
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
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
},
"ignorePatterns": [
"watch.js",
"dist/**"
"dist/**",
"pages/side-panel/src/approval/**"
]
}
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,13 @@ Note: Firefox extensions are temporary and need reloading after browser restart
pnpm i <package> -w

# Install for specific workspace
pnpm i <package> -F @extension/popup
pnpm i <package> -F @extension/sidepanel

# Run command in specific workspace
pnpm -F @extension/e2e e2e

# Build specific packages
turbo build --filter=@extension/popup
turbo build --filter=@extension/sidepanel
```

## State Management
Expand All @@ -168,7 +168,7 @@ Icon changes based on state (online/offline variants).
## Critical Files & Entry Points

- **Background Script**: `chrome-extension/src/background/index.ts`
- **Popup Entry**: `pages/popup/src/index.tsx`
- **Side-panel Entry**: `pages/side-panel/src/index.tsx` (also hosts dApp approval overlay under `src/approval/`)
- **Manifest Config**: `chrome-extension/manifest.js`
- **Chain Handlers**: `chrome-extension/src/background/chains/*.ts`
- **Storage Types**: `packages/storage/lib/types.ts`
14 changes: 14 additions & 0 deletions chrome-extension/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ import deepmerge from 'deepmerge';
const packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf8'));
const isFirefox = process.env.__FIREFOX__ === 'true';

// Firefox has no side-panel API and the approval popup was removed in the
// popup→side-panel merge. Building for Firefox in this state would produce
// an extension with no UI at all for approvals — silently unusable on the
// Firefox side. Fail loud until task #5 restores a Firefox-specific surface
// (a popup shim or `sidebar_action`). Set KEEPKEY_ALLOW_BROKEN_FIREFOX=1 to
// override if you really want to build it anyway (e.g. portfolio-only dev).
if (isFirefox && process.env.KEEPKEY_ALLOW_BROKEN_FIREFOX !== '1') {
throw new Error(
'Firefox build is disabled: approval surface is still missing. ' +
'See task #5 (Firefox fallback) in the popup→side-panel merge PR. ' +
'Set KEEPKEY_ALLOW_BROKEN_FIREFOX=1 to force-build anyway.',
);
}

const sidePanelConfig = {
side_panel: {
default_path: 'side-panel/index.html',
Expand Down
580 changes: 567 additions & 13 deletions chrome-extension/public/injected.js

Large diffs are not rendered by default.

41 changes: 36 additions & 5 deletions chrome-extension/src/background/chains/ethereumHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

import { JsonRpcProvider, parseEther } from 'ethers';
import { createProviderRpcError, ProviderRpcError } from '../utils';
import { requestStorage, web3ProviderStorage, assetContextStorage, blockchainDataStorage } from '@extension/storage';
import {
requestStorage,
web3ProviderStorage,
assetContextStorage,
blockchainDataStorage,
blockchainStorage,
} from '@extension/storage';
import { EIP155_CHAINS } from '../chains';
import { v4 as uuidv4 } from 'uuid';
import { blockchainStorage } from '@extension/storage';
import { ChainToNetworkId, caipToNetworkId, networkIdToIcon } from '../chainConfig';
import * as wallet from '../wallet';

Expand Down Expand Up @@ -470,9 +475,18 @@ const handleWalletAddEthereumChain = async (params, KEEPKEY_WALLET, requestInfo,

console.log(tag, 'Cleaned provider config:', newProvider);

// Require user approval before adding chain
// Require user approval before adding chain. The event id MUST match
// requestInfo.id — methods.ts:requireApproval() keys its eth_sign_response
// listener on requestInfo.id, and the sidebar echoes the stored event.id
// back. The previous code stored a fresh uuid while leaving requestInfo.id
// alone, so approvals never resolved and every wallet_addEthereumChain
// silently timed out after 10 minutes. Match the pattern used by
// handleSigningMethods / handleTransfer below: mutate requestInfo.id to a
// uuid first (collision-safe across concurrent dApp requests) and then
// use it as the event id.
requestInfo.id = uuidv4();
const approvalEvent = {
id: uuidv4(),
id: requestInfo.id,
networkId,
chain: 'ethereum',
type: 'wallet_addEthereumChain',
Expand All @@ -487,6 +501,9 @@ const handleWalletAddEthereumChain = async (params, KEEPKEY_WALLET, requestInfo,
await requestStorage.addEvent(approvalEvent);
const approval = await requireApproval(networkId, requestInfo, 'ethereum', 'wallet_addEthereumChain', params[0]);
if (!approval?.success) {
// UI removes the event on reject, but guard against duplicate state if
// reject came from the approval timeout instead of the user button.
await requestStorage.removeEventById(requestInfo.id).catch(() => {});
throw createProviderRpcError(4001, 'User rejected adding the chain');
}

Expand All @@ -497,6 +514,20 @@ const handleWalletAddEthereumChain = async (params, KEEPKEY_WALLET, requestInfo,

// Switch to the newly added chain
await switchToProvider(newProvider, KEEPKEY_WALLET, tag);

// Unlike signing methods this flow has no txHash and no on-device step,
// so neither signMessage nor sendTransaction emit anything for us. Clean
// up the pending event ourselves and reuse `signature_complete` — it's
// the contract the sidebar uses to dismiss the overlay without trying
// to build a TxidPage (transaction_complete would demand a txHash).
await requestStorage.removeEventById(requestInfo.id).catch(() => {});
chrome.runtime
.sendMessage({
action: 'signature_complete',
eventId: requestInfo.id,
})
.catch(() => {});

return null;
};

Expand Down Expand Up @@ -562,7 +593,7 @@ const handleSigningMethods = async (method, params, requestInfo, ADDRESS, KEEPKE
console.log(tag, 'networkId:', networkId);
if (!networkId) throw Error('Failed to set context before sending!');
// Require user approval
let unsignedTx = params[0];
const unsignedTx = params[0];
requestInfo.id = uuidv4();
const event = {
id: requestInfo.id,
Expand Down
Loading
Loading