diff --git a/packages/connect-examples/expo-example/App.tsx b/packages/connect-examples/expo-example/App.tsx
index 28d9ab294..367f0354a 100644
--- a/packages/connect-examples/expo-example/App.tsx
+++ b/packages/connect-examples/expo-example/App.tsx
@@ -1,9 +1,13 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { enableFreeze, enableScreens } from 'react-native-screens';
+import { View } from 'react-native';
import HomeScreen from './src/views/HomeScreen';
import MockScreen from './src/views/MockConnect';
import { getHardwareSDKInstance } from './src/utils/hardwareInstance';
+import PassphraseTestScreen from './src/views/PassphraseTestScreen';
+import SDKProvider from './src/provider/SDKProvider';
getHardwareSDKInstance();
@@ -11,10 +15,13 @@ const Stack = createNativeStackNavigator();
export default function App() {
return (
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/packages/connect-examples/expo-example/src/components/CommonInput.tsx b/packages/connect-examples/expo-example/src/components/CommonInput.tsx
new file mode 100644
index 000000000..892e522f1
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/CommonInput.tsx
@@ -0,0 +1,48 @@
+import { useCallback } from 'react';
+import { StyleSheet, Text, TextInput, View } from 'react-native';
+
+export type CommonInputProps = {
+ value: string;
+ onChange: (value: string) => void;
+ label: string;
+ type?: 'number' | 'text';
+};
+export const CommonInput = ({ value, onChange, label, type }: CommonInputProps) => {
+ const onChangeCallback = useCallback(
+ (text: string) => {
+ if (type === 'number') {
+ onChange(text.replace(/[^\d]+/, ''));
+ } else {
+ onChange(text);
+ }
+ },
+ [onChange, type]
+ );
+
+ return (
+
+ {label}
+
+
+ );
+};
+const styles = StyleSheet.create({
+ input: {
+ borderColor: '#E0E0E0',
+ borderWidth: 1,
+ borderRadius: 4,
+ padding: 6,
+ margin: 2,
+ fontSize: 16,
+ },
+ label: {
+ fontWeight: 'bold',
+ marginBottom: 4,
+ fontSize: 14,
+ },
+});
diff --git a/packages/connect-examples/expo-example/src/components/CommonParamsView.tsx b/packages/connect-examples/expo-example/src/components/CommonParamsView.tsx
index bc5dcb06d..1f318cdcd 100644
--- a/packages/connect-examples/expo-example/src/components/CommonParamsView.tsx
+++ b/packages/connect-examples/expo-example/src/components/CommonParamsView.tsx
@@ -1,35 +1,7 @@
-import { useContext } from 'react';
-import { StyleSheet, Switch, Text, TextInput, View } from 'react-native';
+import { StyleSheet, Switch, Text, View } from 'react-native';
import { useCommonParams } from '../provider/CommonParamsProvider';
-
-type SwitchInputProps = {
- value: boolean;
- onToggle: (value: boolean) => void;
- label: string;
-};
-const SwitchInput = ({ value, onToggle, label }: SwitchInputProps) => (
-
- {label}
-
-
-);
-
-type NumberInputProps = {
- value: string;
- onChange: (value: string) => void;
- label: string;
-};
-const NumberInput = ({ value, onChange, label }: NumberInputProps) => (
-
- {label}
- onChange(text.replace(/[^\d]+/, ''))}
- />
-
-);
+import { CommonInput } from './CommonInput';
+import { SwitchInput } from './SwitchInput';
export default function CommonParamsView() {
const { commonParams, setCommonParams: setOptionalParams } = useCommonParams();
@@ -47,23 +19,27 @@ export default function CommonParamsView() {
value={!!commonParams.keepSession}
onToggle={value => handleSetParam('keepSession', value)}
/>
- handleSetParam('retryCount', parseInt(value))}
/>
- handleSetParam('pollIntervalTime', parseInt(value))}
/>
- handleSetParam('timeout', parseInt(value))}
/>
- handleSetParam('passphraseState', value)}
/>
diff --git a/packages/connect-examples/expo-example/src/components/HandleSDKEvents.tsx b/packages/connect-examples/expo-example/src/components/HandleSDKEvents.tsx
index ec9688f45..db881e047 100644
--- a/packages/connect-examples/expo-example/src/components/HandleSDKEvents.tsx
+++ b/packages/connect-examples/expo-example/src/components/HandleSDKEvents.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useContext, useEffect, useState } from 'react';
+import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
CoreMessage,
DEVICE,
@@ -8,6 +8,7 @@ import {
UI_RESPONSE,
} from '@onekeyfe/hd-core';
import { View } from 'react-native';
+import { useIsFocused } from '@react-navigation/native';
import HardwareSDKContext from '../provider/HardwareSDKContext';
import { ReceivePin } from './ReceivePin';
@@ -18,6 +19,13 @@ export default function HandleSDKEvents() {
const [showPinInput, setShowPinInput] = useState(false);
const [pinValue, setPinValue] = useState('');
+ const focus = useIsFocused();
+ const focusRef = useRef(false);
+
+ useEffect(() => {
+ focusRef.current = focus;
+ }, [focus]);
+
useEffect(() => {
// 监听 SDK 事件
if (registerListener) {
@@ -30,6 +38,7 @@ export default function HandleSDKEvents() {
});
SDK.on(UI_EVENT, (message: CoreMessage) => {
+ if (!focusRef.current) return;
console.log('TopLEVEL EVENT ===>>>>: ', message);
if (message.type === UI_REQUEST.REQUEST_PIN) {
// setShowPinInput(true);
@@ -75,7 +84,12 @@ export default function HandleSDKEvents() {
console.log('example get disconnect event: ', message);
});
registerListener = true;
- }, [SDK, HardwareLowLevelSDK]);
+
+ return () => {
+ registerListener = false;
+ SDK.removeAllListeners();
+ };
+ }, [SDK, HardwareLowLevelSDK, focus]);
// 输入 pin 码的确认回调
const onConfirmPin = useCallback(
diff --git a/packages/connect-examples/expo-example/src/components/PassphraseTest/TestMultiWalletChangeView.tsx b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestMultiWalletChangeView.tsx
new file mode 100644
index 000000000..fb3fae278
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestMultiWalletChangeView.tsx
@@ -0,0 +1,445 @@
+import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
+import { CoreMessage, UI_EVENT, UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core';
+import { Button, Text, TextInput, View } from 'react-native';
+import { TestChain, requestAddress, styles } from './utils';
+import HardwareSDKContext from '../../provider/HardwareSDKContext';
+import { useDevice } from '../../provider/DeviceProvider';
+import { CommonInput } from '../CommonInput';
+
+type TestPresupposeWallet = {
+ id: string;
+ passphrase: string;
+ emptyPassphraseState?: boolean;
+ addressList?: {
+ index: number;
+ chain: TestChain;
+ }[];
+};
+type TestResult = {
+ id: string;
+ passphrase: string;
+ passphraseState?: string;
+ emptyPassphraseState?: boolean;
+ addressList?: {
+ index: number;
+ chain: TestChain;
+ address?: string;
+ }[];
+};
+const testPresuppose: {
+ normal: TestPresupposeWallet[];
+ normalWithNormalWallet: TestPresupposeWallet[];
+} = {
+ normalWithNormalWallet: [
+ {
+ id: '0',
+ passphrase: '',
+ emptyPassphraseState: true,
+ addressList: [
+ {
+ index: 0,
+ chain: 'btc',
+ },
+ {
+ index: 1,
+ chain: 'evm',
+ },
+ ],
+ },
+ {
+ id: '1',
+ passphrase: '111',
+ addressList: [
+ {
+ index: 0,
+ chain: 'btc',
+ },
+ {
+ index: 1,
+ chain: 'evm',
+ },
+ {
+ index: 2,
+ chain: 'dot',
+ },
+ {
+ index: 3,
+ chain: 'ada',
+ },
+ ],
+ },
+ {
+ id: '2',
+ passphrase: '222',
+ addressList: [
+ {
+ index: 0,
+ chain: 'btc',
+ },
+ {
+ index: 1,
+ chain: 'dot',
+ },
+ ],
+ },
+ {
+ id: '3',
+ passphrase: '444',
+ addressList: [
+ {
+ index: 0,
+ chain: 'evm',
+ },
+ {
+ index: 1,
+ chain: 'ada',
+ },
+ ],
+ },
+ ],
+ normal: [
+ {
+ id: '1',
+ passphrase: '111',
+ addressList: [
+ {
+ index: 0,
+ chain: 'btc',
+ },
+ {
+ index: 1,
+ chain: 'evm',
+ },
+ {
+ index: 2,
+ chain: 'dot',
+ },
+ {
+ index: 3,
+ chain: 'ada',
+ },
+ ],
+ },
+ {
+ id: '2',
+ passphrase: '222',
+ addressList: [
+ {
+ index: 0,
+ chain: 'btc',
+ },
+ {
+ index: 1,
+ chain: 'dot',
+ },
+ ],
+ },
+ {
+ id: '3',
+ passphrase: '444',
+ addressList: [
+ {
+ index: 0,
+ chain: 'evm',
+ },
+ {
+ index: 1,
+ chain: 'ada',
+ },
+ ],
+ },
+ ],
+};
+
+export default function TestMultiWalletChangeView() {
+ const { sdk: SDK } = useContext(HardwareSDKContext);
+ const { selectedDevice } = useDevice();
+
+ const [changeWalletCount, setChangeWalletCount] = useState(20);
+ const [testResult, setTestResult] = useState<{
+ done?: boolean;
+ payload: string;
+ }>();
+
+ const [walletParams, setWalletParams] = useState(
+ JSON.stringify(testPresuppose.normalWithNormalWallet, null, 2)
+ );
+ const [walletResult, setWalletResult] = useState();
+ const currentPassphrase = useRef('');
+
+ useEffect(() => {
+ if (testResult?.done && !!SDK) {
+ SDK.removeAllListeners(UI_EVENT);
+ }
+ }, [SDK, testResult?.done]);
+
+ const setNormalWithNormalWalletTest = useCallback(() => {
+ setWalletParams(JSON.stringify(testPresuppose.normalWithNormalWallet, null, 2));
+ }, []);
+ const setNormalTest = useCallback(() => {
+ setWalletParams(JSON.stringify(testPresuppose.normal, null, 2));
+ }, []);
+
+ const beginTest = useCallback(async () => {
+ const walletList: TestPresupposeWallet[] = JSON.parse(walletParams ?? '[]');
+
+ const testWalletCount = walletList?.length ?? 0;
+ if (testWalletCount < 2) {
+ setTestResult({
+ done: true,
+ payload: '测试钱包数量至少为 2',
+ });
+ return;
+ }
+
+ if (!SDK) return;
+ SDK.removeAllListeners(UI_EVENT);
+
+ setTestResult({
+ done: false,
+ payload: '测试中...',
+ });
+
+ const connectId = selectedDevice?.connectId ?? '';
+ const deviceId = selectedDevice?.features?.device_id ?? '';
+
+ SDK.on(UI_EVENT, (message: CoreMessage) => {
+ console.log('TopLEVEL EVENT ===>>>>: ', message);
+ if (message.type === UI_REQUEST.REQUEST_PIN) {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PIN,
+ payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE',
+ });
+ }
+ if (message.type === UI_REQUEST.REQUEST_PASSPHRASE) {
+ setTimeout(() => {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PASSPHRASE,
+ payload: {
+ value: currentPassphrase.current,
+ },
+ });
+ }, 200);
+ }
+ });
+
+ const cacheWallet = new Map<
+ string,
+ {
+ passphraseState?: string;
+ addressList?: Map<
+ number,
+ {
+ address: string;
+ }
+ >;
+ }
+ >();
+
+ for (const item of walletList ?? []) {
+ let passphraseState: string | undefined;
+ currentPassphrase.current = item.passphrase;
+ if (!item.emptyPassphraseState) {
+ const passphraseStateRes = await SDK.getPassphraseState(connectId, {});
+ if (!passphraseStateRes.success) {
+ setTestResult({
+ done: true,
+ payload: `getPassphraseState failed ${passphraseStateRes?.payload?.error}`,
+ });
+ return;
+ }
+ if (!passphraseStateRes.payload) {
+ setTestResult({
+ done: true,
+ payload: 'passphrase is not enabled on the device.',
+ });
+ return;
+ }
+
+ passphraseState = passphraseStateRes.payload;
+ }
+
+ const cacheAddressList = new Map<
+ number,
+ {
+ address: string;
+ }
+ >();
+ for (const addressItem of item.addressList ?? []) {
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain: addressItem.chain,
+ connectId,
+ deviceId,
+ passphraseState,
+ useEmptyPassphrase: item.emptyPassphraseState,
+ });
+
+ if (!addressRes.success) {
+ setTestResult({
+ done: true,
+ payload: `GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ return;
+ }
+
+ cacheAddressList.set(addressItem.index, {
+ address: addressRes.payload.address ?? '',
+ });
+ }
+
+ cacheWallet.set(item.id, {
+ passphraseState,
+ addressList: cacheAddressList,
+ });
+ }
+
+ const result: TestResult[] = [
+ ...(walletList ?? []).map(current => {
+ const walletRes = cacheWallet.get(current.id);
+ if (!walletRes) return current;
+ return {
+ ...current,
+ passphraseState: walletRes?.passphraseState,
+ addressList: current.addressList?.map(addressItem => {
+ const addressRes = walletRes?.addressList?.get(addressItem.index);
+ if (!addressRes) return addressItem;
+ return {
+ ...addressItem,
+ address: addressRes?.address,
+ };
+ }),
+ };
+ }),
+ ];
+
+ setWalletResult(result);
+
+ // for changeWalletCount count
+ for (let index = 0; index < changeWalletCount; index++) {
+ const item = walletList?.[index % (testWalletCount ?? 0)];
+ if (!item) {
+ setTestResult({
+ done: true,
+ payload: 'changeWalletCount error',
+ });
+ return;
+ }
+
+ currentPassphrase.current = item.passphrase;
+ for (const addressItem of item.addressList ?? []) {
+ const passphraseState = result.find(current => current.id === item.id)?.passphraseState;
+ const address = result
+ .find(current => current.id === item.id)
+ ?.addressList?.find(current => current.index === addressItem.index)?.address;
+
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain: addressItem.chain,
+ connectId,
+ deviceId,
+ passphraseState,
+ useEmptyPassphrase: item.emptyPassphraseState,
+ });
+
+ if (!addressRes.success) {
+ setTestResult({
+ done: true,
+ payload: `id:${item.id} index:${addressItem.index} ${addressItem.chain} GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ return;
+ }
+
+ if (address !== addressRes.payload.address) {
+ setTestResult({
+ done: true,
+ payload: `id:${item.id} index:${addressItem.index} ${addressItem.chain} GetAddress 结果不一致!!!!`,
+ });
+ return;
+ }
+ }
+ }
+
+ setTestResult({
+ done: true,
+ payload: '测试结果正确',
+ });
+ }, [
+ SDK,
+ changeWalletCount,
+ selectedDevice?.connectId,
+ selectedDevice?.features?.device_id,
+ walletParams,
+ ]);
+
+ const ContentView = useMemo(() => {
+ console.log();
+ let result;
+ if (testResult?.done === undefined) {
+ result = '未测试';
+ } else if (testResult?.done) {
+ result = '完成';
+ } else {
+ result = '进行中';
+ }
+
+ return (
+
+ Passphrase Multi Change Test
+ 开始之后根据提示解锁硬件, 根据硬件提示确认 passphrase, 不用自己输入
+ 测试步骤:1. 根据您填写的参数创建 N 个钱包
+ 2. 获取这些钱包多个网络的 address
+
+ 3. 根据测试切换次数,从第一个钱包开始一直切换。会自动填写 passphrase,注意在硬件按确认
+
+
+
+
+
+ {
+ setWalletParams(e.nativeEvent.text);
+ }}
+ placeholder="Test wallet list"
+ multiline
+ editable
+ />
+
+ setChangeWalletCount(parseInt(number))}
+ />
+
+
+ {`${result}`}
+ {`测试结果 ${testResult?.payload}`}
+
+
+
+
+
+
+ );
+ }, [
+ beginTest,
+ changeWalletCount,
+ setNormalTest,
+ setNormalWithNormalWalletTest,
+ testResult?.done,
+ testResult?.payload,
+ walletParams,
+ walletResult,
+ ]);
+
+ return ContentView;
+}
diff --git a/packages/connect-examples/expo-example/src/components/PassphraseTest/TestSessionView.tsx b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestSessionView.tsx
new file mode 100644
index 000000000..52bfd994b
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestSessionView.tsx
@@ -0,0 +1,200 @@
+import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
+import { CoreMessage, UI_EVENT, UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core';
+import { Button, Text, View } from 'react-native';
+import { Picker } from '@react-native-picker/picker';
+import { TestChain, requestAddress, styles } from './utils';
+import { useDevice } from '../../provider/DeviceProvider';
+import HardwareSDKContext from '../../provider/HardwareSDKContext';
+
+export default function TestSessionView() {
+ const { sdk: SDK } = useContext(HardwareSDKContext);
+ const { selectedDevice } = useDevice();
+
+ const [testChain, setTestChain] = useState('btc');
+
+ const allowInputPassphrase = useRef(false);
+ const hasContinue = useRef(false);
+ const [testSessionCountResult, setTestSessionCountResult] = useState<{
+ done?: boolean;
+ payload: string;
+ }>();
+
+ const passphraseStateList = useRef<
+ {
+ passphraseState: string;
+ address: string;
+ }[]
+ >([]);
+
+ useEffect(() => {
+ if (testSessionCountResult?.done && !!SDK) {
+ SDK.removeAllListeners(UI_EVENT);
+ }
+ }, [SDK, testSessionCountResult?.done]);
+
+ const testSessionCount = useCallback(async () => {
+ const connectId = selectedDevice?.connectId ?? '';
+ const deviceId = selectedDevice?.features?.device_id ?? '';
+
+ if (!SDK) return;
+
+ SDK.removeAllListeners(UI_EVENT);
+ passphraseStateList.current = [];
+ setTestSessionCountResult({
+ done: false,
+ payload: '测试中...',
+ });
+
+ SDK.on(UI_EVENT, (message: CoreMessage) => {
+ console.log('TopLEVEL EVENT ===>>>>: ', message);
+ if (message.type === UI_REQUEST.REQUEST_PIN) {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PIN,
+ payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE',
+ });
+ }
+ if (message.type === UI_REQUEST.REQUEST_PASSPHRASE) {
+ if (!allowInputPassphrase.current) {
+ hasContinue.current = false;
+ setTestSessionCountResult({
+ done: true,
+ payload: '测试完成',
+ });
+ SDK.cancel();
+ return;
+ }
+
+ setTimeout(() => {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PASSPHRASE,
+ payload: {
+ value: `${(passphraseStateList.current?.length ?? 0) + 1}`,
+ },
+ });
+ }, 200);
+ }
+ });
+
+ hasContinue.current = true;
+ while (hasContinue.current) {
+ allowInputPassphrase.current = true;
+ const passphraseStateRes = await SDK.getPassphraseState(connectId);
+ if (!passphraseStateRes.success) {
+ hasContinue.current = false;
+ setTestSessionCountResult({
+ done: true,
+ payload: `getPassphraseState failed ${passphraseStateRes?.payload?.error}`,
+ });
+ break;
+ }
+ if (!passphraseStateRes.payload) {
+ hasContinue.current = false;
+ setTestSessionCountResult({
+ done: true,
+ payload: 'passphrase is not enabled on the device.',
+ });
+ break;
+ }
+
+ const passphraseState: string = passphraseStateRes.payload;
+
+ allowInputPassphrase.current = false;
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain,
+ connectId,
+ deviceId,
+ passphraseState,
+ });
+
+ if (!addressRes.success) {
+ hasContinue.current = false;
+ setTestSessionCountResult({
+ done: true,
+ payload: `GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ break;
+ }
+
+ // 查看一下之前的 passphraseState 是否还能用
+ allowInputPassphrase.current = false;
+ for (const item of passphraseStateList.current) {
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain,
+ connectId,
+ deviceId,
+ passphraseState: item.passphraseState,
+ });
+
+ if (!addressRes.success) {
+ hasContinue.current = false;
+ setTestSessionCountResult({
+ done: true,
+ payload: `address:${item.address} passphrase:${item.passphraseState} ${testChain} GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ break;
+ }
+
+ if (item.address !== addressRes.payload.address) {
+ hasContinue.current = false;
+ console.log('Test list:', passphraseStateList.current);
+ setTestSessionCountResult({
+ done: true,
+ payload: `address:${item.address} passphrase:${item.passphraseState} ${testChain} GetAddress 结果不一致!!!!`,
+ });
+ break;
+ }
+ }
+
+ passphraseStateList.current.push({
+ passphraseState,
+ address: addressRes.payload.address ?? '',
+ });
+ }
+ }, [SDK, selectedDevice?.connectId, selectedDevice?.features?.device_id, testChain]);
+
+ const ContentView = useMemo(() => {
+ let result;
+ if (testSessionCountResult?.done === undefined) {
+ result = '未测试';
+ } else if (testSessionCountResult?.done) {
+ result = '完成';
+ } else {
+ result = '进行中';
+ }
+
+ return (
+
+ Passphrase Session Test
+ 开始之后根据提示解锁硬件, 根据硬件提示确认 passphrase, 不用自己输入
+
+ 测试步骤:1. 从零开始创建钱包 passphrase 并记录下来,注意在硬件按确认 passphrase
+
+
+ 2. 每次创建完一个 passphrase 钱包,都会去检查一遍之前创建过的钱包,是否可以正常获取地址
+
+ 2.1 如果可以正常获取地址,测试个数 +1,继续创建下一个钱包
+ 2.2 如果获取失败需要重新输入 passphrase,测试结束
+
+ setTestChain(itemValue)}>
+
+
+
+
+
+
+
+ {`测试 ${passphraseStateList?.current?.length} 个 passphrase`}
+ {`${result}`}
+ {`测试结果 ${testSessionCountResult?.payload}`}
+
+
+
+ );
+ }, [testSessionCountResult?.done, testSessionCountResult?.payload, testChain, testSessionCount]);
+
+ return ContentView;
+}
diff --git a/packages/connect-examples/expo-example/src/components/PassphraseTest/TestWalletChangeView.tsx b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestWalletChangeView.tsx
new file mode 100644
index 000000000..6ce234c3e
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/PassphraseTest/TestWalletChangeView.tsx
@@ -0,0 +1,282 @@
+import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
+import { CoreMessage, UI_EVENT, UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core';
+import { Button, Text, TextInput, View } from 'react-native';
+import { Picker } from '@react-native-picker/picker';
+import { useDevice } from '../../provider/DeviceProvider';
+import HardwareSDKContext from '../../provider/HardwareSDKContext';
+import { TestChain, requestAddress, styles } from './utils';
+import { CommonInput } from '../CommonInput';
+import { SwitchInput } from '../SwitchInput';
+
+export default function TestWalletChangeView() {
+ const { sdk: SDK } = useContext(HardwareSDKContext);
+ const { selectedDevice } = useDevice();
+
+ const [includeNormal, setIncludeNormal] = useState(true);
+ const [testChain, setTestChain] = useState('btc');
+ const [testWalletCount, setTestWalletCount] = useState(5);
+ const [changeWalletCount, setChangeWalletCount] = useState(20);
+ const [testResult, setTestResult] = useState<{
+ done?: boolean;
+ payload: string;
+ }>();
+
+ const passphraseStateList = useRef<
+ {
+ id: string;
+ passphrase: string;
+ passphraseState?: string;
+ address?: string;
+ emptyPassphraseState?: boolean;
+ }[]
+ >();
+ const currentPassphrase = useRef('');
+
+ useEffect(() => {
+ if (testResult?.done && !!SDK) {
+ SDK.removeAllListeners(UI_EVENT);
+ }
+ }, [SDK, testResult?.done]);
+
+ useEffect(() => {
+ // for testWalletCount count
+ passphraseStateList.current = [];
+ Array.from({ length: testWalletCount }).forEach((_, index) => {
+ if (index === 0 && includeNormal) {
+ passphraseStateList.current?.push({
+ id: `${index}`,
+ passphrase: '',
+ emptyPassphraseState: true,
+ });
+ } else {
+ passphraseStateList.current?.push({
+ id: `${index}`,
+ passphrase: `${index}`,
+ });
+ }
+ });
+ }, [includeNormal, testWalletCount]);
+
+ const beginTest = useCallback(async () => {
+ // for passphraseStateList
+ if (!SDK) return;
+ SDK.removeAllListeners(UI_EVENT);
+
+ setTestResult({
+ done: false,
+ payload: '测试中...',
+ });
+
+ const connectId = selectedDevice?.connectId ?? '';
+ const deviceId = selectedDevice?.features?.device_id ?? '';
+
+ SDK.on(UI_EVENT, (message: CoreMessage) => {
+ console.log('TopLEVEL EVENT ===>>>>: ', message);
+ if (message.type === UI_REQUEST.REQUEST_PIN) {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PIN,
+ payload: '@@ONEKEY_INPUT_PIN_IN_DEVICE',
+ });
+ }
+ if (message.type === UI_REQUEST.REQUEST_PASSPHRASE) {
+ setTimeout(() => {
+ SDK.uiResponse({
+ type: UI_RESPONSE.RECEIVE_PASSPHRASE,
+ payload: {
+ value: currentPassphrase.current,
+ },
+ });
+ }, 200);
+ }
+ });
+
+ const cacheAddress = new Map<
+ string,
+ {
+ address: string;
+ passphraseState?: string;
+ }
+ >();
+ for (const item of passphraseStateList.current ?? []) {
+ let passphraseState: string | undefined;
+ currentPassphrase.current = item.passphrase;
+ if (!item.emptyPassphraseState) {
+ const passphraseStateRes = await SDK.getPassphraseState(connectId, {});
+ if (!passphraseStateRes.success) {
+ setTestResult({
+ done: true,
+ payload: `getPassphraseState failed ${passphraseStateRes?.payload?.error}`,
+ });
+ return;
+ }
+ if (!passphraseStateRes.payload) {
+ setTestResult({
+ done: true,
+ payload: 'passphrase is not enabled on the device.',
+ });
+ return;
+ }
+
+ passphraseState = passphraseStateRes.payload;
+ }
+
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain,
+ connectId,
+ deviceId,
+ passphraseState,
+ useEmptyPassphrase: item.emptyPassphraseState,
+ });
+
+ if (!addressRes.success) {
+ setTestResult({
+ done: true,
+ payload: `GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ return;
+ }
+
+ cacheAddress.set(item.id, {
+ address: addressRes.payload.address ?? '',
+ passphraseState,
+ });
+ }
+
+ passphraseStateList.current = [
+ ...(passphraseStateList.current ?? []).map(current => {
+ const addressRes = cacheAddress.get(current.id);
+ if (!addressRes) return current;
+ return {
+ ...current,
+ address: addressRes?.address,
+ passphraseState: addressRes?.passphraseState,
+ };
+ }),
+ ];
+
+ // for changeWalletCount count
+ for (let index = 0; index < changeWalletCount; index++) {
+ const item = passphraseStateList.current?.[index % (testWalletCount ?? 0)];
+ if (!item) {
+ setTestResult({
+ done: true,
+ payload: 'changeWalletCount error',
+ });
+ return;
+ }
+
+ currentPassphrase.current = item.passphrase;
+ const addressRes = await requestAddress({
+ sdk: SDK,
+ testChain,
+ connectId,
+ deviceId,
+ passphraseState: item.passphraseState,
+ useEmptyPassphrase: item.emptyPassphraseState,
+ });
+
+ if (!addressRes.success) {
+ setTestResult({
+ done: true,
+ payload: `id:${item.id} passphrase:${item.passphrase} ${testChain} GetAddress 失败 ${addressRes?.payload?.error}`,
+ });
+ return;
+ }
+
+ if (item.address !== addressRes.payload.address) {
+ console.log('Test list:', passphraseStateList.current);
+ setTestResult({
+ done: true,
+ payload: `id:${item.id} passphrase:${item.passphrase} ${testChain} GetAddress 结果不一致!!!!`,
+ });
+ return;
+ }
+ }
+
+ setTestResult({
+ done: true,
+ payload: '测试结果正确',
+ });
+ }, [
+ SDK,
+ changeWalletCount,
+ selectedDevice?.connectId,
+ selectedDevice?.features?.device_id,
+ testChain,
+ testWalletCount,
+ ]);
+
+ const ContentView = useMemo(() => {
+ console.log();
+ let result;
+ if (testResult?.done === undefined) {
+ result = '未测试';
+ } else if (testResult?.done) {
+ result = '完成';
+ } else {
+ result = '进行中';
+ }
+
+ return (
+
+ Passphrase Change Test
+ 开始之后根据提示解锁硬件, 根据硬件提示确认 passphrase, 不用自己输入
+ 测试步骤:1. 创建 N 个钱包,包含一个非 passphrase 钱包
+ 2. 获取这些钱包的 address
+
+ 3. 根据测试切换次数,从第一个钱包开始一直切换。会自动填写 passphrase,注意在硬件按确认
+
+
+ Tips: 可以创建小数量的钱包,然后测试切换次数大一点,这样可以测试到不同的钱包之间切换
+ 可以创建大数量的钱包,测试缓存失效的情况
+
+
+ setTestWalletCount(parseInt(number))}
+ />
+ setChangeWalletCount(parseInt(number))}
+ />
+
+ setTestChain(itemValue)}>
+
+
+
+
+
+
+
+ {`${result}`}
+ {`测试结果 ${testResult?.payload}`}
+
+
+
+
+
+
+ );
+ }, [
+ beginTest,
+ changeWalletCount,
+ includeNormal,
+ testChain,
+ testResult?.done,
+ testResult?.payload,
+ testWalletCount,
+ ]);
+
+ return ContentView;
+}
diff --git a/packages/connect-examples/expo-example/src/components/PassphraseTest/index.tsx b/packages/connect-examples/expo-example/src/components/PassphraseTest/index.tsx
new file mode 100644
index 000000000..32d9ec4d7
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/PassphraseTest/index.tsx
@@ -0,0 +1,31 @@
+import { View } from 'react-native';
+import { useContext } from 'react';
+import { DeviceProvider, useDevice } from '../../provider/DeviceProvider';
+import { styles } from './utils';
+import HardwareSDKContext from '../../provider/HardwareSDKContext';
+import TestSessionView from './TestSessionView';
+import TestWalletChangeView from './TestWalletChangeView';
+import TestMultiWalletChangeView from './TestMultiWalletChangeView';
+
+export default function PassphraseTestScreen() {
+ return (
+
+
+
+
+
+ );
+}
+
+function PassphraseTestView() {
+ const { sdk: SDK, type } = useContext(HardwareSDKContext);
+ const { selectedDevice } = useDevice();
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/connect-examples/expo-example/src/components/PassphraseTest/utils.ts b/packages/connect-examples/expo-example/src/components/PassphraseTest/utils.ts
new file mode 100644
index 000000000..3aeb15517
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/PassphraseTest/utils.ts
@@ -0,0 +1,111 @@
+import { CoreApi } from '@onekeyfe/hd-core';
+import { StyleSheet } from 'react-native';
+
+export type TestChain = 'btc' | 'evm' | 'dot' | 'ada';
+
+export const requestAddress = async ({
+ sdk,
+ testChain,
+ connectId,
+ deviceId,
+ passphraseState,
+ useEmptyPassphrase,
+}: {
+ sdk: CoreApi;
+ testChain: TestChain;
+ connectId: string;
+ deviceId: string;
+ passphraseState?: string;
+ useEmptyPassphrase?: boolean;
+}) => {
+ if (testChain === 'evm') {
+ const evmAddressRes = await sdk.evmGetAddress(connectId, deviceId, {
+ path: "m/44'/60'/0'/0/1024",
+ showOnOneKey: false,
+ passphraseState,
+ useEmptyPassphrase,
+ });
+ return evmAddressRes;
+ }
+ if (testChain === 'dot') {
+ // @ts-expect-error
+ const ethAddressRes = await sdk.polkadotGetAddress(connectId, deviceId, {
+ path: "m/44'/354'/0'/0'/1001'",
+ prefix: '0',
+ network: 'polkadot',
+ showOnOneKey: false,
+ passphraseState,
+ useEmptyPassphrase,
+ });
+ return ethAddressRes;
+ }
+ if (testChain === 'ada') {
+ const adaAddressRes = await sdk.cardanoGetAddress(connectId, deviceId, {
+ addressParameters: {
+ addressType: 0,
+ path: "m/1852'/1815'/0'/0/1002",
+ stakingPath: "m/1852'/1815'/0'/2/0",
+ },
+ protocolMagic: 764824073,
+ networkId: 1,
+ derivationType: 1,
+ address: '',
+ showOnOneKey: false,
+ isCheck: false,
+ passphraseState,
+ useEmptyPassphrase,
+ });
+ return adaAddressRes;
+ }
+ const btcAddressRes = await sdk.btcGetAddress(connectId, deviceId, {
+ path: "m/44'/0'/0'/0/1000",
+ coin: 'btc',
+ showOnOneKey: false,
+ passphraseState,
+ useEmptyPassphrase,
+ });
+ return btcAddressRes;
+};
+
+export const styles = StyleSheet.create({
+ container: {
+ width: '100%',
+ },
+ subContainer: {
+ width: '100%',
+ marginTop: 16,
+ padding: 10,
+ backgroundColor: '#FFF',
+ borderColor: '#E0E0E0',
+ borderWidth: 1,
+ borderRadius: 8,
+ },
+ fullItem: {
+ width: '100%',
+ },
+ resultItem: {
+ width: '100%',
+ flexDirection: 'row',
+ },
+ item: {
+ margin: 16,
+ },
+ input: {
+ borderColor: '#E0E0E0',
+ borderWidth: 1,
+ borderRadius: 4,
+ padding: 6,
+ margin: 2,
+ fontSize: 16,
+ },
+ switchContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 8,
+ },
+ responseInput: {
+ backgroundColor: '#f7f7f7',
+ minHeight: 200,
+ },
+});
diff --git a/packages/connect-examples/expo-example/src/components/PlaygroundManager.tsx b/packages/connect-examples/expo-example/src/components/PlaygroundManager.tsx
index 16d811ba8..fd6552590 100644
--- a/packages/connect-examples/expo-example/src/components/PlaygroundManager.tsx
+++ b/packages/connect-examples/expo-example/src/components/PlaygroundManager.tsx
@@ -127,8 +127,7 @@ const PlaygroundManager = () => (
const styles = StyleSheet.create({
container: {
- flex: 1,
- gap: 24,
+ width: '100%',
},
flatListContainer: {
marginTop: 12,
diff --git a/packages/connect-examples/expo-example/src/components/SwitchInput.tsx b/packages/connect-examples/expo-example/src/components/SwitchInput.tsx
new file mode 100644
index 000000000..72579e470
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/components/SwitchInput.tsx
@@ -0,0 +1,23 @@
+import { StyleSheet, Switch, Text, View } from 'react-native';
+
+export type SwitchInputProps = {
+ value: boolean;
+ onToggle: (value: boolean) => void;
+ label: string;
+};
+export const SwitchInput = ({ value, onToggle, label }: SwitchInputProps) => (
+
+ {label}
+
+
+);
+const styles = StyleSheet.create({
+ commonParamItem: {
+ marginBottom: 16,
+ },
+ label: {
+ fontWeight: 'bold',
+ marginBottom: 4,
+ fontSize: 14,
+ },
+});
diff --git a/packages/connect-examples/expo-example/src/provider/DeviceProvider.tsx b/packages/connect-examples/expo-example/src/provider/DeviceProvider.tsx
index 2e6434773..09c898b5a 100644
--- a/packages/connect-examples/expo-example/src/provider/DeviceProvider.tsx
+++ b/packages/connect-examples/expo-example/src/provider/DeviceProvider.tsx
@@ -29,10 +29,10 @@ export const DeviceProvider = ({ children }: { children: React.ReactNode }) => {
return (
-
+ <>
{childMemo}
-
+ >
);
};
diff --git a/packages/connect-examples/expo-example/src/provider/SDKProvider/index.native.tsx b/packages/connect-examples/expo-example/src/provider/SDKProvider/index.native.tsx
index 27419ac97..21167d10e 100644
--- a/packages/connect-examples/expo-example/src/provider/SDKProvider/index.native.tsx
+++ b/packages/connect-examples/expo-example/src/provider/SDKProvider/index.native.tsx
@@ -34,7 +34,7 @@ const requestBluetoothScanPermission = async () => {
return granted;
};
-export default function Bluetooth() {
+export default function Bluetooth({ children }: { children: React.ReactNode }) {
const [sdk, createSDK] = React.useState();
const sdkInit = () => {
getHardwareSDKInstance().then(res => {
@@ -79,15 +79,14 @@ export default function Bluetooth() {
);
return (
-
-
-
- This is Bluetooth example page, will run on iOS / Android device.
- {/* {!!sdk && } */}
- {!!sdk && }
-
-
-
+
+ <>
+ This is Bluetooth example page, will run on iOS / Android device.
+ {sdk ? 'SDK loading complete' : 'SDK loading...'}
+ {/* {!!sdk && } */}
+ {children}
+ >
+
);
}
diff --git a/packages/connect-examples/expo-example/src/provider/SDKProvider/index.tsx b/packages/connect-examples/expo-example/src/provider/SDKProvider/index.tsx
index 7d2d48562..a8d795beb 100644
--- a/packages/connect-examples/expo-example/src/provider/SDKProvider/index.tsx
+++ b/packages/connect-examples/expo-example/src/provider/SDKProvider/index.tsx
@@ -7,7 +7,7 @@ import HardwareSDKContext from '../HardwareSDKContext';
import PlaygroundManager from '../../components/PlaygroundManager';
let isSdkInit = false;
-export default function USB() {
+export default function USB({ children }: { children: React.ReactNode }) {
const [sdk, createSDK] = useState();
const [lowLevelSDK, createLowLevelSDK] = useState();
const [useLowLevelApi, setUseLowLevelApi] = useState(false);
@@ -46,7 +46,8 @@ export default function USB() {
This is USB example page, will run on desktop browser.
- {showContent && }
+ {showContent ? 'SDK loading complete' : 'SDK loading...'}
+ {children}
{/* {showContent && } */}
diff --git a/packages/connect-examples/expo-example/src/views/HomeScreen.tsx b/packages/connect-examples/expo-example/src/views/HomeScreen.tsx
index b7360f06e..97aab80f6 100644
--- a/packages/connect-examples/expo-example/src/views/HomeScreen.tsx
+++ b/packages/connect-examples/expo-example/src/views/HomeScreen.tsx
@@ -1,21 +1,30 @@
import React from 'react';
-import { Button, StyleSheet, View } from 'react-native';
+import { Button, ScrollView, StyleSheet, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
-import SDKProvider from '../provider/SDKProvider';
+import PlaygroundManager from '../components/PlaygroundManager';
export default function HomeScreen() {
const navigation = useNavigation();
return (
-
- {/* @ts-expect-error */}
-
+
+
+ {/* @ts-expect-error */}
+
+
);
}
const styles = StyleSheet.create({
container: {
+ width: '100%',
display: 'flex',
backgroundColor: '#fff',
alignItems: 'center',
diff --git a/packages/connect-examples/expo-example/src/views/PassphraseTestScreen.tsx b/packages/connect-examples/expo-example/src/views/PassphraseTestScreen.tsx
new file mode 100644
index 000000000..ae1e360e8
--- /dev/null
+++ b/packages/connect-examples/expo-example/src/views/PassphraseTestScreen.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Button, ScrollView, StyleSheet, View } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import PassphraseTestView from '../components/PassphraseTest';
+
+export default function PassphraseTestScreen() {
+ const navigation = useNavigation();
+ return (
+
+
+ {/* @ts-expect-error */}
+ navigation.replace('Home')} />
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ width: '100%',
+ display: 'flex',
+ backgroundColor: '#fff',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 32,
+ },
+});