\ No newline at end of file
diff --git a/BoldChrome/static/permission-grant.html b/BoldChrome/static/permission-grant.html
deleted file mode 100644
index 3c8da4b..0000000
--- a/BoldChrome/static/permission-grant.html
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
-
- Camera Permission
-
-
-
-
-
-
Camera Permission Setup
-
-
Requesting camera permission...
-
Please click "Allow" when your browser asks for camera access.
-
-
-
-
-
-
diff --git a/README.md b/README.md
index 6726950..76f7887 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,8 @@
# Bold Extension - Overview
+
+### [On Chrome Store](https://chromewebstore.google.com/detail/bold-wallet/dpgigdojkmhknnoedgbkfdeilmlbdecf?hl=en&authuser=0)
+
+
### Bold Extension is a Watch-only wallet:
- No private keys or keyshares imported.
- Track your wallet balance and transactions
diff --git a/BoldChrome/fix-build.js b/fix-build.js
similarity index 100%
rename from BoldChrome/fix-build.js
rename to fix-build.js
diff --git a/BoldChrome/package-lock.json b/package-lock.json
similarity index 99%
rename from BoldChrome/package-lock.json
rename to package-lock.json
index 943c205..b8fa261 100644
--- a/BoldChrome/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "boldchrome",
- "version": "0.0.1",
+ "version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "boldchrome",
- "version": "0.0.1",
+ "version": "1.0.1",
"dependencies": {
"@noble/hashes": "^2.0.1",
"@noble/secp256k1": "^3.0.0",
diff --git a/BoldChrome/package.json b/package.json
similarity index 98%
rename from BoldChrome/package.json
rename to package.json
index b75aaec..6cbe94d 100644
--- a/BoldChrome/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "boldchrome",
"private": true,
- "version": "0.0.1",
+ "version": "1.0.3",
"type": "module",
"scripts": {
"dev": "vite dev",
diff --git a/BoldChrome/pnpm-lock.yaml b/pnpm-lock.yaml
similarity index 100%
rename from BoldChrome/pnpm-lock.yaml
rename to pnpm-lock.yaml
diff --git a/BoldChrome/pnpm-workspace.yaml b/pnpm-workspace.yaml
similarity index 100%
rename from BoldChrome/pnpm-workspace.yaml
rename to pnpm-workspace.yaml
diff --git a/BoldChrome/src/app.d.ts b/src/app.d.ts
similarity index 100%
rename from BoldChrome/src/app.d.ts
rename to src/app.d.ts
diff --git a/BoldChrome/src/app.html b/src/app.html
similarity index 100%
rename from BoldChrome/src/app.html
rename to src/app.html
diff --git a/BoldChrome/src/hooks.client.ts b/src/hooks.client.ts
similarity index 100%
rename from BoldChrome/src/hooks.client.ts
rename to src/hooks.client.ts
diff --git a/BoldChrome/src/jsqr.d.ts b/src/jsqr.d.ts
similarity index 100%
rename from BoldChrome/src/jsqr.d.ts
rename to src/jsqr.d.ts
diff --git a/BoldChrome/src/lib/assets/Icon-App-40x40@2x.png b/src/lib/assets/Icon-App-40x40@2x.png
similarity index 100%
rename from BoldChrome/src/lib/assets/Icon-App-40x40@2x.png
rename to src/lib/assets/Icon-App-40x40@2x.png
diff --git a/BoldChrome/src/lib/assets/about-icon.png b/src/lib/assets/about-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/about-icon.png
rename to src/lib/assets/about-icon.png
diff --git a/BoldChrome/src/lib/assets/address-type-icon.png b/src/lib/assets/address-type-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/address-type-icon.png
rename to src/lib/assets/address-type-icon.png
diff --git a/BoldChrome/src/lib/assets/advanced-icon.png b/src/lib/assets/advanced-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/advanced-icon.png
rename to src/lib/assets/advanced-icon.png
diff --git a/BoldChrome/src/lib/assets/api-icon.png b/src/lib/assets/api-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/api-icon.png
rename to src/lib/assets/api-icon.png
diff --git a/BoldChrome/src/lib/assets/backup-icon.png b/src/lib/assets/backup-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/backup-icon.png
rename to src/lib/assets/backup-icon.png
diff --git a/BoldChrome/src/lib/assets/bind-icon.png b/src/lib/assets/bind-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bind-icon.png
rename to src/lib/assets/bind-icon.png
diff --git a/BoldChrome/src/lib/assets/bitcoin-icon.png b/src/lib/assets/bitcoin-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bitcoin-icon.png
rename to src/lib/assets/bitcoin-icon.png
diff --git a/BoldChrome/src/lib/assets/bitcoin-logo.png b/src/lib/assets/bitcoin-logo.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bitcoin-logo.png
rename to src/lib/assets/bitcoin-logo.png
diff --git a/BoldChrome/src/lib/assets/bold-bitcoin-icon.png b/src/lib/assets/bold-bitcoin-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bold-bitcoin-icon.png
rename to src/lib/assets/bold-bitcoin-icon.png
diff --git a/BoldChrome/src/lib/assets/bold-icon-inverted.png b/src/lib/assets/bold-icon-inverted.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bold-icon-inverted.png
rename to src/lib/assets/bold-icon-inverted.png
diff --git a/BoldChrome/src/lib/assets/bold-icon.png b/src/lib/assets/bold-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bold-icon.png
rename to src/lib/assets/bold-icon.png
diff --git a/BoldChrome/src/lib/assets/bricks-icon.png b/src/lib/assets/bricks-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bricks-icon.png
rename to src/lib/assets/bricks-icon.png
diff --git a/BoldChrome/src/lib/assets/bulb-icon.png b/src/lib/assets/bulb-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/bulb-icon.png
rename to src/lib/assets/bulb-icon.png
diff --git a/BoldChrome/src/lib/assets/capability-icon.png b/src/lib/assets/capability-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/capability-icon.png
rename to src/lib/assets/capability-icon.png
diff --git a/BoldChrome/src/lib/assets/check-icon.png b/src/lib/assets/check-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/check-icon.png
rename to src/lib/assets/check-icon.png
diff --git a/BoldChrome/src/lib/assets/clock-icon.png b/src/lib/assets/clock-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/clock-icon.png
rename to src/lib/assets/clock-icon.png
diff --git a/BoldChrome/src/lib/assets/consolidate-icon.png b/src/lib/assets/consolidate-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/consolidate-icon.png
rename to src/lib/assets/consolidate-icon.png
diff --git a/BoldChrome/src/lib/assets/copy-icon.png b/src/lib/assets/copy-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/copy-icon.png
rename to src/lib/assets/copy-icon.png
diff --git a/BoldChrome/src/lib/assets/cosign-icon.png b/src/lib/assets/cosign-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/cosign-icon.png
rename to src/lib/assets/cosign-icon.png
diff --git a/BoldChrome/src/lib/assets/currency-icon.png b/src/lib/assets/currency-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/currency-icon.png
rename to src/lib/assets/currency-icon.png
diff --git a/BoldChrome/src/lib/assets/dark-icon.png b/src/lib/assets/dark-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/dark-icon.png
rename to src/lib/assets/dark-icon.png
diff --git a/BoldChrome/src/lib/assets/delete-icon.png b/src/lib/assets/delete-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/delete-icon.png
rename to src/lib/assets/delete-icon.png
diff --git a/BoldChrome/src/lib/assets/descriptor-icon.png b/src/lib/assets/descriptor-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/descriptor-icon.png
rename to src/lib/assets/descriptor-icon.png
diff --git a/BoldChrome/src/lib/assets/dna-icon.png b/src/lib/assets/dna-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/dna-icon.png
rename to src/lib/assets/dna-icon.png
diff --git a/BoldChrome/src/lib/assets/extension-icon.png b/src/lib/assets/extension-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/extension-icon.png
rename to src/lib/assets/extension-icon.png
diff --git a/BoldChrome/src/lib/assets/eye-off-icon.png b/src/lib/assets/eye-off-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/eye-off-icon.png
rename to src/lib/assets/eye-off-icon.png
diff --git a/BoldChrome/src/lib/assets/eye-on-icon.png b/src/lib/assets/eye-on-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/eye-on-icon.png
rename to src/lib/assets/eye-on-icon.png
diff --git a/BoldChrome/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg
similarity index 100%
rename from BoldChrome/src/lib/assets/favicon.svg
rename to src/lib/assets/favicon.svg
diff --git a/BoldChrome/src/lib/assets/fingerprint.png b/src/lib/assets/fingerprint.png
similarity index 100%
rename from BoldChrome/src/lib/assets/fingerprint.png
rename to src/lib/assets/fingerprint.png
diff --git a/BoldChrome/src/lib/assets/font-icon.png b/src/lib/assets/font-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/font-icon.png
rename to src/lib/assets/font-icon.png
diff --git a/BoldChrome/src/lib/assets/icon copy.png b/src/lib/assets/icon copy.png
similarity index 100%
rename from BoldChrome/src/lib/assets/icon copy.png
rename to src/lib/assets/icon copy.png
diff --git a/BoldChrome/src/lib/assets/icon-inverted.png b/src/lib/assets/icon-inverted.png
similarity index 100%
rename from BoldChrome/src/lib/assets/icon-inverted.png
rename to src/lib/assets/icon-inverted.png
diff --git a/BoldChrome/src/lib/assets/icon.png b/src/lib/assets/icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/icon.png
rename to src/lib/assets/icon.png
diff --git a/BoldChrome/src/lib/assets/in-icon.png b/src/lib/assets/in-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/in-icon.png
rename to src/lib/assets/in-icon.png
diff --git a/BoldChrome/src/lib/assets/info-icon.png b/src/lib/assets/info-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/info-icon.png
rename to src/lib/assets/info-icon.png
diff --git a/BoldChrome/src/lib/assets/join-icon.png b/src/lib/assets/join-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/join-icon.png
rename to src/lib/assets/join-icon.png
diff --git a/BoldChrome/src/lib/assets/key-icon.png b/src/lib/assets/key-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/key-icon.png
rename to src/lib/assets/key-icon.png
diff --git a/BoldChrome/src/lib/assets/legal-icon.png b/src/lib/assets/legal-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/legal-icon.png
rename to src/lib/assets/legal-icon.png
diff --git a/BoldChrome/src/lib/assets/light-icon.png b/src/lib/assets/light-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/light-icon.png
rename to src/lib/assets/light-icon.png
diff --git a/BoldChrome/src/lib/assets/link-icon.png b/src/lib/assets/link-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/link-icon.png
rename to src/lib/assets/link-icon.png
diff --git a/BoldChrome/src/lib/assets/locker-icon.png b/src/lib/assets/locker-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/locker-icon.png
rename to src/lib/assets/locker-icon.png
diff --git a/BoldChrome/src/lib/assets/logo copy.png b/src/lib/assets/logo copy.png
similarity index 100%
rename from BoldChrome/src/lib/assets/logo copy.png
rename to src/lib/assets/logo copy.png
diff --git a/BoldChrome/src/lib/assets/logo.png b/src/lib/assets/logo.png
similarity index 100%
rename from BoldChrome/src/lib/assets/logo.png
rename to src/lib/assets/logo.png
diff --git a/BoldChrome/src/lib/assets/mainnet-icon.png b/src/lib/assets/mainnet-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/mainnet-icon.png
rename to src/lib/assets/mainnet-icon.png
diff --git a/BoldChrome/src/lib/assets/mempool-icon.png b/src/lib/assets/mempool-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/mempool-icon.png
rename to src/lib/assets/mempool-icon.png
diff --git a/BoldChrome/src/lib/assets/mode-icon.png b/src/lib/assets/mode-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/mode-icon.png
rename to src/lib/assets/mode-icon.png
diff --git a/BoldChrome/src/lib/assets/network-icon.png b/src/lib/assets/network-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/network-icon.png
rename to src/lib/assets/network-icon.png
diff --git a/BoldChrome/src/lib/assets/new-icon.png b/src/lib/assets/new-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/new-icon.png
rename to src/lib/assets/new-icon.png
diff --git a/BoldChrome/src/lib/assets/nostr-icon.png b/src/lib/assets/nostr-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/nostr-icon.png
rename to src/lib/assets/nostr-icon.png
diff --git a/BoldChrome/src/lib/assets/numbers-icon.png b/src/lib/assets/numbers-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/numbers-icon.png
rename to src/lib/assets/numbers-icon.png
diff --git a/BoldChrome/src/lib/assets/out-icon.png b/src/lib/assets/out-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/out-icon.png
rename to src/lib/assets/out-icon.png
diff --git a/BoldChrome/src/lib/assets/pair-icon.png b/src/lib/assets/pair-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/pair-icon.png
rename to src/lib/assets/pair-icon.png
diff --git a/BoldChrome/src/lib/assets/pairing-icon.png b/src/lib/assets/pairing-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/pairing-icon.png
rename to src/lib/assets/pairing-icon.png
diff --git a/BoldChrome/src/lib/assets/paste-icon.png b/src/lib/assets/paste-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/paste-icon.png
rename to src/lib/assets/paste-icon.png
diff --git a/BoldChrome/src/lib/assets/pending-icon.png b/src/lib/assets/pending-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/pending-icon.png
rename to src/lib/assets/pending-icon.png
diff --git a/BoldChrome/src/lib/assets/phone-icon.png b/src/lib/assets/phone-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/phone-icon.png
rename to src/lib/assets/phone-icon.png
diff --git a/BoldChrome/src/lib/assets/playstore-icon.png b/src/lib/assets/playstore-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/playstore-icon.png
rename to src/lib/assets/playstore-icon.png
diff --git a/BoldChrome/src/lib/assets/prefs-icon.png b/src/lib/assets/prefs-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/prefs-icon.png
rename to src/lib/assets/prefs-icon.png
diff --git a/BoldChrome/src/lib/assets/prepare-icon.png b/src/lib/assets/prepare-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/prepare-icon.png
rename to src/lib/assets/prepare-icon.png
diff --git a/BoldChrome/src/lib/assets/privacy-icon.png b/src/lib/assets/privacy-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/privacy-icon.png
rename to src/lib/assets/privacy-icon.png
diff --git a/BoldChrome/src/lib/assets/qr-icon.png b/src/lib/assets/qr-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/qr-icon.png
rename to src/lib/assets/qr-icon.png
diff --git a/BoldChrome/src/lib/assets/qrc-icon.png b/src/lib/assets/qrc-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/qrc-icon.png
rename to src/lib/assets/qrc-icon.png
diff --git a/BoldChrome/src/lib/assets/receive-icon.png b/src/lib/assets/receive-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/receive-icon.png
rename to src/lib/assets/receive-icon.png
diff --git a/BoldChrome/src/lib/assets/recycle-icon.png b/src/lib/assets/recycle-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/recycle-icon.png
rename to src/lib/assets/recycle-icon.png
diff --git a/BoldChrome/src/lib/assets/refresh-icon.png b/src/lib/assets/refresh-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/refresh-icon.png
rename to src/lib/assets/refresh-icon.png
diff --git a/BoldChrome/src/lib/assets/restore-icon.png b/src/lib/assets/restore-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/restore-icon.png
rename to src/lib/assets/restore-icon.png
diff --git a/BoldChrome/src/lib/assets/scan-icon.png b/src/lib/assets/scan-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/scan-icon.png
rename to src/lib/assets/scan-icon.png
diff --git a/BoldChrome/src/lib/assets/search-icon.png b/src/lib/assets/search-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/search-icon.png
rename to src/lib/assets/search-icon.png
diff --git a/BoldChrome/src/lib/assets/security-icon.png b/src/lib/assets/security-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/security-icon.png
rename to src/lib/assets/security-icon.png
diff --git a/BoldChrome/src/lib/assets/send-icon.png b/src/lib/assets/send-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/send-icon.png
rename to src/lib/assets/send-icon.png
diff --git a/BoldChrome/src/lib/assets/settings-icon.png b/src/lib/assets/settings-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/settings-icon.png
rename to src/lib/assets/settings-icon.png
diff --git a/BoldChrome/src/lib/assets/share-icon.png b/src/lib/assets/share-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/share-icon.png
rename to src/lib/assets/share-icon.png
diff --git a/BoldChrome/src/lib/assets/spy-icon.png b/src/lib/assets/spy-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/spy-icon.png
rename to src/lib/assets/spy-icon.png
diff --git a/BoldChrome/src/lib/assets/start-icon.png b/src/lib/assets/start-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/start-icon.png
rename to src/lib/assets/start-icon.png
diff --git a/BoldChrome/src/lib/assets/storage-icon.png b/src/lib/assets/storage-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/storage-icon.png
rename to src/lib/assets/storage-icon.png
diff --git a/BoldChrome/src/lib/assets/success-icon.png b/src/lib/assets/success-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/success-icon.png
rename to src/lib/assets/success-icon.png
diff --git a/BoldChrome/src/lib/assets/testnet-icon.png b/src/lib/assets/testnet-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/testnet-icon.png
rename to src/lib/assets/testnet-icon.png
diff --git a/BoldChrome/src/lib/assets/theme-icon.png b/src/lib/assets/theme-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/theme-icon.png
rename to src/lib/assets/theme-icon.png
diff --git a/BoldChrome/src/lib/assets/upload-icon.png b/src/lib/assets/upload-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/upload-icon.png
rename to src/lib/assets/upload-icon.png
diff --git a/BoldChrome/src/lib/assets/utxo-icon.png b/src/lib/assets/utxo-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/utxo-icon.png
rename to src/lib/assets/utxo-icon.png
diff --git a/BoldChrome/src/lib/assets/vpn-icon.png b/src/lib/assets/vpn-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/vpn-icon.png
rename to src/lib/assets/vpn-icon.png
diff --git a/BoldChrome/src/lib/assets/wallet-icon.png b/src/lib/assets/wallet-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/wallet-icon.png
rename to src/lib/assets/wallet-icon.png
diff --git a/BoldChrome/src/lib/assets/warning-icon.png b/src/lib/assets/warning-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/warning-icon.png
rename to src/lib/assets/warning-icon.png
diff --git a/BoldChrome/src/lib/assets/wifi-icon.png b/src/lib/assets/wifi-icon.png
similarity index 100%
rename from BoldChrome/src/lib/assets/wifi-icon.png
rename to src/lib/assets/wifi-icon.png
diff --git a/BoldChrome/src/lib/components/QRScanner.svelte b/src/lib/components/QRScanner.svelte
similarity index 100%
rename from BoldChrome/src/lib/components/QRScanner.svelte
rename to src/lib/components/QRScanner.svelte
diff --git a/BoldChrome/src/lib/components/QRScannerPopup.svelte b/src/lib/components/QRScannerPopup.svelte
similarity index 100%
rename from BoldChrome/src/lib/components/QRScannerPopup.svelte
rename to src/lib/components/QRScannerPopup.svelte
diff --git a/BoldChrome/src/lib/components/SendTransaction.svelte b/src/lib/components/SendTransaction.svelte
similarity index 100%
rename from BoldChrome/src/lib/components/SendTransaction.svelte
rename to src/lib/components/SendTransaction.svelte
diff --git a/BoldChrome/src/lib/index.ts b/src/lib/index.ts
similarity index 100%
rename from BoldChrome/src/lib/index.ts
rename to src/lib/index.ts
diff --git a/BoldChrome/src/lib/initialization.ts b/src/lib/initialization.ts
similarity index 100%
rename from BoldChrome/src/lib/initialization.ts
rename to src/lib/initialization.ts
diff --git a/BoldChrome/src/lib/services/blockchain.ts b/src/lib/services/blockchain.ts
similarity index 88%
rename from BoldChrome/src/lib/services/blockchain.ts
rename to src/lib/services/blockchain.ts
index 6083e7d..62370d5 100644
--- a/BoldChrome/src/lib/services/blockchain.ts
+++ b/src/lib/services/blockchain.ts
@@ -74,8 +74,26 @@ export interface FeeEstimate {
[blocks: string]: number; // blocks as key, fee rate as value
}
+/** Mempool.space /v1/fees/recommended response (sat/vB). */
+export interface RecommendedFees {
+ fastestFee: number;
+ halfHourFee: number;
+ hourFee: number;
+ economyFee?: number;
+ minimumFee?: number;
+}
+
const DEFAULT_MAINNET_API = 'https://mempool.space/api';
const DEFAULT_TESTNET_API = 'https://mempool.space/testnet/api';
+const FETCH_TIMEOUT_MS = 5000;
+
+function fetchWithTimeout(url: string, init?: RequestInit): Promise {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
+ return fetch(url, { ...init, signal: controller.signal }).finally(() =>
+ clearTimeout(timeoutId)
+ );
+}
class BlockchainService {
private baseUrl = DEFAULT_MAINNET_API;
@@ -136,7 +154,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/address/${address}`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -188,7 +206,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/address/${address}/utxo`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -217,7 +235,7 @@ class BlockchainService {
}
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -245,7 +263,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/tx/${txid}`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -270,7 +288,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/tx/${txid}/hex`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -284,20 +302,21 @@ class BlockchainService {
}
/**
- * Get fee estimates
+ * Get recommended fee estimates (economy, 1hr, 30m, fast) in sat/vB.
*/
- async getFeeEstimates(): Promise {
+ async getFeeEstimates(): Promise {
console.log('[Blockchain] Fetching fee estimates');
const url = `${this.getBaseUrl()}/v1/fees/recommended`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
- return data;
+ console.log('[Blockchain] Fee estimates:', data);
+ return data as RecommendedFees;
} catch (error) {
console.error('[Blockchain] Error fetching fee estimates:', error);
throw error;
@@ -312,7 +331,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/tx`;
try {
- const response = await fetch(url, {
+ const response = await fetchWithTimeout(url, {
method: 'POST',
headers: {
'Content-Type': 'text/plain'
@@ -343,7 +362,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/blocks/tip/height`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -371,7 +390,7 @@ class BlockchainService {
const url = `${this.getBaseUrl()}/v1/prices`;
try {
- const response = await fetch(url);
+ const response = await fetchWithTimeout(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
@@ -385,7 +404,7 @@ class BlockchainService {
} catch (error) {
console.error('[Blockchain] Error fetching Bitcoin prices:', error);
try {
- const fallback = await fetch('https://api.coinbase.com/v2/prices/BTC-USD/spot');
+ const fallback = await fetchWithTimeout('https://api.coinbase.com/v2/prices/BTC-USD/spot');
const fallbackData = await fallback.json();
const usd = parseFloat(fallbackData?.data?.amount ?? 0);
return usd ? { USD: usd } : {};
diff --git a/BoldChrome/src/lib/services/extensionBind.ts b/src/lib/services/extensionBind.ts
similarity index 100%
rename from BoldChrome/src/lib/services/extensionBind.ts
rename to src/lib/services/extensionBind.ts
diff --git a/BoldChrome/src/lib/services/hdwallet.ts b/src/lib/services/hdwallet.ts
similarity index 82%
rename from BoldChrome/src/lib/services/hdwallet.ts
rename to src/lib/services/hdwallet.ts
index 1179c6c..0bf6f37 100644
--- a/BoldChrome/src/lib/services/hdwallet.ts
+++ b/src/lib/services/hdwallet.ts
@@ -244,8 +244,20 @@ export interface DerivedAddress {
path: string;
index: number;
type: 'legacy' | 'segwit-nested' | 'segwit-native';
+ chain?: 'receive' | 'change';
}
+export interface HdState {
+ externalIndex: number;
+ changeIndex: number;
+ maxUsedExternal: number;
+ discoveryDone: boolean;
+ discoveryLastAt: number;
+ addressType: 'segwit-native' | 'segwit-nested' | 'legacy';
+}
+
+export const GAP_LIMIT = 5;
+
export interface HDWalletConfig {
publicKey: string; // Extended public key (base58)
chainCode: string; // Chain code in hex
@@ -264,13 +276,15 @@ class HDWalletService {
config: HDWalletConfig,
addressType: 'legacy' | 'segwit-nested' | 'segwit-native',
count: number = 20,
- startIndex: number = 0
+ startIndex: number = 0,
+ chain: 0 | 1 = 0
): DerivedAddress[] {
const network = config.network === 'mainnet'
? bitcoin.networks.bitcoin
: bitcoin.networks.testnet;
const coinType = config.network === 'testnet' ? 1 : 0;
const basePath = this.getBasePath(addressType);
+ const chainLabel: 'receive' | 'change' = chain === 0 ? 'receive' : 'change';
const addresses: DerivedAddress[] = [];
@@ -283,13 +297,14 @@ class HDWalletService {
const accountNode = root.derive(bipPath).derive(coinType).derive(0);
for (let i = startIndex; i < startIndex + count; i++) {
- const child = accountNode.derive(0).derive(i);
+ const child = accountNode.derive(chain).derive(i);
const address = this.getAddress(child, addressType, network);
addresses.push({
address,
- path: `${basePath}/0/${i}`,
+ path: `${basePath}/${chain}/${i}`,
index: i,
- type: addressType
+ type: addressType,
+ chain: chainLabel
});
}
} catch (error) {
@@ -396,6 +411,71 @@ class HDWalletService {
segwitNative: this.deriveAddresses(config, 'segwit-native', countPerType)
};
}
+
+ /**
+ * Derive all HD addresses for a given address type up to the known indexes.
+ * Returns both receive (0..externalEnd) and change (0..changeEnd) addresses.
+ */
+ deriveHdAddresses(
+ config: HDWalletConfig,
+ addressType: 'legacy' | 'segwit-nested' | 'segwit-native',
+ externalEnd: number,
+ changeEnd: number
+ ): DerivedAddress[] {
+ const receive = this.deriveAddresses(config, addressType, externalEnd + 1, 0, 0);
+ const change = changeEnd >= 0
+ ? this.deriveAddresses(config, addressType, changeEnd + 1, 0, 1)
+ : [];
+ return [...receive, ...change];
+ }
+
+ /**
+ * Gap-limit discovery: scan the blockchain to find all used addresses.
+ * Returns discovered indexes. Matches mobile's discoverHdIndexesForNetwork.
+ */
+ async discoverIndexes(
+ config: HDWalletConfig,
+ addressType: 'legacy' | 'segwit-nested' | 'segwit-native',
+ getAddressStats: (address: string) => Promise<{ tx_count: number }>,
+ onProgress?: (chain: 'receive' | 'change', index: number) => void
+ ): Promise<{ maxUsedExternal: number; externalNext: number; changeNext: number }> {
+ let maxUsedExternal = -1;
+ let maxUsedChange = -1;
+
+ // External (receive) chain
+ let consecutiveUnused = 0;
+ for (let i = 0; consecutiveUnused < GAP_LIMIT; i++) {
+ const [addr] = this.deriveAddresses(config, addressType, 1, i, 0);
+ onProgress?.('receive', i);
+ const stats = await getAddressStats(addr.address);
+ if (stats.tx_count > 0) {
+ maxUsedExternal = i;
+ consecutiveUnused = 0;
+ } else {
+ consecutiveUnused++;
+ }
+ }
+
+ // Internal (change) chain
+ consecutiveUnused = 0;
+ for (let i = 0; consecutiveUnused < GAP_LIMIT; i++) {
+ const [addr] = this.deriveAddresses(config, addressType, 1, i, 1);
+ onProgress?.('change', i);
+ const stats = await getAddressStats(addr.address);
+ if (stats.tx_count > 0) {
+ maxUsedChange = i;
+ consecutiveUnused = 0;
+ } else {
+ consecutiveUnused++;
+ }
+ }
+
+ return {
+ maxUsedExternal,
+ externalNext: Math.max(0, maxUsedExternal + 1),
+ changeNext: Math.max(0, maxUsedChange + 1),
+ };
+ }
}
export const hdWallet = new HDWalletService();
diff --git a/BoldChrome/src/lib/services/pairing.ts b/src/lib/services/pairing.ts
similarity index 100%
rename from BoldChrome/src/lib/services/pairing.ts
rename to src/lib/services/pairing.ts
diff --git a/BoldChrome/src/lib/services/pin.ts b/src/lib/services/pin.ts
similarity index 100%
rename from BoldChrome/src/lib/services/pin.ts
rename to src/lib/services/pin.ts
diff --git a/BoldChrome/src/lib/services/psbt.ts b/src/lib/services/psbt.ts
similarity index 90%
rename from BoldChrome/src/lib/services/psbt.ts
rename to src/lib/services/psbt.ts
index 30cdb4d..d843fc8 100644
--- a/BoldChrome/src/lib/services/psbt.ts
+++ b/src/lib/services/psbt.ts
@@ -8,7 +8,7 @@ import { Point, getPublicKey, sign as ecdsaSign, verify as ecdsaVerify, schnorr,
import { sha256 } from '@noble/hashes/sha2.js';
import { hmac } from '@noble/hashes/hmac.js';
import { writable, get } from 'svelte/store';
-import { walletStore } from '../stores/wallet';
+import { walletStore, getNextChangeAddress, type TaggedUTXO } from '../stores/wallet';
import { blockchain } from './blockchain';
import { qr } from './qr';
@@ -232,9 +232,10 @@ class PsbtService {
public session = { subscribe: this.currentSession.subscribe };
/**
- * Create a PSBT for spending Bitcoin
+ * Create a PSBT for spending Bitcoin using tagged UTXOs from all HD addresses.
+ * Change goes to a fresh HD change address (not the sending address).
*/
- async createPsbt(params: CreatePsbtParams): Promise<{ psbtBase64: string; feeSats: number; psbtId: string }> {
+ async createPsbt(params: CreatePsbtParams): Promise<{ psbtBase64: string; feeSats: number; psbtId: string; utxosJson: string; changeAddress: string }> {
const { recipientAddress, amountSats, feeRate = 5 } = params;
console.log('[PSBT] Creating PSBT:', {
@@ -244,35 +245,27 @@ class PsbtService {
});
const wallet = get(walletStore);
- if (!wallet.address) {
- throw new Error('No active wallet address');
+ if (!wallet.utxos || wallet.utxos.length === 0) {
+ throw new Error('No UTXOs available for spending');
}
const network = wallet.network === 'testnet'
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin;
- // Fetch UTXOs for the current address
- const utxos = await blockchain.getUTXOs(wallet.address);
- if (!utxos || utxos.length === 0) {
- throw new Error('No UTXOs available for spending');
- }
+ console.log('[PSBT] Found', wallet.utxos.length, 'tagged UTXOs across all addresses');
- console.log('[PSBT] Found', utxos.length, 'UTXOs');
-
- // Create PSBT
const psbt = new bitcoin.Psbt({ network });
- // Select UTXOs to cover amount + estimated fee
- const estimatedSize = 250; // Rough estimate for 1-in, 2-out transaction
+ const estimatedSize = 250;
const estimatedFee = Math.ceil(estimatedSize * feeRate);
const targetAmount = amountSats + estimatedFee;
let totalInput = 0;
- const selectedUtxos: UTXO[] = [];
+ const selectedUtxos: TaggedUTXO[] = [];
- // Simple UTXO selection - use largest first
- const sortedUtxos = [...utxos].sort((a, b) => b.value - a.value);
+ // Largest-first coin selection from the aggregated tagged UTXO set
+ const sortedUtxos = [...wallet.utxos].sort((a, b) => b.value - a.value);
for (const utxo of sortedUtxos) {
if (totalInput >= targetAmount) break;
@@ -280,10 +273,8 @@ class PsbtService {
selectedUtxos.push(utxo);
totalInput += utxo.value;
- // Fetch the full transaction hex for this UTXO
const txHex = await blockchain.getTransactionHex(utxo.txid);
- // Add input to PSBT (use browser-friendly Uint8Array from hex)
const txBytes = (function hexToU8Local(hex: string) {
const clean = (hex || '').replace(/^0x/, '').replace(/\s+/g, '');
const len = Math.ceil(clean.length / 2);
@@ -303,37 +294,47 @@ class PsbtService {
throw new Error(`Insufficient funds. Have ${totalInput} sats, need ${targetAmount} sats`);
}
- // Calculate actual fee based on selected inputs
const actualSize = this.estimateTransactionSize(selectedUtxos.length, 2);
const actualFee = Math.ceil(actualSize * feeRate);
const changeAmount = totalInput - amountSats - actualFee;
- // Add recipient output
+ // Recipient output
psbt.addOutput({
address: recipientAddress,
value: BigInt(amountSats),
});
- // Add change output if significant (> dust threshold)
- if (changeAmount > 546) {
+ // Change goes to a fresh HD change address
+ const changeAddr = getNextChangeAddress();
+ if (changeAmount > 546 && changeAddr) {
psbt.addOutput({
- address: wallet.address,
+ address: changeAddr.address,
value: BigInt(changeAmount),
});
}
+ // Build utxosJson matching mobile's expected format:
+ // [{txid, vout, value, derivationPath, address}]
+ const utxosJson = JSON.stringify(selectedUtxos.map(u => ({
+ txid: u.txid,
+ vout: u.vout,
+ value: u.value,
+ derivationPath: u.derivationPath,
+ address: u.address,
+ })));
+
console.log('[PSBT] Transaction details:', {
inputs: selectedUtxos.length,
totalInput,
recipient: amountSats,
change: changeAmount,
+ changeAddress: changeAddr?.address?.slice(0, 12),
fee: actualFee,
feeRate
});
const psbtBase64 = psbt.toBase64();
- // Create session
const psbtId = `psbt-${Date.now()}-${Math.random().toString(36).substring(7)}`;
this.currentSession.set({
psbtId,
@@ -345,7 +346,7 @@ class PsbtService {
feeSats: actualFee
});
- return { psbtBase64, feeSats: actualFee, psbtId };
+ return { psbtBase64, feeSats: actualFee, psbtId, utxosJson, changeAddress: changeAddr?.address || '' };
}
/**
diff --git a/BoldChrome/src/lib/services/qr.ts b/src/lib/services/qr.ts
similarity index 97%
rename from BoldChrome/src/lib/services/qr.ts
rename to src/lib/services/qr.ts
index b58c3ae..9393db9 100644
--- a/BoldChrome/src/lib/services/qr.ts
+++ b/src/lib/services/qr.ts
@@ -46,7 +46,8 @@ export interface QRSession {
}
/**
- * Encode send-bitcoin QR payload: toAddress|amount|fee|spendingHash|addressType|derivationPath|network
+ * Encode send-bitcoin QR payload (v5 format — matches mobile's decodeSendBitcoinQR).
+ * toAddress|amount|fee|spendingHash|addressType|derivationPath|network|utxosJson|changeAddress
*/
export const encodeSendBitcoinQR = (
toAddress: string,
@@ -55,11 +56,13 @@ export const encodeSendBitcoinQR = (
spendingHash: string = '',
addressType: string = '',
derivationPath: string = '',
- network: string = ''
+ network: string = '',
+ utxosJson: string = '',
+ changeAddress: string = ''
): string => {
const amount = typeof amountSats === 'string' ? amountSats : amountSats.toString();
const fee = typeof feeSats === 'string' ? feeSats : feeSats.toString();
- return `${toAddress}|${amount}|${fee}|${spendingHash || ''}|${addressType || ''}|${derivationPath || ''}|${network || ''}`;
+ return `${toAddress}|${amount}|${fee}|${spendingHash || ''}|${addressType || ''}|${derivationPath || ''}|${network || ''}|${utxosJson || ''}|${changeAddress || ''}`;
};
class QRService {
@@ -274,14 +277,15 @@ class QRService {
spendingHash: string = '',
addressType: string = '',
derivationPath: string = '',
- network: string = ''
+ network: string = '',
+ utxosJson: string = '',
+ changeAddress: string = ''
): Promise<{ dataUrl: string; payload: string }> {
const id = `send-${Date.now()}-${Math.random().toString(36).substring(7)}`;
- // Build base payload without spendingHash, then set spendingHash = sha256(qrData + Date.now())
- const basePayload = encodeSendBitcoinQR(toAddress, amountSats, feeSats, '', addressType, derivationPath, network);
+ const basePayload = encodeSendBitcoinQR(toAddress, amountSats, feeSats, '', addressType, derivationPath, network, utxosJson, changeAddress);
const computedHash = spendingHash || CryptoJS.SHA256(basePayload + Date.now()).toString();
- const payload = encodeSendBitcoinQR(toAddress, amountSats, feeSats, computedHash, addressType, derivationPath, network);
+ const payload = encodeSendBitcoinQR(toAddress, amountSats, feeSats, computedHash, addressType, derivationPath, network, utxosJson, changeAddress);
try {
const dataUrl = await this.generateQRCode(payload);
@@ -666,7 +670,7 @@ class QRService {
console.log('[QR] Attempting relay lookup:', url);
const controller = new AbortController();
- const timeout = setTimeout(() => controller.abort(), 4000);
+ const timeout = setTimeout(() => controller.abort(), 5000);
try {
const resp = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' }, signal: controller.signal });
diff --git a/BoldChrome/src/lib/services/socket.ts b/src/lib/services/socket.ts
similarity index 100%
rename from BoldChrome/src/lib/services/socket.ts
rename to src/lib/services/socket.ts
diff --git a/BoldChrome/src/lib/services/storage.ts b/src/lib/services/storage.ts
similarity index 94%
rename from BoldChrome/src/lib/services/storage.ts
rename to src/lib/services/storage.ts
index 44e053e..bf3bfaa 100644
--- a/BoldChrome/src/lib/services/storage.ts
+++ b/src/lib/services/storage.ts
@@ -28,8 +28,14 @@ export interface StorageData {
/** Mempool API: undefined = not chosen (show preference after pairing), '' = default mempool.space, else custom mainnet URL */
mempoolMainnetUrl?: string | null;
+ // HD wallet state (gap-limit discovery results)
+ hdState?: string; // JSON stringified HdState
+
// PIN lock (extension)
pinHash?: string; // SHA-256 hash of PIN, never store raw PIN
+
+ // First-load camera permission prompt (extension)
+ cameraPermissionChecked?: boolean;
}
/**
diff --git a/BoldChrome/src/lib/stores/device.ts b/src/lib/stores/device.ts
similarity index 100%
rename from BoldChrome/src/lib/stores/device.ts
rename to src/lib/stores/device.ts
diff --git a/BoldChrome/src/lib/stores/index.ts b/src/lib/stores/index.ts
similarity index 100%
rename from BoldChrome/src/lib/stores/index.ts
rename to src/lib/stores/index.ts
diff --git a/BoldChrome/src/lib/stores/wallet.ts b/src/lib/stores/wallet.ts
similarity index 53%
rename from BoldChrome/src/lib/stores/wallet.ts
rename to src/lib/stores/wallet.ts
index 8140972..c48775e 100644
--- a/BoldChrome/src/lib/stores/wallet.ts
+++ b/src/lib/stores/wallet.ts
@@ -1,7 +1,9 @@
import { writable, derived } from 'svelte/store';
import { storage } from '../services/storage';
import { blockchain } from '../services/blockchain';
-import { hdWallet, type DerivedAddress as HDDerivedAddress } from '../services/hdwallet';
+import { hdWallet, type DerivedAddress as HDDerivedAddress, type HdState, GAP_LIMIT } from '../services/hdwallet';
+
+const HD_DISCOVERY_STALE_MS = 2 * 60 * 60 * 1000; // 2 hours
export interface Transaction {
txid: string;
@@ -17,42 +19,54 @@ export interface Transaction {
export interface DerivedAddress {
address: string;
- path: string; // BIP44 derivation path (e.g., m/84'/0'/0'/0/0)
- index: number; // Address index
- type: 'legacy' | 'segwit-nested' | 'segwit-native'; // Address type
- label?: string; // Optional user-defined label
- balance?: string; // Cached balance in BTC
- lastUsed?: number; // Timestamp of last transaction
+ path: string;
+ index: number;
+ type: 'legacy' | 'segwit-nested' | 'segwit-native';
+ chain?: 'receive' | 'change';
+ label?: string;
+ balance?: string;
+ lastUsed?: number;
+}
+
+export interface TaggedUTXO {
+ txid: string;
+ vout: number;
+ value: number;
+ address: string;
+ derivationPath: string;
+ status: { confirmed: boolean; block_height?: number };
}
export interface WalletState {
- // Identity
- address: string; // Currently selected address
- addresses: DerivedAddress[]; // All derived addresses from public key
+ address: string;
+ addresses: DerivedAddress[];
network: 'mainnet' | 'testnet';
- // HD Wallet Public Key (for address derivation)
- publicKey?: string; // Extended public key (xpub/ypub/zpub)
- chainCode?: string; // Chain code for HD derivation
+ publicKey?: string;
+ chainCode?: string;
+
+ // HD state
+ hdState?: HdState;
- // Balance
+ // Aggregate balance (across all HD addresses)
btc: string;
usd: string;
lastBalanceUpdate: number;
- // Transactions
+ // Merged transactions from all addresses
transactions: Transaction[];
lastTxUpdate: number;
hasMoreTransactions: boolean;
- // UI state
+ // Tagged UTXOs (each knows its source address + derivation path)
+ utxos: TaggedUTXO[];
+
isLoading: boolean;
isLoadingMoreTransactions: boolean;
error?: string;
- // Watch-only configuration
isWatchOnly: boolean;
- pairedDevices?: string[]; // Mobile device IDs that can sign
+ pairedDevices?: string[];
}
const initialState: WalletState = {
@@ -65,9 +79,10 @@ const initialState: WalletState = {
transactions: [],
lastTxUpdate: 0,
hasMoreTransactions: true,
+ utxos: [],
isLoading: false,
isLoadingMoreTransactions: false,
- isWatchOnly: true, // Chrome extension is always watch-only
+ isWatchOnly: true,
};
/**
@@ -92,6 +107,7 @@ export async function resetWallet() {
'chainCode',
'address',
'addresses',
+ 'hdState',
'network',
'pairedDevices',
'pinHash'
@@ -102,9 +118,11 @@ export async function resetWallet() {
addresses: [],
publicKey: undefined,
chainCode: undefined,
+ hdState: undefined,
btc: '0',
usd: '0',
transactions: [],
+ utxos: [],
isLoading: false,
error: undefined
});
@@ -120,6 +138,8 @@ export async function initializeWalletStore() {
const addressesJson = await storage.get('addresses');
const addresses: DerivedAddress[] = addressesJson ? JSON.parse(addressesJson) : [];
const network = await storage.get('network') as 'mainnet' | 'testnet';
+ const hdStateJson = await storage.get('hdState');
+ const hdState: HdState | undefined = hdStateJson ? JSON.parse(hdStateJson) : undefined;
walletStore.update(state => ({
...state,
@@ -127,6 +147,7 @@ export async function initializeWalletStore() {
publicKey: publicKey ?? undefined,
chainCode: chainCode ?? undefined,
addresses,
+ hdState,
network: network || 'mainnet'
}));
}
@@ -347,6 +368,7 @@ export async function updateWalletFromPairing(data: {
/**
* Derive initial addresses from public key.
* Exactly 3 addresses, each on first path (../0/0): native segwit, nested segwit, legacy.
+ * Used as a quick bootstrap before full HD discovery completes.
*/
export async function deriveInitialAddresses() {
const publicKey = await storage.get('publicKey');
@@ -361,59 +383,192 @@ export async function deriveInitialAddresses() {
try {
console.log('[Wallet] Deriving 3 addresses (first derivation: native segwit, nested segwit, legacy)...');
- // One address per type at index 0
const derived = hdWallet.deriveAllTypes(
{ publicKey, chainCode, network },
- 1 // 1 address per type (index 0 only)
+ 1
);
- // Order: 1) native segwit (default), 2) nested segwit, 3) legacy
const addresses: DerivedAddress[] = [
- ...derived.segwitNative.map(addr => ({ ...addr, type: 'segwit-native' as const })),
- ...derived.segwitNested.map(addr => ({ ...addr, type: 'segwit-nested' as const })),
- ...derived.legacy.map(addr => ({ ...addr, type: 'legacy' as const }))
+ ...derived.segwitNative.map(addr => ({ ...addr, type: 'segwit-native' as const, chain: 'receive' as const })),
+ ...derived.segwitNested.map(addr => ({ ...addr, type: 'segwit-nested' as const, chain: 'receive' as const })),
+ ...derived.legacy.map(addr => ({ ...addr, type: 'legacy' as const, chain: 'receive' as const }))
];
console.log('[Wallet] Derived', addresses.length, 'addresses');
-
await updateAddresses(addresses);
- // Default to native segwit (first in list)
if (derived.segwitNative.length > 0) {
await setAddress(derived.segwitNative[0].address);
}
+
+ // Kick off full HD discovery in the background
+ runHdDiscovery().catch(err =>
+ console.error('[Wallet] Background HD discovery error:', err)
+ );
} catch (error) {
console.error('[Wallet] Address derivation error:', error);
throw new Error(`Failed to derive addresses: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
-/** Map raw mempool.space tx to our Transaction format (shared by refresh and fetchMore). */
+/**
+ * Run gap-limit HD discovery for the active address type.
+ * Scans the blockchain for used addresses, updates hdState & address list.
+ */
+/**
+ * @returns `true` if discovery actually ran (and refreshed data), `false` if skipped.
+ */
+export async function runHdDiscovery(force = false, overrideAddressType?: 'segwit-native' | 'segwit-nested' | 'legacy'): Promise {
+ const publicKey = await storage.get('publicKey');
+ const chainCode = await storage.get('chainCode');
+ const network = (await storage.get('network') as 'mainnet' | 'testnet') || 'mainnet';
+ if (!publicKey || !chainCode) return false;
+
+ const hdStateJson = await storage.get('hdState');
+ const existing: HdState | null = hdStateJson ? JSON.parse(hdStateJson) : null;
+
+ const addressType = overrideAddressType || existing?.addressType || 'segwit-native';
+
+ if (!overrideAddressType && !force && existing?.discoveryDone) {
+ const age = Date.now() - (existing.discoveryLastAt || 0);
+ if (age < HD_DISCOVERY_STALE_MS) {
+ console.log('[Wallet] HD discovery still fresh, skipping');
+ return false;
+ }
+ }
+ const config = { publicKey, chainCode, network };
+
+ console.log('[Wallet] Running HD discovery for', addressType);
+
+ const getStats = async (address: string) => {
+ const stats = await blockchain.getAddressStats(address);
+ return { tx_count: stats.chain_stats.tx_count + stats.mempool_stats.tx_count };
+ };
+
+ const result = await hdWallet.discoverIndexes(config, addressType, getStats);
+
+ const newHdState: HdState = {
+ externalIndex: result.externalNext,
+ changeIndex: result.changeNext,
+ maxUsedExternal: result.maxUsedExternal,
+ discoveryDone: true,
+ discoveryLastAt: Date.now(),
+ addressType,
+ };
+
+ await storage.set('hdState', JSON.stringify(newHdState));
+
+ // Derive the full address set from discovered indexes
+ const externalEnd = Math.max(result.externalNext, result.maxUsedExternal);
+ const changeEnd = result.changeNext;
+ const allAddrs = hdWallet.deriveHdAddresses(config, addressType, externalEnd, changeEnd > 0 ? changeEnd - 1 : -1);
+
+ const addresses: DerivedAddress[] = allAddrs.map(a => ({
+ ...a,
+ type: addressType,
+ }));
+
+ await updateAddresses(addresses);
+
+ // Set active address to current receive address
+ if (result.externalNext >= 0) {
+ const [receiveAddr] = hdWallet.deriveAddresses(config, addressType, 1, result.externalNext, 0);
+ if (receiveAddr) {
+ await storage.set('address', receiveAddr.address);
+ walletStore.update(s => ({ ...s, address: receiveAddr.address }));
+ }
+ }
+
+ walletStore.update(s => ({ ...s, hdState: newHdState }));
+ console.log('[Wallet] HD discovery complete:', newHdState);
+
+ // Re-aggregate balance/txs/UTXOs now that the full address set is known
+ await refreshWalletData();
+ return true;
+}
+
+/**
+ * Switch the active address type, re-run HD discovery, and refresh wallet data.
+ */
+export async function switchAddressType(newType: 'segwit-native' | 'segwit-nested' | 'legacy'): Promise {
+ const state = getStoreValue();
+ if (state.hdState?.addressType === newType) return;
+ await runHdDiscovery(true, newType);
+}
+
+/**
+ * Get the current receive address (at externalIndex).
+ */
+export function getCurrentReceiveAddress(): DerivedAddress | null {
+ const state = getStoreValue();
+ if (!state.publicKey || !state.chainCode || !state.hdState) return null;
+ const config = { publicKey: state.publicKey, chainCode: state.chainCode, network: state.network };
+ const [addr] = hdWallet.deriveAddresses(config, state.hdState.addressType, 1, state.hdState.externalIndex, 0);
+ return addr ? { ...addr, type: state.hdState.addressType, chain: 'receive' } : null;
+}
+
+/**
+ * Get the next change address (at changeIndex).
+ */
+export function getNextChangeAddress(): DerivedAddress | null {
+ const state = getStoreValue();
+ if (!state.publicKey || !state.chainCode || !state.hdState) return null;
+ const config = { publicKey: state.publicKey, chainCode: state.chainCode, network: state.network };
+ const [addr] = hdWallet.deriveAddresses(config, state.hdState.addressType, 1, state.hdState.changeIndex, 1);
+ return addr ? { ...addr, type: state.hdState.addressType, chain: 'change' } : null;
+}
+
+function getStoreValue(): WalletState {
+ let val: WalletState = initialState;
+ walletStore.subscribe(s => { val = s; })();
+ return val;
+}
+
+/** Map raw mempool.space tx to our Transaction format (single-address, for fetchMore). */
function mapRawTxToTransaction(tx: any, currentAddress: string): Transaction {
+ return mapRawTxMultiAddress(tx, new Set([currentAddress]));
+}
+
+/** Map raw mempool.space tx considering multiple wallet addresses. */
+function mapRawTxMultiAddress(tx: any, walletAddresses: Set): Transaction {
const receivedByUs = tx.vout
- .filter((v: any) => v.scriptpubkey_address === currentAddress)
+ .filter((v: any) => walletAddresses.has(v.scriptpubkey_address))
.reduce((sum: number, v: any) => sum + v.value, 0);
const sentFromUs = tx.vin
- .filter((v: any) => v.prevout?.scriptpubkey_address === currentAddress)
+ .filter((v: any) => walletAddresses.has(v.prevout?.scriptpubkey_address))
.reduce((sum: number, v: any) => sum + (v.prevout?.value || 0), 0);
const sentToOthers = tx.vout
- .filter((v: any) => v.scriptpubkey_address && v.scriptpubkey_address !== currentAddress)
+ .filter((v: any) => v.scriptpubkey_address && !walletAddresses.has(v.scriptpubkey_address))
.reduce((sum: number, v: any) => sum + v.value, 0);
const netAmount = receivedByUs - sentFromUs;
- const type: Transaction['type'] = netAmount > 0 ? 'receive' : 'send';
+
+ // If all inputs and outputs belong to us, it's a consolidation
+ const allInputsOurs = tx.vin.every((v: any) => walletAddresses.has(v.prevout?.scriptpubkey_address));
+ const allOutputsOurs = tx.vout.every((v: any) => walletAddresses.has(v.scriptpubkey_address));
+ const isConsolidation = allInputsOurs && allOutputsOurs;
+
+ const type: Transaction['type'] = isConsolidation
+ ? 'consolidation'
+ : netAmount > 0
+ ? 'receive'
+ : 'send';
const amount =
- type === 'receive'
+ type === 'consolidation'
? receivedByUs
- : sentToOthers > 0
- ? sentToOthers
- : Math.abs(netAmount);
+ : type === 'receive'
+ ? receivedByUs
+ : sentToOthers > 0
+ ? sentToOthers
+ : Math.abs(netAmount);
- const recipientVout = tx.vout?.find((v: any) => v.scriptpubkey_address && v.scriptpubkey_address !== currentAddress);
- const senderVin = tx.vin?.find((v: any) => v.prevout?.scriptpubkey_address && v.prevout.scriptpubkey_address !== currentAddress);
+ const firstOurAddress = tx.vout?.find((v: any) => walletAddresses.has(v.scriptpubkey_address))?.scriptpubkey_address
+ || Array.from(walletAddresses)[0];
+ const recipientVout = tx.vout?.find((v: any) => v.scriptpubkey_address && !walletAddresses.has(v.scriptpubkey_address));
+ const senderVin = tx.vin?.find((v: any) => v.prevout?.scriptpubkey_address && !walletAddresses.has(v.prevout.scriptpubkey_address));
return {
txid: tx.txid,
@@ -422,45 +577,90 @@ function mapRawTxToTransaction(tx: any, currentAddress: string): Transaction {
fee: tx.fee || 0,
status: tx.status.confirmed ? 'confirmed' : 'pending',
type,
- address: currentAddress,
- from: type === 'receive' ? senderVin?.prevout?.scriptpubkey_address : currentAddress,
- to: type === 'send' ? recipientVout?.scriptpubkey_address : currentAddress,
+ address: firstOurAddress,
+ from: type === 'receive' ? senderVin?.prevout?.scriptpubkey_address : firstOurAddress,
+ to: type === 'send' ? recipientVout?.scriptpubkey_address : firstOurAddress,
};
}
/**
- * Refresh wallet data from blockchain API
+ * Refresh wallet data from blockchain API.
+ * Aggregates balance, transactions, and UTXOs across all HD addresses.
*/
export async function refreshWalletData() {
- let currentAddress = '';
+ let addresses: DerivedAddress[] = [];
walletStore.update(state => {
- currentAddress = state.address;
+ addresses = state.addresses;
return { ...state, isLoading: true, error: undefined, hasMoreTransactions: true };
});
- if (!currentAddress) {
+ if (!addresses.length) {
walletStore.update(state => ({
...state,
isLoading: false,
- error: 'No wallet address configured'
+ error: 'No wallet addresses configured'
}));
return;
}
+ const allAddressStrings = new Set(addresses.map(a => a.address));
+
try {
- const [addressStats, txHistory] = await Promise.all([
- blockchain.getAddressStats(currentAddress),
- blockchain.getTransactions(currentAddress)
- ]);
+ // Aggregate balance across all addresses
+ let totalConfirmed = 0;
+ let totalUnconfirmed = 0;
+ for (const addr of addresses) {
+ try {
+ const stats = await blockchain.getAddressStats(addr.address);
+ totalConfirmed += stats.chain_stats.funded_txo_sum - stats.chain_stats.spent_txo_sum;
+ totalUnconfirmed += stats.mempool_stats.funded_txo_sum - stats.mempool_stats.spent_txo_sum;
+ } catch {
+ // Skip addresses that fail (rate limit, etc.)
+ }
+ }
- const balanceSats = addressStats.chain_stats.funded_txo_sum - addressStats.chain_stats.spent_txo_sum;
+ const balanceSats = totalConfirmed + totalUnconfirmed;
const balanceBTC = (balanceSats / 100_000_000).toFixed(8);
-
const price = await blockchain.getBitcoinPrice();
const balanceUSD = (parseFloat(balanceBTC) * price).toFixed(2);
- const transactions: Transaction[] = txHistory.map((tx: any) => mapRawTxToTransaction(tx, currentAddress));
+ // Aggregate transactions, dedup by txid
+ const txMap = new Map();
+ for (const addr of addresses) {
+ try {
+ const txHistory = await blockchain.getTransactions(addr.address);
+ for (const rawTx of txHistory) {
+ if (!txMap.has(rawTx.txid)) {
+ txMap.set(rawTx.txid, mapRawTxMultiAddress(rawTx, allAddressStrings));
+ }
+ }
+ } catch {
+ // Skip on error
+ }
+ }
+ const transactions = Array.from(txMap.values())
+ .sort((a, b) => b.timestamp - a.timestamp);
+
+ // Aggregate UTXOs with address/path tagging
+ const taggedUtxos: TaggedUTXO[] = [];
+ for (const addr of addresses) {
+ try {
+ const utxos = await blockchain.getUTXOs(addr.address);
+ for (const u of utxos) {
+ taggedUtxos.push({
+ txid: u.txid,
+ vout: u.vout,
+ value: u.value,
+ address: addr.address,
+ derivationPath: addr.path,
+ status: u.status,
+ });
+ }
+ } catch {
+ // Skip
+ }
+ }
walletStore.update(state => ({
...state,
@@ -469,7 +669,8 @@ export async function refreshWalletData() {
lastBalanceUpdate: Date.now(),
transactions,
lastTxUpdate: Date.now(),
- hasMoreTransactions: txHistory.length > 0,
+ hasMoreTransactions: transactions.length > 0,
+ utxos: taggedUtxos,
isLoading: false,
error: undefined
}));
@@ -485,27 +686,43 @@ export async function refreshWalletData() {
}
/**
- * Fetch more transactions (paging), appending to the list. Uses last txid in list and /chain/:txid.
+ * Fetch more transactions (paging), appending to the list.
*/
export async function fetchMoreTransactions() {
- let currentAddress = '';
+ let addresses: DerivedAddress[] = [];
let lastTxid = '';
walletStore.update(state => {
- currentAddress = state.address;
+ addresses = state.addresses;
const txs = state.transactions;
lastTxid = txs.length > 0 ? txs[txs.length - 1].txid : '';
return { ...state, isLoadingMoreTransactions: true };
});
- if (!currentAddress || !lastTxid) {
+ if (!addresses.length || !lastTxid) {
walletStore.update(state => ({ ...state, isLoadingMoreTransactions: false, hasMoreTransactions: false }));
return;
}
+ const allAddressStrings = new Set(addresses.map(a => a.address));
+
try {
- const nextPage = await blockchain.getTransactions(currentAddress, lastTxid);
- const newTransactions = nextPage.map((tx: any) => mapRawTxToTransaction(tx, currentAddress));
+ const txMap = new Map();
+ for (const addr of addresses) {
+ try {
+ const nextPage = await blockchain.getTransactions(addr.address, lastTxid);
+ for (const rawTx of nextPage) {
+ if (!txMap.has(rawTx.txid)) {
+ txMap.set(rawTx.txid, mapRawTxMultiAddress(rawTx, allAddressStrings));
+ }
+ }
+ } catch {
+ // Skip on error
+ }
+ }
+
+ const newTransactions = Array.from(txMap.values())
+ .sort((a, b) => b.timestamp - a.timestamp);
walletStore.update(state => {
const existingIds = new Set(state.transactions.map(t => t.txid));
@@ -514,7 +731,7 @@ export async function fetchMoreTransactions() {
...state,
transactions: [...state.transactions, ...appended],
lastTxUpdate: Date.now(),
- hasMoreTransactions: nextPage.length > 0,
+ hasMoreTransactions: newTransactions.length > 0,
isLoadingMoreTransactions: false
};
});
diff --git a/BoldChrome/src/lib/styles/theme.ts b/src/lib/styles/theme.ts
similarity index 100%
rename from BoldChrome/src/lib/styles/theme.ts
rename to src/lib/styles/theme.ts
diff --git a/BoldChrome/src/popup/SendBitcoin.svelte b/src/popup/SendBitcoin.svelte
similarity index 100%
rename from BoldChrome/src/popup/SendBitcoin.svelte
rename to src/popup/SendBitcoin.svelte
diff --git a/BoldChrome/src/routes/+layout.svelte b/src/routes/+layout.svelte
similarity index 100%
rename from BoldChrome/src/routes/+layout.svelte
rename to src/routes/+layout.svelte
diff --git a/BoldChrome/src/routes/+layout.ts b/src/routes/+layout.ts
similarity index 100%
rename from BoldChrome/src/routes/+layout.ts
rename to src/routes/+layout.ts
diff --git a/BoldChrome/src/routes/+page.svelte b/src/routes/+page.svelte
similarity index 100%
rename from BoldChrome/src/routes/+page.svelte
rename to src/routes/+page.svelte
diff --git a/BoldChrome/src/routes/permission/+page.svelte b/src/routes/permission/+page.svelte
similarity index 100%
rename from BoldChrome/src/routes/permission/+page.svelte
rename to src/routes/permission/+page.svelte
diff --git a/src/routes/popup.html/+page.svelte b/src/routes/popup.html/+page.svelte
new file mode 100644
index 0000000..3ed56c5
--- /dev/null
+++ b/src/routes/popup.html/+page.svelte
@@ -0,0 +1,5143 @@
+
+
+
+ {#if showCameraPermissionScreen}
+
+
+
+ {#if cameraPermissionDeniedHint}
+
Camera was blocked
+
+ The browser won't show the prompt again. To allow camera: open Chrome Settings → Privacy and security → Site settings → Camera, find this extension and set it to Allow.
+
+
+
+
+
+
+ {:else}
+
Camera access
+
+ {#if pendingActionAfterCamera === "pairing"}
+ Camera is needed to scan the response QR from your phone. Grant
+ access to continue with pairing.
+ {:else}
+ Bold Wallet uses your camera to scan QR codes for pairing and
+ sending Bitcoin. Grant access to get started.
+ {/if}
+
Please click "Allow" when your browser asks for camera access.
+
+
To allow camera for Bold Wallet:
+
+
Click Open camera settings below (or go to Chrome Settings → Privacy and security → Site settings → Camera).
+
Find Bold Wallet in the list and set it to Allow.
+
Return to the extension popup and click Grant camera permission again, or close this tab and reopen the extension.
+
+
+
After changing the setting, you can try again from the extension popup.
+
+
+
+
+
+
+
diff --git a/BoldChrome/static/robots.txt b/static/robots.txt
similarity index 100%
rename from BoldChrome/static/robots.txt
rename to static/robots.txt
diff --git a/BoldChrome/svelte.config.js b/svelte.config.js
similarity index 100%
rename from BoldChrome/svelte.config.js
rename to svelte.config.js
diff --git a/BoldChrome/tsconfig.json b/tsconfig.json
similarity index 100%
rename from BoldChrome/tsconfig.json
rename to tsconfig.json
diff --git a/BoldChrome/verify-setup.cjs b/verify-setup.cjs
similarity index 100%
rename from BoldChrome/verify-setup.cjs
rename to verify-setup.cjs
diff --git a/BoldChrome/verify-setup.js b/verify-setup.js
similarity index 100%
rename from BoldChrome/verify-setup.js
rename to verify-setup.js
diff --git a/BoldChrome/vite.config.ts b/vite.config.ts
similarity index 100%
rename from BoldChrome/vite.config.ts
rename to vite.config.ts