mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
docs: add WV-07 and WV-08 specs for proving machine in webview-app (#1861)
WV-07 covers SelfClient assembly: exporting useProvingStore from the browser entry point, mapping bridge adapters to SDK interfaces, creating a keychain-backed DocumentsAdapter via the existing secureStorage bridge, and wiring a real SelfClient in the webview-app provider. WV-08 covers the tunnel proving flow: replacing the mock 3-second timer with real provingMachine integration (register → disclose), storing Sumsub KYC results as KycData documents in native keychain, and driving UI from proving state transitions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,8 @@ On **March 11, 2026**, the active SDK scope changed to **WebView only, with no c
|
||||
| WV-04 | Define the host callback contract for launch, dismiss, and final result without native modules | Done | Medium | WV-02 | [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | Browser host fallback now uses `postMessage` for iframe/popup embedding while native transports keep their current behavior |
|
||||
| WV-05 | Integrate KYC provider Web SDK into ProviderLaunchScreen (Sumsub as default) | In Progress | High | WV-02 | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | Code complete on `feat/webview-sdk`, needs testing |
|
||||
| WV-06 | Wire KYC result through verification pipeline to host lifecycle callback | Ready | High | WV-05 | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | Sumsub result → kycResultStore → ConfirmIdentificationScreen → lifecycle.setResult() |
|
||||
| WV-07 | SelfClient assembly and proving machine export for WebView | Ready | High | SC-03 | [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | Export useProvingStore, map bridge→SDK adapters, keychain-backed documents, create real SelfClient |
|
||||
| WV-08 | Wire tunnel flow with real proving machine (register → disclose) | Ready | High | WV-07 | [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | Replace mock tunnel proving with real provingMachine: Sumsub → store doc → prove → disclose → result |
|
||||
|
||||
Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done`
|
||||
|
||||
@@ -65,6 +67,8 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done`
|
||||
| [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | WV-04 | Done |
|
||||
| [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | WV-05 | In Progress (code complete, needs testing) |
|
||||
| [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | WV-06 | Ready |
|
||||
| [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | WV-07 | Ready |
|
||||
| [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | WV-08 | Ready |
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
# WV-07: SelfClient Assembly & Proving Machine Export for WebView
|
||||
|
||||
> Last updated: 2026-03-24
|
||||
> Status: Ready
|
||||
> Priority: High
|
||||
> Depends on: SC-03 (Ready — creates `createWebNetworkAdapter()`)
|
||||
|
||||
- Workstream: webview
|
||||
- Backlog ID: WV-07
|
||||
- Owner: TBD
|
||||
- Branch: TBD
|
||||
- PR: TBD
|
||||
|
||||
## Why
|
||||
|
||||
The webview-app needs to run the proving machine for ZK proof generation, but
|
||||
today it has no `SelfClient` — only raw bridge adapters with incompatible
|
||||
interfaces. The proving machine (`useProvingStore`) is not exported from the
|
||||
SDK's browser entry point, and no adapter mapping exists to bridge the gap
|
||||
between `BridgeCryptoAdapter` / `BridgeStorageAdapter` and the SDK's
|
||||
`CryptoAdapter` / `DocumentsAdapter` interfaces.
|
||||
|
||||
This spec wires the missing pieces so WV-08 can drive the proving machine from
|
||||
the tunnel flow.
|
||||
|
||||
## What You Will Do
|
||||
|
||||
### 1. Export proving machine from browser entry point
|
||||
|
||||
**File:** `packages/mobile-sdk-alpha/src/browser.ts`
|
||||
|
||||
The proving machine is already browser-compatible (zero RN imports). Add value
|
||||
exports alongside the existing type-only exports:
|
||||
|
||||
```typescript
|
||||
// Existing (keep):
|
||||
export type { ProvingStateType, provingMachineCircuitType } from './proving/provingMachine';
|
||||
|
||||
// Add:
|
||||
export type { ProvingState } from './proving/provingMachine';
|
||||
export { useProvingStore, getPostVerificationRoute } from './proving/provingMachine';
|
||||
```
|
||||
|
||||
Additive-only — no RN regression risk.
|
||||
|
||||
### 2. Create a keychain-backed DocumentsAdapter
|
||||
|
||||
**Create:** `packages/webview-bridge/src/adapters/keychain-documents.ts`
|
||||
|
||||
The SDK's `DocumentsAdapter` interface requires structured document CRUD. The
|
||||
webview-app must persist passport data in the native keychain (security
|
||||
boundary), not IndexedDB. Use the existing `secureStorage` bridge domain.
|
||||
|
||||
```typescript
|
||||
import type { WebViewBridge } from '../bridge';
|
||||
import type { DocumentsAdapter, DocumentCatalog, IDDocument } from '@selfxyz/mobile-sdk-alpha';
|
||||
|
||||
const CATALOG_KEY = 'self_document_catalog';
|
||||
const DOC_PREFIX = 'self_doc_';
|
||||
|
||||
export function createKeychainDocumentsAdapter(bridge: WebViewBridge): DocumentsAdapter {
|
||||
async function storageGet(key: string): Promise<string | null> {
|
||||
const result = await bridge.request<{ value: string | null }>(
|
||||
'secureStorage', 'get', { key },
|
||||
);
|
||||
return result?.value ?? null;
|
||||
}
|
||||
|
||||
async function storageSet(key: string, value: string): Promise<void> {
|
||||
await bridge.request('secureStorage', 'set', { key, value });
|
||||
}
|
||||
|
||||
async function storageRemove(key: string): Promise<void> {
|
||||
await bridge.request('secureStorage', 'remove', { key });
|
||||
}
|
||||
|
||||
return {
|
||||
async loadDocumentCatalog(): Promise<DocumentCatalog> {
|
||||
const raw = await storageGet(CATALOG_KEY);
|
||||
return raw ? JSON.parse(raw) : { documents: [], selectedId: null };
|
||||
},
|
||||
async saveDocumentCatalog(catalog: DocumentCatalog): Promise<void> {
|
||||
await storageSet(CATALOG_KEY, JSON.stringify(catalog));
|
||||
},
|
||||
async loadDocumentById(id: string): Promise<IDDocument | null> {
|
||||
const raw = await storageGet(`${DOC_PREFIX}${id}`);
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
},
|
||||
async saveDocument(id: string, doc: IDDocument): Promise<void> {
|
||||
await storageSet(`${DOC_PREFIX}${id}`, JSON.stringify(doc));
|
||||
},
|
||||
async deleteDocument(id: string): Promise<void> {
|
||||
await storageRemove(`${DOC_PREFIX}${id}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Export from `packages/webview-bridge/src/adapters/index.ts` barrel.
|
||||
|
||||
### 3. Create SDK adapter mapping functions
|
||||
|
||||
**Create:** `packages/webview-bridge/src/adapters/sdk-adapter-map.ts`
|
||||
|
||||
Bridge adapters have different interfaces from SDK adapters. Create thin mapping
|
||||
functions. The bridge crypto adapter already implements the right methods
|
||||
(`hash`, `sign`, `generateKey`, `getPublicKey`) — it just needs type coercion.
|
||||
|
||||
```typescript
|
||||
import type { WebViewBridge } from '../bridge';
|
||||
import type {
|
||||
Adapters,
|
||||
CryptoAdapter,
|
||||
AuthAdapter,
|
||||
NavigationAdapter,
|
||||
NetworkAdapter,
|
||||
NFCScannerAdapter,
|
||||
} from '@selfxyz/mobile-sdk-alpha';
|
||||
import { bridgeCryptoAdapter } from './crypto';
|
||||
import { bridgeAuthAdapter } from './auth';
|
||||
import { createKeychainDocumentsAdapter } from './keychain-documents';
|
||||
import {
|
||||
createWebAnalyticsAdapter,
|
||||
createWebNetworkAdapter, // from SC-03
|
||||
webNFCScannerShim,
|
||||
} from '@selfxyz/mobile-sdk-alpha/browser';
|
||||
|
||||
export interface CreateSdkAdaptersOpts {
|
||||
bridge: WebViewBridge;
|
||||
navigate: (path: string) => void;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
export function createSdkAdapters(opts: CreateSdkAdaptersOpts): Adapters {
|
||||
const { bridge, navigate, goBack } = opts;
|
||||
const bridgeCrypto = bridgeCryptoAdapter(bridge);
|
||||
|
||||
const crypto: CryptoAdapter = {
|
||||
hash: bridgeCrypto.hash,
|
||||
sign: bridgeCrypto.sign,
|
||||
generateKey: bridgeCrypto.generateKey,
|
||||
getPublicKey: bridgeCrypto.getPublicKey,
|
||||
};
|
||||
|
||||
const bridgeAuth = bridgeAuthAdapter(bridge);
|
||||
const auth: AuthAdapter = {
|
||||
getPrivateKey: bridgeAuth.getPrivateKey,
|
||||
};
|
||||
|
||||
const navigation: NavigationAdapter = {
|
||||
goBack,
|
||||
goTo: (routeName, params) => {
|
||||
const query = params ? `?${new URLSearchParams(params as Record<string, string>)}` : '';
|
||||
navigate(`/${routeName}${query}`);
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
scanner: webNFCScannerShim(),
|
||||
crypto,
|
||||
network: createWebNetworkAdapter(),
|
||||
auth,
|
||||
documents: createKeychainDocumentsAdapter(bridge),
|
||||
navigation,
|
||||
analytics: createWebAnalyticsAdapter(),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Export from `packages/webview-bridge/src/adapters/index.ts` barrel.
|
||||
|
||||
### 4. Replace SelfClientProvider with real SelfClient
|
||||
|
||||
**File:** `packages/webview-app/src/providers/SelfClientProvider.tsx`
|
||||
|
||||
Replace the current `SelfClientAdapters` bag-of-adapters with a real
|
||||
`SelfClient` instance created via `createSelfClient()`.
|
||||
|
||||
```typescript
|
||||
import { createSelfClient, createListenersMap } from '@selfxyz/mobile-sdk-alpha/browser';
|
||||
import { createSdkAdapters } from '@selfxyz/webview-bridge/adapters';
|
||||
|
||||
// Replace SelfClientAdapters type with SelfClient from SDK
|
||||
// Keep lifecycle, haptic, biometrics as supplementary adapters
|
||||
// (they are WebView-specific and not part of SDK Adapters interface)
|
||||
|
||||
export interface WebViewAdapters {
|
||||
client: SelfClient; // real SDK client with provingMachine access
|
||||
lifecycle: BridgeLifecycleAdapter;
|
||||
haptic: BridgeHapticAdapter;
|
||||
biometrics: BridgeBiometricsAdapter;
|
||||
}
|
||||
```
|
||||
|
||||
The `SelfClient` instance gives webview-app access to:
|
||||
- `client.useProvingStore` — Zustand hook for proving state
|
||||
- `client.getProvingState()` — snapshot accessor
|
||||
- `client.emit()` / `client.on()` — event system
|
||||
- All internal stores and adapters the proving machine needs
|
||||
|
||||
### 5. Add dependencies and Buffer polyfill to webview-app
|
||||
|
||||
**File:** `packages/webview-app/package.json`
|
||||
|
||||
Add packages externalized by the SDK's tsup build:
|
||||
|
||||
| Package | Version | Why |
|
||||
|--------------------|---------|----------------------------------|
|
||||
| `socket.io-client` | ^4.8.3 | TEE status WebSocket listener |
|
||||
| `xstate` | ^5.20.2 | Internal state machine |
|
||||
| `node-forge` | ^1.3.3 | AES-256-GCM encryption |
|
||||
| `buffer` | ^6.0.3 | Node.js Buffer polyfill |
|
||||
| `elliptic` | ^6.5.4 | Crypto ops via @selfxyz/common |
|
||||
|
||||
`zustand` and `@selfxyz/common` are already deps.
|
||||
|
||||
**File:** `packages/webview-app/src/main.tsx`
|
||||
|
||||
Add at the very top (before any other imports):
|
||||
|
||||
```typescript
|
||||
import { Buffer } from 'buffer';
|
||||
globalThis.Buffer = Buffer;
|
||||
```
|
||||
|
||||
The proving machine uses `Buffer` at lines 196, 219, 567, 606, 826.
|
||||
|
||||
### 6. Update SelfClientProvider to pass config
|
||||
|
||||
**File:** `packages/webview-app/src/providers/SelfClientProvider.tsx`
|
||||
|
||||
Pass required config fields to `createSelfClient()`:
|
||||
|
||||
```typescript
|
||||
const client = useMemo(() => {
|
||||
const adapters = createSdkAdapters({ bridge, navigate, goBack: () => navigate(-1) });
|
||||
const listeners = createListenersMap();
|
||||
return createSelfClient({
|
||||
config: {
|
||||
platform: 'webview',
|
||||
debug: import.meta.env.DEV,
|
||||
env: verificationRequest?.env ?? 'prod',
|
||||
},
|
||||
adapters,
|
||||
listeners,
|
||||
});
|
||||
}, [bridge, navigate]);
|
||||
```
|
||||
|
||||
## Files You Will Create
|
||||
|
||||
| File | What | Risk |
|
||||
|-----------------------------------------------------------|---------------------------------------------------|----------|
|
||||
| `packages/webview-bridge/src/adapters/keychain-documents.ts` | Keychain-backed DocumentsAdapter via secureStorage | **Low** |
|
||||
| `packages/webview-bridge/src/adapters/sdk-adapter-map.ts` | Bridge→SDK adapter mapping + factory | **Low** |
|
||||
|
||||
## Files You Will Modify
|
||||
|
||||
| File | Change | Risk |
|
||||
|--------------------------------------------------------------|------------------------------------------------------|------------|
|
||||
| `packages/mobile-sdk-alpha/src/browser.ts` | Add `useProvingStore`, `ProvingState` exports | **Low** |
|
||||
| `packages/webview-bridge/src/adapters/index.ts` | Add barrel exports for new adapters | **Low** |
|
||||
| `packages/webview-app/src/providers/SelfClientProvider.tsx` | Replace adapter bag with real SelfClient | **Medium** |
|
||||
| `packages/webview-app/package.json` | Add 5 dependencies | **Low** |
|
||||
| `packages/webview-app/src/main.tsx` | Add Buffer polyfill (2 lines) | **Low** |
|
||||
| `specs/projects/sdk/workstreams/webview/SPEC.md` | Add WV-07 to backlog | **None** |
|
||||
|
||||
## Files You Will NOT Modify
|
||||
|
||||
| File | Why |
|
||||
|---------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| `packages/mobile-sdk-alpha/src/proving/provingMachine.ts` | Engine is already browser-compatible; no changes needed |
|
||||
| `packages/mobile-sdk-alpha/src/client.ts` | `createSelfClient()` factory is correct as-is |
|
||||
| `packages/native-shell-android/**` | secureStorage handler already exists and handles JSON values |
|
||||
| `packages/native-shell-ios/**` | secureStorage handler already exists and handles JSON values |
|
||||
| `packages/webview-app/src/screens/**` | Screen wiring is WV-08 scope |
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No regressions in the RN app.** Browser.ts exports are additive. No
|
||||
existing exports change.
|
||||
- **Keychain is always native-managed.** Documents go through secureStorage
|
||||
bridge to native keychain. No IndexedDB fallback for passport data.
|
||||
- **Bridge protocol unchanged.** No new bridge domains. Uses existing
|
||||
`secureStorage` domain for document persistence.
|
||||
- **Don't wire screens.** This spec creates the SelfClient and makes
|
||||
provingMachine accessible. WV-08 wires screens to it.
|
||||
- **SC-03 must land first.** `createWebNetworkAdapter()` is imported by the
|
||||
adapter mapping layer.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
# SDK types still pass (additive exports only)
|
||||
cd packages/mobile-sdk-alpha && yarn types
|
||||
|
||||
# Bridge builds
|
||||
cd packages/webview-bridge && yarn build
|
||||
|
||||
# webview-app builds with new imports
|
||||
cd packages/webview-app && yarn build
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] `useProvingStore` and `ProvingState` exported from `browser.ts`
|
||||
- [ ] `createKeychainDocumentsAdapter()` persists documents via secureStorage bridge
|
||||
- [ ] `createSdkAdapters()` maps bridge adapters to SDK Adapters interface
|
||||
- [ ] `SelfClientProvider` creates a real `SelfClient` via `createSelfClient()`
|
||||
- [ ] Buffer polyfill added to webview-app entry point
|
||||
- [ ] `yarn types` clean in mobile-sdk-alpha
|
||||
- [ ] `yarn build` clean in webview-bridge and webview-app
|
||||
- [ ] Backlog row added in SPEC.md
|
||||
|
||||
## Status Log
|
||||
|
||||
- 2026-03-24: Plan created.
|
||||
@@ -0,0 +1,262 @@
|
||||
# WV-08: Wire Tunnel Flow with Real Proving Machine
|
||||
|
||||
> Last updated: 2026-03-24
|
||||
> Status: Ready
|
||||
> Priority: High
|
||||
> Depends on: WV-07 (Ready — SelfClient assembly + proving machine export)
|
||||
|
||||
- Workstream: webview
|
||||
- Backlog ID: WV-08
|
||||
- Owner: TBD
|
||||
- Branch: TBD
|
||||
- PR: TBD
|
||||
|
||||
## Why
|
||||
|
||||
The tunnel flow is currently a pure UI mockup — hardcoded data, a 3-second
|
||||
timer for "proving," and no real backend interaction. With WV-07 landing
|
||||
`SelfClient` and `useProvingStore` in webview-app, the tunnel flow can drive
|
||||
real ZK proof generation.
|
||||
|
||||
The tunnel flow is the first end-to-end integration: **Sumsub → store document
|
||||
in keychain → provingMachine (register) → disclose → lifecycle.setResult()**.
|
||||
Getting this working validates the entire WebView proving pipeline.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **WV-07 done** — `SelfClient` available in webview-app, `useProvingStore`
|
||||
accessible, keychain-backed documents adapter working
|
||||
- **WV-05 done** — Sumsub Web SDK integrated in `ProviderLaunchScreen`
|
||||
- **WV-06 done** — KYC result normalization into `KycProviderResult`
|
||||
|
||||
## What You Will Do
|
||||
|
||||
### 1. Wire Sumsub result → document storage
|
||||
|
||||
**File:** `packages/webview-app/src/screens/tunnel/` (new or modified screen)
|
||||
|
||||
After Sumsub returns a successful `KycProviderResult`:
|
||||
|
||||
1. Extract attestation data (`serializedApplicantInfo`, `signature`, `pubkey`)
|
||||
2. Transform into an `IDDocument` (using `@selfxyz/common` parsing utilities)
|
||||
3. Call `storePassportData(selfClient, document)` — this persists to native
|
||||
keychain via the `createKeychainDocumentsAdapter` from WV-07
|
||||
|
||||
The KYC provider result contains the fields the circuit needs: country, idType,
|
||||
idNumber, issuanceDate, expiryDate, fullName, dob, photoHash, phoneNumber,
|
||||
gender, address.
|
||||
|
||||
### 2. Replace mock TunnelProvingScreen with real proving
|
||||
|
||||
**File:** `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx`
|
||||
|
||||
Replace the 3-second timer mock with real proving machine integration:
|
||||
|
||||
```typescript
|
||||
import { useProvingStore } from '@selfxyz/mobile-sdk-alpha/browser';
|
||||
import { useSelfClient } from '../../providers/SelfClientProvider';
|
||||
|
||||
export const TunnelProvingScreen: React.FC = () => {
|
||||
const { client } = useSelfClient();
|
||||
const currentState = useProvingStore(s => s.currentState);
|
||||
const init = useProvingStore(s => s.init);
|
||||
const error_code = useProvingStore(s => s.error_code);
|
||||
const reason = useProvingStore(s => s.reason);
|
||||
|
||||
useEffect(() => {
|
||||
// Init proving machine with 'register' circuit type
|
||||
init(client, 'register');
|
||||
}, [client, init]);
|
||||
|
||||
// Drive ProofGenerationScreen UI from currentState
|
||||
// idle → parsing_id_document → fetching_data → validating_document
|
||||
// → init_tee_connexion → ready_to_prove → proving → post_proving → completed
|
||||
};
|
||||
```
|
||||
|
||||
**State → UI mapping:**
|
||||
|
||||
| provingMachine state | UI shown |
|
||||
|--------------------------|--------------------------------------------------|
|
||||
| `idle` | Loading spinner |
|
||||
| `parsing_id_document` | "Preparing document..." |
|
||||
| `fetching_data` | "Fetching verification data..." |
|
||||
| `validating_document` | "Validating document..." |
|
||||
| `init_tee_connexion` | "Connecting to prover..." |
|
||||
| `ready_to_prove` | "Ready" (auto-confirm or user confirms) |
|
||||
| `proving` | "Generating proof..." (with Euclid animation) |
|
||||
| `post_proving` | "Finalizing..." |
|
||||
| `completed` | Navigate to result screen (success) |
|
||||
| `error` / `failure` | Navigate to result screen (error with code) |
|
||||
| `passport_not_supported` | Show unsupported document error |
|
||||
| `passport_data_not_found`| Show missing document error |
|
||||
|
||||
### 3. Wire disclose flow after register
|
||||
|
||||
**File:** `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx`
|
||||
|
||||
The tunnel flow runs register → disclose in sequence. After `completed` state
|
||||
for register:
|
||||
|
||||
1. Listen for `completed` state on register circuit
|
||||
2. Re-init proving machine with `'disclose'` circuit type
|
||||
3. Show disclosure proving UI
|
||||
4. On disclose `completed`, navigate to result screen
|
||||
|
||||
Use `useProvingStore` subscription to detect state transitions:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
if (currentState === 'completed' && circuitPhase === 'register') {
|
||||
// Register done, start disclose
|
||||
setCircuitPhase('disclose');
|
||||
init(client, 'disclose');
|
||||
} else if (currentState === 'completed' && circuitPhase === 'disclose') {
|
||||
// Both done, navigate to result
|
||||
navigate('/tunnel/proof/result', { state: { success: true } });
|
||||
} else if (currentState === 'error' || currentState === 'failure') {
|
||||
navigate('/tunnel/proof/result', {
|
||||
state: { success: false, error_code, reason },
|
||||
});
|
||||
}
|
||||
}, [currentState, circuitPhase]);
|
||||
```
|
||||
|
||||
### 4. Update TunnelResultScreen with real result
|
||||
|
||||
**File:** `packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx`
|
||||
|
||||
Replace hardcoded success with navigation state from proving:
|
||||
|
||||
- Success: show "Identity Verified" with Euclid StatusState
|
||||
- Failure: show error with code and reason, offer retry
|
||||
- On "Continue": call `lifecycle.setResult()` then `lifecycle.dismiss()`
|
||||
|
||||
### 5. Wire tunnel route sequence
|
||||
|
||||
**File:** `packages/webview-app/src/App.tsx`
|
||||
|
||||
Update tunnel routes if needed. The flow becomes:
|
||||
|
||||
```
|
||||
/tunnel/tour/:step
|
||||
→ /tunnel/kyc
|
||||
→ /tunnel/registration/country
|
||||
→ /tunnel/registration/id-type
|
||||
→ /tunnel/provider (Sumsub — from WV-05)
|
||||
→ /tunnel/provider-result (normalize KYC result — from WV-06)
|
||||
→ /tunnel/proof/generating (real provingMachine — this spec)
|
||||
→ /tunnel/proof/result (real result — this spec)
|
||||
```
|
||||
|
||||
### 6. Handle user confirmation gate
|
||||
|
||||
**File:** `packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx`
|
||||
|
||||
The proving machine has a `ready_to_prove` state where it waits for user
|
||||
confirmation. Wire the receipt screen's "Verify" button to call
|
||||
`useProvingStore.getState().setUserConfirmed(client)` so the proving machine
|
||||
transitions from `ready_to_prove` → `proving`.
|
||||
|
||||
Alternatively, pass `userConfirmed: true` to `init()` to auto-confirm in the
|
||||
tunnel flow.
|
||||
|
||||
## Files You Will Modify
|
||||
|
||||
| File | Change | Risk |
|
||||
|----------------------------------------------------------------------------|-------------------------------------------------|------------|
|
||||
| `packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx` | Replace mock with real provingMachine | **Medium** |
|
||||
| `packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx` | Wire real result from proving state | **Low** |
|
||||
| `packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx` | Wire user confirmation to provingMachine | **Low** |
|
||||
| `packages/webview-app/src/App.tsx` | Update tunnel routes if needed | **Low** |
|
||||
| `specs/projects/sdk/workstreams/webview/SPEC.md` | Add WV-08 to backlog | **None** |
|
||||
|
||||
## Files You Will NOT Modify
|
||||
|
||||
| File | Why |
|
||||
|---------------------------------------------------------------|--------------------------------------------------------|
|
||||
| `packages/mobile-sdk-alpha/src/proving/provingMachine.ts` | Engine unchanged — consumed as-is |
|
||||
| `packages/webview-bridge/**` | Bridge layer unchanged — WV-07 already handled mapping |
|
||||
| `packages/native-shell-android/**` | No new native handlers needed |
|
||||
| `packages/native-shell-ios/**` | No new native handlers needed |
|
||||
| `packages/webview-app/src/screens/proving/ProvingScreen.tsx` | Non-tunnel proving screen — separate concern |
|
||||
| `packages/webview-app/src/providers/SelfClientProvider.tsx` | Already wired in WV-07 |
|
||||
|
||||
## Files You May Create
|
||||
|
||||
| File | What |
|
||||
|-----------------------------------------------------------------------|---------------------------------------------------|
|
||||
| `packages/webview-app/src/hooks/useProvingFlow.ts` | Optional: shared hook for register→disclose chain |
|
||||
| `packages/webview-app/src/screens/tunnel/TunnelProviderScreen.tsx` | If tunnel needs its own Sumsub launch screen |
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No provingMachine changes.** The engine is consumed as-is from
|
||||
mobile-sdk-alpha. If behavior doesn't match expectations, file a separate
|
||||
SDK Core issue.
|
||||
- **Tunnel flow is the proving integration point.** Do not change the
|
||||
non-tunnel `ProvingScreen` — that follows a different flow (external app
|
||||
requests proof of an already-registered document).
|
||||
- **Register then disclose.** The tunnel flow always runs both circuits in
|
||||
sequence. Register creates the on-chain identity, disclose proves specific
|
||||
attributes to the requesting party.
|
||||
- **Sumsub provides the document.** NFC scanning is not part of this flow.
|
||||
The `IDDocument` is constructed from Sumsub's KYC attestation, not from
|
||||
passport chip data.
|
||||
|
||||
## Resolved Questions
|
||||
|
||||
1. **IDDocument shape from Sumsub attestation** — `KycData` (from
|
||||
`@selfxyz/common/utils/types`) is already a subtype of `IDDocument`.
|
||||
Construct it directly from Sumsub's attestation:
|
||||
```typescript
|
||||
const kycData: KycData = {
|
||||
documentType: deserializeApplicantInfo(attestation.serializedApplicantInfo).idType,
|
||||
documentCategory: 'kyc',
|
||||
mock: false,
|
||||
signature: attestation.signature,
|
||||
pubkey: attestation.pubkey,
|
||||
serializedApplicantInfo: attestation.serializedApplicantInfo,
|
||||
};
|
||||
```
|
||||
`deserializeApplicantInfo()` from `@selfxyz/common/utils/kyc/api` parses
|
||||
the base64 blob into structured fields. The proving machine calls this
|
||||
internally when it needs circuit inputs. This is the same pattern the RN
|
||||
app uses (`app/src/hooks/useSumsubWebSocket.ts`).
|
||||
2. **DSC circuit** — Not needed for KYC documents. The tunnel flow runs
|
||||
`register → disclose` only. DSC is only relevant for NFC-scanned
|
||||
passports where the Document Signer Certificate needs updating.
|
||||
3. **Auto-confirm vs user gate** — Deferred. This is a design decision for
|
||||
when the full Euclid screen set is integrated. For now, use
|
||||
`userConfirmed: true` in the `init()` call to auto-confirm. The receipt
|
||||
screen already shows disclosures before proving starts.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
# webview-app builds
|
||||
cd packages/webview-app && yarn build
|
||||
|
||||
# Manual: launch tunnel flow in test app, verify:
|
||||
# 1. Sumsub completes → document stored in keychain
|
||||
# 2. Proving machine transitions through states
|
||||
# 3. Proof generated successfully (or meaningful error)
|
||||
# 4. Result screen shows real outcome
|
||||
# 5. lifecycle.setResult() sends proof to host
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] TunnelProvingScreen drives real provingMachine (no 3-second mock)
|
||||
- [ ] Sumsub result stored as IDDocument in native keychain via secureStorage
|
||||
- [ ] Register circuit runs to completion (or meaningful error state)
|
||||
- [ ] Disclose circuit runs after register completes
|
||||
- [ ] TunnelResultScreen shows real success/failure from proving state
|
||||
- [ ] `lifecycle.setResult()` called with proof data on success
|
||||
- [ ] Error/failure states show actionable UI
|
||||
- [ ] `yarn build` passes for webview-app
|
||||
- [ ] Backlog row added in SPEC.md
|
||||
|
||||
## Status Log
|
||||
|
||||
- 2026-03-24: Plan created.
|
||||
Reference in New Issue
Block a user