diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f0ac2de0d4..829e706eba 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -24,6 +24,12 @@
+
+
+
+
+
+
2.2.3)
- MMKVCore (2.2.3)
+ - MultiplatformBleAdapter (0.2.0)
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
@@ -1301,6 +1323,28 @@ PODS:
- React-Core
- react-native-biometrics (2.2.0):
- React-Core
+ - react-native-ble-plx (3.5.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - MultiplatformBleAdapter (= 0.2.0)
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- react-native-blob-util (0.18.3):
- React-Core
- react-native-change-icon (5.0.0):
@@ -1922,6 +1966,7 @@ PODS:
- ZXingObjC/All (3.6.9)
DEPENDENCIES:
+ - "BleUtils (from `../node_modules/@onekeyfe/react-native-ble-utils`)"
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
@@ -1967,6 +2012,7 @@ DEPENDENCIES:
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
+ - react-native-ble-plx (from `../node_modules/react-native-ble-plx`)
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
- react-native-change-icon (from `../node_modules/react-native-change-icon`)
- react-native-config (from `../node_modules/react-native-config`)
@@ -2046,6 +2092,7 @@ SPEC REPOS:
- libwebp
- MMKV
- MMKVCore
+ - MultiplatformBleAdapter
- nanopb
- PDFGenerator
- PromisesObjC
@@ -2057,6 +2104,8 @@ SPEC REPOS:
- ZXingObjC
EXTERNAL SOURCES:
+ BleUtils:
+ :path: "../node_modules/@onekeyfe/react-native-ble-utils"
boost:
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
BVLinearGradient:
@@ -2140,6 +2189,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-background-timer"
react-native-biometrics:
:path: "../node_modules/react-native-biometrics"
+ react-native-ble-plx:
+ :path: "../node_modules/react-native-ble-plx"
react-native-blob-util:
:path: "../node_modules/react-native-blob-util"
react-native-change-icon:
@@ -2277,6 +2328,7 @@ CHECKOUT OPTIONS:
:git: https://github.com/bithyve/libportal-ios.git
SPEC CHECKSUMS:
+ BleUtils: 7837f24770460d054aa9acf20c7c147a75f17057
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
@@ -2298,6 +2350,7 @@ SPEC CHECKSUMS:
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
MMKV: 941e8774da0e6fdf12c6b3fcc833ca687ae5a42d
MMKVCore: 6d5cc1bacce539f4c974985dfe646fb65a5d27d2
+ MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
PDFGenerator: 17d6bc525db0a3fcd156fbf00f9d1b8d5cc75d1e
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
@@ -2333,6 +2386,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: 3b784cf40f7c3c9500b9a5f9e4eec5c9bbfbef8f
react-native-background-timer: 4638ae3bee00320753647900b21260b10587b6f7
react-native-biometrics: ecca256a9e1c8b430f78c4e3dcb16b57ea1418e0
+ react-native-ble-plx: 39e6b732ac5bce0a7a40c1fc33ae58a92b444656
react-native-blob-util: 4be49d669870f01645b63d33342718e95d0d48b1
react-native-change-icon: cf7be79da0c88778d626d50d3a78912b10143521
react-native-config: 8e425892a531627c52db765be3088185cb871e19
diff --git a/ios/hexa_keeper/Info.plist b/ios/hexa_keeper/Info.plist
index 86c03c42f7..350e74b896 100644
--- a/ios/hexa_keeper/Info.plist
+++ b/ios/hexa_keeper/Info.plist
@@ -53,6 +53,10 @@
NSAllowsLocalNetworking
+ NSBluetoothAlwaysUsageDescription
+ Allow Keeper to scan and connect to your OneKey hardware wallet over Bluetooth
+ NSBluetoothPeripheralUsageDescription
+ Allow Keeper to scan and connect to your OneKey hardware wallet over Bluetooth
NSCameraUsageDescription
Please allow camera access for QR scanner
NSContactsUsageDescription
diff --git a/ios/hexa_keeper_dev-Info.plist b/ios/hexa_keeper_dev-Info.plist
index 3413ca61a8..d5057c47fc 100644
--- a/ios/hexa_keeper_dev-Info.plist
+++ b/ios/hexa_keeper_dev-Info.plist
@@ -46,6 +46,10 @@
NFCReaderUsageDescription
Allow $(PRODUCT_NAME) to interact with nearby NFC devices
+ NSBluetoothAlwaysUsageDescription
+ Allow $(PRODUCT_NAME) to scan and connect to your OneKey hardware wallet over Bluetooth
+ NSBluetoothPeripheralUsageDescription
+ Allow $(PRODUCT_NAME) to scan and connect to your OneKey hardware wallet over Bluetooth
NSAppTransportSecurity
diff --git a/metro.config.js b/metro.config.js
index 1748c03ea2..1e11c8b303 100644
--- a/metro.config.js
+++ b/metro.config.js
@@ -16,6 +16,8 @@ const config = {
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg'],
+ // OneKey hd-core uses package.json "exports" field
+ unstable_enablePackageExports: true,
},
};
diff --git a/package.json b/package.json
index 35db7552d3..a4a7fbd7f6 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,11 @@
"dependencies": {
"@bitcoinerlab/miniscript": "1.4.0",
"@ngraveio/bc-ur": "1.1.6",
+ "@noble/hashes": "^1.3.3",
"@noble/secp256k1": "1.6.3",
+ "@onekeyfe/hd-ble-sdk": "1.1.16",
+ "@onekeyfe/hd-core": "1.1.16",
+ "@onekeyfe/react-native-ble-utils": "^0.1.4",
"@react-native-clipboard/clipboard": "1.16.2",
"@react-native-community/netinfo": "11.4.1",
"@react-native-firebase/app": "14.11.1",
@@ -74,6 +78,7 @@
"react-localization": "1.0.19",
"react-native": "0.77.3",
"react-native-background-timer": "2.4.1",
+ "react-native-ble-plx": "3.5.0",
"react-native-biometrics": "2.2.0",
"react-native-blob-util": "0.18.3",
"react-native-change-icon": "5.0.0",
@@ -118,6 +123,7 @@
"redux-persist": "6.0.0",
"redux-saga": "1.1.3",
"rn-qr-generator": "^1.4.4",
+ "ripple-keypairs": "^1.3.1",
"satochip-react-native": "git+https://github.com/Toporin/satochip-react-native.git#1076e7c097571d561b5b903f31c6337455def19e",
"semver": "7.3.8",
"socket.io-client": "4.5.4",
diff --git a/src/assets/images/flag-hongkong.svg b/src/assets/images/flag-hongkong.svg
new file mode 100644
index 0000000000..e1a7ee6868
--- /dev/null
+++ b/src/assets/images/flag-hongkong.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/assets/images/flag-japan.svg b/src/assets/images/flag-japan.svg
new file mode 100644
index 0000000000..3c980d2058
--- /dev/null
+++ b/src/assets/images/flag-japan.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/assets/images/onekey-devices/classic-pure.png b/src/assets/images/onekey-devices/classic-pure.png
new file mode 100644
index 0000000000..de31d59dc5
Binary files /dev/null and b/src/assets/images/onekey-devices/classic-pure.png differ
diff --git a/src/assets/images/onekey-devices/classic.png b/src/assets/images/onekey-devices/classic.png
new file mode 100644
index 0000000000..aaf787a4f0
Binary files /dev/null and b/src/assets/images/onekey-devices/classic.png differ
diff --git a/src/assets/images/onekey-devices/pro-black.png b/src/assets/images/onekey-devices/pro-black.png
new file mode 100644
index 0000000000..3057f8bc51
Binary files /dev/null and b/src/assets/images/onekey-devices/pro-black.png differ
diff --git a/src/assets/images/onekey-devices/touch.png b/src/assets/images/onekey-devices/touch.png
new file mode 100644
index 0000000000..fb85963c15
Binary files /dev/null and b/src/assets/images/onekey-devices/touch.png differ
diff --git a/src/assets/images/onekey-green-dark.svg b/src/assets/images/onekey-green-dark.svg
new file mode 100644
index 0000000000..7eb3c742a1
--- /dev/null
+++ b/src/assets/images/onekey-green-dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/onekey-green-light.svg b/src/assets/images/onekey-green-light.svg
new file mode 100644
index 0000000000..a9166d0064
--- /dev/null
+++ b/src/assets/images/onekey-green-light.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/onekey-shop-device.png b/src/assets/images/onekey-shop-device.png
new file mode 100644
index 0000000000..e57d06cfab
Binary files /dev/null and b/src/assets/images/onekey-shop-device.png differ
diff --git a/src/assets/images/onekey_icon.svg b/src/assets/images/onekey_icon.svg
new file mode 100644
index 0000000000..afa35e0aac
--- /dev/null
+++ b/src/assets/images/onekey_icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/onekey_icon_light.svg b/src/assets/images/onekey_icon_light.svg
new file mode 100644
index 0000000000..afa35e0aac
--- /dev/null
+++ b/src/assets/images/onekey_icon_light.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/images/onekey_illustration.svg b/src/assets/images/onekey_illustration.svg
new file mode 100644
index 0000000000..1b36bb43f3
--- /dev/null
+++ b/src/assets/images/onekey_illustration.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/assets/images/onekey_logo.svg b/src/assets/images/onekey_logo.svg
new file mode 100644
index 0000000000..51b2596ff4
--- /dev/null
+++ b/src/assets/images/onekey_logo.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/images/onekey_logo_white.svg b/src/assets/images/onekey_logo_white.svg
new file mode 100644
index 0000000000..4d3d1cd7db
--- /dev/null
+++ b/src/assets/images/onekey_logo_white.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/components/OneKeyBleModal.tsx b/src/components/OneKeyBleModal.tsx
new file mode 100644
index 0000000000..6b4d516c09
--- /dev/null
+++ b/src/components/OneKeyBleModal.tsx
@@ -0,0 +1,545 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { ActivityIndicator, FlatList, Image, StyleSheet, TouchableOpacity } from 'react-native';
+import { Box, useColorMode } from 'native-base';
+import { useDispatch } from 'react-redux';
+import KeeperModal from 'src/components/KeeperModal';
+import Text from 'src/components/KeeperText';
+import useToastMessage from 'src/hooks/useToastMessage';
+import ToastErrorIcon from 'src/assets/images/toast_error.svg';
+import TickIcon from 'src/assets/images/icon_tick.svg';
+import { useAppSelector } from 'src/store/hooks';
+import { NetworkType, SignerType } from 'src/services/wallets/enums';
+import { UI_REQUEST } from '@onekeyfe/hd-core';
+import {
+ ensureOneKeyBLEReady,
+ fetchOneKeySignerData,
+ getOneKeyDeviceInfo,
+ onekeyUIEmitter,
+ ONEKEY_UI_EVENT,
+ searchOneKeyDevices,
+ verifyAddressOnOneKey,
+ type OneKeyUIEvent,
+} from 'src/services/onekeyBle';
+import {
+ getDeviceImage,
+ getDeviceDisplayName,
+ getDeviceTypeName,
+} from 'src/services/onekeyBle/deviceConstants';
+import { setupUSBSigner } from 'src/hardware/signerSetup';
+import { addSigningDevice, updateKeyDetails } from 'src/store/sagaActions/vaults';
+import { healthCheckStatusUpdate } from 'src/store/sagaActions/bhr';
+import { hcStatusType } from 'src/models/interfaces/HeathCheckTypes';
+import type { VaultSigner } from 'src/services/wallets/interfaces/vault';
+import { captureError } from 'src/services/sentry';
+import { LocalizationContext } from 'src/context/Localization/LocContext';
+import type { Signer } from 'src/services/wallets/interfaces/vault';
+import type { SearchDevice } from '@onekeyfe/hd-core';
+
+// ─── SDK UI event descriptions ──────────────────────────────────────────────
+
+const UI_PROMPTS: Record = {
+ [UI_REQUEST.REQUEST_PIN]: 'Please enter PIN on your OneKey device',
+ [UI_REQUEST.REQUEST_BUTTON]: 'Please confirm on your OneKey device',
+ [UI_REQUEST.REQUEST_PASSPHRASE]: 'Please enter passphrase on your OneKey device',
+ idle: '',
+};
+
+// ─── Types ──────────────────────────────────────────────────────────────────
+
+type ModalMode = 'setup' | 'health-check' | 'verify-address';
+type ModalPhase = 'scan' | 'connecting' | 'sdk-prompt' | 'done';
+
+const BLE_OPERATION_TIMEOUT_MS = 30_000;
+
+type Props = {
+ visible: boolean;
+ close: () => void;
+ mode: ModalMode;
+ signer?: Signer;
+ isMultisig?: boolean;
+ addSignerFlow?: boolean;
+ accountNumber?: number;
+ onSignerAdded?: (signer: Signer) => void;
+ // verify-address mode props
+ vaultKey?: VaultSigner;
+ vaultId?: string;
+ receiveAddressIndex?: number;
+ receivingAddress?: string;
+};
+
+// ─── Component ──────────────────────────────────────────────────────────────
+
+function OneKeyBleModal({
+ visible,
+ close,
+ mode,
+ signer,
+ isMultisig = true,
+ addSignerFlow = false,
+ accountNumber = 0,
+ onSignerAdded,
+ vaultKey,
+ vaultId,
+ receiveAddressIndex,
+ receivingAddress,
+}: Props) {
+ const { colorMode } = useColorMode();
+ const dispatch = useDispatch();
+ const { showToast } = useToastMessage();
+ const { translations } = useContext(LocalizationContext);
+ const { common } = translations;
+
+ const { bitcoinNetworkType } = useAppSelector((state) => state.settings);
+ const networkType =
+ bitcoinNetworkType === NetworkType.TESTNET ? NetworkType.TESTNET : NetworkType.MAINNET;
+
+ const [phase, setPhase] = useState('scan');
+ const [devices, setDevices] = useState([]);
+ const [scanning, setScanning] = useState(false);
+ const [statusMessage, setStatusMessage] = useState('');
+ const [sdkPrompt, setSdkPrompt] = useState('idle');
+
+ // Listen to SDK UI events
+ useEffect(() => {
+ const handler = (event: OneKeyUIEvent) => {
+ if (event === 'idle') {
+ setSdkPrompt('idle');
+ if (phase === 'sdk-prompt') setPhase('connecting');
+ } else {
+ setSdkPrompt(event);
+ setPhase('sdk-prompt');
+ }
+ };
+ const subscription = onekeyUIEmitter.addListener(ONEKEY_UI_EVENT, handler);
+ return () => { subscription.remove(); };
+ }, [phase]);
+
+ // Reset state when modal opens
+ useEffect(() => {
+ if (visible) {
+ setDevices([]);
+ setScanning(false);
+ setStatusMessage('');
+ setSdkPrompt('idle');
+
+ if (mode === 'health-check' || mode === 'verify-address') {
+ // Direct connect modes: skip scan
+ setPhase('connecting');
+ setTimeout(() => mode === 'verify-address' ? runVerifyAddress() : runHealthCheck(), 300);
+ } else {
+ // Setup: show scan UI
+ setPhase('scan');
+ setTimeout(() => scanDevices(), 300);
+ }
+ }
+ }, [visible]);
+
+ // ─── Scan ──────────────────────────────────────────────────────────────────
+
+ const scanDevices = async () => {
+ if (scanning) return;
+ try {
+ setScanning(true);
+ setDevices([]);
+ const bleReady = await ensureOneKeyBLEReady();
+ if (!bleReady.ready) {
+ showToast(
+ bleReady.reason === 'MISSING_PERMISSION'
+ ? 'Please grant Bluetooth permissions'
+ : 'Please turn on Bluetooth and try again',
+
+ );
+ return;
+ }
+ const found = await searchOneKeyDevices();
+ setDevices(found || []);
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ } finally {
+ setScanning(false);
+ }
+ };
+
+ // ─── Setup: tap device → connect → import keys ─────────────────────────────
+
+ const handleSetupTap = async (device: SearchDevice) => {
+ if (!device?.connectId) return;
+ try {
+ setPhase('connecting');
+ setStatusMessage('Connecting to device...');
+
+ const deviceInfo = await getOneKeyDeviceInfo(device.connectId);
+
+ // Clear any SDK prompt after device info is fetched
+ setPhase('connecting');
+ setStatusMessage('Importing keys...');
+ const signerData = await fetchOneKeySignerData({
+ connectId: device.connectId,
+ deviceId: deviceInfo.deviceId,
+ networkType,
+ accountNumber,
+ });
+
+ // Clear any SDK prompt after keys imported
+ setPhase('connecting');
+ setStatusMessage('Finalizing...');
+
+ const { signer: newSigner } = setupUSBSigner(SignerType.ONEKEY, signerData, isMultisig);
+
+ // Title: "OneKey Pro" / "OneKey Classic", Subtitle: BLE name (e.g. "Pro 04DD")
+ newSigner.signerName = getDeviceTypeName(device);
+ const bleName = device?.name;
+ if (bleName && bleName !== 'Unknown') {
+ newSigner.signerDescription = bleName;
+ }
+ newSigner.extraData = { ...newSigner.extraData, bleConnectId: deviceInfo.connectId };
+
+ dispatch(addSigningDevice([newSigner]));
+ setPhase('done');
+ showToast('OneKey added successfully', );
+ onSignerAdded?.(newSigner);
+ close();
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ setPhase('scan'); // Back to scan so user can retry
+ }
+ };
+
+ // ─── Health Check: direct connect via stored connectId ─────────────────────
+
+ const runHealthCheck = async () => {
+ if (!signer) return;
+ try {
+ setPhase('connecting');
+
+ const bleReady = await ensureOneKeyBLEReady();
+ if (!bleReady.ready) {
+ showToast('Please turn on Bluetooth and try again', );
+ close();
+ return;
+ }
+
+ const storedConnectId = signer?.extraData?.bleConnectId;
+ if (!storedConnectId) {
+ showToast('No stored connection info. Please re-add this device.', );
+ close();
+ return;
+ }
+
+ // BLE needs a brief scan to discover peripherals before connecting
+ setStatusMessage('Connecting to device...');
+ await searchOneKeyDevices();
+
+ setStatusMessage('Verifying device...');
+ const deviceInfo = await Promise.race([
+ getOneKeyDeviceInfo(storedConnectId),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Operation timed out. Device not found.')), BLE_OPERATION_TIMEOUT_MS)
+ ),
+ ]);
+
+ // Clear any SDK prompt after verification
+ setPhase('connecting');
+ setStatusMessage('Checking result...');
+
+ if (signer.masterFingerprint === deviceInfo.masterFingerprint) {
+ dispatch(
+ healthCheckStatusUpdate([
+ { signerId: signer.masterFingerprint, status: hcStatusType.HEALTH_CHECK_SUCCESSFULL },
+ ])
+ );
+ setPhase('done');
+ showToast('OneKey verification successful', );
+ close();
+ } else {
+ showToast('Fingerprint mismatch. Wrong device connected.', );
+ close();
+ }
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ close();
+ }
+ };
+
+ // ─── Verify Address: direct connect → show address on device ─────────────
+
+ const runVerifyAddress = async () => {
+ if (!signer || !vaultKey || !receivingAddress) return;
+ try {
+ setPhase('connecting');
+
+ const bleReady = await ensureOneKeyBLEReady();
+ if (!bleReady.ready) {
+ showToast('Please turn on Bluetooth and try again', );
+ close();
+ return;
+ }
+
+ const storedConnectId = signer?.extraData?.bleConnectId;
+ if (!storedConnectId) {
+ showToast('No stored connection info. Please re-add this device.', );
+ close();
+ return;
+ }
+
+ setStatusMessage('Connecting to device...');
+ await searchOneKeyDevices();
+
+ const withTimeout = (promise: Promise, ms = BLE_OPERATION_TIMEOUT_MS): Promise =>
+ Promise.race([
+ promise,
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Operation timed out. Device not found.')), ms)
+ ),
+ ]);
+
+ setStatusMessage('Reading device info...');
+ const deviceInfo = await withTimeout(getOneKeyDeviceInfo(storedConnectId));
+
+ setPhase('connecting');
+ setStatusMessage('Verifying address on device...');
+ const addressPath = `${vaultKey.derivationPath}/0/${receiveAddressIndex}`;
+ const deviceAddress = await withTimeout(verifyAddressOnOneKey({
+ connectId: storedConnectId,
+ deviceId: deviceInfo.deviceId,
+ path: addressPath,
+ networkType,
+ }));
+
+ setPhase('connecting');
+
+ if (deviceAddress === receivingAddress) {
+ dispatch(updateKeyDetails(vaultKey, 'registered', { registered: true, vaultId }));
+ dispatch(
+ healthCheckStatusUpdate([
+ { signerId: signer.masterFingerprint, status: hcStatusType.HEALTH_CHECK_VERIFICATION },
+ ])
+ );
+ showToast('Address verified successfully on OneKey', );
+ } else {
+ showToast('Address mismatch! The address on device does not match.', );
+ }
+ close();
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ close();
+ }
+ };
+
+ const handleDeviceTap = handleSetupTap;
+
+ // ─── Render helpers ────────────────────────────────────────────────────────
+
+ const renderDevice = ({ item: device }: { item: SearchDevice }) => {
+ const img = getDeviceImage(device?.deviceType);
+ return (
+ handleDeviceTap(device)}
+ activeOpacity={0.7}
+ >
+
+ {img ? (
+
+ ) : (
+
+ OK
+
+ )}
+
+
+ {getDeviceDisplayName(device)}
+
+
+ ›
+
+
+ );
+ };
+
+ const ModalContent = () => {
+ // SDK prompt phase — show device interaction prompt
+ if (phase === 'sdk-prompt' && sdkPrompt !== 'idle') {
+ return (
+
+
+
+ {UI_PROMPTS[sdkPrompt]}
+
+
+ );
+ }
+
+ // Connecting phase
+ if (phase === 'connecting') {
+ return (
+
+
+
+ {statusMessage || 'Connecting...'}
+
+
+ );
+ }
+
+ // Scan phase — show device list or loading
+ if (scanning && devices.length === 0) {
+ return (
+
+
+
+ Looking for devices...
+
+
+ );
+ }
+
+ if (!scanning && devices.length === 0) {
+ return (
+
+
+ No devices found. Make sure your OneKey is unlocked and nearby.
+
+
+
+ Rescan
+
+
+
+ );
+ }
+
+ // Devices found
+ return (
+
+
+
+ Select your device
+
+
+
+ Rescan
+
+
+
+ `${d?.uuid || ''}-${d?.connectId || ''}`}
+ contentContainerStyle={styles.listContent}
+ showsVerticalScrollIndicator={false}
+ style={styles.list}
+ />
+
+ );
+ };
+
+ if (!visible) return null;
+
+ const title =
+ mode === 'setup' ? 'Setting up OneKey'
+ : mode === 'verify-address' ? 'Verify Address'
+ : 'Verify OneKey';
+ const subTitle =
+ mode === 'setup' ? 'Connect OneKey hardware wallet via Bluetooth'
+ : mode === 'verify-address' ? 'Confirm the address matches on your OneKey device'
+ : 'Verify your OneKey device is accessible';
+
+ return (
+
+ );
+}
+
+const styles = StyleSheet.create({
+ centerContent: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: 40,
+ gap: 16,
+ },
+ promptText: {
+ fontSize: 16,
+ fontWeight: '600',
+ textAlign: 'center',
+ paddingHorizontal: 20,
+ },
+ statusText: {
+ fontSize: 14,
+ textAlign: 'center',
+ paddingHorizontal: 20,
+ },
+ rescanText: {
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ listHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ listHeaderText: {
+ fontSize: 13,
+ },
+ list: {
+ maxHeight: 300,
+ },
+ listContent: {
+ gap: 8,
+ },
+ deviceItem: {
+ borderWidth: 1,
+ borderRadius: 10,
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ },
+ deviceRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ deviceImage: {
+ width: 40,
+ height: 40,
+ borderRadius: 8,
+ marginRight: 12,
+ },
+ fallbackIcon: {
+ width: 40,
+ height: 40,
+ borderRadius: 8,
+ backgroundColor: '#44D62C',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginRight: 12,
+ },
+ fallbackText: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: '#000',
+ },
+ deviceName: {
+ fontSize: 15,
+ fontWeight: '500',
+ },
+ arrow: {
+ fontSize: 22,
+ fontWeight: '300',
+ marginLeft: 8,
+ },
+});
+
+export default OneKeyBleModal;
diff --git a/src/components/ThemedSvg.tsx/ThemedIcons.js b/src/components/ThemedSvg.tsx/ThemedIcons.js
index c5ac8bedeb..69a9c37b3c 100644
--- a/src/components/ThemedSvg.tsx/ThemedIcons.js
+++ b/src/components/ThemedSvg.tsx/ThemedIcons.js
@@ -150,6 +150,7 @@ import SatochipSetupSVG from 'src/assets/images/SatochipSetup.svg';
import PrivateSatochipIllustration from 'src/assets/privateImages/satochip-illustration.svg';
import PortalIllustration from 'src/assets/images/portal_illustration.svg';
import PrivatePortalIllustration from 'src/assets/privateImages/portal-illustration.svg';
+import OneKeyIllustration from 'src/assets/images/onekey_illustration.svg';
import WalletRecoveryIcon from 'src/assets/images/walletRecoveryIcon.svg';
import PrivateWalletRecovery from 'src/assets/privateImages/wallet-recovery-illlustration.svg';
import OrganizationIcon from 'src/assets/images/organizationIcon.svg';
@@ -767,6 +768,12 @@ const themeIcons = {
PRIVATE: PrivatePortalIllustration,
PRIVATE_LIGHT: PrivatePortalIllustration,
},
+ onekey_illustration: {
+ DARK: OneKeyIllustration,
+ LIGHT: OneKeyIllustration,
+ PRIVATE: OneKeyIllustration,
+ PRIVATE_LIGHT: OneKeyIllustration,
+ },
wallet_Recovery_icon: {
DARK: WalletRecoveryIcon,
diff --git a/src/constants/HardwareReferralLinks.ts b/src/constants/HardwareReferralLinks.ts
index d402333bee..32a2576b1e 100644
--- a/src/constants/HardwareReferralLinks.ts
+++ b/src/constants/HardwareReferralLinks.ts
@@ -39,6 +39,11 @@ export const sellers = [
link: 'https://affil.trezor.io/aff_c?offer_id=134&aff_id=35017',
id: '67befce7bb95d55d985d844a',
},
+ {
+ identifier: 'onekey',
+ link: 'https://onekey.so/zh_CN/products/onekey-pro/',
+ id: 'onekey_pro',
+ },
];
export const resellers = [
{
diff --git a/src/hardware/index.ts b/src/hardware/index.ts
index d1a5e65662..31cad2a02a 100644
--- a/src/hardware/index.ts
+++ b/src/hardware/index.ts
@@ -183,6 +183,9 @@ export const getSignerNameFromType = (type: SignerType, isMock = false, isAmf =
case SignerType.LEDGER:
name = 'Ledger';
break;
+ case SignerType.ONEKEY:
+ name = 'OneKey';
+ break;
case SignerType.MOBILE_KEY:
name = 'Recovery Key';
break;
@@ -476,6 +479,9 @@ export const getSDMessage = ({ type }: { type: SignerType }) => {
case SignerType.TREZOR: {
return 'Trusted signers from SatoshiLabs';
}
+ case SignerType.ONEKEY: {
+ return 'OneKey hardware wallet over Bluetooth';
+ }
case SignerType.OTHER_SD: {
return 'Varies with different signer';
}
diff --git a/src/navigation/Navigator.tsx b/src/navigation/Navigator.tsx
index e88bcaa427..1888935421 100644
--- a/src/navigation/Navigator.tsx
+++ b/src/navigation/Navigator.tsx
@@ -47,8 +47,10 @@ import WalletSettings from 'src/screens/WalletDetails/WalletSettings';
import Colors from 'src/theme/Colors';
import NodeSettings from 'src/screens/AppSettings/Node/NodeSettings';
import ConnectChannel from 'src/screens/Channel/ConnectChannel';
+import SignMessageOneKeyBle from 'src/screens/OneKey/SignMessageOneKeyBle';
import RegisterWithChannel from 'src/screens/QRScreens/RegisterWithChannel';
import SignWithChannel from 'src/screens/QRScreens/SignWithChannel';
+import SignWithOneKeyBle from 'src/screens/SignTransaction/SignWithOneKeyBle';
import UTXOLabeling from 'src/screens/UTXOManagement/UTXOLabeling';
import UTXOManagement from 'src/screens/UTXOManagement/UTXOManagement';
import ImportWalletDetailsScreen from 'src/screens/ImportWalletDetailsScreen/ImportWalletDetailsScreen';
@@ -207,6 +209,7 @@ function LoginStack() {
{/* Channel Based SDs */}
+
{/* Mobile Key, Seed Key */}
@@ -318,9 +321,11 @@ function AppStack() {
+
+
diff --git a/src/navigation/types.ts b/src/navigation/types.ts
index 021dd59a55..f14743a9bf 100644
--- a/src/navigation/types.ts
+++ b/src/navigation/types.ts
@@ -95,9 +95,11 @@ export type AppStackParams = {
ScanNode: undefined;
PrivacyAndDisplay: undefined;
ConnectChannel: undefined;
+ SignMessageOneKeyBle: undefined;
RegisterWithChannel: undefined;
SetupOtherSDScreen: undefined;
SignWithChannel: undefined;
+ SignWithOneKeyBle: undefined;
CosignerDetails: { signer: Signer };
AdditionalDetails: { signer: Signer };
KeyHistory: undefined;
diff --git a/src/screens/Hardware/components/DeviceCard.tsx b/src/screens/Hardware/components/DeviceCard.tsx
index 4017c6c0d4..2ca15edc92 100644
--- a/src/screens/Hardware/components/DeviceCard.tsx
+++ b/src/screens/Hardware/components/DeviceCard.tsx
@@ -146,7 +146,9 @@ const styles = StyleSheet.create({
flagContainer: {
flexDirection: 'row',
alignItems: 'center',
- gap: wp(8),
+ gap: wp(4),
+ flexWrap: 'wrap',
+ flex: 1,
},
subText: {
marginTop: hp(10),
diff --git a/src/screens/Hardware/components/HardwareDevices.tsx b/src/screens/Hardware/components/HardwareDevices.tsx
index f5eb453382..e18d46cc00 100644
--- a/src/screens/Hardware/components/HardwareDevices.tsx
+++ b/src/screens/Hardware/components/HardwareDevices.tsx
@@ -8,6 +8,13 @@ import ColdCard from 'src/assets/images/coinkite-image.svg';
import Passport from 'src/assets/images/foundation-passport-icon.svg';
import Legder from 'src/assets/images/Ledger-icon.svg';
import Trezor from 'src/assets/images/TREZOR-icon.svg';
+import { Image, ImageStyle } from 'react-native';
+
+const OnekeyDevice = ({ width, height }: { width: number; height: number }) => (
+
+);
+import FlagHongKong from 'src/assets/images/flag-hongkong.svg';
+import FlagJapan from 'src/assets/images/flag-japan.svg';
import FlagCanada from 'src/assets/images/flag-canada.svg';
import FlagUSA from 'src/assets/images/flag-usa.svg';
import FlagSwizerland from 'src/assets/images/flag-swizerland.svg';
@@ -83,6 +90,16 @@ const HardwareDevices = ({ sellers }) => {
subscribeText: '',
unSubscribeText: '',
},
+ {
+ id: 6,
+ title: 'OneKey',
+ image: ,
+ flagIcon: <>>,
+ country: 'HK & Japan',
+ link: getSellerLink('onekey'),
+ subscribeText: '',
+ unSubscribeText: '',
+ },
];
return (
diff --git a/src/screens/Home/components/Keys/SignerContent.tsx b/src/screens/Home/components/Keys/SignerContent.tsx
index 12f4737228..41db531b4e 100644
--- a/src/screens/Home/components/Keys/SignerContent.tsx
+++ b/src/screens/Home/components/Keys/SignerContent.tsx
@@ -44,6 +44,7 @@ const SignerContent = ({ navigation, handleModalClose }) => {
background: 'headerWhite',
isTrue: false,
},
+ { type: SignerType.ONEKEY, background: 'pantoneGreen', isTrue: true },
];
const hardwareSnippet = hardwareSigners.map(({ type, background, isTrue }) => ({
diff --git a/src/screens/OneKey/SignMessageOneKeyBle.tsx b/src/screens/OneKey/SignMessageOneKeyBle.tsx
new file mode 100644
index 0000000000..37b4cece58
--- /dev/null
+++ b/src/screens/OneKey/SignMessageOneKeyBle.tsx
@@ -0,0 +1,158 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { ActivityIndicator, StyleSheet } from 'react-native';
+import { Box, useColorMode } from 'native-base';
+import { CommonActions, useNavigation, useRoute } from '@react-navigation/native';
+import ScreenWrapper from 'src/components/ScreenWrapper';
+import WalletHeader from 'src/components/WalletHeader';
+import Text from 'src/components/KeeperText';
+import useToastMessage from 'src/hooks/useToastMessage';
+import ToastErrorIcon from 'src/assets/images/toast_error.svg';
+import TickIcon from 'src/assets/images/icon_tick.svg';
+import { useAppSelector } from 'src/store/hooks';
+import { NetworkType } from 'src/services/wallets/enums';
+import {
+ ensureOneKeyBLEReady,
+ getOneKeyDeviceInfo,
+ searchOneKeyDevices,
+ signMessageWithOneKey,
+ onekeyUIEmitter,
+ ONEKEY_UI_EVENT,
+ type OneKeyUIEvent,
+} from 'src/services/onekeyBle';
+import { UI_REQUEST } from '@onekeyfe/hd-core';
+import { captureError } from 'src/services/sentry';
+import { LocalizationContext } from 'src/context/Localization/LocContext';
+import type { Signer } from 'src/services/wallets/interfaces/vault';
+
+const UI_PROMPTS: Record = {
+ [UI_REQUEST.REQUEST_PIN]: 'Please enter PIN on your OneKey device',
+ [UI_REQUEST.REQUEST_BUTTON]: 'Please confirm on your OneKey device',
+ [UI_REQUEST.REQUEST_PASSPHRASE]: 'Please enter passphrase on your OneKey device',
+};
+
+const BLE_TIMEOUT_MS = 30_000;
+
+type Params = {
+ message: string;
+ address: string;
+ derivationPath: string;
+ signer: Signer;
+ onSignatureReceived: (signature: string, address: string) => void;
+};
+
+function SignMessageOneKeyBle() {
+ const { colorMode } = useColorMode();
+ const { params } = useRoute();
+ const navigation = useNavigation();
+ const { showToast } = useToastMessage();
+ const { translations } = useContext(LocalizationContext);
+ const { common } = translations;
+
+ const { message, address, derivationPath, signer, onSignatureReceived } = params as Params;
+
+ const { bitcoinNetworkType } = useAppSelector((state) => state.settings);
+ const networkType =
+ bitcoinNetworkType === NetworkType.TESTNET ? NetworkType.TESTNET : NetworkType.MAINNET;
+
+ const [statusMessage, setStatusMessage] = useState('Preparing...');
+ const [sdkPrompt, setSdkPrompt] = useState('');
+
+ // Listen to SDK UI events
+ useEffect(() => {
+ const sub = onekeyUIEmitter.addListener(ONEKEY_UI_EVENT, (event: OneKeyUIEvent) => {
+ if (UI_PROMPTS[event]) {
+ setSdkPrompt(UI_PROMPTS[event]);
+ }
+ });
+ return () => sub.remove();
+ }, []);
+
+ // Auto-run on mount
+ useEffect(() => {
+ const timer = setTimeout(() => runSignMessage(), 300);
+ return () => clearTimeout(timer);
+ }, []);
+
+ const withTimeout = (promise: Promise): Promise =>
+ Promise.race([
+ promise,
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Operation timed out. Device not found.')), BLE_TIMEOUT_MS)
+ ),
+ ]);
+
+ const runSignMessage = async () => {
+ try {
+ const bleReady = await ensureOneKeyBLEReady();
+ if (!bleReady.ready) {
+ showToast('Please turn on Bluetooth and try again', );
+ navigation.dispatch(CommonActions.goBack());
+ return;
+ }
+
+ const storedConnectId = signer?.extraData?.bleConnectId;
+ if (!storedConnectId) {
+ showToast('No stored connection info. Please re-add this device.', );
+ navigation.dispatch(CommonActions.goBack());
+ return;
+ }
+
+ // BLE needs a brief scan to discover peripherals
+ setStatusMessage('Connecting to device...');
+ await searchOneKeyDevices();
+
+ setStatusMessage('Reading device info...');
+ setSdkPrompt('');
+ const deviceInfo = await withTimeout(getOneKeyDeviceInfo(storedConnectId));
+
+ setStatusMessage('Signing message...');
+ setSdkPrompt('');
+ const result = await withTimeout(signMessageWithOneKey({
+ connectId: storedConnectId,
+ deviceId: deviceInfo.deviceId,
+ path: derivationPath,
+ message,
+ networkType,
+ }));
+
+ setSdkPrompt('');
+ showToast('Message signed successfully', );
+ onSignatureReceived?.(result.signature, result.address);
+ navigation.dispatch(CommonActions.goBack());
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ navigation.dispatch(CommonActions.goBack());
+ }
+ };
+
+ const displayText = sdkPrompt || statusMessage;
+
+ return (
+
+
+
+
+
+ {displayText}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 16,
+ paddingHorizontal: 20,
+ },
+ statusText: {
+ fontSize: 15,
+ textAlign: 'center',
+ },
+});
+
+export default SignMessageOneKeyBle;
diff --git a/src/screens/Recieve/ReceiveScreen.tsx b/src/screens/Recieve/ReceiveScreen.tsx
index 55580525ad..4aad421dc8 100644
--- a/src/screens/Recieve/ReceiveScreen.tsx
+++ b/src/screens/Recieve/ReceiveScreen.tsx
@@ -45,6 +45,7 @@ import useExchangeRates from 'src/hooks/useExchangeRates';
import useCurrencyCode from 'src/store/hooks/state-selectors/useCurrencyCode';
import { SATOSHIS_IN_BTC } from 'src/constants/Bitcoin';
import { InteracationMode } from '../Vault/HardwareModalMap';
+import OneKeyBleModal from 'src/components/OneKeyBleModal';
import { Vault } from 'src/services/wallets/interfaces/vault';
import KeyPadView from 'src/components/AppNumPad/KeyPadView';
import AmountDetailsInput from '../Send/AmountDetailsInput';
@@ -58,6 +59,7 @@ const AddressVerifiableSigners = [
SignerType.COLDCARD,
SignerType.JADE,
SignerType.PORTAL,
+ SignerType.ONEKEY,
];
const SignerTypesNeedingRegistration = [
@@ -81,6 +83,7 @@ function ReceiveScreen({ route }: { route }) {
// const amount = route?.params?.amount;
const [receivingAddress, setReceivingAddress] = useState(null);
const [paymentURI, setPaymentURI] = useState(null);
+ const [onekeyVerifyState, setOnekeyVerifyState] = useState({ visible: false });
const { translations } = useContext(LocalizationContext);
const { common, home, wallet: walletTranslation, vault: vaultTranslations } = translations;
@@ -281,6 +284,18 @@ function ReceiveScreen({ route }: { route }) {
receiveAddressIndex: currentAddressIdx - 1,
})
);
+ } else if (signer.type === SignerType.ONEKEY) {
+ const vKey = vaultSigners?.find(
+ (vs) => vs.signer?.masterFingerprint === signer.masterFingerprint
+ )?.vaultSigner;
+ setOnekeyVerifyState({
+ visible: true,
+ signer,
+ vaultKey: vKey,
+ vaultId: wallet.id,
+ receiveAddressIndex: currentAddressIdx - 1,
+ receivingAddress,
+ });
} else {
navigation.dispatch(
CommonActions.navigate('ConnectChannel', {
@@ -348,6 +363,7 @@ function ReceiveScreen({ route }: { route }) {
};
return (
+ <>
)}
+ setOnekeyVerifyState({ visible: false })}
+ mode="verify-address"
+ signer={onekeyVerifyState.signer}
+ vaultKey={onekeyVerifyState.vaultKey}
+ vaultId={onekeyVerifyState.vaultId}
+ receiveAddressIndex={onekeyVerifyState.receiveAddressIndex}
+ receivingAddress={onekeyVerifyState.receivingAddress}
+ />
+ >
);
}
diff --git a/src/screens/SignTransaction/SignTransactionScreen.tsx b/src/screens/SignTransaction/SignTransactionScreen.tsx
index 082f7110c0..ab6c9c2ec0 100644
--- a/src/screens/SignTransaction/SignTransactionScreen.tsx
+++ b/src/screens/SignTransaction/SignTransactionScreen.tsx
@@ -144,6 +144,7 @@ function SignTransactionScreen() {
const [trezorModal, setTrezorModal] = useState(false);
const [bitbox02Modal, setBitbox02Modal] = useState(false);
const [otherSDModal, setOtherSDModal] = useState(false);
+ const [oneKeyModal, setOneKeyModal] = useState(false);
const [otpModal, showOTPModal] = useState(false);
const [passwordModal, setPasswordModal] = useState(false);
const [confirmPassVisible, setConfirmPassVisible] = useState(false);
@@ -673,6 +674,9 @@ function SignTransactionScreen() {
case SignerType.KRUX:
setKruxModal(true);
break;
+ case SignerType.ONEKEY:
+ setOneKeyModal(true);
+ break;
default:
showToast(`action not set for ${signer.type}`);
break;
@@ -837,10 +841,12 @@ function SignTransactionScreen() {
trezorModal={trezorModal}
bitbox02Modal={bitbox02Modal}
otherSDModal={otherSDModal}
+ oneKeyModal={oneKeyModal}
specterModal={specterModal}
kruxModal={kruxModal}
setSpecterModal={setSpecterModal}
setOtherSDModal={setOtherSDModal}
+ setOneKeyModal={setOneKeyModal}
setTrezorModal={setTrezorModal}
setBitbox02Modal={setBitbox02Modal}
setJadeModal={setJadeModal}
diff --git a/src/screens/SignTransaction/SignWithOneKeyBle.tsx b/src/screens/SignTransaction/SignWithOneKeyBle.tsx
new file mode 100644
index 0000000000..b6d1088105
--- /dev/null
+++ b/src/screens/SignTransaction/SignWithOneKeyBle.tsx
@@ -0,0 +1,195 @@
+import React, { useContext, useEffect, useMemo, useState } from 'react';
+import { ActivityIndicator, StyleSheet } from 'react-native';
+import { Box, useColorMode } from 'native-base';
+import { CommonActions, useNavigation, useRoute } from '@react-navigation/native';
+import ScreenWrapper from 'src/components/ScreenWrapper';
+import WalletHeader from 'src/components/WalletHeader';
+import Text from 'src/components/KeeperText';
+import { VaultSigner } from 'src/services/wallets/interfaces/vault';
+import { useDispatch } from 'react-redux';
+import { useAppSelector } from 'src/store/hooks';
+import { SerializedPSBTEnvelop } from 'src/services/wallets/interfaces';
+import { updatePSBTEnvelops } from 'src/store/reducers/send_and_receive';
+import { captureError } from 'src/services/sentry';
+import { LocalizationContext } from 'src/context/Localization/LocContext';
+import useToastMessage from 'src/hooks/useToastMessage';
+import ToastErrorIcon from 'src/assets/images/toast_error.svg';
+import {
+ ensureOneKeyBLEReady,
+ getOneKeyDeviceInfo,
+ searchOneKeyDevices,
+ signPsbtWithOneKey,
+ onekeyUIEmitter,
+ ONEKEY_UI_EVENT,
+ type OneKeyUIEvent,
+} from 'src/services/onekeyBle';
+import { UI_REQUEST } from '@onekeyfe/hd-core';
+import useSignerFromKey from 'src/hooks/useSignerFromKey';
+import { healthCheckStatusUpdate } from 'src/store/sagaActions/bhr';
+import { hcStatusType } from 'src/models/interfaces/HeathCheckTypes';
+import { validatePSBT } from 'src/utils/utilities';
+import { NetworkType } from 'src/services/wallets/enums';
+
+const UI_PROMPTS: Record = {
+ [UI_REQUEST.REQUEST_PIN]: 'Please enter PIN on your OneKey device',
+ [UI_REQUEST.REQUEST_BUTTON]: 'Please confirm on your OneKey device',
+ [UI_REQUEST.REQUEST_PASSPHRASE]: 'Please enter passphrase on your OneKey device',
+};
+
+const BLE_TIMEOUT_MS = 30_000;
+
+type SignWithOneKeyBleParams = {
+ vaultKey: VaultSigner;
+ isRemoteKey?: boolean;
+ serializedPSBTEnvelopFromProps?: SerializedPSBTEnvelop;
+ signTransaction: (args: { signedSerializedPSBT: string }) => void;
+};
+
+function SignWithOneKeyBle() {
+ const { colorMode } = useColorMode();
+ const { params } = useRoute();
+ const navigation = useNavigation();
+ const dispatch = useDispatch();
+ const { showToast } = useToastMessage();
+ const { translations } = useContext(LocalizationContext);
+ const { common, error: errorText, signer: signerText } = translations;
+
+ const {
+ vaultKey,
+ isRemoteKey = false,
+ serializedPSBTEnvelopFromProps,
+ signTransaction,
+ } = params as SignWithOneKeyBleParams;
+
+ const { signer } = useSignerFromKey(vaultKey);
+ const { bitcoinNetworkType } = useAppSelector((state) => state.settings);
+ const serializedPSBTEnvelops: SerializedPSBTEnvelop[] = useAppSelector(
+ (state) => state.sendAndReceive.sendPhaseTwo.serializedPSBTEnvelops
+ );
+
+ const serializedPSBTEnvelop = useMemo(() => {
+ if (isRemoteKey) return serializedPSBTEnvelopFromProps;
+ return serializedPSBTEnvelops?.find((envelop) => envelop.xfp === vaultKey.xfp);
+ }, [isRemoteKey, serializedPSBTEnvelopFromProps, serializedPSBTEnvelops, vaultKey.xfp]);
+
+ const networkType =
+ bitcoinNetworkType === NetworkType.TESTNET ? NetworkType.TESTNET : NetworkType.MAINNET;
+
+ const [statusMessage, setStatusMessage] = useState('Preparing...');
+ const [sdkPrompt, setSdkPrompt] = useState('');
+
+ // Listen to SDK UI events
+ useEffect(() => {
+ const sub = onekeyUIEmitter.addListener(ONEKEY_UI_EVENT, (event: OneKeyUIEvent) => {
+ if (UI_PROMPTS[event]) {
+ setSdkPrompt(UI_PROMPTS[event]);
+ }
+ });
+ return () => sub.remove();
+ }, []);
+
+ // Auto-run on mount
+ useEffect(() => {
+ const timer = setTimeout(() => runAutoSign(), 300);
+ return () => clearTimeout(timer);
+ }, []);
+
+ const withTimeout = (promise: Promise): Promise =>
+ Promise.race([
+ promise,
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Operation timed out. Device not found.')), BLE_TIMEOUT_MS)
+ ),
+ ]);
+
+ const runAutoSign = async () => {
+ if (!serializedPSBTEnvelop?.serializedPSBT) {
+ showToast('No PSBT found to sign', );
+ return;
+ }
+
+ try {
+ setStatusMessage('Checking Bluetooth...');
+ const bleReady = await ensureOneKeyBLEReady();
+ if (!bleReady.ready) {
+ showToast('Please turn on Bluetooth and try again', );
+ return;
+ }
+
+ // Use stored connectId for direct connection
+ const storedConnectId = signer?.extraData?.bleConnectId;
+ if (!storedConnectId) {
+ showToast('No stored connection info. Please re-add this device.', );
+ return;
+ }
+
+ // BLE needs a brief scan to discover peripherals
+ setStatusMessage('Connecting to device...');
+ await searchOneKeyDevices();
+
+ setSdkPrompt('');
+ setStatusMessage('Reading device info...');
+ const deviceInfo = await withTimeout(getOneKeyDeviceInfo(storedConnectId));
+
+ setSdkPrompt('');
+ setStatusMessage('Signing transaction on device...');
+ const signedSerializedPSBT = await withTimeout(signPsbtWithOneKey({
+ connectId: storedConnectId,
+ deviceId: deviceInfo.deviceId,
+ networkType,
+ serializedPSBT: serializedPSBTEnvelop.serializedPSBT,
+ }));
+
+ setSdkPrompt('');
+ validatePSBT(serializedPSBTEnvelop.serializedPSBT, signedSerializedPSBT, signer, errorText);
+
+ dispatch(
+ healthCheckStatusUpdate([
+ { signerId: signer.masterFingerprint, status: hcStatusType.HEALTH_CHECK_SIGNING },
+ ])
+ );
+
+ if (isRemoteKey) {
+ signTransaction({ signedSerializedPSBT });
+ navigation.dispatch(CommonActions.goBack());
+ return;
+ }
+
+ dispatch(updatePSBTEnvelops({ signedSerializedPSBT, xfp: vaultKey.xfp }));
+ navigation.dispatch(CommonActions.navigate({ name: 'SignTransactionScreen', merge: true }));
+ } catch (error) {
+ captureError(error);
+ showToast(error?.message || common.somethingWrong, );
+ }
+ };
+
+ const displayText = sdkPrompt || statusMessage;
+
+ return (
+
+
+
+
+
+ {displayText}
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 16,
+ paddingHorizontal: 20,
+ },
+ statusText: {
+ fontSize: 15,
+ textAlign: 'center',
+ },
+});
+
+export default SignWithOneKeyBle;
diff --git a/src/screens/SignTransaction/SignerModals.tsx b/src/screens/SignTransaction/SignerModals.tsx
index 680ea5c175..762dc8c7e1 100644
--- a/src/screens/SignTransaction/SignerModals.tsx
+++ b/src/screens/SignTransaction/SignerModals.tsx
@@ -593,6 +593,22 @@ const getSupportedSigningOptions = (signerType: SignerType, colorMode) => {
},
],
};
+ case SignerType.ONEKEY:
+ return {
+ supportedSigningOptions: [
+ {
+ title: 'Bluetooth',
+ icon: (
+ }
+ backgroundColor={`${colorMode}.pantoneGreen`}
+ width={35}
+ />
+ ),
+ name: SigningMode.USB,
+ },
+ ],
+ };
default:
return {
supportedSigningOptions: [],
@@ -618,8 +634,10 @@ function SignerModals({
bitbox02Modal,
portalModal,
otherSDModal,
+ oneKeyModal,
kruxModal,
setOtherSDModal,
+ setOneKeyModal,
setTrezorModal,
setBitbox02Modal,
setJadeModal,
@@ -665,8 +683,10 @@ function SignerModals({
bitbox02Modal: boolean;
portalModal: boolean;
otherSDModal: boolean;
+ oneKeyModal: boolean;
kruxModal: boolean;
setOtherSDModal: any;
+ setOneKeyModal: any;
setTrezorModal: any;
setBitbox02Modal: any;
setJadeModal: any;
@@ -759,6 +779,18 @@ function SignerModals({
})
);
};
+
+ const navigateToOneKeySigning = (vaultKey: VaultSigner) => {
+ setOneKeyModal(false);
+ navigation.dispatch(
+ CommonActions.navigate('SignWithOneKeyBle', {
+ signTransaction,
+ vaultKey,
+ isRemoteKey,
+ serializedPSBTEnvelopFromProps,
+ })
+ );
+ };
const [registeredSigner, setRegisteredSigner] = useState(null);
const [registeredVaultKey, setRegisteredVaultKey] = useState(null);
const [registerActiveVault, setRegisterActiveVault] = useState(null);
@@ -1664,6 +1696,23 @@ function SignerModals({
>
);
}
+ if (signer.type === SignerType.ONEKEY) {
+ return (
+ setOneKeyModal(false)}
+ title="Connect OneKey"
+ subTitle="Connect your OneKey via Bluetooth to sign the transaction"
+ modalBackground={`${colorMode}.modalWhiteBackground`}
+ textColor={`${colorMode}.textGreen`}
+ subTitleColor={`${colorMode}.modalSubtitleBlack`}
+ buttonText={common.proceed}
+ buttonCallback={() => navigateToOneKeySigning(vaultKey)}
+ Content={() => null}
+ />
+ );
+ }
return null;
})}
diff --git a/src/screens/Vault/HardwareModalMap.tsx b/src/screens/Vault/HardwareModalMap.tsx
index fd92704fee..9f6aec8525 100644
--- a/src/screens/Vault/HardwareModalMap.tsx
+++ b/src/screens/Vault/HardwareModalMap.tsx
@@ -29,6 +29,7 @@ import CVVInputsView from 'src/components/HealthCheck/CVVInputsView';
import DeleteIcon from 'src/assets/images/deleteBlack.svg';
import RecoverImage from 'src/assets/images/recover_white.svg';
import KeeperModal from 'src/components/KeeperModal';
+import OneKeyBleModal from 'src/components/OneKeyBleModal';
import KeyPadView from 'src/components/AppNumPad/KeyPadView';
import { LocalizationContext } from 'src/context/Localization/LocContext';
import { Signer, VaultSigner } from 'src/services/wallets/interfaces/vault';
@@ -542,6 +543,18 @@ const getSignerContent = (
subTitle: ledger.SetupDescription,
options: [],
};
+ case SignerType.ONEKEY:
+ return {
+ type: SignerType.ONEKEY,
+ Illustration: ,
+ Instructions: [
+ 'Unlock your OneKey device and enable Bluetooth.',
+ 'Keep the device nearby and tap Proceed to connect.',
+ ],
+ title: isHealthcheck ? `${common.verify} OneKey` : `${signerText.settingUp} OneKey`,
+ subTitle: 'Connect OneKey hardware wallet via Bluetooth',
+ options: [],
+ };
case SignerType.SEED_WORDS:
return {
type: SignerType.SEED_WORDS,
@@ -1052,6 +1065,7 @@ function HardwareModalMap({
const data = useQuery(RealmSchema.BackupHistory);
const [backupModalVisible, setBackupModalVisible] = useState(false);
const [openSetup, setOpenSetup] = useState(false);
+ const [onekeyBleModalVisible, setOnekeyBleModalVisible] = useState(false);
const getNfcSupport = async () => {
const isSupported = await NFC.isNFCSupported();
@@ -1333,6 +1347,7 @@ function HardwareModalMap({
);
};
+
const importSeedWordsBasedKey = (mnemonic, remember = false) => {
try {
const { signer, key } = setupSeedWordsBasedKey(mnemonic, isMultisig, remember);
@@ -2021,7 +2036,8 @@ function HardwareModalMap({
signerType === SignerType.PASSPORT ||
signerType === SignerType.SEED_WORDS ||
signerType === SignerType.KEEPER ||
- signerType === SignerType.KRUX
+ signerType === SignerType.KRUX ||
+ signerType === SignerType.ONEKEY
) {
return (
@@ -2165,6 +2181,10 @@ function HardwareModalMap({
return navigateToSetupWithOtherSD();
case SignerType.PORTAL:
return navigateToPortalSetup();
+ case SignerType.ONEKEY:
+ close();
+ setOnekeyBleModalVisible(true);
+ return;
default:
return null;
}
@@ -2447,6 +2467,22 @@ function HardwareModalMap({
/>
{inProgress && }
+ setOnekeyBleModalVisible(false)}
+ mode={isHealthcheck ? 'health-check' : 'setup'}
+ signer={signer}
+ isMultisig={isMultisig}
+ addSignerFlow={addSignerFlow}
+ accountNumber={accountNumber}
+ onSignerAdded={(addedSigner) => {
+ setOnekeyBleModalVisible(false);
+ const navigationState = addSignerFlow
+ ? { name: 'Home', params: { selectedOption: 'Keys', addedSigner } }
+ : { name: 'AddSigningDevice', merge: true, params: { addedSigner } };
+ navigation.dispatch(CommonActions.navigate(navigationState));
+ }}
+ />
>
);
}
diff --git a/src/screens/Vault/SignerCategoryList.tsx b/src/screens/Vault/SignerCategoryList.tsx
index 768ab5688d..7e1edef9ac 100644
--- a/src/screens/Vault/SignerCategoryList.tsx
+++ b/src/screens/Vault/SignerCategoryList.tsx
@@ -61,6 +61,7 @@ function SignerCategoryList() {
{ type: SignerType.SPECTER, background: 'pantoneGreen', isTrue: false },
{ type: SignerType.KEYSTONE, background: 'brownBackground', isTrue: false },
{ type: SignerType.LEDGER, background: 'headerWhite', isTrue: false },
+ { type: SignerType.ONEKEY, background: 'headerWhite', isTrue: false },
{ type: SignerType.PORTAL, background: 'pantoneGreen', isTrue: false },
{ type: SignerType.TREZOR, background: 'brownBackground', isTrue: false },
{ type: SignerType.BITBOX02, background: 'headerWhite', isTrue: false },
diff --git a/src/screens/Vault/SigningDeviceIcons.tsx b/src/screens/Vault/SigningDeviceIcons.tsx
index 8d4908c215..c750b92660 100644
--- a/src/screens/Vault/SigningDeviceIcons.tsx
+++ b/src/screens/Vault/SigningDeviceIcons.tsx
@@ -1,6 +1,10 @@
import React from 'react';
import { SignerStorage, SignerType } from 'src/services/wallets/enums';
+import ONEKEYICON from 'src/assets/images/onekey_icon.svg';
+import ONEKEYICONLIGHT from 'src/assets/images/onekey_icon_light.svg';
+import ONEKEYLOGO from 'src/assets/images/onekey_logo.svg';
+import ONEKEYLOGOWHITE from 'src/assets/images/onekey_logo_white.svg';
import COLDCARDICON from 'src/assets/images/coldcard_icon.svg';
import COLDCARDICONLIGHT from 'src/assets/images/coldcard_light.svg';
import COLDCARDLOGO from 'src/assets/images/coldcard_logo.svg';
@@ -84,6 +88,8 @@ import BITBOXGREENLIGHT from 'src/assets/images/bitbox-green-light.svg';
import BITBOXGREENDARK from 'src/assets/images/bitbox-green-dark.svg';
import TREZORGREENLIGHT from 'src/assets/images/trezor-green-light.svg';
import TREZORGREENDARK from 'src/assets/images/trezor-green-dark.svg';
+import ONEKEYGREENLIGHT from 'src/assets/images/onekey-green-light.svg';
+import ONEKEYGREENDARK from 'src/assets/images/onekey-green-dark.svg';
import PortalLogo from 'src/assets/images/portalLogo.svg';
import PortalLogoLight from 'src/assets/images/PortalLogoLight.svg';
import PortalIcon from 'src/assets/images/portalIcon.svg';
@@ -156,6 +162,12 @@ export const SDIcons = ({ type, light = true, width = 20, height = 20 }: SDIconO
Logo: colorMode === 'dark' ? : ,
type: SignerStorage.COLD,
};
+ case SignerType.ONEKEY:
+ return {
+ Icon: getColouredIcon(, , light, width, height),
+ Logo: colorMode === 'dark' ? : ,
+ type: SignerStorage.COLD,
+ };
case SignerType.MOBILE_KEY:
return {
Icon: getColouredIcon(, , light, width, height),
@@ -312,6 +324,11 @@ export const SDColoredIcons = (type: SignerType, light = true, width = 20, heigh
Icon: getColouredIcon(, , light, width, height),
type: SignerStorage.COLD,
};
+ case SignerType.ONEKEY:
+ return {
+ Icon: getColouredIcon(, , light, width, height),
+ type: SignerStorage.COLD,
+ };
case SignerType.MOBILE_KEY:
return {
Icon: getColouredIcon(
diff --git a/src/screens/Vault/SigningDeviceList.tsx b/src/screens/Vault/SigningDeviceList.tsx
index 2cbdc1f0fd..678125962d 100644
--- a/src/screens/Vault/SigningDeviceList.tsx
+++ b/src/screens/Vault/SigningDeviceList.tsx
@@ -89,6 +89,7 @@ const SigningDeviceList = () => {
SignerType.KEYSTONE,
SignerType.KRUX,
SignerType.LEDGER,
+ SignerType.ONEKEY,
SignerType.PASSPORT,
SignerType.PORTAL,
SignerType.SATOCHIP,
diff --git a/src/screens/WalletDetails/SignMessageScreen.tsx b/src/screens/WalletDetails/SignMessageScreen.tsx
index c3838b8e08..e115b282e2 100644
--- a/src/screens/WalletDetails/SignMessageScreen.tsx
+++ b/src/screens/WalletDetails/SignMessageScreen.tsx
@@ -18,10 +18,12 @@ import useWallets from 'src/hooks/useWallets';
import { useDispatch } from 'react-redux';
import { refreshWallets } from 'src/store/sagaActions/wallets';
import useVault from 'src/hooks/useVault';
-import { EntityKind, KeyGenerationMode } from 'src/services/wallets/enums';
+import useSigners from 'src/hooks/useSigners';
+import { EntityKind, KeyGenerationMode, SignerType } from 'src/services/wallets/enums';
import ShowXPub from 'src/components/XPub/ShowXPub';
-import { CommonActions } from '@react-navigation/native';
+import { CommonActions, useNavigation } from '@react-navigation/native';
import { InteracationMode } from '../Vault/HardwareModalMap';
+import BLEIcon from 'src/assets/images/usb_white.svg';
import CircleIconWrapper from 'src/components/CircleIconWrapper';
import QRComms from 'src/assets/images/qr_comms.svg';
import ImportIcon from 'src/assets/images/import.svg';
@@ -38,7 +40,8 @@ export const SignMessageScreen = ({ route, navigation }) => {
const { walletId = null, vaultId = null, type } = route.params;
const wallet = useWallets({ walletIds: [walletId] }).wallets[0];
const { activeVault } = useVault({ vaultId: vaultId ?? '' });
- const { xpriv, addresses } = wallet.specs;
+ const { vaultSigners } = useSigners(vaultId ?? '');
+ const { xpriv, addresses } = wallet?.specs ?? {};
const receiveAddressCache = addresses?.external;
const { colorMode } = useColorMode();
const [message, setMessage] = useState('');
@@ -114,8 +117,45 @@ export const SignMessageScreen = ({ route, navigation }) => {
navigation.pop();
};
+ const hasOneKeySigner =
+ activeVault?.signers?.some((s) => {
+ const signerInfo = vaultSigners?.find(
+ (vs) => vs.masterFingerprint === s.masterFingerprint
+ );
+ return signerInfo?.type === SignerType.ONEKEY;
+ }) ?? false;
+
const onSigningMediumSelection = (medium) => {
setMediumModal(false);
+
+ // OneKey BLE direct signing
+ if (medium === 'BLE') {
+ const onekeyVaultKey = activeVault?.signers?.find((s) => {
+ const signerInfo = vaultSigners?.find(
+ (vs) => vs.masterFingerprint === s.masterFingerprint
+ );
+ return signerInfo?.type === SignerType.ONEKEY;
+ });
+ const oneKeySigner = vaultSigners?.find(
+ (vs) => vs.type === SignerType.ONEKEY
+ );
+ if (onekeyVaultKey && oneKeySigner) {
+ navigation.dispatch(
+ CommonActions.navigate('SignMessageOneKeyBle', {
+ message: message.trim(),
+ address,
+ derivationPath: `${onekeyVaultKey.derivationPath}/0/0`,
+ signer: oneKeySigner,
+ onSignatureReceived: (sig: string, addr: string) => {
+ setSignature(sig);
+ if (addr) setAddress(addr);
+ },
+ })
+ );
+ }
+ return;
+ }
+
if (mediumMode == MEDIUM_MODES.EXPORT) {
if (medium === KeyGenerationMode.QR) {
const qrData = WalletOperations.createSignMessageString(
@@ -321,7 +361,7 @@ export const SignMessageScreen = ({ route, navigation }) => {
modalBackground={`${colorMode}.modalWhiteBackground`}
textColor={`${colorMode}.textGreen`}
subTitleColor={`${colorMode}.modalSubtitleBlack`}
- Content={() => mediumSelectionContent(onSigningMediumSelection)}
+ Content={() => mediumSelectionContent(onSigningMediumSelection, hasOneKeySigner && mediumMode === MEDIUM_MODES.EXPORT)}
/>
);
@@ -374,7 +414,7 @@ const styles = StyleSheet.create({
},
});
-const mediumSelectionContent = (onSigningMediumSelection) => {
+const mediumSelectionContent = (onSigningMediumSelection, showBleOption = false) => {
const { colorMode } = useColorMode();
const options = [
@@ -400,6 +440,21 @@ const mediumSelectionContent = (onSigningMediumSelection) => {
),
name: KeyGenerationMode.FILE,
},
+ ...(showBleOption
+ ? [
+ {
+ title: 'OneKey (BLE)',
+ icon: (
+ }
+ backgroundColor={`${colorMode}.pantoneGreen`}
+ width={35}
+ />
+ ),
+ name: 'BLE',
+ },
+ ]
+ : []),
];
return (
diff --git a/src/services/onekeyBle/deviceConstants.ts b/src/services/onekeyBle/deviceConstants.ts
new file mode 100644
index 0000000000..9211be8bef
--- /dev/null
+++ b/src/services/onekeyBle/deviceConstants.ts
@@ -0,0 +1,37 @@
+import { ImageSourcePropType } from 'react-native';
+import type { SearchDevice } from '@onekeyfe/hd-core';
+
+// ─── Device images ───────────────────────────────────────────────────────────
+
+export const DEVICE_IMAGES: Record = {
+ classic: require('src/assets/images/onekey-devices/classic.png'),
+ classic1s: require('src/assets/images/onekey-devices/classic.png'),
+ classicpure: require('src/assets/images/onekey-devices/classic-pure.png'),
+ touch: require('src/assets/images/onekey-devices/touch.png'),
+ pro: require('src/assets/images/onekey-devices/pro-black.png'),
+};
+
+// ─── Device type names ───────────────────────────────────────────────────────
+
+export const DEVICE_TYPE_NAMES: Record = {
+ classic: 'OneKey Classic',
+ classic1s: 'OneKey Classic 1S',
+ classicpure: 'OneKey Classic 1S Pure',
+ touch: 'OneKey Touch',
+ pro: 'OneKey Pro',
+};
+
+// ─── Helper functions ────────────────────────────────────────────────────────
+
+export const getDeviceImage = (deviceOrType?: SearchDevice | string): ImageSourcePropType | null => {
+ const dt = typeof deviceOrType === 'string' ? deviceOrType : deviceOrType?.deviceType;
+ return dt ? DEVICE_IMAGES[dt] || null : null;
+};
+
+export const getDeviceDisplayName = (device: SearchDevice): string => {
+ if (device?.name && device.name !== 'Unknown') return device.name;
+ return DEVICE_TYPE_NAMES[device?.deviceType] || 'OneKey Device';
+};
+
+export const getDeviceTypeName = (device: SearchDevice): string =>
+ DEVICE_TYPE_NAMES[device?.deviceType] || 'OneKey';
diff --git a/src/services/onekeyBle/index.ts b/src/services/onekeyBle/index.ts
new file mode 100644
index 0000000000..eaffc6a51d
--- /dev/null
+++ b/src/services/onekeyBle/index.ts
@@ -0,0 +1,423 @@
+import HardwareBLESDK from '@onekeyfe/hd-ble-sdk';
+import {
+ type CoreApi,
+ type Features,
+ type SearchDevice,
+ UI_EVENT,
+ UI_REQUEST,
+ UI_RESPONSE,
+} from '@onekeyfe/hd-core';
+import { BleManager } from 'react-native-ble-plx';
+import { PermissionsAndroid, Platform } from 'react-native';
+import { DeviceEventEmitter } from 'react-native';
+import { NetworkType } from 'src/services/wallets/enums';
+
+// ─── UI Event Emitter ────────────────────────────────────────────────────────
+// Components can listen to these events to show appropriate UI prompts.
+
+export const onekeyUIEmitter = DeviceEventEmitter;
+export const ONEKEY_UI_EVENT = 'onekey-ui-event';
+
+// Use SDK's own constants as event values
+export type OneKeyUIEvent = typeof UI_REQUEST.REQUEST_PIN | typeof UI_REQUEST.REQUEST_BUTTON | typeof UI_REQUEST.REQUEST_PASSPHRASE | 'idle';
+
+// ─── Types ────────────────────────────────────────────────────────────────────
+
+type SDKResult = {
+ success: boolean;
+ payload: T & { error?: string; message?: string };
+};
+
+export type OneKeySignerData = {
+ multiSigPath: string;
+ multiSigXpub: string;
+ singleSigPath: string;
+ singleSigXpub: string;
+ taprootPath: string;
+ taprootXpub: string;
+ mfp: string;
+};
+
+export type OneKeyDeviceInfo = {
+ connectId: string;
+ deviceId: string;
+ masterFingerprint: string;
+ deviceLabel: string; // Device name shown on device (e.g. "My OneKey")
+ serialNo: string; // Hardware serial number (e.g. "PRA471B")
+};
+
+// ─── Singleton state ──────────────────────────────────────────────────────────
+
+let sdkInstance: CoreApi | null = null;
+let sdkInitPromise: Promise | null = null;
+let bleManager: BleManager | null = null;
+let uiListenerBound = false;
+
+const SCAN_TIMEOUT_MS = 15_000;
+
+// ─── SDK core ─────────────────────────────────────────────────────────────────
+
+const getCoreSdk = () => HardwareBLESDK as unknown as CoreApi;
+
+const handleUIEvent = (message: any) => {
+ if (!sdkInstance) return;
+
+ if (message?.type === UI_REQUEST.REQUEST_PIN) {
+ onekeyUIEmitter.emit(ONEKEY_UI_EVENT, UI_REQUEST.REQUEST_PIN);
+ sdkInstance.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PIN,
+ payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE',
+ });
+ return;
+ }
+
+ if (message?.type === UI_REQUEST.REQUEST_BUTTON) {
+ onekeyUIEmitter.emit(ONEKEY_UI_EVENT, UI_REQUEST.REQUEST_BUTTON);
+ return;
+ }
+
+ if (message?.type === UI_REQUEST.REQUEST_PASSPHRASE) {
+ onekeyUIEmitter.emit(ONEKEY_UI_EVENT, UI_REQUEST.REQUEST_PASSPHRASE);
+ sdkInstance.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PASSPHRASE,
+ payload: {
+ value: '',
+ passphraseOnDevice: true,
+ save: false,
+ },
+ });
+ }
+};
+
+const bindUIListener = (sdk: CoreApi) => {
+ if (uiListenerBound) return;
+ sdk.on(UI_EVENT, handleUIEvent);
+ uiListenerBound = true;
+};
+
+const getErrorMessage = (result: any): string => {
+ const code = result?.payload?.code;
+ const error = result?.payload?.error || result?.payload?.message || '';
+
+ // Friendly messages for known error codes
+ if (code === 801 || error.includes('Pin invalid')) {
+ return 'PIN is incorrect. Please try again on your device.';
+ }
+ if (code === 802 || error.includes('Pin cancelled')) {
+ return 'PIN entry was cancelled.';
+ }
+ if (error.includes('Failure_ActionCancelled')) {
+ return 'Action was cancelled on the device.';
+ }
+
+ return error || 'OneKey operation failed';
+};
+
+export const getOneKeySdk = async (): Promise => {
+ if (sdkInstance) return sdkInstance;
+ if (sdkInitPromise) return sdkInitPromise;
+
+ sdkInitPromise = (async () => {
+ const sdk = getCoreSdk();
+ await sdk.init({ debug: false, fetchConfig: true });
+ sdkInstance = sdk;
+ bindUIListener(sdk);
+ return sdk;
+ })();
+
+ try {
+ return await sdkInitPromise;
+ } catch (e) {
+ // Only clear on failure so successful init is cached
+ sdkInitPromise = null;
+ throw e;
+ }
+};
+
+// ─── BLE readiness ────────────────────────────────────────────────────────────
+
+const ensureAndroidBLEPermissions = async (): Promise => {
+ if (Platform.OS !== 'android') return true;
+
+ const permissions: string[] = [];
+ if (Number(Platform.Version) >= 31) {
+ permissions.push(
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
+ PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
+ );
+ }
+ permissions.push(
+ PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
+ PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION
+ );
+
+ const result = await PermissionsAndroid.requestMultiple(permissions);
+ return Object.values(result).every((v) => v === PermissionsAndroid.RESULTS.GRANTED);
+};
+
+/**
+ * Wait for BLE adapter to reach a definitive state (PoweredOn / PoweredOff / Unauthorized).
+ * BleManager may report 'Unknown' or 'Resetting' transiently after construction; we listen
+ * for the settled state via onStateChange (with emitCurrentState=true) and resolve as soon
+ * as we get something actionable, or after a 3 s timeout.
+ */
+const waitForBleState = (mgr: BleManager): Promise =>
+ new Promise((resolve) => {
+ const sub = mgr.onStateChange((state) => {
+ if (state !== 'Unknown' && state !== 'Resetting') {
+ sub.remove();
+ resolve(state);
+ }
+ }, true); // emitCurrentState = true
+ setTimeout(() => {
+ sub.remove();
+ mgr.state().then(resolve);
+ }, 3000);
+ });
+
+export const ensureOneKeyBLEReady = async () => {
+ const hasPermission = await ensureAndroidBLEPermissions();
+ if (!hasPermission) {
+ return { ready: false as const, reason: 'MISSING_PERMISSION' as const };
+ }
+
+ if (!bleManager) bleManager = new BleManager();
+
+ const bleState = await waitForBleState(bleManager);
+ if (bleState !== 'PoweredOn') {
+ return { ready: false as const, reason: 'BLE_OFF' as const };
+ }
+
+ return { ready: true as const, reason: null };
+};
+
+// ─── Device discovery ─────────────────────────────────────────────────────────
+
+export const searchOneKeyDevices = async (): Promise => {
+ const sdk = await getOneKeySdk();
+
+ const result = await Promise.race([
+ sdk.searchDevices() as Promise>,
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('BLE scan timed out')), SCAN_TIMEOUT_MS)
+ ),
+ ]);
+
+ if (!result?.success) throw new Error(getErrorMessage(result));
+ return result.payload || [];
+};
+
+/**
+ * Resolve device_id and master fingerprint from a connected device.
+ * Returns both so callers can verify device identity.
+ */
+export const getOneKeyDeviceInfo = async (connectId: string): Promise => {
+ const sdk = await getOneKeySdk();
+ const result = (await sdk.getFeatures(connectId)) as SDKResult;
+ if (!result?.success) throw new Error(getErrorMessage(result));
+
+ const deviceId = result?.payload?.device_id;
+ if (!deviceId) throw new Error('Failed to get OneKey device_id');
+
+ const serialNo =
+ (result.payload as any)?.onekey_serial_no ||
+ (result.payload as any)?.onekey_serial ||
+ (result.payload as any)?.serial_no ||
+ '';
+ const deviceLabel =
+ result?.payload?.label || result?.payload?.ble_name || `OneKey ${deviceId.slice(-4)}`;
+
+ // Fetch root fingerprint via a lightweight key derivation
+ const fpResult = (await sdk.btcGetPublicKey(connectId, deviceId, {
+ path: "m/84'/0'/0'",
+ showOnOneKey: false,
+ useEmptyPassphrase: true,
+ })) as SDKResult;
+
+ if (!fpResult?.success) throw new Error(getErrorMessage(fpResult));
+
+ const mfp = toMasterFingerprint(fpResult?.payload?.root_fingerprint);
+ return { connectId, deviceId, masterFingerprint: mfp, deviceLabel, serialNo };
+};
+
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+
+const getCoinTypeByNetwork = (networkType: NetworkType) =>
+ networkType === NetworkType.TESTNET ? 1 : 0;
+
+const getCoinNameByNetwork = (networkType: NetworkType) =>
+ networkType === NetworkType.TESTNET ? 'TEST' : 'Bitcoin';
+
+const toMasterFingerprint = (rootFingerprint?: number): string => {
+ if (rootFingerprint === undefined || rootFingerprint === null) {
+ throw new Error('Missing OneKey root_fingerprint');
+ }
+ const fp = rootFingerprint >>> 0; // Force unsigned 32-bit
+ return fp.toString(16).padStart(8, '0').toUpperCase();
+};
+
+const extractXpub = (payload: any): string => {
+ if (!payload?.xpub) throw new Error('Invalid xpub from OneKey');
+ return payload.xpub;
+};
+
+// ─── Signer data (xpub fetch) ─────────────────────────────────────────────────
+
+export const fetchOneKeySignerData = async ({
+ connectId,
+ deviceId,
+ networkType,
+ accountNumber = 0,
+}: {
+ connectId: string;
+ deviceId: string;
+ networkType: NetworkType;
+ accountNumber?: number;
+}): Promise => {
+ const sdk = await getOneKeySdk();
+ const coinType = getCoinTypeByNetwork(networkType);
+
+ const singleSigPath = `m/84'/${coinType}'/${accountNumber}'`;
+ const multiSigPath = `m/48'/${coinType}'/${accountNumber}'/2'`;
+ const taprootPath = `m/86'/${coinType}'/${accountNumber}'`;
+
+ const singleSigResult = (await sdk.btcGetPublicKey(connectId, deviceId, {
+ path: singleSigPath,
+ showOnOneKey: false,
+ useEmptyPassphrase: true,
+ })) as SDKResult;
+ if (!singleSigResult?.success) throw new Error(getErrorMessage(singleSigResult));
+
+ const multiSigResult = (await sdk.btcGetPublicKey(connectId, deviceId, {
+ path: multiSigPath,
+ showOnOneKey: false,
+ useEmptyPassphrase: true,
+ })) as SDKResult;
+ if (!multiSigResult?.success) throw new Error(getErrorMessage(multiSigResult));
+
+ const taprootResult = (await sdk.btcGetPublicKey(connectId, deviceId, {
+ path: taprootPath,
+ showOnOneKey: false,
+ useEmptyPassphrase: true,
+ })) as SDKResult;
+ if (!taprootResult?.success) throw new Error(getErrorMessage(taprootResult));
+
+ const mfp = toMasterFingerprint(
+ singleSigResult?.payload?.root_fingerprint ??
+ multiSigResult?.payload?.root_fingerprint ??
+ taprootResult?.payload?.root_fingerprint
+ );
+
+ return {
+ multiSigPath,
+ multiSigXpub: extractXpub(multiSigResult.payload),
+ singleSigPath,
+ singleSigXpub: extractXpub(singleSigResult.payload),
+ taprootPath,
+ taprootXpub: extractXpub(taprootResult.payload),
+ mfp,
+ };
+};
+
+// ─── PSBT signing ─────────────────────────────────────────────────────────────
+
+const convertSignedPsbtToBase64 = (psbt: string): string => {
+ const sanitized = psbt?.startsWith('0x') ? psbt.slice(2) : psbt;
+ if (sanitized && /^[a-fA-F0-9]+$/.test(sanitized)) {
+ return Buffer.from(sanitized, 'hex').toString('base64');
+ }
+ return psbt;
+};
+
+export const signPsbtWithOneKey = async ({
+ connectId,
+ deviceId,
+ networkType,
+ serializedPSBT,
+}: {
+ connectId: string;
+ deviceId: string;
+ networkType: NetworkType;
+ serializedPSBT: string;
+}): Promise => {
+ const sdk = await getOneKeySdk();
+ const psbtHex = Buffer.from(serializedPSBT, 'base64').toString('hex');
+ const coin = getCoinNameByNetwork(networkType);
+
+ const result = (await sdk.btcSignPsbt(connectId, deviceId, {
+ psbt: psbtHex,
+ coin,
+ useEmptyPassphrase: true,
+ })) as SDKResult<{ psbt: string }>;
+
+ if (!result?.success) throw new Error(getErrorMessage(result));
+
+ const signedPsbt = result?.payload?.psbt;
+ if (!signedPsbt) throw new Error('OneKey returned empty signed PSBT');
+
+ return convertSignedPsbtToBase64(signedPsbt);
+};
+
+// ─── Address verification ─────────────────────────────────────────────────────
+
+export const verifyAddressOnOneKey = async ({
+ connectId,
+ deviceId,
+ path,
+ networkType,
+}: {
+ connectId: string;
+ deviceId: string;
+ path: string;
+ networkType: NetworkType;
+}): Promise => {
+ const sdk = await getOneKeySdk();
+ const coin = getCoinNameByNetwork(networkType);
+
+ const result = (await sdk.btcGetAddress(connectId, deviceId, {
+ path,
+ coin,
+ showOnOneKey: true,
+ useEmptyPassphrase: true,
+ })) as SDKResult<{ address: string }>;
+
+ if (!result?.success) throw new Error(getErrorMessage(result));
+ if (!result?.payload?.address) throw new Error('OneKey returned empty address');
+
+ return result.payload.address;
+};
+
+// ─── Message signing ──────────────────────────────────────────────────────────
+
+export const signMessageWithOneKey = async ({
+ connectId,
+ deviceId,
+ path,
+ message,
+ networkType,
+}: {
+ connectId: string;
+ deviceId: string;
+ path: string;
+ message: string;
+ networkType: NetworkType;
+}): Promise<{ address: string; signature: string }> => {
+ const sdk = await getOneKeySdk();
+ const coin = getCoinNameByNetwork(networkType);
+ const messageHex = Buffer.from(message, 'utf8').toString('hex');
+
+ const result = (await sdk.btcSignMessage(connectId, deviceId, {
+ path,
+ messageHex,
+ coin,
+ useEmptyPassphrase: true,
+ })) as SDKResult<{ address: string; signature: string }>;
+
+ if (!result?.success) throw new Error(getErrorMessage(result));
+ if (!result?.payload?.signature) throw new Error('OneKey returned empty signature');
+
+ return {
+ address: result.payload.address,
+ signature: result.payload.signature,
+ };
+};
diff --git a/src/services/wallets/enums/index.ts b/src/services/wallets/enums/index.ts
index ba2d2ae832..e79ab8e63f 100644
--- a/src/services/wallets/enums/index.ts
+++ b/src/services/wallets/enums/index.ts
@@ -95,6 +95,7 @@ export enum SignerType {
MY_KEEPER = 'MY_KEEPER',
TREZOR = 'TREZOR',
LEDGER = 'LEDGER',
+ ONEKEY = 'ONEKEY',
COLDCARD = 'COLDCARD',
PASSPORT = 'PASSPORT',
JADE = 'JADE',
diff --git a/src/services/wallets/interfaces/vault.ts b/src/services/wallets/interfaces/vault.ts
index 85f2a136b8..19eeda073c 100644
--- a/src/services/wallets/interfaces/vault.ts
+++ b/src/services/wallets/interfaces/vault.ts
@@ -84,6 +84,7 @@ export type SignerExtraData = {
familyName?: string;
recordID?: string;
thumbnailPath?: string;
+ bleConnectId?: string; // OneKey BLE connectId for direct reconnection
};
export interface HealthCheckDetails {
diff --git a/yarn.lock b/yarn.lock
index 262e69876d..282f1d90a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1792,7 +1792,7 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
-"@noble/hashes@^1.2.0":
+"@noble/hashes@^1.2.0", "@noble/hashes@^1.3.3":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a"
integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==
@@ -1828,6 +1828,58 @@
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
+"@onekeyfe/hd-ble-sdk@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/hd-ble-sdk/-/hd-ble-sdk-1.1.16.tgz#124c5cb9bb3c0d16b6ca2ea615e1867cbf55ba18"
+ integrity sha512-8t5mhPS7QQkcQXAqJJ0VVr+7EX5vT327U32cUYwxdf5N/opvNRkNc3k1io8N8YO6XprGk5+vAqWLT95/C30/Bw==
+ dependencies:
+ "@onekeyfe/hd-core" "1.1.16"
+ "@onekeyfe/hd-shared" "1.1.16"
+ "@onekeyfe/hd-transport-react-native" "1.1.16"
+
+"@onekeyfe/hd-core@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/hd-core/-/hd-core-1.1.16.tgz#31df132b7cc7e31275b39fdd0a08f001a7afa9a1"
+ integrity sha512-ViTzwUySTXWB81kVHvZuxaHScfcQUZ1+Tgk9We7UXz8zKwLRsU5GfIbqpS1qSZA65/OCCB/fJoMA0zto84FH0Q==
+ dependencies:
+ "@onekeyfe/hd-shared" "1.1.16"
+ "@onekeyfe/hd-transport" "1.1.16"
+ axios "1.12.2"
+ bignumber.js "^9.0.2"
+ bytebuffer "^5.0.1"
+ jszip "^3.10.1"
+ parse-uri "^1.0.7"
+ semver "^7.3.7"
+
+"@onekeyfe/hd-shared@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/hd-shared/-/hd-shared-1.1.16.tgz#7fa2b8f1e52348c924a8fc0be18f7d2f0f1b9043"
+ integrity sha512-PLIK9yZyT8TpbfN+4EaBKzY4mFXSVNBvOLNKmIxH/GnIy5hDBdGyybbhtCTEXjGr55aFLwR0GBy1I+nYXQmoEw==
+
+"@onekeyfe/hd-transport-react-native@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/hd-transport-react-native/-/hd-transport-react-native-1.1.16.tgz#381e7117e08767bc28c28ab23218a32540a7ea66"
+ integrity sha512-CDlmOWYKgJ2pxelEC4w8dSG44Iz6bSJ1LY65lbFdutZ4ZO84GtUmceZ//K+i88dbULKe4qogcyqKG7P6EZcTqA==
+ dependencies:
+ "@onekeyfe/hd-shared" "1.1.16"
+ "@onekeyfe/hd-transport" "1.1.16"
+ "@onekeyfe/react-native-ble-utils" "^0.1.4"
+ react-native-ble-plx "3.5.0"
+
+"@onekeyfe/hd-transport@1.1.16":
+ version "1.1.16"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/hd-transport/-/hd-transport-1.1.16.tgz#3ec4ee1f786df1a630e79113dc561b33c5d57bd9"
+ integrity sha512-z+HKqYkGz+Ub1GV+REFqJqTG+tpZt7296O3qIlwmsURWdWXX4TcpXmCQDcXMt7vru8m9A4MUMkpGr+23MFBluw==
+ dependencies:
+ bytebuffer "^5.0.1"
+ long "^4.0.0"
+ protobufjs "^6.11.2"
+
+"@onekeyfe/react-native-ble-utils@^0.1.4":
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/@onekeyfe/react-native-ble-utils/-/react-native-ble-utils-0.1.4.tgz#4a262d30cd226e94f1c697c08a3a85a44310690c"
+ integrity sha512-DTKjjFKdkGktx/qqdu7STFAjpD6ZI3Rdfcyvv/a1Ho4GBs9F/nOJyxEhqiUNbY4XZoVHSwYy2cxYrdX4zGY6eA==
+
"@otplib/core@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/core/-/core-12.0.1.tgz#73720a8cedce211fe5b3f683cd5a9c098eaf0f8d"
@@ -1866,6 +1918,59 @@
"@otplib/plugin-crypto" "^12.0.1"
"@otplib/plugin-thirty-two" "^12.0.1"
+"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
+ integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
+
+"@protobufjs/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
+ integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
+"@protobufjs/codegen@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
+ integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
+
+"@protobufjs/eventemitter@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
+ integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
+
+"@protobufjs/fetch@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
+ integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.1"
+ "@protobufjs/inquire" "^1.1.0"
+
+"@protobufjs/float@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
+ integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
+
+"@protobufjs/inquire@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
+ integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
+
+"@protobufjs/path@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
+ integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
+
+"@protobufjs/pool@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
+ integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
+
+"@protobufjs/utf8@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
+ integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+
"@react-aria/checkbox@3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@react-aria/checkbox/-/checkbox-3.2.1.tgz#493d9d584b4db474645a4565c4f899ee3a579f07"
@@ -3591,6 +3696,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+"@types/long@^4.0.1":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
+ integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
+
"@types/node-forge@^1.3.0":
version "1.3.14"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.14.tgz#006c2616ccd65550560c2757d8472eb6d3ecea0b"
@@ -3617,6 +3727,13 @@
dependencies:
undici-types "~6.19.2"
+"@types/node@>=13.7.0":
+ version "25.2.3"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-25.2.3.tgz#9c18245be768bdb4ce631566c7da303a5c99a7f8"
+ integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==
+ dependencies:
+ undici-types "~7.16.0"
+
"@types/prop-types@*":
version "15.7.15"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7"
@@ -3868,6 +3985,11 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+Base64@~0.2.0:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028"
+ integrity sha512-reGEWshDmTDQDsCec/HduOO9Wyj6yMOupMfhIf3ugN1TDlK2NQW4DDJSqNNtp380SNcvRfXtO8HSCQot0d0SMw==
+
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -4215,6 +4337,15 @@ axe-core@^4.10.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.0.tgz#16f74d6482e343ff263d4f4503829e9ee91a86b6"
integrity sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==
+axios@1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7"
+ integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.4"
+ proxy-from-env "^1.1.0"
+
axios@1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979"
@@ -4395,7 +4526,7 @@ base-x@^1.1.0:
resolved "https://registry.yarnpkg.com/base-x/-/base-x-1.1.0.tgz#42d3d717474f9ea02207f6d1aa1f426913eeb7ac"
integrity sha512-c0WLeG3K5OlL4Skz2/LVdS+MjggByKhowxQpG+JpCLA48s/bGwIDyzA1naFjywtNvp/37fLK0p0FpjTNNLLUXQ==
-base-x@^3.0.2:
+base-x@^3.0.2, base-x@^3.0.9:
version "3.0.11"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.11.tgz#40d80e2a1aeacba29792ccc6c5354806421287ff"
integrity sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==
@@ -4451,7 +4582,7 @@ bignumber.js@9.1.2:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
-bignumber.js@^9.0.1:
+bignumber.js@^9.0.1, bignumber.js@^9.0.2:
version "9.3.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7"
integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==
@@ -4553,7 +4684,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99"
integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==
-bn.js@^5.2.1, bn.js@^5.2.2:
+bn.js@^5.1.1, bn.js@^5.2.1, bn.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566"
integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==
@@ -4599,7 +4730,7 @@ braces@^3.0.2, braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
-brorand@^1.0.1, brorand@^1.1.0:
+brorand@^1.0.1, brorand@^1.0.5, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
@@ -4616,7 +4747,7 @@ browserify-aes@^1.0.4, browserify-aes@^1.0.6, browserify-aes@^1.2.0:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-browserify-cipher@^1.0.0:
+browserify-cipher@^1.0.0, browserify-cipher@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
@@ -4644,7 +4775,7 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.1.1:
randombytes "^2.1.0"
safe-buffer "^5.2.1"
-browserify-sign@^4.0.4:
+browserify-sign@^4.0.4, browserify-sign@^4.2.3:
version "4.2.5"
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.5.tgz#3979269fa8af55ba18aac35deef11b45515cd27d"
integrity sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==
@@ -4659,6 +4790,13 @@ browserify-sign@^4.0.4:
readable-stream "^2.3.8"
safe-buffer "^5.2.1"
+browserify-zlib@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
+ integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
+ dependencies:
+ pako "~1.0.5"
+
browserslist@^4.24.0, browserslist@^4.28.0:
version "4.28.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929"
@@ -4802,6 +4940,13 @@ buffer@^5.1.0, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
+bytebuffer@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
+ integrity sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ==
+ dependencies:
+ long "~3"
+
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
@@ -5249,7 +5394,7 @@ crc@^3.8.0:
dependencies:
buffer "^5.1.0"
-create-ecdh@^4.0.0:
+create-ecdh@^4.0.0, create-ecdh@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
@@ -5313,6 +5458,24 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
shebang-command "^2.0.0"
which "^2.0.1"
+crypto-browserify@^3.12.0:
+ version "3.12.1"
+ resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz#bb8921bec9acc81633379aa8f52d69b0b69e0dac"
+ integrity sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==
+ dependencies:
+ browserify-cipher "^1.0.1"
+ browserify-sign "^4.2.3"
+ create-ecdh "^4.0.4"
+ create-hash "^1.2.0"
+ create-hmac "^1.1.7"
+ diffie-hellman "^5.0.3"
+ hash-base "~3.0.4"
+ inherits "^2.0.4"
+ pbkdf2 "^3.1.2"
+ public-encrypt "^4.0.3"
+ randombytes "^2.1.0"
+ randomfill "^1.0.4"
+
crypto-js@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
@@ -5589,7 +5752,7 @@ diff-sequences@^29.6.3:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
-diffie-hellman@^5.0.0:
+diffie-hellman@^5.0.0, diffie-hellman@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
@@ -6621,7 +6784,7 @@ for-own@^1.0.0:
dependencies:
for-in "^1.0.1"
-form-data@^4.0.0:
+form-data@^4.0.0, form-data@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
@@ -6922,6 +7085,14 @@ hash-base@^3.0.0, hash-base@^3.1.2:
safe-buffer "^5.2.1"
to-buffer "^1.2.1"
+hash-base@~3.0.4:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.5.tgz#52480e285395cf7fba17dc4c9e47acdc7f248a8a"
+ integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==
+ dependencies:
+ inherits "^2.0.4"
+ safe-buffer "^5.2.1"
+
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
@@ -6987,6 +7158,14 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+http-browserify@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/http-browserify/-/http-browserify-1.7.0.tgz#33795ade72df88acfbfd36773cefeda764735b20"
+ integrity sha512-Irf/LJXmE3cBzU1eaR4+NEX6bmVLqt1wkmDiA7kBwH7zmb0D8kBAXsDmQ88hhj/qv9iEZKlyGx/hrMcFi8sOHw==
+ dependencies:
+ Base64 "~0.2.0"
+ inherits "~2.0.1"
+
http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
@@ -6998,6 +7177,11 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
+https-browserify@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
+ integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==
+
https-proxy-agent@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
@@ -7063,6 +7247,11 @@ image-size@^1.0.2:
dependencies:
queue "6.0.2"
+immediate@~3.0.5:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+ integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
immer@^9.0.7:
version "9.0.21"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
@@ -8065,6 +8254,16 @@ jsonfile@^4.0.0:
object.assign "^4.1.4"
object.values "^1.1.6"
+jszip@^3.10.1:
+ version "3.10.1"
+ resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+ integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+ dependencies:
+ lie "~3.3.0"
+ pako "~1.0.2"
+ readable-stream "~2.3.6"
+ setimmediate "^1.0.5"
+
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -8123,6 +8322,13 @@ levn@^0.4.1:
version "0.3.0"
resolved "git+https://github.com/bithyve/libportal-react-native.git#2ff681b0b725009768acadb35b2731fb1a402fa3"
+lie@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+ integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+ dependencies:
+ immediate "~3.0.5"
+
lighthouse-logger@^1.0.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa"
@@ -8304,6 +8510,16 @@ logkitty@^0.7.1:
dayjs "^1.8.15"
yargs "^15.1.0"
+long@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
+ integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
+
+long@~3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
+ integrity sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==
+
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -9376,6 +9592,11 @@ pako@2.1.0:
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
+pako@~1.0.2, pako@~1.0.5:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+ integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -9412,6 +9633,11 @@ parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
+parse-uri@^1.0.7:
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/parse-uri/-/parse-uri-1.0.16.tgz#9e730ccc7358d080f90a29e0334c80c8c845e544"
+ integrity sha512-WMX9ygt2zzbtd3UlChi8S2Uj/dZa0N9QaotTkyRD7v06c50dor4qEWrM5ZvHiiaZYpXal4otRS9hynwwX0DVoA==
+
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -9498,7 +9724,7 @@ pbkdf2@3.0.8:
dependencies:
create-hmac "^1.1.2"
-pbkdf2@^3.0.9, pbkdf2@^3.1.5:
+pbkdf2@^3.0.9, pbkdf2@^3.1.2, pbkdf2@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.5.tgz#444a59d7a259a95536c56e80c89de31cc01ed366"
integrity sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==
@@ -9683,12 +9909,31 @@ prop-types@*, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.0, prop-t
object-assign "^4.1.1"
react-is "^16.13.1"
+protobufjs@^6.11.2:
+ version "6.11.4"
+ resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa"
+ integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.2"
+ "@protobufjs/base64" "^1.1.2"
+ "@protobufjs/codegen" "^2.0.4"
+ "@protobufjs/eventemitter" "^1.1.0"
+ "@protobufjs/fetch" "^1.1.0"
+ "@protobufjs/float" "^1.0.2"
+ "@protobufjs/inquire" "^1.1.0"
+ "@protobufjs/path" "^1.1.2"
+ "@protobufjs/pool" "^1.1.0"
+ "@protobufjs/utf8" "^1.1.0"
+ "@types/long" "^4.0.1"
+ "@types/node" ">=13.7.0"
+ long "^4.0.0"
+
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
-public-encrypt@^4.0.0:
+public-encrypt@^4.0.0, public-encrypt@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
@@ -9713,7 +9958,7 @@ punycode@1.2.4:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.2.4.tgz#54008ac972aec74175def9cba6df7fa9d3918740"
integrity sha512-h/vscxLPvI2l7k/0dFUKZ5I5TgMCJ/Pl+J6rw77PDuQM6UApf/GaRVkjv/YSm2k+fbp7Yw8dxsoe29DolT7h7w==
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
@@ -9780,7 +10025,7 @@ randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
dependencies:
safe-buffer "^5.1.0"
-randomfill@^1.0.3:
+randomfill@^1.0.3, randomfill@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==
@@ -9862,6 +10107,11 @@ react-native-biometrics@2.2.0:
resolved "https://registry.yarnpkg.com/react-native-biometrics/-/react-native-biometrics-2.2.0.tgz#6fc3e801b83389ae1f6c9c41f7b37b50e7014c4e"
integrity sha512-V2O0s2ic7PxVP76CguCfBXvVEyazbjtWv7r20T7D+ZQqBB1XSZ1WzK/Gnr12CVRxHLUZ3/tKHOsz7mzIaXyNoA==
+react-native-ble-plx@3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/react-native-ble-plx/-/react-native-ble-plx-3.5.0.tgz#6cfa33c007bf5cc8b573dfcca8915de57cec60be"
+ integrity sha512-PeSnRswHLwLRVMQkOfDaRICtrGmo94WGKhlSC09XmHlqX2EuYgH+vNJpGcLkd8lyiYpEJyf8wlFAdj9Akliwmw==
+
react-native-blob-util@0.18.3:
version "0.18.3"
resolved "https://registry.yarnpkg.com/react-native-blob-util/-/react-native-blob-util-0.18.3.tgz#e3b408dfdd5b175093bbef23f33811f8ef3407ee"
@@ -10132,6 +10382,13 @@ react-native-tcp-socket@5.6.2:
buffer "^5.4.3"
eventemitter3 "^4.0.7"
+react-native-url-polyfill@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589"
+ integrity sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==
+ dependencies:
+ whatwg-url-without-unicode "8.0.0-3"
+
react-native-vision-camera@^4.7.2:
version "4.7.3"
resolved "https://registry.yarnpkg.com/react-native-vision-camera/-/react-native-vision-camera-4.7.3.tgz#ed03cedabcaec54774f5aa40e69afa30069924d4"
@@ -10282,7 +10539,7 @@ readable-stream@^1.0.27-1:
isarray "0.0.1"
string_decoder "~0.10.x"
-readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.8:
+readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@^2.3.8, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -10588,6 +10845,25 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3:
hash-base "^3.1.2"
inherits "^2.0.4"
+ripple-address-codec@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.3.1.tgz#68fbaf646bb8567f70743af7f1ce4479f73efbf6"
+ integrity sha512-Qa3+9wKVvpL/xYtT6+wANsn0A1QcC5CT6IMZbRJZ/1lGt7gmwIfsrCuz1X0+LCEO7zgb+3UT1I1dc0k/5dwKQQ==
+ dependencies:
+ base-x "^3.0.9"
+ create-hash "^1.1.2"
+
+ripple-keypairs@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.3.1.tgz#7fa531df36b138134afb53555a87d7f5eb465b2e"
+ integrity sha512-dmPlraWKJciFJxHcoubDahGnoIalG5e/BtV6HNDUs7wLXmtnLMHt6w4ed9R8MTL2zNrVPiIdI/HCtMMo0Tm7JQ==
+ dependencies:
+ bn.js "^5.1.1"
+ brorand "^1.0.5"
+ elliptic "^6.5.4"
+ hash.js "^1.0.3"
+ ripple-address-codec "^4.3.1"
+
"rn-nodeify@github:tradle/rn-nodeify#338d8d6ba8438403093e9409e9a9d88ad884926f":
version "10.3.0"
resolved "https://codeload.github.com/tradle/rn-nodeify/tar.gz/338d8d6ba8438403093e9409e9a9d88ad884926f"
@@ -10830,6 +11106,11 @@ set-proto@^1.0.0:
es-errors "^1.3.0"
es-object-atoms "^1.0.0"
+setimmediate@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+ integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
+
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@@ -11865,11 +12146,25 @@ webidl-conversions@^3.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+webidl-conversions@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+ integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+
whatwg-fetch@^3.0.0:
version "3.6.20"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70"
integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==
+whatwg-url-without-unicode@8.0.0-3:
+ version "8.0.0-3"
+ resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b"
+ integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==
+ dependencies:
+ buffer "^5.4.3"
+ punycode "^2.1.1"
+ webidl-conversions "^5.0.0"
+
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"