@terminal3/t3n-sdk 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +33 -796
  2. package/dist/index.d.ts +281 -115
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +1 -1
  5. package/package.json +10 -60
  6. package/README.OIDC.md +0 -216
  7. package/dist/demo.d.ts +0 -25
  8. package/dist/src/client/actions.d.ts +0 -31
  9. package/dist/src/client/config.d.ts +0 -33
  10. package/dist/src/client/contract-response.d.ts +0 -59
  11. package/dist/src/client/delegation.d.ts +0 -388
  12. package/dist/src/client/encryption.d.ts +0 -30
  13. package/dist/src/client/handlers.d.ts +0 -73
  14. package/dist/src/client/index.d.ts +0 -13
  15. package/dist/src/client/org-data.d.ts +0 -276
  16. package/dist/src/client/request-parser.d.ts +0 -48
  17. package/dist/src/client/t3n-client.d.ts +0 -544
  18. package/dist/src/client/transport.d.ts +0 -131
  19. package/dist/src/config/index.d.ts +0 -82
  20. package/dist/src/config/loader.d.ts +0 -8
  21. package/dist/src/config/types.d.ts +0 -25
  22. package/dist/src/index.d.ts +0 -39
  23. package/dist/src/types/auth.d.ts +0 -66
  24. package/dist/src/types/index.d.ts +0 -45
  25. package/dist/src/types/kyc.d.ts +0 -135
  26. package/dist/src/types/org-data.d.ts +0 -180
  27. package/dist/src/types/session.d.ts +0 -24
  28. package/dist/src/types/token.d.ts +0 -102
  29. package/dist/src/types/user.d.ts +0 -236
  30. package/dist/src/utils/contract-version.d.ts +0 -5
  31. package/dist/src/utils/crypto.d.ts +0 -52
  32. package/dist/src/utils/errors.d.ts +0 -144
  33. package/dist/src/utils/index.d.ts +0 -10
  34. package/dist/src/utils/logger.d.ts +0 -102
  35. package/dist/src/utils/redaction.d.ts +0 -13
  36. package/dist/src/utils/session.d.ts +0 -37
  37. package/dist/src/utils/shape.d.ts +0 -30
  38. package/dist/src/wasm/index.d.ts +0 -5
  39. package/dist/src/wasm/interface.d.ts +0 -110
  40. package/dist/src/wasm/loader.d.ts +0 -43
  41. package/dist/src/wasm/quote-verifier/quote_verifier_bytes.d.ts +0 -1
  42. package/dist/src/wasm/quote-verifier-loader.d.ts +0 -58
package/README.md CHANGED
@@ -9,7 +9,6 @@ A minimal TypeScript SDK that mirrors the server's RPC handler approach, keeping
9
9
  - **WASM-Powered**: All cryptographic complexity and state machine logic isolated in WASM components
10
10
  - **Type Safe**: Full TypeScript support with comprehensive type definitions
11
11
  - **Secure**: Encrypted communication with T3n nodes
12
- - **Extensible**: Easy to add new authentication methods without changing TypeScript code
13
12
 
14
13
  ## Installation
15
14
 
@@ -42,7 +41,7 @@ const client = new T3nClient({
42
41
  },
43
42
  });
44
43
 
45
- await client.startHandshake();
44
+ await client.handshake();
46
45
 
