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
+
+