···104104 (invoke('sign_with_device_key', { data: Array.from(data) }) as Promise<number[]>).then(
105105 (bytes) => new Uint8Array(bytes),
106106 );
107107+108108+// ── perform_did_ceremony ─────────────────────────────────────────────────────
109109+110110+/**
111111+ * Successful result from the `perform_did_ceremony` Rust command.
112112+ * This is a pure data shape returned on success.
113113+ */
114114+export type DIDCeremonyResult = {
115115+ did: string;
116116+};
117117+118118+/**
119119+ * Error returned by the `perform_did_ceremony` Rust command.
120120+ *
121121+ * Serialized as `{ code: "NO_RELAY_SIGNING_KEY" }` etc. by the Rust backend.
122122+ * The `message` field is present only on the NETWORK_ERROR variant.
123123+ * This is a pure data shape used for error handling.
124124+ */
125125+export type DIDCeremonyError = {
126126+ code:
127127+ | 'KEY_NOT_FOUND'
128128+ | 'RELAY_KEY_FETCH_FAILED'
129129+ | 'NO_RELAY_SIGNING_KEY'
130130+ | 'SIGNING_FAILED'
131131+ | 'DID_CREATION_FAILED'
132132+ | 'KEYCHAIN_ERROR'
133133+ | 'NETWORK_ERROR';
134134+ message?: string;
135135+};
136136+137137+/**
138138+ * Perform the DID ceremony: fetch relay key, build signed genesis op, post to relay,
139139+ * persist DID and upgraded session token in Keychain.
140140+ *
141141+ * On success, the DID and new session token are stored in Keychain by the Rust backend.
142142+ * On failure, the Promise rejects with a `DIDCeremonyError`.
143143+ */
144144+export const performDIDCeremony = (handle: string): Promise<DIDCeremonyResult> =>
145145+ invoke('perform_did_ceremony', { handle });
+18-4
apps/identity-wallet/src/routes/+page.svelte
···44 import EmailScreen from '$lib/components/onboarding/EmailScreen.svelte';
55 import HandleScreen from '$lib/components/onboarding/HandleScreen.svelte';
66 import LoadingScreen from '$lib/components/onboarding/LoadingScreen.svelte';
77+ import DIDCeremonyScreen from '$lib/components/onboarding/DIDCeremonyScreen.svelte';
88+ import DIDSuccessScreen from '$lib/components/onboarding/DIDSuccessScreen.svelte';
79 import { createAccount, type CreateAccountError } from '$lib/ipc';
810911 // ── Onboarding step type ─────────────────────────────────────────────────
···2224 | 'email'
2325 | 'handle'
2426 | 'loading'
2525- | 'did_ceremony';
2727+ | 'did_ceremony'
2828+ | 'did_success'
2929+ | 'shamir_backup';
26302731 // ── State ────────────────────────────────────────────────────────────────
28322933 let step = $state<OnboardingStep>('welcome');
3030- let form = $state({ claimCode: '', email: '', handle: '' });
3434+ let form = $state({ claimCode: '', email: '', handle: '', did: '' });
31353236 /**
3337 * Per-field error messages displayed by each screen.
···135139 {:else if step === 'loading'}
136140 <LoadingScreen statusText="Creating your account…" />
137141 {:else if step === 'did_ceremony'}
142142+ <DIDCeremonyScreen
143143+ handle={form.handle}
144144+ onsuccess={(did) => { form.did = did; step = 'did_success'; }}
145145+ />
146146+ {:else if step === 'did_success'}
147147+ <DIDSuccessScreen
148148+ did={form.did}
149149+ oncontinue={() => { step = 'shamir_backup'; }}
150150+ />
151151+ {:else if step === 'shamir_backup'}
138152 <div class="placeholder">
139139- <h2>Account Created!</h2>
140140- <p>DID ceremony coming soon…</p>
153153+ <h2>Backup</h2>
154154+ <p>Shamir backup coming soon…</p>
141155 </div>
142156 {/if}
143157</div>