47
46
  const did = await client.authenticate(
48
47
  createEthAuthInput(eth_get_address(privateKey))
@@ -54,6 +53,7 @@ const did = await client.authenticate(
54
53
  ```typescript
55
54
  import {
56
55
  T3nClient,
56
+ loadWasmComponent,
57
57
  createEthAuthInput,
58
58
  eth_get_address,
59
59
  metamask_sign,
@@ -69,7 +69,7 @@ const client = new T3nClient({
69
69
  },
70
70
  });
71
71
 
72
- await client.startHandshake();
72
+ await client.handshake();
73
73
  const did = await client.authenticate(
74
74
  createEthAuthInput(eth_get_address(privateKey))
75
75
  );
@@ -80,42 +80,39 @@ const did = await client.authenticate(
80
80
  ```typescript
81
81
  import { createOidcAuthInput } from "@terminal3/t3n-sdk";
82
82
 
83
- const oidcCredentials = {
84
- provider: "google",
85
- idToken: "jwt_token_here",
86
- };
87
-
88
- const did = await client.authenticate(createOidcAuthInput(oidcCredentials));
83
+ // `client` is an already-handshaked T3nClient (see Quick Start above).
84
+ const did = await client.authenticate(
85
+ createOidcAuthInput({
86
+ provider: "google",
87
+ // The T3n node mints a session-binding nonce. Pass it to your provider's
88
+ // authorization request and return the resulting id_token JWT.
89
+ getIdToken: async (nonce) => getGoogleIdToken({ nonce }),
90
+ })
91
+ );
89
92
  ```
90
93
 
91
- ## Migrating from 1.x
94
+ ## Environments
95
+
96
+ The SDK targets the public T3n networks.
92
97
 
93
- `@terminal3/t3n-sdk@2.0.0` cuts over to `tee:user/contracts@2.0.0`
94
- (MAT-1374). The implicit-dispatch monolith `user-upsert` on the user
95
- contract was split into three explicit functions on the same
96
- contract:
98
+ - `testnet` the public test network, for integration and pre-production use.
99
+ - `production` the public mainnet network.
97
100
 
98
- - `otp-request` — request + dispatch an OTP code.
99
- - `otp-verify` redeem an OTP and bind the contact.
100
- - `user-upsert` (slim) Level 1 user-input ingest only. Rejects
101
- callers without a verified email with the typed
102
- `UserUpsertError { kind: "EmailNotVerified" }` (wire form
103
- `email_not_verified:<detail>`).
101
+ Select the network with `setEnvironment("testnet" | "production")` — this sets the
102
+ default node used by clients created afterwards. To target a specific node, pass an
103
+ explicit `baseUrl` to `new T3nClient({ baseUrl, })`; `baseUrl` takes precedence over
104
+ the environment default.
104
105
 
105
- If you used the typed `T3nClient` methods to wrap `executeAction`,
106
- the SDK now ships `client.otpRequest` / `client.otpVerify` /
107
- `client.submitUserInput` (plus a convenience
108
- `client.runOtpThenUserInput`) so you can migrate one call site at a
109
- time:
106
+ ## OTP-backed user flows
110
107
 
111
- | Pre-2.0.0 (`tee:user@1.5.0`) | 2.x (`tee:user@2.0.0`, contract 2.1.0 for discriminated OTP) |
112
- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
113
- | `executeAction({ function_name: "user-upsert", input: { profile: { email_address } } })` | `client.otpRequest({ emailChannel: { emailAddress } })` |
114
- | `executeAction({ function_name: "user-upsert", input: { profile, otp_code } })` | `client.otpVerify({ otpCode, request: { emailChannel: { emailAddress } } })` |
115
- | `executeAction({ function_name: "user-upsert", input: { profile, keys: { generic_api: { otp_channel: "sms" } } } })` | `client.otpRequest({ smsChannel: { phoneNumber } })` |
116
- | `executeAction({ function_name: "user-upsert", input: { profile } })` (post-OTP) | `client.submitUserInput({ profile })` |
108
+ `@terminal3/t3n-sdk` ships typed helpers for the explicit OTP roundtrip and the
109
+ slim Level-1 user-input ingest:
117
110
 
118
- ### Worked example: full email + L1 ingest
111
+ - `client.otpRequest` request and dispatch an OTP code to an email or SMS
112
+ channel.
113
+ - `client.otpVerify` — redeem an OTP and bind the verified contact.
114
+ - `client.submitUserInput` — Level-1 user-input ingest. Rejects callers without
115
+ a verified email with the typed `UserUpsertError({ kind: "EmailNotVerified" })`.
119
116
 
120
117
  ```typescript
121
118
  import { T3nClient, UserUpsertError } from "@terminal3/t3n-sdk";
@@ -130,787 +127,27 @@ await client.otpVerify({
130
127
  request: { emailChannel: { emailAddress: "alice@example.com" } },
131
128
  });
132
129
 
133
- // 2) Slim user-upsert: Level 1 user-input ingest. Rejects with
134
- // UserUpsertError(kind: "EmailNotVerified") if step 1 was skipped.
130
+ // 2) Slim user-upsert: Level-1 user-input ingest.
135
131
  try {
136
132
  const result = await client.submitUserInput({
137
133
  profile: {
138
134
  first_name: "Alice",
139
135
  last_name: "Smith",
140
136
  country_of_residence: "US",
141
- // ...other Level-1 fields
142
137
  },
143
138
  });
144
139
  console.log("tx:", result.txHash);
145
140
  } catch (err) {
146
141
  if (err instanceof UserUpsertError && err.kind === "EmailNotVerified") {
147
- // run otp-request + otp-verify, then retry.
142
+ // run otpRequest + otpVerify, then retry.
148
143
  }
149
144
  throw err;
150
145
  }
151
146
  ```
152
147
 
153
- For tests that "just want it to work", `runOtpThenUserInput` chains
154
- the three calls behind a single `getOtpCode` callback.
155
-
156
- ### Hard-error fields
157
-
158
- Passing any of these to the wrong function returns the typed
159
- `UserUpsertError(kind: "LegacyField")` (wire form
160
- `legacy_field:<detail>`):
161
-
162
- - `otp_code` to anything other than `otp-verify`.
163
- - `keys.generic_api.otp_channel` to anything (channel is now a
164
- top-level field).
165
-
166
- ### Raw `executeAction` callers
167
-
168
- If you bypass the typed wrappers, see the migration table above:
169
- the function names (`otp-request` / `otp-verify` / `user-upsert`)
170
- and JSON input shapes line up 1:1 with the wrappers' camelCase
171
- fields converted to `snake_case`.
172
-
173
- ## Architecture
174
-
175
- The T3n SDK follows the same architectural principles as the server's `rpc.rs`:
176
-
177
- - **Completely agnostic** about authentication methods (Ethereum, OIDC, etc.)
178
- - **No knowledge** of internal state machine phases or details
179
- - **Simply calls** WASM `next()` and eagerly tries to `finish()`
180
- - **Lets WASM handle** all the complexity internally
181
- - **Works with** completely opaque byte arrays (just like the WIT interface)
182
-
183
- ### What's Hidden in WASM (Everything Important)
184
-
185
- - All state machine phases and transitions
186
- - Authentication method-specific logic (challenge/response, OIDC flows, signature verification)
187
- - Cryptographic operations and key derivation
188
- - Protocol-specific message formatting and parsing
189
- - Error handling and retry logic
190
- - Internal state representations (all states are opaque byte arrays)
191
-
192
- ### What's Exposed in TypeScript (Minimal Surface)
193
-
194
- - Input helpers only: `createEthAuthInput`, `createOidcAuthInput`, and signer handlers
195
- - High-level status: Just mirrors server's `SessionStatus` enum
196
- - Simple API methods: `startHandshake()`, `authenticate()`
197
- - Basic configuration: `baseUrl`, `sessionId`
198
- - Opaque state management: Byte arrays we never interpret
199
-
200
- ## API Reference
201
-
202
- ### T3nClient
203
-
204
- The main client class for interacting with T3n nodes.
205
-
206
- #### Constructor
207
-
208
- ```typescript
209
- new T3nClient(config: T3nClientConfig)
210
- ```
211
-
212
- #### Methods
213
-
214
- - `startHandshake(): Promise<void>` - Establish secure session with the node
215
- - `authenticate(authInput: AuthInput): Promise<Did>` - Authenticate using provided credentials
216
- - `getSessionId(): SessionId | null` - Get the server-minted session ID (`null` until handshake completes; pentest M-1 / MAT-983 moved session-id minting from the client to the server)
217
- - `getStatus(): SessionStatus` - Get current session status
218
- - `getDid(): Did | null` - Get authenticated DID (null if not authenticated)
219
- - `isAuthenticated(): boolean` - Check if client is authenticated
220
-
221
- ### Types
222
-
223
- #### SessionStatus
224
-
225
- ```typescript
226
- enum SessionStatus {
227
- Init = 0,
228
- Encrypted = 1,
229
- Authenticated = 2,
230
- }
231
- ```
232
-
233
- #### AuthInput
234
-
235
- ```typescript
236
- enum AuthMethod {
237
- Ethereum = "eth",
238
- OIDC = "oidc",
239
- }
240
-
241
- interface EthAuthInput {
242
- method: AuthMethod.Ethereum;
243
- address: string;
244
- }
245
-
246
- interface OidcCredentials {
247
- provider: string;
248
- idToken: string;
249
- }
250
-
251
- interface OidcAuthInput {
252
- method: AuthMethod.OIDC;
253
- credentials: OidcCredentials;
254
- }
255
-
256
- type AuthInput = EthAuthInput | OidcAuthInput;
257
- ```
258
-
259
- Helper utilities:
260
-
261
- ```typescript
262
- createEthAuthInput(address: string): EthAuthInput;
263
- createOidcAuthInput(credentials: OidcCredentials): OidcAuthInput;
264
- ```
265
-
266
- ## Error Handling
267
-
268
- The SDK provides specific error types for different failure scenarios:
269
-
270
- ```typescript
271
- import {
272
- T3nError,
273
- SessionStateError,
274
- AuthenticationError,
275
- HandshakeError,
276
- RpcError
277
- } from '@terminal3/t3n-sdk';
278
-
279
- try {
280
- await client.authenticate(signer);
281
- } catch (error) {
282
- if (error instanceof AuthenticationError) {
283
- console.log('Authentication failed:', error.message);
284
- } else if (error instanceof SessionStateError) {
285
- console.log('Invalid session state:', error.currentState);
286
- }
287
- }
288
- ```
289
-
290
- ## Configuration
291
-
292
- ### T3nClientConfig
293
-
294
- ```typescript
295
- interface T3nClientConfig {
296
- baseUrl?: string; // T3n node URL (defaults to "http://localhost:3000" if not provided)
297
- wasmComponent: WasmComponent; // WASM component instance
298
- transport?: Transport; // Optional transport layer (uses HttpTransport with baseUrl if not provided)
299
- sessionId?: SessionId; // Optional custom session ID
300
- timeout?: number; // Request timeout (default: 30000ms)
301
- headers?: Record<string, string>; // Custom headers
302
- logLevel?: LogLevel; // Log level for this client (defaults to global log level, which is LogLevel.ERROR)
303
- logger?: Logger; // Optional custom logger (overrides logLevel if provided)
304
- handlers?: GuestToHostHandlers; // Optional guest-to-host request handlers
305
- }
306
- ```
307
-
308
- ### Example with Custom Configuration
309
-
310
- ```typescript
311
- import { T3nClient, LogLevel, eth_get_address, metamask_sign } from '@terminal3/t3n-sdk';
312
-
313
- const privateKey = process.env.T3N_DEMO_KEY!;
314
- const address = eth_get_address(privateKey);
315
-
316
- const client = new T3nClient({
317
- baseUrl: "https://t3n-node.example.com",
318
- wasmComponent: await loadWasmComponent(),
319
- timeout: 60000, // 60 second timeout
320
- logLevel: LogLevel.DEBUG, // Enable debug logging
321
- headers: {
322
- 'User-Agent': 'MyApp/1.0.0',
323
- 'X-Custom-Header': 'custom-value'
324
- },
325
- handlers: {
326
- EthSign: metamask_sign(address, undefined, privateKey),
327
- MlKemPublicKey: ml_kem_public_key(),
328
- Random: random(),
329
- }
330
- });
331
- ```
332
-
333
- ## Logging
334
-
335
- The T3n SDK provides flexible logging capabilities with hierarchical log levels, allowing you to control verbosity for debugging and monitoring purposes.
336
-
337
- ### Log Levels
338
-
339
- The SDK supports four log levels in hierarchical order (from most to least verbose):
340
-
341
- - **`LogLevel.DEBUG`** (0) - Most verbose, includes all log messages
342
- - **`LogLevel.INFO`** (1) - Informational messages, warnings, and errors
343
- - **`LogLevel.WARN`** (2) - Warnings and errors only
344
- - **`LogLevel.ERROR`** (3) - Errors only (default)
345
-
346
- Log levels are hierarchical: setting a level includes all higher-priority levels. For example, setting `LogLevel.INFO` will log INFO, WARN, and ERROR messages, but not DEBUG messages.
347
-
348
- ### Default Behavior
349
-
350
- By default, the SDK uses `LogLevel.ERROR`, which means only error messages are logged. This ensures minimal console output in production environments.
351
-
352
- ### Global Log Level Control
353
-
354
- The SDK provides a global log level that serves as the default for all loggers created without an explicit level. Use `setGlobalLogLevel()` to configure this default, typically at application startup.
355
-
356
- **How it works:**
357
-
358
- - `setGlobalLogLevel()` affects all loggers created via `getLogger()` or `createLogger()` without an explicit level
359
- - Only loggers created *after* calling `setGlobalLogLevel()` will use the new level
360
- - Existing logger instances retain their original log level
361
-
362
- **Interaction with per-component overrides:**
363
-
364
- - When creating a `T3nClient`, you can override the global level by providing `logLevel` in the config
365
- - Per-component `logLevel` takes precedence over the global setting
366
- - If neither is provided, the global default (`LogLevel.ERROR`) is used
367
-
368
- ```typescript
369
- import { setGlobalLogLevel, LogLevel, T3nClient } from '@terminal3/t3n-sdk';
370
-
371
- // Set global level at application startup
372
- setGlobalLogLevel(LogLevel.DEBUG);
373
-
374
- // This client will use DEBUG level (from global)
375
- const client1 = new T3nClient({
376
- wasmComponent: await loadWasmComponent(),
377
- });
378
-
379
- // This client overrides global level with INFO
380
- const client2 = new T3nClient({
381
- wasmComponent: await loadWasmComponent(),
382
- logLevel: LogLevel.INFO, // Overrides global DEBUG
383
- });
384
- ```
385
-
386
- ### Creating Logger Instances
387
-
388
- You can create logger instances programmatically:
389
-
390
- ```typescript
391
- import { createLogger, getLogger, LogLevel } from '@terminal3/t3n-sdk';
392
-
393
- // Create a logger with a specific level
394
- const debugLogger = createLogger(LogLevel.DEBUG);
395
-
396
- // Get a logger using the global log level
397
- const globalLogger = getLogger();
398
-
399
- // Use logger in handlers
400
- const handlers = {
401
- EthSign: metamask_sign(address, debugLogger),
402
- };
403
- ```
404
-
405
- ### Best Practices
406
-
407
- - **Production**: Use `LogLevel.ERROR` (default) to minimize console output
408
- - **Development**: Use `LogLevel.DEBUG` for detailed debugging information
409
- - **Staging**: Use `LogLevel.INFO` for monitoring without excessive verbosity
410
- - **Custom Logging**: Implement a custom logger to integrate with your logging infrastructure (e.g., Winston, Pino, or cloud logging services)
411
-
412
- ### Example: Environment-Based Logging
413
-
414
- ```typescript
415
- import { setGlobalLogLevel, LogLevel } from '@terminal3/t3n-sdk';
416
-
417
- // Set log level based on environment
418
- const logLevel = process.env.NODE_ENV === 'production'
419
- ? LogLevel.ERROR
420
- : LogLevel.DEBUG;
421
-
422
- setGlobalLogLevel(logLevel);
423
- ```
424
-
425
- ## Development
426
-
427
- ### Building
428
-
429
- ```bash
430
- pnpm build
431
- ```
432
-
433
- ### Testing
434
-
435
- ```bash
436
- pnpm test
437
- pnpm run test:coverage
438
- ```
439
-
440
- ### Linting
441
-
442
- ```bash
443
- pnpm lint
444
- pnpm run lint:fix
445
- ```
446
-
447
- ### Demo
448
-
449
- ```bash
450
- # Run the demo (builds first)
451
- pnpm demo
452
-
453
- # Run demo in development mode
454
- pnpm demo:dev
455
-
456
- # Run demo with real WASM component
457
- pnpm demo:real-wasm
458
-
459
- # Run demo with real WASM component and upsert profile
460
- pnpm demo:real-wasm --upsert
461
-
462
- # Run demo with real WASM component and upsert profile from file
463
- pnpm demo:real-wasm --upsert path/to/profile.json
464
-
465
- # Run demo with real WASM component, upsert profile, and specify email
466
- pnpm demo:real-wasm --upsert --email user@example.com
467
-
468
- # Run demo with real WASM component, upsert profile from file, and specify email
469
- pnpm demo:real-wasm --upsert path/to/profile.json --email user@example.com
470
-
471
- # Provider webhook (default: user update only; no OID4VP present)
472
- pnpm demo:real-wasm --provider
473
-
474
- # Provider webhook: user update then OID4VP present (add --oid4vp or --oid4vp-json)
475
- pnpm demo:real-wasm --provider --oid4vp
476
-
477
- # Provider webhook with custom user-update payload from file
478
- pnpm demo:real-wasm --provider --user-update-json path/to/user-update.json
479
-
480
- # Provider with custom OID4VP payload (runs user update then OID4VP with this file)
481
- pnpm demo:real-wasm --provider --oid4vp-json path/to/oid4vp.json
482
-
483
- # OID4VP present only (skip user update; use existing DID)
484
- pnpm demo:real-wasm --provider --oid4vp-only --did did:t3n:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
485
-
486
- # Provider with custom signing key (must match TEE provider config public_key)
487
- pnpm demo:real-wasm --provider --provider-signing-key 0x0123...your32bytehex
488
-
489
- # Provider webhook call with custom email
490
- pnpm demo:real-wasm --provider --email test@example.com
491
-
492
- # Provider webhook call with custom provider ID
493
- pnpm demo:real-wasm --provider --provider-id t3 --email test@example.com
494
-
495
- # Get profile after authentication (requires session authentication)
496
- pnpm demo:real-wasm --get-profile --email test@example.com
497
-
498
- # Get profile with JSON path filters
499
- pnpm demo:real-wasm --get-profile --email test@example.com --filter "$.first_name" --filter "$.email_address"
500
-
501
- # Provider webhook + Get profile (email automatically passed from provider call)
502
- pnpm demo:real-wasm --provider --get-profile
503
- ```
504
-
505
- #### Provider and OID4VP
506
-
507
- With `--provider`, the demo runs a **user update** (create/update profile) by default. No OID4VP present request is sent unless you pass an OID4VP-related flag:
508
-
509
- 1. **User update only (default)** – POST a configurable payload (`profile`, `attestations`, `credentials`) to the provider webhook. The TEE creates or updates the profile and returns a `did` and optional `tx_hash`.
510
-
511
- 2. **User update then OID4VP present** – When you pass **`--oid4vp`** or **`--oid4vp-json <path>`**, after step 1 the demo also sends an `oid4vp_present` request with `profile.did` from step 1, plus `dcql_query`, `response_uri`, `nonce`, and `client_id`.
512
-
513
- 3. **OID4VP present only** – When you pass **`--oid4vp-only --did <did>`**, the demo skips user update and sends only the OID4VP present request for the given DID.
514
-
515
- Payloads are configurable via **defaults** or **JSON files** (`--user-update-json`, `--oid4vp-json`). The request body is signed with EIP-191 using the provider’s EOA key. The key must match `auth_method.public_key` for the provider in the TEE's provider config. By default the demo uses the test key (`0x01`×32); override with `--provider-signing-key` or `PROVIDER_SIGNING_KEY` for other environments.
516
-
517
- ```bash
518
- # User update only (default)
519
- pnpm demo:real-wasm --provider
520
-
521
- # User update then OID4VP present (default OID4VP payload)
522
- pnpm demo:real-wasm --provider --oid4vp
523
-
524
- # User update then OID4VP present (custom OID4VP JSON)
525
- pnpm demo:real-wasm --provider --oid4vp-json ./my-oid4vp.json
526
-
527
- # OID4VP only for an existing DID
528
- pnpm demo:real-wasm --provider --oid4vp-only --did did:t3n:your-uuid-here
529
- ```
530
-
531
- #### Demo Command-Line Arguments
532
-
533
- The demo supports the following command-line arguments:
534
-
535
- - `--upsert` or `-u`: Enable upsert profile action after authentication. Optionally provide a JSON file path to load profile data from.
536
-
537
- - `--email` or `-e`: Specify email address for profile operations. If not provided, the demo will prompt you interactively.
538
-
539
- - `--provider` or `-p`: Call the provider webhook endpoint. This simulates a third-party provider (e.g., KYC service) sending user data to T3n. The webhook call:
540
- - Signs the payload with an EOA signature using a test private key (or `--provider-signing-key` / `PROVIDER_SIGNING_KEY`)
541
- - Sends profile data to `/api/{provider_id}` endpoint
542
- - Returns a DID and transaction hash
543
- - Does not require session authentication
544
- - **Default flow:** With `--provider` only, the demo runs **user update only** (create/update profile). No OID4VP present request is sent.
545
- - To also run **OID4VP present** after user update, pass **`--oid4vp`** (default OID4VP payload) or **`--oid4vp-json <path>`** (custom payload).
546
- - To run **only** OID4VP present (no user update), use **`--oid4vp-only`** with **`--did <did>`**.
547
-
548
- - `--provider-id`: Specify the provider ID for webhook calls (default: "t3"). Used with `--provider` flag.
549
-
550
- - `--user-update-json`: Path to a JSON file for the user-update (create profile) payload. Shape: `{ "profile": { ... }, "attestations": [ ... ], "credentials": [ ... ] }`. If omitted, default payload is used (includes a test SD-JWT so OID4VP with default DCQL can be satisfied).
551
-
552
- - `--oid4vp-json`: Path to a JSON file for the OID4VP present payload. When running the full two-step flow, `profile.did` is always set from step 1. If omitted, default payload is used (dcql_query, response_uri from `VP_VERIFIER_URL`, nonce, client_id). **Presence of this flag (with a path) also enables the OID4VP present step** after user update.
553
-
554
- - `--oid4vp`: With `--provider`, run user update then OID4VP present using the default OID4VP payload. Use this to trigger the full two-step flow without providing `--oid4vp-json`.
555
-
556
- - `--oid4vp-only`: With `--provider`, skip step 1 and run only OID4VP present. Requires `--did <did>`.
557
-
558
- - `--did`: DID to use for OID4VP when `--oid4vp-only` is set (e.g. `did:t3n:uuid`).
559
-
560
- - `--wait-tx`: After step 1 (user update), wait for transaction confirmation before step 2. Can also set env `WAIT_FOR_TX=true`. (Tx wait is not implemented; flag is accepted for future use.)
561
-
562
- - `--provider-signing-key`: EOA private key (32-byte hex, e.g. `0x01...01`) for signing provider webhooks. Must match the TEE’s provider config `auth_method.public_key` for the provider. Default is the test key used in integration. Can also set env `PROVIDER_SIGNING_KEY`.
563
-
564
- - `--script-version`: Optional override for contract script version used by session-based actions (`--upsert`, `--get-profile`, `--agent-auth`). If omitted, the demo resolves the latest version dynamically from `/api/contracts/current` for `tee:user/contracts`.
565
-
566
- - `--get-profile` or `-g`: Retrieve user profile using the session client. This command:
567
- - Requires authentication (handled automatically)
568
- - Uses email from provider webhook call or `--email` argument
569
- - Supports optional JSON path filtering
570
-
571
- - `--filter` or `-f`: Specify JSON path filters for get-profile command. Can be used multiple times to filter specific profile fields. Example: `--filter "$.first_name" --filter "$.email_address"`.
572
-
573
- **Combining Commands:**
574
-
575
- You can combine multiple flags for more complex workflows:
576
-
577
- ```bash
578
- # Provider webhook call followed by get-profile (email automatically passed)
579
- pnpm demo:real-wasm --provider --get-profile
580
-
581
- # Provider webhook with custom email, then get-profile with filters
582
- pnpm demo:real-wasm --provider --email test@example.com --get-profile --filter "$.first_name"
583
-
584
- # Upsert profile, then get-profile
585
- pnpm demo:real-wasm --upsert --email user@example.com --get-profile
586
- ```
587
-
588
- #### Demo Environment Variables
589
-
590
- The demo supports the following environment variables:
591
-
592
- - `CRYPTO_NODE_URL`: The base URL of the T3n crypto node API (defaults to `http://localhost:3000`).
593
- - `TEE_API_URL`: The base URL of the T3n TEE API endpoint (defaults to `http://localhost:3000/api`). Used by the `--provider` flag for webhook calls.
594
- - `VP_VERIFIER_URL`: Base URL for the OID4VP response endpoint (defaults to `http://localhost:8100`). The demo sets `response_uri` to `{VP_VERIFIER_URL}/post` in the default OID4VP payload.
595
- - `PROVIDER_SIGNING_KEY`: EOA private key (32-byte hex) for signing provider webhooks. Overrides the default test key when set.
596
- - `WAIT_FOR_TX`: When set to `"true"` or `"1"`, enables waiting for tx confirmation before OID4VP (same as `--wait-tx`).
597
- - `SCRIPT_VERSION`: Optional script version override for session-based demo actions. Priority is `--script-version` > `SCRIPT_VERSION` > dynamic latest lookup from `/api/contracts/current`.
598
- - `DEBUG`: Enable debug logging when set to `"true"` (defaults to disabled).
599
-
600
- Example usage:
601
-
602
- ```bash
603
- # With custom crypto node URL
604
- CRYPTO_NODE_URL=http://localhost:3000 pnpm demo:real-wasm
605
-
606
- # With custom TEE API URL for provider webhook calls
607
- TEE_API_URL=http://localhost:3000/api pnpm demo:real-wasm --provider
608
-
609
- # With custom VP verifier URL for OID4VP response_uri
610
- VP_VERIFIER_URL=http://localhost:8100 pnpm demo:real-wasm --provider
611
-
612
- # With custom provider signing key (must match TEE provider config)
613
- PROVIDER_SIGNING_KEY=0x0123... pnpm demo:real-wasm --provider
614
-
615
- # Override script version explicitly (otherwise latest is resolved dynamically)
616
- pnpm demo:real-wasm --get-profile --script-version 1.0.0
617
-
618
- # With debug logging enabled
619
- DEBUG=true pnpm demo:real-wasm
620
-
621
- # With both custom URLs and debug logging
622
- CRYPTO_NODE_URL=http://localhost:3000 TEE_API_URL=http://localhost:3000/api DEBUG=true pnpm demo:real-wasm --provider --get-profile
623
- ```
624
-
625
- #### Email Verification (OTP Flow)
626
-
627
- When upserting a profile with an email address (either for a new profile or when changing an existing email), the system requires email verification via OTP (One-Time Password):
628
-
629
- 1. **Initial Upsert**: When you run `--upsert` with an email, the system sends a 6-digit OTP code to the specified email address.
630
-
631
- 2. **OTP Prompt**: The demo will automatically detect that OTP verification is needed and prompt you:
632
-
633
- ```text
634
- 📧 Email verification required
635
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ address. The demo will prompt you to enter the 6-digit OTP code sent to your email.
636
-
637
- 3. **Enter OTP Code**: When prompted, enter the 6-digit code you received in your email.
638
-
639
- 4. **Verification**: The demo will automatically retry the upsert with your OTP code and complete the profile upsert.
640
-
641
- **Note**: In debug/mock mode (`DEBUG=true` or `MOCK_MODE=true`), the OTP code will be displayed in the console for testing purposes.
642
-
643
- **Note**: The demo supports interactive OIDC authentication. When you select OIDC authentication, the demo will:
644
-
645
- 1. Start a local web server on port 8081
646
- 2. Open your browser automatically
647
- 3. Guide you through the Google OAuth flow
648
- 4. Allow you to paste the ID token back into the terminal
649
-
650
- #### KYC Walkthrough (MAT-1371)
651
-
652
- The `--kyc-walkthrough` mode drives the full MetaMask KYC L1 + L2 flow against a running Trinity node, end-to-end, without needing the actual MetaMask front-end. It is useful for:
653
-
654
- - Smoke-testing a freshly-built node (`node/bin/start-local`) before pointing the real FE at it.
655
- - Reproducing edge cases (rejected KYC, OTP expiry, merge suggestions) deterministically.
656
- - Showing internal stakeholders what the L1 + L2 flow looks like without spinning up MetaMask Snap + Veriff sandbox.
657
-
658
- **Quick start (interactive):**
659
-
660
- ```bash
661
- pnpm demo:real-wasm --kyc-walkthrough
662
- ```
663
-
664
- The walkthrough then prompts at each step in the order Trinity expects:
665
-
666
- 1. **Handshake** + **authenticate** (existing prompts — wallet / OIDC).
667
- 2. **Email OTP** — request + verify on `tee:user/contracts::user-upsert` (email channel).
668
- 3. **Phone OTP** — request + verify on the same function (SMS channel via `keys.generic_api.otp_channel: "sms"` shadow). Gated by the contract on a verified email.
669
- 4. **Level 1 user-input** — single `user-upsert` call carrying `first_name`, `last_name`, `country_of_residence`, `document_issuance_country`, `ssn`, `address`.
670
- 5. **`create-kyc-provider-session`** ([MAT-1284](https://linear.app/terminal3/issue/MAT-1284)) — prints the Veriff `session_url`. The CLI does **not** auto-open the URL.
671
- 6. **`kyc-status` poll** — until terminal status (`verified`, `rejected`, or `orphan`) or the timeout fires. Each snapshot is logged.
672
-
673
- **Veriff is driven manually.** The CLI prints the `session_url` and waits — open it in your browser, drive the Veriff flow to the desired terminal state (approve / reject / abandon), then come back to the terminal. The poll loop will see the contract update and surface the terminal status.
674
-
675
- ##### KYC walkthrough flags
676
-
677
- | Flag | Default | Meaning |
678
- |---|---|---|
679
- | `--kyc-walkthrough` | off | Enable the walkthrough mode. Implicitly enabled by `--scenario`. |
680
- | `--scenario <name>` | none | One of `happy-path-l1`, `happy-path-l2`, `rejected`. See *Scenarios* below. |
681
- | `--auth-method <eth\|metamask\|oidc>` | interactive prompt | Skip the auth-method prompt. |
682
- | `--email <addr>` | interactive prompt (or `demo+kyc@example.com` in scenario mode) | Email to verify in the L1 OTP step. |
683
- | `--phone <e164>` | interactive prompt (or `+14155552671` in scenario mode) | Phone in E.164 format for the SMS OTP step. |
684
- | `--first-name <name>` | interactive prompt (or `Ada` in scenario mode) | L1 first name. |
685
- | `--last-name <name>` | interactive prompt (or `Lovelace` in scenario mode) | L1 last name. |
686
- | `--country <ISO-2>` | `US` | Convenience: applies to both `country_of_residence` and `document_issuance_country` unless overridden individually. |
687
- | `--country-of-residence <ISO-2>` | `--country` value | L1 residence country. |
688
- | `--document-issuance-country <ISO-2>` | `--country` value | L1 document issuance country. |
689
- | `--ssn <value>` | interactive prompt (or `123-45-6789` in scenario mode) | L1 SSN (9 digits or `XXX-XX-XXXX`). |
690
- | `--address <text>` | interactive prompt (or default address in scenario mode) | L1 residential address. |
691
- | `--kyc-provider-id <id>` | `veriff` | Provider id passed to `create-kyc-provider-session` and `kyc-status`. |
692
- | `--kyc-poll-fast-ms <ms>` | `2000` | Fast-cadence poll interval (T3-TS-026 §8.4). |
693
- | `--kyc-poll-slow-ms <ms>` | `5000` | Slow-cadence poll interval. |
694
- | `--kyc-poll-switch-at-ms <ms>` | `30000` | Elapsed threshold to switch from fast to slow. |
695
- | `--kyc-poll-timeout-ms <ms>` | `300000` | Total cap before `KycStatusTimeoutError` is raised. |
696
- | `--skip-create-kyc-session` | off | Stop after L1 — useful when no Veriff sandbox is configured. |
697
-
698
- ##### Scenarios
699
-
700
- Pre-baked shortcuts that auto-feed answers (mock-mode OTP codes, default L1 fields) so the walkthrough runs without operator input. All scenarios still require a running Trinity node configured with the **mock OTP provider** (otherwise the deterministic OTP code algorithm doesn't match what the node actually generated).
701
-
702
- | Scenario | What it does | Veriff |
703
- |---|---|---|
704
- | `happy-path-l1` | Email OTP → phone OTP → L1 upsert. Stops before `create-kyc-provider-session`. | Not exercised. |
705
- | `happy-path-l2` | Full L1 + L2 flow. Asserts `kyc-status` terminates with `verified`. | **Manual**: open the printed `session_url` and drive Veriff to approval. |
706
- | `rejected` | Full L1 + L2 flow. Asserts `kyc-status` terminates with `rejected`. | **Manual**: open the printed `session_url` and drive Veriff to a rejection. |
707
-
708
- Examples:
709
-
710
- ```bash
711
- # L1-only happy path — no Veriff sandbox needed
712
- pnpm demo:real-wasm --scenario happy-path-l1
713
-
714
- # Full L1 + L2 happy path — drive Veriff to approved manually
715
- pnpm demo:real-wasm --scenario happy-path-l2
716
-
717
- # Rejection scenario — drive Veriff to a rejection manually
718
- pnpm demo:real-wasm --scenario rejected
719
-
720
- # Custom inputs without a scenario
721
- pnpm demo:real-wasm --kyc-walkthrough \
722
- --auth-method eth \
723
- --email me@example.com \
724
- --phone +14155551234 \
725
- --country US \
726
- --first-name Ada --last-name Lovelace
727
- ```
728
-
729
- ##### Mock-mode OTP code
730
-
731
- When the Trinity node runs against the mock OTP provider (the default for `node/bin/start-local`), the OTP code is a **deterministic** 6-digit hash of the contact value. The walkthrough always prints the calculated mock code in the OTP-pending log line, so even without a scenario you can simply enter that code at the prompt to advance.
732
-
733
- The algorithm matches `node/wasm/src/otp.rs` byte-for-byte: `String((hash * 31 + byte_i) % 1_000_000).padStart(6, "0")` over the UTF-8 bytes of the contact (email address or E.164 phone).
734
-
735
- ##### Sample output (excerpt)
736
-
737
- ```text
738
- 🚦 KYC walkthrough mode enabled (MAT-1371)
739
- Scenario: happy-path-l1
740
-
741
- 📧 Step 5/8 — Email OTP: demo+kyc@example.com
742
- 5️⃣ Executing action: user-upsert...
743
- ✅ Action executed successfully
744
- 📨 Email verification required
745
- 📬 OTP code sent to: demo+kyc@example.com
746
- 📡 Channel: email
747
- 💡 Mock mode OTP code (deterministic): 482913
748
- 🤖 Scenario auto-feeding OTP code: 482913
749
- 🔄 Verifying Email OTP code...
750
- 5️⃣ Executing action: user-upsert...
751
- ✅ OTP verified successfully
752
- 📝 Transaction hash: tx:1:42
753
-
754
- 📱 Step 6/8 — Phone OTP: +14155552671
755
- 6️⃣ Executing action: user-upsert...
756
- ...
757
-
758
- 📋 KYC walkthrough summary
759
- ✅ email-otp demo+kyc@example.com
760
- ✅ phone-otp +14155552671
761
- ✅ l1-upsert tx:1:44
762
- ⏭️ create-kyc-session (scenario)
763
- ⏭️ kyc-status-poll (scenario)
764
- ```
765
-
766
- ##### Caveats
767
-
768
- - **No version bump.** The walkthrough is dev tooling — it does not introduce new `@terminal3/t3n-sdk` public-API surface, so `package.json` is not bumped.
769
- - **Spec alignment.** The walkthrough follows the as-built code, which differs from earlier T3-TS-026 versions on two points: `user-upsert` has no `op:` discriminator (dispatch is implicit on input shape), and the function for L2 session creation is `tee:user/contracts::create-kyc-provider-session` rather than `tee:kyc/contracts::create-session`. See [T3-TS-026 v0.6.0 changelog](../../docs/specs/T3-TS-026-kyc-frontend-integration.md) and [MAT-1374](https://linear.app/terminal3/issue/MAT-1374) for the deferred discriminator-vs-split decision.
770
- - **`STRICT_SCENARIO=true`** in the environment turns scenario assertion failures into a non-zero exit code (otherwise they're logged but the demo exits zero). `--scenario` always exits non-zero on a terminal-status mismatch.
771
-
772
- ### Tenant Developer Demo
773
-
774
- Three-entity walkthrough: an **Admin** admits a tenant, the **Tenant** registers the Duffel flight contract and seeds credentials, a **User** (created via `demo:dev`) authenticates and searches / books flights. Each step is a separate command so you can run them independently.
775
-
776
- #### Three entities
777
-
778
- | Entity | Key env var | Role |
779
- |---|---|---|
780
- | Admin | `ADMIN_KEY` | Signs `tenant.admit` via the admin API |
781
- | Tenant | `TENANT_KEY` | Registers contract, creates KV map, injects Duffel API key |
782
- | User | `USER_KEY` | Authenticates and calls `search-offers` / `book-offer` on behalf of an AI agent |
783
-
784
- #### Environment variables
785
-
786
- | Variable | Default | Used by |
787
- |---|---|---|
788
- | `CRYPTO_NODE_URL` | `http://localhost:3000` | all commands |
789
- | `ADMIN_KEY` | `0x000...0001` | `admit` |
790
- | `TENANT_KEY` | `0x000...0002` | `admit`, `setup` |
791
- | `DUFFEL_API_KEY` | `duffel_test_placeholder` | `setup` |
792
- | `FLIGHT_WASM_PATH` | _(unset)_ | `setup` — path to compiled `z-tenant-flight` WASM; falls back to a placeholder |
793
- | `USER_KEY` | `0x000...0003` | `search`, `book` — ETH key of a user already registered via `demo:dev` |
794
- | `OFFER_ID` | _(required)_ | `book` — offer ID printed by `search` |
795
- | `TOTAL_AMOUNT` | _(required)_ | `book` — total price printed by `search` |
796
- | `TOTAL_CURRENCY` | _(required)_ | `book` — currency code printed by `search` |
797
-
798
- #### Step 1 — Admit the tenant (Admin)
799
-
800
- ```bash
801
- ADMIN_KEY=0x... TENANT_KEY=0x... pnpm demo:tenant:admit
802
- ```
803
-
804
- #### Step 2 — Register contract + seed credentials (Tenant)
805
-
806
- ```bash
807
- TENANT_KEY=0x... DUFFEL_API_KEY=duffel_test_... FLIGHT_WASM_PATH=../z-tenant-flight/target/wasm32-wasip2/release/z_tenant_flight.wasm pnpm demo:tenant:setup
808
- ```
809
-
810
- This registers `z:<tid>:duffel-flight`, creates the `z:<tid>:secrets` KV map restricted to that contract, then calls `store-credentials` on the contract to write the Duffel API key into the map.
811
-
812
- #### Step 3 — Create the user (User)
813
-
814
- Use the standard user demo — no changes needed:
815
-
816
- ```bash
817
- USER_KEY=0x... pnpm demo:dev
818
- ```
819
-
820
- #### Step 4 — Search for flights (User / Agent)
821
-
822
- ```bash
823
- USER_KEY=0x... pnpm demo:tenant:search
824
- ```
825
-
826
- Prints up to 5 offers. The last line of output is the exact `book` command with the correct `OFFER_ID`, `TOTAL_AMOUNT`, and `TOTAL_CURRENCY` pre-filled.
827
-
828
- #### Step 5 — Book a flight (User / Agent)
829
-
830
- ```bash
831
- USER_KEY=0x... OFFER_ID=off_... TOTAL_AMOUNT=199.00 TOTAL_CURRENCY=GBP pnpm demo:tenant:book
832
- ```
833
-
834
- The passenger record is a hardcoded fixture (Jane Doe, GB passport). PII enters the TEE enclave and is never returned — only `booking_id`, `pnr`, and `status` cross the WIT boundary back to the caller.
835
-
836
- ---
837
-
838
- ## WASM Integration
839
-
840
- The SDK can work with both real T3n WASM components and mock components for testing.
841
-
842
- ### Automatic WASM Loading
843
-
844
- ```typescript
845
- import { loadWasmComponent } from '@terminal3/t3n-sdk';
846
-
847
- // Automatically loads real WASM if available, falls back to mock
848
- const wasmComponent = await loadWasmComponent();
849
- ```
850
-
851
- The loader will automatically:
852
-
853
- 1. Try to load the real T3n WASM component (if available)
854
- 2. Fall back to a mock component for testing
855
-
856
- ### Real WASM Component
857
-
858
- To use the real WASM component:
859
-
860
- 1. **Build the WASM component** (from T3n project root):
861
-
862
- ```bash
863
- cd node
864
- cargo build -p session-client --target wasm32-wasip2 --release
865
- ```
866
-
867
- 2. **Copy the WASM file** to the SDK directory:
868
-
869
- ```bash
870
- cp target/debug/build/cn-api-*/out/session.wasm t3n-sdk/
871
- ```
872
-
873
- 3. **The SDK will automatically detect and use it**:
874
-
875
- ```bash
876
- pnpm demo:real-wasm
877
- ```
878
-
879
- ### Mock Component for Testing
880
-
881
- `loadWasmComponent()` automatically falls back to a built-in mock when the real WASM file is not present. No extra setup needed in test environments:
882
-
883
- ```typescript
884
- import { loadWasmComponent } from '@terminal3/t3n-sdk';
885
-
886
- // In test environments (no real WASM file present), this returns a mock automatically
887
- const wasmComponent = await loadWasmComponent();
888
- ```
889
-
890
- ### WASM Component Features
891
-
892
- When using the real WASM component, you get:
893
-
894
- - **Actual cryptographic operations** from the T3n session module
895
- - **Real state machine logic** for handshake and authentication
896
- - **Production-grade security** for session establishment
897
- - **Full protocol compatibility** with T3n nodes
898
-
899
- ## Examples
900
-
901
- See the [examples](src/examples/) directory for more comprehensive usage examples:
902
-
903
- - [Basic Usage](src/examples/basic-usage.ts) - Simple session establishment and RPC
904
- - [Advanced Usage](src/examples/advanced-usage.ts) - Error handling, custom configuration, and integration patterns
148
+ For tests that "just want it to work", `runOtpThenUserInput` chains the three
149
+ calls behind a single `getOtpCode` callback.
905
150
 
906
151
  ## License
907
152
 
908
153
  MIT
909
-
910
- ## Contributing
911
-
912
- Please read our contributing guidelines and submit pull requests to our repository.
913
-
914
- ## Support
915
-
916
- For support, please open an issue on our GitHub repository or contact the T3n team.