From 91c87b73d02af59e2f0fbf56316e97e7f22d10d9 Mon Sep 17 00:00:00 2001
From: coderofstuff <114628839+coderofstuff@users.noreply.github.com>
Date: Sun, 22 Dec 2024 00:11:29 -0700
Subject: [PATCH 1/4] Add bluetooth support
---
package-lock.json | 37 ++++++++++++++++++++++++------------
package.json | 1 +
src/app/page.tsx | 27 ++++++++++++++++++++++----
src/app/wallet/page.tsx | 2 +-
src/components/send-form.tsx | 2 +-
src/lib/ledger.ts | 10 +++++++++-
6 files changed, 60 insertions(+), 19 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index cc26d7b..5a76858 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@emotion/server": "^11.11.0",
"@ledgerhq/errors": "^6.16.0",
"@ledgerhq/hw-transport": "^6.30.0",
+ "@ledgerhq/hw-transport-web-ble": "^6.29.4",
"@ledgerhq/hw-transport-webhid": "^6.28.0",
"@mantine/core": "^7.1.5",
"@mantine/form": "^7.2.2",
@@ -3350,32 +3351,44 @@
}
},
"node_modules/@ledgerhq/devices": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.2.2.tgz",
- "integrity": "sha512-SKahGA4p0mZ3ovypOJ2wa5mUvUkArE3HBrwWKYf+cRs+t/Licp3OJfhj+DHIxP3AfyH2xR6CFFWECYHeKwGsDQ==",
+ "version": "8.4.4",
+ "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.4.tgz",
+ "integrity": "sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A==",
"dependencies": {
- "@ledgerhq/errors": "^6.16.3",
+ "@ledgerhq/errors": "^6.19.1",
"@ledgerhq/logs": "^6.12.0",
"rxjs": "^7.8.1",
"semver": "^7.3.5"
}
},
"node_modules/@ledgerhq/errors": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.16.3.tgz",
- "integrity": "sha512-3w7/SJVXOPa9mpzyll7VKoKnGwDD3BzWgN1Nom8byR40DiQvOKjHX+kKQausCedTHVNBn9euzPCNsftZ9+mxfw=="
+ "version": "6.19.1",
+ "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.19.1.tgz",
+ "integrity": "sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw=="
},
"node_modules/@ledgerhq/hw-transport": {
- "version": "6.30.5",
- "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.30.5.tgz",
- "integrity": "sha512-JMl//7BgPBvWxrWyMu82jj6JEYtsQyOyhYtonWNgtxn6KUZWht3gU4gxmLpeIRr+DiS7e50mW7m3GA+EudZmmA==",
+ "version": "6.31.4",
+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz",
+ "integrity": "sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A==",
"dependencies": {
- "@ledgerhq/devices": "^8.2.2",
- "@ledgerhq/errors": "^6.16.3",
+ "@ledgerhq/devices": "^8.4.4",
+ "@ledgerhq/errors": "^6.19.1",
"@ledgerhq/logs": "^6.12.0",
"events": "^3.3.0"
}
},
+ "node_modules/@ledgerhq/hw-transport-web-ble": {
+ "version": "6.29.4",
+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.29.4.tgz",
+ "integrity": "sha512-OJyp6CryvyFlg1L9uifo5hYYdDt+WPw8/0ijBixYhYmGvlRz2W6/F2c5rG/zBQWcNnNydPOLjLJM0vR070RfCw==",
+ "dependencies": {
+ "@ledgerhq/devices": "^8.4.4",
+ "@ledgerhq/errors": "^6.19.1",
+ "@ledgerhq/hw-transport": "^6.31.4",
+ "@ledgerhq/logs": "^6.12.0",
+ "rxjs": "^7.8.1"
+ }
+ },
"node_modules/@ledgerhq/hw-transport-webhid": {
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.28.0.tgz",
diff --git a/package.json b/package.json
index 422503d..f233ba4 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@emotion/server": "^11.11.0",
"@ledgerhq/errors": "^6.16.0",
"@ledgerhq/hw-transport": "^6.30.0",
+ "@ledgerhq/hw-transport-web-ble": "^6.29.4",
"@ledgerhq/hw-transport-webhid": "^6.28.0",
"@mantine/core": "^7.1.5",
"@mantine/form": "^7.2.2",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 81354f5..5417bab 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -87,7 +87,10 @@ export default function Home() {
}
}
- setIsShowDemo(window.location.hostname !== 'kasvault.io');
+ setIsShowDemo(
+ window.location.hostname === 'preview.kasvault.io' ||
+ window.location.search.includes('demo'),
+ );
}, []);
const smallStyles = width <= 48 * 16 ? { fontSize: '1rem' } : {};
@@ -107,7 +110,23 @@ export default function Home() {
(Replaced with bluetooth in the future)
- ) : null;
+ ) : (
+ {
+ getAppData(navigate, 'bluetooth');
+ }}
+ align='center'
+ >
+
+
+ Connect with Bluetooth
+ ->
+
+
+ Nano X, Stax and Flex
+
+ );
return (
@@ -138,11 +157,11 @@ export default function Home() {
}}
align='center'
>
-
+
Connect with USB ->
-
+
All Ledger devices
diff --git a/src/app/wallet/page.tsx b/src/app/wallet/page.tsx
index 9b962a5..3fe4787 100644
--- a/src/app/wallet/page.tsx
+++ b/src/app/wallet/page.tsx
@@ -426,7 +426,7 @@ export default function Dashboard() {
return;
}
- if (deviceType === 'usb') {
+ if (deviceType === 'usb' || deviceType === 'bluetooth') {
loadOrScanAddressBatch(bip32base, setAddresses, setRawAddresses, userSettings).finally(
() => {
setEnableGenerate(true);
diff --git a/src/components/send-form.tsx b/src/components/send-form.tsx
index f3dd7bb..adc343f 100644
--- a/src/components/send-form.tsx
+++ b/src/components/send-form.tsx
@@ -181,7 +181,7 @@ export default function SendForm(props: SendFormProps) {
if (deviceType == 'demo') {
simulateConfirmation(notifId);
- } else if (deviceType == 'usb') {
+ } else if (deviceType == 'usb' || deviceType == 'bluetooth') {
try {
const { tx } = createTransaction(
kasToSompi(Number(form.values.amount)),
diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts
index 1a16faa..d555e59 100644
--- a/src/lib/ledger.ts
+++ b/src/lib/ledger.ts
@@ -1,4 +1,5 @@
import TransportWebHID from '@ledgerhq/hw-transport-webhid';
+import BluetoothTransport from '@ledgerhq/hw-transport-web-ble';
import axios from 'axios';
import axiosRetry from 'axios-retry';
@@ -156,7 +157,14 @@ export async function initTransport(type = 'usb') {
return await transportState.initPromise;
}
- transportState.initPromise = TransportWebHID.create();
+ if (type === 'usb') {
+ transportState.initPromise = TransportWebHID.create();
+ } else if (type === 'bluetooth') {
+ transportState.initPromise = BluetoothTransport.create();
+ } else {
+ throw new Error('Unknown device type');
+ }
+
transportState.transport = await transportState.initPromise;
transportState.type = type;
From b96a7c4b78657026c9937efacd6ec114e5969dda Mon Sep 17 00:00:00 2001
From: coderofstuff <114628839+coderofstuff@users.noreply.github.com>
Date: Sun, 22 Dec 2024 00:20:22 -0700
Subject: [PATCH 2/4] Add temp page whitelist for bluetooth
---
src/app/page.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 5417bab..77d2d37 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -67,6 +67,7 @@ const WHITELIST = [
'preview.kasvault.io',
'privatepreview.kasvault.io',
'kasvault.vercel.app',
+ 'bluetooth.kasvault.io',
];
export default function Home() {
From 64601d3ec508271cec4ff55a76c25e8adc4cfd3a Mon Sep 17 00:00:00 2001
From: coderofstuff <114628839+coderofstuff@users.noreply.github.com>
Date: Mon, 12 May 2025 15:49:35 -0600
Subject: [PATCH 3/4] Add Ledger transport Initialized helper
---
src/lib/ledger.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts
index d555e59..3421806 100644
--- a/src/lib/ledger.ts
+++ b/src/lib/ledger.ts
@@ -20,6 +20,7 @@ let transportState = {
transport: null,
initPromise: null,
type: null,
+ initialized: false,
};
const kaspaState = {
@@ -167,10 +168,15 @@ export async function initTransport(type = 'usb') {
transportState.transport = await transportState.initPromise;
transportState.type = type;
+ transportState.initialized = true;
return transportState.transport;
}
+export function isLedgerTransportInitialized() {
+ return transportState.initialized;
+}
+
export async function fetchTransactionCount(address) {
const { data: txCount } = await axios.get(
`https://api.kaspa.org/addresses/${address}/transactions-count`,
From da3f025d148f4edbe61b240f96a9e00409c01471 Mon Sep 17 00:00:00 2001
From: coderofstuff <114628839+coderofstuff@users.noreply.github.com>
Date: Mon, 12 May 2025 16:16:42 -0600
Subject: [PATCH 4/4] Add modal for device connection
---
src/app/wallet/page.tsx | 92 ++++++++++++++++++++++++++++++++---------
1 file changed, 72 insertions(+), 20 deletions(-)
diff --git a/src/app/wallet/page.tsx b/src/app/wallet/page.tsx
index 3fe4787..17b684c 100644
--- a/src/app/wallet/page.tsx
+++ b/src/app/wallet/page.tsx
@@ -6,9 +6,10 @@ import {
fetchAddressDetails,
initTransport,
fetchAddressBalance,
+ isLedgerTransportInitialized,
} from '../../lib/ledger';
import { useState, useEffect } from 'react';
-import { Stack, Tabs, Breadcrumbs, Anchor, Button, Center } from '@mantine/core';
+import { Stack, Tabs, Breadcrumbs, Anchor, Button, Center, Modal, Text } from '@mantine/core';
import Header from '../../components/header';
import AddressesTab from './addresses-tab';
import OverviewTab from './overview-tab';
@@ -279,6 +280,7 @@ export default function Dashboard() {
const [enableGenerate, setEnableGenerate] = useState(false);
const [mempoolEntryToReplace, setMempoolEntryToReplace] = useState(null);
const [pendingTxId, setPendingTxId] = useState(null);
+ const [showConnectModal, setShowConnectModal] = useState(false);
const { ref: containerRef, width: containerWidth, height: containerHeight } = useElementSize();
@@ -376,27 +378,32 @@ export default function Dashboard() {
let unloaded = false;
- initTransport(deviceType)
- .then(() => {
- if (!unloaded) {
- setTransportInitialized(true);
-
- return getXPubFromLedger().then((xpub) =>
- setBIP32Base(new KaspaBIP32(xpub.compressedPublicKey, xpub.chainCode)),
- );
- }
+ if (!isLedgerTransportInitialized()) {
+ setShowConnectModal(true);
+ } else {
+ initTransport(deviceType)
+ .then(() => {
+ if (!unloaded) {
+ setTransportInitialized(true);
+
+ return getXPubFromLedger().then((xpub) =>
+ setBIP32Base(new KaspaBIP32(xpub.compressedPublicKey, xpub.chainCode)),
+ );
+ }
- return null;
- })
- .catch((e) => {
- notifications.show({
- title: 'Error',
- color: 'red',
- message: 'Please make sure your device is unlocked and the Kaspa app is open',
- autoClose: false,
+ return null;
+ })
+ .catch((e) => {
+ notifications.show({
+ title: 'Error',
+ color: 'red',
+ message:
+ 'Please make sure your device is unlocked and the Kaspa app is open',
+ autoClose: false,
+ });
+ console.error(e);
});
- console.error(e);
- });
+ }
return () => {
unloaded = true;
@@ -455,6 +462,51 @@ export default function Dashboard() {
{breadcrumbs}
+ setShowConnectModal(false)}
+ title={'Connect Ledger Device via ' + deviceType}
+ >
+
+ Please connect your Ledger device and open the Kaspa app.
+
+
+
+