From aac96823c58aff4ee833fe03c1f1e3e464907e4e Mon Sep 17 00:00:00 2001 From: Pitrick3141 Date: Thu, 13 Aug 2026 12:39:53 +0800 Subject: [PATCH] feat: Allow automatically create a device key upon root key creation --- apps/wallet/src/App.tsx | 31 +++++- apps/wallet/src/components/flows.tsx | 25 +++++ apps/wallet/src/lib/create-identity.test.ts | 112 ++++++++++++++++++++ apps/wallet/src/lib/create-identity.ts | 63 +++++++++++ apps/wallet/src/lib/wallet-adapter.ts | 13 ++- packages/wallet-core/src/types.ts | 5 + packages/wallet-core/src/wallet-core.ts | 36 +++++++ packages/wallet-core/test/device-v2.test.ts | 37 +++++++ tests/browser-e2e/security.spec.ts | 32 ++++++ 9 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 apps/wallet/src/lib/create-identity.test.ts create mode 100644 apps/wallet/src/lib/create-identity.ts diff --git a/apps/wallet/src/App.tsx b/apps/wallet/src/App.tsx index bac4f76..12a8537 100644 --- a/apps/wallet/src/App.tsx +++ b/apps/wallet/src/App.tsx @@ -48,6 +48,7 @@ import { installAndActivateDevice, type DeviceInstallResult, } from './lib/device-management'; +import { createIdentityWithOptionalLocalDevice } from './lib/create-identity'; import { walletAdapter, type CreateIdentityInput, @@ -136,11 +137,35 @@ function App() { const scopedCount = identities.filter((identity) => identity.localScopes.length > 0).length; const createIdentity = async (input: CreateIdentityInput) => { - const created = await walletAdapter.createIdentity(input); + const created = await createIdentityWithOptionalLocalDevice(walletAdapter, input); await refresh(); - setSelectedId(created.localId); + setSelectedId(created.device?.localId ?? created.root.localId); setFlow(undefined); - setNotice({ kind: 'success', message: 'Independent identity created and registered.' }); + if (created.deviceState === 'active' && created.deviceError === undefined) { + setNotice({ + kind: 'success', + message: 'Identity created. Its separate device key is active on this device.', + }); + return; + } + if (created.device !== undefined) { + setNotice({ + kind: 'error', + message: + created.deviceState === 'pending-activation' + ? `Identity created and the device key was installed safely, but activation is pending. Open it and retry. ${created.deviceError ?? ''}`.trim() + : `Identity and device key created. ${created.deviceError ?? 'The local device setup needs attention.'}`, + }); + return; + } + if (created.deviceError !== undefined) { + setNotice({ + kind: 'error', + message: `Identity created and registered, but its device key could not be installed. You can add one from the identity details. ${created.deviceError}`, + }); + return; + } + setNotice({ kind: 'success', message: 'Independent root identity created and registered.' }); }; const retryRegistration = async (identity: LocalIdentitySummary) => { diff --git a/apps/wallet/src/components/flows.tsx b/apps/wallet/src/components/flows.tsx index 5357256..cc6ced2 100644 --- a/apps/wallet/src/components/flows.tsx +++ b/apps/wallet/src/components/flows.tsx @@ -10,6 +10,7 @@ import { Link2, Loader2, ShieldAlert, + Smartphone, Sparkles, Trash2, } from 'lucide-react'; @@ -35,6 +36,7 @@ interface CreateIdentityModalProps { export function CreateIdentityModal({ onClose, onCreate }: CreateIdentityModalProps) { const [label, setLabel] = useState(''); + const [withDeviceKey, setWithDeviceKey] = useState(true); const [withAgreementKey, setWithAgreementKey] = useState(false); const [state, setState] = useState(idle); @@ -45,6 +47,7 @@ export function CreateIdentityModal({ onClose, onCreate }: CreateIdentityModalPr const normalized = label.trim(); await onCreate({ ...(normalized === '' ? {} : { label: normalized }), + withDeviceKey, withAgreementKey, }); } catch (error) { @@ -79,6 +82,28 @@ export function CreateIdentityModal({ onClose, onCreate }: CreateIdentityModalPr

+ +