@terminal3/t3n-sdk
v3.0.0
Published
T3n TypeScript SDK - A minimal SDK that mirrors the server's RPC handler approach
Readme
T3n TypeScript SDK
A minimal TypeScript SDK that mirrors the server's RPC handler approach, keeping all state machine logic hidden in WASM and providing a clean, agnostic wrapper that doesn't expose authentication methods or internal states.
Features
- Simple API: Clean, minimal interface that's easy to use
- Method Agnostic: Supports multiple authentication methods (Ethereum, OIDC) without exposing implementation details
- WASM-Powered: All cryptographic complexity and state machine logic isolated in WASM components
- Type Safe: Full TypeScript support with comprehensive type definitions
- Secure: Encrypted communication with T3n nodes
- Extensible: Easy to add new authentication methods without changing TypeScript code
Installation
pnpm add @terminal3/t3n-sdkQuick Start
Basic Usage
import {
T3nClient,
loadWasmComponent,
createEthAuthInput,
eth_get_address,
eth_sign,
} from "@terminal3/t3n-sdk";
const wasmComponent = await loadWasmComponent();
const privateKey = process.env.T3N_DEMO_KEY!;
const client = new T3nClient({
baseUrl: "https://t3n-node.example.com",
wasmComponent,
handlers: {
EthSign: eth_sign(privateKey),
},
});
await client.startHandshake();
const did = await client.authenticate(
createEthAuthInput(eth_get_address(privateKey))
);Ethereum Authentication
import {
T3nClient,
createEthAuthInput,
eth_get_address,
eth_sign,
} from "@terminal3/t3n-sdk";
const privateKey = "0x...";
const client = new T3nClient({
wasmComponent: await loadWasmComponent(),
handlers: {
EthSign: eth_sign(privateKey),
},
});
await client.startHandshake();
const did = await client.authenticate(
createEthAuthInput(eth_get_address(privateKey))
);OIDC Authentication
import { createOidcAuthInput } from "@terminal3/t3n-sdk";
const oidcCredentials = {
provider: "google",
idToken: "jwt_token_here",
};
const did = await client.authenticate(createOidcAuthInput(oidcCredentials));Migrating from 1.x
@terminal3/[email protected] cuts over to tee:user/[email protected]
(MAT-1374). The implicit-dispatch monolith user-upsert on the user
contract was split into three explicit functions on the same
contract:
otp-request— request + dispatch an OTP code.otp-verify— redeem an OTP and bind the contact.user-upsert(slim) — Level 1 user-input ingest only. Rejects callers without a verified email with the typedUserUpsertError { kind: "EmailNotVerified" }(wire formemail_not_verified:<detail>).
If you used the typed T3nClient methods to wrap executeAction,
the SDK now ships client.otpRequest / client.otpVerify /
client.submitUserInput (plus a convenience
client.runOtpThenUserInput) so you can migrate one call site at a
time:
| Pre-2.0.0 (tee:[email protected]) | 2.x (tee:[email protected], contract ≥ 2.1.0 for discriminated OTP) |
| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| executeAction({ function_name: "user-upsert", input: { profile: { email_address } } }) | client.otpRequest({ emailChannel: { emailAddress } }) |
| executeAction({ function_name: "user-upsert", input: { profile, otp_code } }) | client.otpVerify({ otpCode, request: { emailChannel: { emailAddress } } }) |
| executeAction({ function_name: "user-upsert", input: { profile, keys: { generic_api: { otp_channel: "sms" } } } }) | client.otpRequest({ smsChannel: { phoneNumber } }) |
| executeAction({ function_name: "user-upsert", input: { profile } }) (post-OTP) | client.submitUserInput({ profile }) |
Worked example: full email + L1 ingest
import { T3nClient, UserUpsertError } from "@terminal3/t3n-sdk";
// 1) Bind the user's email via OTP.
const requested = await client.otpRequest({
emailChannel: { emailAddress: "[email protected]" },
});
const code = await prompt(`Code sent to ${requested.contact}: `);
await client.otpVerify({
otpCode: code,
request: { emailChannel: { emailAddress: "[email protected]" } },
});
// 2) Slim user-upsert: Level 1 user-input ingest. Rejects with
// UserUpsertError(kind: "EmailNotVerified") if step 1 was skipped.
try {
const result = await client.submitUserInput({
profile: {
first_name: "Alice",
last_name: "Smith",
country_of_residence: "US",
// ...other Level-1 fields
},
});
console.log("tx:", result.txHash);
} catch (err) {
if (err instanceof UserUpsertError && err.kind === "EmailNotVerified") {
// run otp-request + otp-verify, then retry.
}
throw err;
}For tests that "just want it to work", runOtpThenUserInput chains
the three calls behind a single getOtpCode callback.
Hard-error fields
Passing any of these to the wrong function returns the typed
UserUpsertError(kind: "LegacyField") (wire form
legacy_field:<detail>):
otp_codeto anything other thanotp-verify.keys.generic_api.otp_channelto anything (channel is now a top-level field).
Raw executeAction callers
If you bypass the typed wrappers, see the migration table above:
the function names (otp-request / otp-verify / user-upsert)
and JSON input shapes line up 1:1 with the wrappers' camelCase
fields converted to snake_case.
Architecture
The T3n SDK follows the same architectural principles as the server's rpc.rs:
- Completely agnostic about authentication methods (Ethereum, OIDC, etc.)
- No knowledge of internal state machine phases or details
- Simply calls WASM
next()and eagerly tries tofinish() - Lets WASM handle all the complexity internally
- Works with completely opaque byte arrays (just like the WIT interface)
What's Hidden in WASM (Everything Important)
- All state machine phases and transitions
- Authentication method-specific logic (challenge/response, OIDC flows, signature verification)
- Cryptographic operations and key derivation
- Protocol-specific message formatting and parsing
- Error handling and retry logic
- Internal state representations (all states are opaque byte arrays)
What's Exposed in TypeScript (Minimal Surface)
- Input helpers only:
createEthAuthInput,createOidcAuthInput, and signer handlers - High-level status: Just mirrors server's
SessionStatusenum - Simple API methods:
startHandshake(),authenticate() - Basic configuration:
baseUrl,sessionId - Opaque state management: Byte arrays we never interpret
API Reference
T3nClient
The main client class for interacting with T3n nodes.
Constructor
new T3nClient(config: T3nClientConfig)Methods
startHandshake(): Promise<void>- Establish secure session with the nodeauthenticate(authInput: AuthInput): Promise<Did>- Authenticate using provided credentialsgetSessionId(): SessionId | null- Get the server-minted session ID (nulluntil handshake completes; pentest M-1 / MAT-983 moved session-id minting from the client to the server)getStatus(): SessionStatus- Get current session statusgetDid(): Did | null- Get authenticated DID (null if not authenticated)isAuthenticated(): boolean- Check if client is authenticated
Types
SessionStatus
enum SessionStatus {
Init = 0,
Encrypted = 1,
Authenticated = 2,
}AuthInput
enum AuthMethod {
Ethereum = "eth",
OIDC = "oidc",
}
interface EthAuthInput {
method: AuthMethod.Ethereum;
address: string;
}
interface OidcCredentials {
provider: string;
idToken: string;
}
interface OidcAuthInput {
method: AuthMethod.OIDC;
credentials: OidcCredentials;
}
type AuthInput = EthAuthInput | OidcAuthInput;Helper utilities:
createEthAuthInput(address: string): EthAuthInput;
createOidcAuthInput(credentials: OidcCredentials): OidcAuthInput;Error Handling
The SDK provides specific error types for different failure scenarios:
import {
T3nError,
SessionStateError,
AuthenticationError,
HandshakeError,
RpcError
} from '@terminal3/t3n-sdk';
try {
await client.authenticate(signer);
} catch (error) {
if (error instanceof AuthenticationError) {
console.log('Authentication failed:', error.message);
} else if (error instanceof SessionStateError) {
console.log('Invalid session state:', error.currentState);
}
}Configuration
T3nClientConfig
interface T3nClientConfig {
baseUrl?: string; // T3n node URL (defaults to "http://localhost:3000" if not provided)
wasmComponent: WasmComponent; // WASM component instance
transport?: Transport; // Optional transport layer (uses HttpTransport with baseUrl if not provided)
sessionId?: SessionId; // Optional custom session ID
timeout?: number; // Request timeout (default: 30000ms)
headers?: Record<string, string>; // Custom headers
logLevel?: LogLevel; // Log level for this client (defaults to global log level, which is LogLevel.ERROR)
logger?: Logger; // Optional custom logger (overrides logLevel if provided)
handlers?: GuestToHostHandlers; // Optional guest-to-host request handlers
}Example with Custom Configuration
import { T3nClient, LogLevel } from '@terminal3/t3n-sdk';
const client = new T3nClient({
baseUrl: "https://t3n-node.example.com",
wasmComponent: await loadWasmComponent(),
timeout: 60000, // 60 second timeout
logLevel: LogLevel.DEBUG, // Enable debug logging
headers: {
'User-Agent': 'MyApp/1.0.0',
'X-Custom-Header': 'custom-value'
},
handlers: {
EthSign: eth_sign(privateKey),
MlKemPublicKey: ml_kem_public_key(),
Random: random(),
}
});Logging
The T3n SDK provides flexible logging capabilities with hierarchical log levels, allowing you to control verbosity for debugging and monitoring purposes.
Log Levels
The SDK supports four log levels in hierarchical order (from most to least verbose):
LogLevel.DEBUG(0) - Most verbose, includes all log messagesLogLevel.INFO(1) - Informational messages, warnings, and errorsLogLevel.WARN(2) - Warnings and errors onlyLogLevel.ERROR(3) - Errors only (default)
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.
Default Behavior
By default, the SDK uses LogLevel.ERROR, which means only error messages are logged. This ensures minimal console output in production environments.
Global Log Level Control
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.
How it works:
setGlobalLogLevel()affects all loggers created viagetLogger()orcreateLogger()without an explicit level- Only loggers created after calling
setGlobalLogLevel()will use the new level - Existing logger instances retain their original log level
Interaction with per-component overrides:
- When creating a
T3nClient, you can override the global level by providinglogLevelin the config - Per-component
logLeveltakes precedence over the global setting - If neither is provided, the global default (
LogLevel.ERROR) is used
import { setGlobalLogLevel, LogLevel, T3nClient } from '@terminal3/t3n-sdk';
// Set global level at application startup
setGlobalLogLevel(LogLevel.DEBUG);
// This client will use DEBUG level (from global)
const client1 = new T3nClient({
wasmComponent: await loadWasmComponent(),
});
// This client overrides global level with INFO
const client2 = new T3nClient({
wasmComponent: await loadWasmComponent(),
logLevel: LogLevel.INFO, // Overrides global DEBUG
});Creating Logger Instances
You can create logger instances programmatically:
import { createLogger, getLogger, LogLevel } from '@terminal3/t3n-sdk';
// Create a logger with a specific level
const debugLogger = createLogger(LogLevel.DEBUG);
// Get a logger using the global log level
const globalLogger = getLogger();
// Use logger in handlers
const handlers = {
EthSign: metamask_sign(address, debugLogger),
};Best Practices
- Production: Use
LogLevel.ERROR(default) to minimize console output - Development: Use
LogLevel.DEBUGfor detailed debugging information - Staging: Use
LogLevel.INFOfor monitoring without excessive verbosity - Custom Logging: Implement a custom logger to integrate with your logging infrastructure (e.g., Winston, Pino, or cloud logging services)
Example: Environment-Based Logging
import { setGlobalLogLevel, LogLevel } from '@terminal3/t3n-sdk';
// Set log level based on environment
const logLevel = process.env.NODE_ENV === 'production'
? LogLevel.ERROR
: LogLevel.DEBUG;
setGlobalLogLevel(logLevel);Development
Building
pnpm buildTesting
pnpm test
pnpm run test:coverageLinting
pnpm lint
pnpm run lint:fixDemo
# Run the demo (builds first)
pnpm demo
# Run demo in development mode
pnpm demo:dev
# Run demo with real WASM component
pnpm demo:real-wasm
# Run demo with real WASM component and upsert profile
pnpm demo:real-wasm --upsert
# Run demo with real WASM component and upsert profile from file
pnpm demo:real-wasm --upsert path/to/profile.json
# Run demo with real WASM component, upsert profile, and specify email
pnpm demo:real-wasm --upsert --email [email protected]
# Run demo with real WASM component, upsert profile from file, and specify email
pnpm demo:real-wasm --upsert path/to/profile.json --email [email protected]
# Provider webhook (default: user update only; no OID4VP present)
pnpm demo:real-wasm --provider
# Provider webhook: user update then OID4VP present (add --oid4vp or --oid4vp-json)
pnpm demo:real-wasm --provider --oid4vp
# Provider webhook with custom user-update payload from file
pnpm demo:real-wasm --provider --user-update-json path/to/user-update.json
# Provider with custom OID4VP payload (runs user update then OID4VP with this file)
pnpm demo:real-wasm --provider --oid4vp-json path/to/oid4vp.json
# OID4VP present only (skip user update; use existing DID)
pnpm demo:real-wasm --provider --oid4vp-only --did did:t3n:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# Provider with custom signing key (must match TEE provider config public_key)
pnpm demo:real-wasm --provider --provider-signing-key 0x0123...your32bytehex
# Provider webhook call with custom email
pnpm demo:real-wasm --provider --email [email protected]
# Provider webhook call with custom provider ID
pnpm demo:real-wasm --provider --provider-id t3 --email [email protected]
# Get profile after authentication (requires session authentication)
pnpm demo:real-wasm --get-profile --email [email protected]
# Get profile with JSON path filters
pnpm demo:real-wasm --get-profile --email [email protected] --filter "$.first_name" --filter "$.email_address"
# Provider webhook + Get profile (email automatically passed from provider call)
pnpm demo:real-wasm --provider --get-profileProvider and OID4VP
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:
User update only (default) – POST a configurable payload (
profile,attestations,credentials) to the provider webhook. The TEE creates or updates the profile and returns adidand optionaltx_hash.User update then OID4VP present – When you pass
--oid4vpor--oid4vp-json <path>, after step 1 the demo also sends anoid4vp_presentrequest withprofile.didfrom step 1, plusdcql_query,response_uri,nonce, andclient_id.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.
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.
# User update only (default)
pnpm demo:real-wasm --provider
# User update then OID4VP present (default OID4VP payload)
pnpm demo:real-wasm --provider --oid4vp
# User update then OID4VP present (custom OID4VP JSON)
pnpm demo:real-wasm --provider --oid4vp-json ./my-oid4vp.json
# OID4VP only for an existing DID
pnpm demo:real-wasm --provider --oid4vp-only --did did:t3n:your-uuid-hereDemo Command-Line Arguments
The demo supports the following command-line arguments:
--upsertor-u: Enable upsert profile action after authentication. Optionally provide a JSON file path to load profile data from.--emailor-e: Specify email address for profile operations. If not provided, the demo will prompt you interactively.--provideror-p: Call the provider webhook endpoint. This simulates a third-party provider (e.g., KYC service) sending user data to T3n. The webhook call:- Signs the payload with an EOA signature using a test private key (or
--provider-signing-key/PROVIDER_SIGNING_KEY) - Sends profile data to
/api/{provider_id}endpoint - Returns a DID and transaction hash
- Does not require session authentication
- Signs the payload with an EOA signature using a test private key (or
Default flow: With
--provideronly, the demo runs user update only (create/update profile). No OID4VP present request is sent.- To also run OID4VP present after user update, pass
--oid4vp(default OID4VP payload) or--oid4vp-json <path>(custom payload). - To run only OID4VP present (no user update), use
--oid4vp-onlywith--did <did>.
- To also run OID4VP present after user update, pass
--provider-id: Specify the provider ID for webhook calls (default: "t3"). Used with--providerflag.--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).--oid4vp-json: Path to a JSON file for the OID4VP present payload. When running the full two-step flow,profile.didis always set from step 1. If omitted, default payload is used (dcql_query, response_uri fromVP_VERIFIER_URL, nonce, client_id). Presence of this flag (with a path) also enables the OID4VP present step after user update.--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.--oid4vp-only: With--provider, skip step 1 and run only OID4VP present. Requires--did <did>.--did: DID to use for OID4VP when--oid4vp-onlyis set (e.g.did:t3n:uuid).--wait-tx: After step 1 (user update), wait for transaction confirmation before step 2. Can also set envWAIT_FOR_TX=true. (Tx wait is not implemented; flag is accepted for future use.)--provider-signing-key: EOA private key (32-byte hex, e.g.0x01...01) for signing provider webhooks. Must match the TEE’s provider configauth_method.public_keyfor the provider. Default is the test key used in integration. Can also set envPROVIDER_SIGNING_KEY.--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/currentfortee:user/contracts.--get-profileor-g: Retrieve user profile using the session client. This command:- Requires authentication (handled automatically)
- Uses email from provider webhook call or
--emailargument - Supports optional JSON path filtering
--filteror-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".
Combining Commands:
You can combine multiple flags for more complex workflows:
# Provider webhook call followed by get-profile (email automatically passed)
pnpm demo:real-wasm --provider --get-profile
# Provider webhook with custom email, then get-profile with filters
pnpm demo:real-wasm --provider --email [email protected] --get-profile --filter "$.first_name"
# Upsert profile, then get-profile
pnpm demo:real-wasm --upsert --email [email protected] --get-profileDemo Environment Variables
The demo supports the following environment variables:
CRYPTO_NODE_URL: The base URL of the T3n crypto node API (defaults tohttp://localhost:3000).TEE_API_URL: The base URL of the T3n TEE API endpoint (defaults tohttp://localhost:3000/api). Used by the--providerflag for webhook calls.VP_VERIFIER_URL: Base URL for the OID4VP response endpoint (defaults tohttp://localhost:8100). The demo setsresponse_urito{VP_VERIFIER_URL}/postin the default OID4VP payload.PROVIDER_SIGNING_KEY: EOA private key (32-byte hex) for signing provider webhooks. Overrides the default test key when set.WAIT_FOR_TX: When set to"true"or"1", enables waiting for tx confirmation before OID4VP (same as--wait-tx).SCRIPT_VERSION: Optional script version override for session-based demo actions. Priority is--script-version>SCRIPT_VERSION> dynamic latest lookup from/api/contracts/current.DEBUG: Enable debug logging when set to"true"(defaults to disabled).
Example usage:
# With custom crypto node URL
CRYPTO_NODE_URL=http://localhost:3000 pnpm demo:real-wasm
# With custom TEE API URL for provider webhook calls
TEE_API_URL=http://localhost:3000/api pnpm demo:real-wasm --provider
# With custom VP verifier URL for OID4VP response_uri
VP_VERIFIER_URL=http://localhost:8100 pnpm demo:real-wasm --provider
# With custom provider signing key (must match TEE provider config)
PROVIDER_SIGNING_KEY=0x0123... pnpm demo:real-wasm --provider
# Override script version explicitly (otherwise latest is resolved dynamically)
pnpm demo:real-wasm --get-profile --script-version 1.0.0
# With debug logging enabled
DEBUG=true pnpm demo:real-wasm
# With both custom URLs and debug logging
CRYPTO_NODE_URL=http://localhost:3000 TEE_API_URL=http://localhost:3000/api DEBUG=true pnpm demo:real-wasm --provider --get-profileEmail Verification (OTP Flow)
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):
Initial Upsert: When you run
--upsertwith an email, the system sends a 6-digit OTP code to the specified email address.OTP Prompt: The demo will automatically detect that OTP verification is needed and prompt you:
📧 Email verification required ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ address. The demo will prompt you to enter the 6-digit OTP code sent to your email.Enter OTP Code: When prompted, enter the 6-digit code you received in your email.
Verification: The demo will automatically retry the upsert with your OTP code and complete the profile upsert.
Note: In debug/mock mode (DEBUG=true or MOCK_MODE=true), the OTP code will be displayed in the console for testing purposes.
Note: The demo supports interactive OIDC authentication. When you select OIDC authentication, the demo will:
- Start a local web server on port 8081
- Open your browser automatically
- Guide you through the Google OAuth flow
- Allow you to paste the ID token back into the terminal
KYC Walkthrough (MAT-1371)
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:
- Smoke-testing a freshly-built node (
node/bin/start-local) before pointing the real FE at it. - Reproducing edge cases (rejected KYC, OTP expiry, merge suggestions) deterministically.
- Showing internal stakeholders what the L1 + L2 flow looks like without spinning up MetaMask Snap + Veriff sandbox.
Quick start (interactive):
pnpm demo:real-wasm --kyc-walkthroughThe walkthrough then prompts at each step in the order Trinity expects:
- Handshake + authenticate (existing prompts — wallet / OIDC).
- Email OTP — request + verify on
tee:user/contracts::user-upsert(email channel). - 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. - Level 1 user-input — single
user-upsertcall carryingfirst_name,last_name,country_of_residence,document_issuance_country,ssn,address. create-kyc-provider-session(MAT-1284) — prints the Veriffsession_url. The CLI does not auto-open the URL.kyc-statuspoll — until terminal status (verified,rejected, ororphan) or the timeout fires. Each snapshot is logged.
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.
KYC walkthrough flags
| Flag | Default | Meaning |
|---|---|---|
| --kyc-walkthrough | off | Enable the walkthrough mode. Implicitly enabled by --scenario. |
| --scenario <name> | none | One of happy-path-l1, happy-path-l2, rejected. See Scenarios below. |
| --auth-method <eth\|metamask\|oidc> | interactive prompt | Skip the auth-method prompt. |
| --email <addr> | interactive prompt (or [email protected] in scenario mode) | Email to verify in the L1 OTP step. |
| --phone <e164> | interactive prompt (or +14155552671 in scenario mode) | Phone in E.164 format for the SMS OTP step. |
| --first-name <name> | interactive prompt (or Ada in scenario mode) | L1 first name. |
| --last-name <name> | interactive prompt (or Lovelace in scenario mode) | L1 last name. |
| --country <ISO-2> | US | Convenience: applies to both country_of_residence and document_issuance_country unless overridden individually. |
| --country-of-residence <ISO-2> | --country value | L1 residence country. |
| --document-issuance-country <ISO-2> | --country value | L1 document issuance country. |
| --ssn <value> | interactive prompt (or 123-45-6789 in scenario mode) | L1 SSN (9 digits or XXX-XX-XXXX). |
| --address <text> | interactive prompt (or default address in scenario mode) | L1 residential address. |
| --kyc-provider-id <id> | veriff | Provider id passed to create-kyc-provider-session and kyc-status. |
| --kyc-poll-fast-ms <ms> | 2000 | Fast-cadence poll interval (T3-TS-026 §8.4). |
| --kyc-poll-slow-ms <ms> | 5000 | Slow-cadence poll interval. |
| --kyc-poll-switch-at-ms <ms> | 30000 | Elapsed threshold to switch from fast to slow. |
| --kyc-poll-timeout-ms <ms> | 300000 | Total cap before KycStatusTimeoutError is raised. |
| --skip-create-kyc-session | off | Stop after L1 — useful when no Veriff sandbox is configured. |
Scenarios
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).
| Scenario | What it does | Veriff |
|---|---|---|
| happy-path-l1 | Email OTP → phone OTP → L1 upsert. Stops before create-kyc-provider-session. | Not exercised. |
| happy-path-l2 | Full L1 + L2 flow. Asserts kyc-status terminates with verified. | Manual: open the printed session_url and drive Veriff to approval. |
| rejected | Full L1 + L2 flow. Asserts kyc-status terminates with rejected. | Manual: open the printed session_url and drive Veriff to a rejection. |
Examples:
# L1-only happy path — no Veriff sandbox needed
pnpm demo:real-wasm --scenario happy-path-l1
# Full L1 + L2 happy path — drive Veriff to approved manually
pnpm demo:real-wasm --scenario happy-path-l2
# Rejection scenario — drive Veriff to a rejection manually
pnpm demo:real-wasm --scenario rejected
# Custom inputs without a scenario
pnpm demo:real-wasm --kyc-walkthrough \
--auth-method eth \
--email [email protected] \
--phone +14155551234 \
--country US \
--first-name Ada --last-name LovelaceMock-mode OTP code
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.
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).
Sample output (excerpt)
🚦 KYC walkthrough mode enabled (MAT-1371)
Scenario: happy-path-l1
📧 Step 5/8 — Email OTP: [email protected]
5️⃣ Executing action: user-upsert...
✅ Action executed successfully
📨 Email verification required
📬 OTP code sent to: [email protected]
📡 Channel: email
💡 Mock mode OTP code (deterministic): 482913
🤖 Scenario auto-feeding OTP code: 482913
🔄 Verifying Email OTP code...
5️⃣ Executing action: user-upsert...
✅ OTP verified successfully
📝 Transaction hash: tx:1:42
📱 Step 6/8 — Phone OTP: +14155552671
6️⃣ Executing action: user-upsert...
...
📋 KYC walkthrough summary
✅ email-otp [email protected]
✅ phone-otp +14155552671
✅ l1-upsert tx:1:44
⏭️ create-kyc-session (scenario)
⏭️ kyc-status-poll (scenario)Caveats
- No version bump. The walkthrough is dev tooling — it does not introduce new
@terminal3/t3n-sdkpublic-API surface, sopackage.jsonis not bumped. - Spec alignment. The walkthrough follows the as-built code, which differs from earlier T3-TS-026 versions on two points:
user-upserthas noop:discriminator (dispatch is implicit on input shape), and the function for L2 session creation istee:user/contracts::create-kyc-provider-sessionrather thantee:kyc/contracts::create-session. See T3-TS-026 v0.6.0 changelog and MAT-1374 for the deferred discriminator-vs-split decision. STRICT_SCENARIO=truein the environment turns scenario assertion failures into a non-zero exit code (otherwise they're logged but the demo exits zero).--scenarioalways exits non-zero on a terminal-status mismatch.
Tenant Developer Demo
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.
Three entities
| Entity | Key env var | Role |
|---|---|---|
| Admin | ADMIN_KEY | Signs tenant.admit via the admin API |
| Tenant | TENANT_KEY | Registers contract, creates KV map, injects Duffel API key |
| User | USER_KEY | Authenticates and calls search-offers / book-offer on behalf of an AI agent |
Environment variables
| Variable | Default | Used by |
|---|---|---|
| CRYPTO_NODE_URL | http://localhost:3000 | all commands |
| ADMIN_KEY | 0x000...0001 | admit |
| TENANT_KEY | 0x000...0002 | admit, setup |
| DUFFEL_API_KEY | duffel_test_placeholder | setup |
| FLIGHT_WASM_PATH | (unset) | setup — path to compiled z-tenant-flight WASM; falls back to a placeholder |
| USER_KEY | 0x000...0003 | search, book — ETH key of a user already registered via demo:dev |
| OFFER_ID | (required) | book — offer ID printed by search |
| TOTAL_AMOUNT | (required) | book — total price printed by search |
| TOTAL_CURRENCY | (required) | book — currency code printed by search |
Step 1 — Admit the tenant (Admin)
ADMIN_KEY=0x... TENANT_KEY=0x... pnpm demo:tenant:admitStep 2 — Register contract + seed credentials (Tenant)
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:setupThis 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.
Step 3 — Create the user (User)
Use the standard user demo — no changes needed:
USER_KEY=0x... pnpm demo:devStep 4 — Search for flights (User / Agent)
USER_KEY=0x... pnpm demo:tenant:searchPrints 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.
Step 5 — Book a flight (User / Agent)
USER_KEY=0x... OFFER_ID=off_... TOTAL_AMOUNT=199.00 TOTAL_CURRENCY=GBP pnpm demo:tenant:bookThe 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.
WASM Integration
The SDK can work with both real T3n WASM components and mock components for testing.
Automatic WASM Loading
import { loadWasmComponent } from '@terminal3/t3n-sdk';
// Automatically loads real WASM if available, falls back to mock
const wasmComponent = await loadWasmComponent();The loader will automatically:
- Try to load the real T3n WASM component (if available)
- Fall back to a mock component for testing
Real WASM Component
To use the real WASM component:
Build the WASM component (from T3n project root):
cd node cargo build -p session-client --target wasm32-wasip2 --releaseCopy the WASM file to the SDK directory:
cp target/debug/build/cn-api-*/out/session.wasm t3n-sdk/The SDK will automatically detect and use it:
pnpm demo:real-wasm
Mock Component for Testing
import { createMockWasmComponent } from '@terminal3/t3n-sdk';
// Use mock component for testing
const mockComponent = createMockWasmComponent();WASM Component Features
When using the real WASM component, you get:
- Actual cryptographic operations from the T3n session module
- Real state machine logic for handshake and authentication
- Production-grade security for session establishment
- Full protocol compatibility with T3n nodes
Examples
See the examples directory for more comprehensive usage examples:
- Basic Usage - Simple session establishment and RPC
- Advanced Usage - Error handling, custom configuration, and integration patterns
License
MIT
Contributing
Please read our contributing guidelines and submit pull requests to our repository.
Support
For support, please open an issue on our GitHub repository or contact the T3n team.
