@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.
- package/README.md +33 -796
- package/dist/index.d.ts +281 -115
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +10 -60
- package/README.OIDC.md +0 -216
- package/dist/demo.d.ts +0 -25
- package/dist/src/client/actions.d.ts +0 -31
- package/dist/src/client/config.d.ts +0 -33
- package/dist/src/client/contract-response.d.ts +0 -59
- package/dist/src/client/delegation.d.ts +0 -388
- package/dist/src/client/encryption.d.ts +0 -30
- package/dist/src/client/handlers.d.ts +0 -73
- package/dist/src/client/index.d.ts +0 -13
- package/dist/src/client/org-data.d.ts +0 -276
- package/dist/src/client/request-parser.d.ts +0 -48
- package/dist/src/client/t3n-client.d.ts +0 -544
- package/dist/src/client/transport.d.ts +0 -131
- package/dist/src/config/index.d.ts +0 -82
- package/dist/src/config/loader.d.ts +0 -8
- package/dist/src/config/types.d.ts +0 -25
- package/dist/src/index.d.ts +0 -39
- package/dist/src/types/auth.d.ts +0 -66
- package/dist/src/types/index.d.ts +0 -45
- package/dist/src/types/kyc.d.ts +0 -135
- package/dist/src/types/org-data.d.ts +0 -180
- package/dist/src/types/session.d.ts +0 -24
- package/dist/src/types/token.d.ts +0 -102
- package/dist/src/types/user.d.ts +0 -236
- package/dist/src/utils/contract-version.d.ts +0 -5
- package/dist/src/utils/crypto.d.ts +0 -52
- package/dist/src/utils/errors.d.ts +0 -144
- package/dist/src/utils/index.d.ts +0 -10
- package/dist/src/utils/logger.d.ts +0 -102
- package/dist/src/utils/redaction.d.ts +0 -13
- package/dist/src/utils/session.d.ts +0 -37
- package/dist/src/utils/shape.d.ts +0 -30
- package/dist/src/wasm/index.d.ts +0 -5
- package/dist/src/wasm/interface.d.ts +0 -110
- package/dist/src/wasm/loader.d.ts +0 -43
- package/dist/src/wasm/quote-verifier/quote_verifier_bytes.d.ts +0 -1
- 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.
|
|
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.
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
##
|
|
94
|
+
## Environments
|
|
95
|
+
|
|
96
|
+
The SDK targets the public T3n networks.
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|