@terminal3/t3n-sdk 1.3.2 → 2.3.0

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/dist/index.d.ts CHANGED
@@ -325,6 +325,281 @@ type AuthInput = EthAuthInput | OidcAuthInput;
325
325
  declare function createEthAuthInput(address: string): EthAuthInput;
326
326
  declare function createOidcAuthInput(credentials: OidcCredentials): OidcAuthInput;
327
327
 
328
+ /**
329
+ * Error classes for T3n SDK
330
+ */
331
+ /**
332
+ * Base error class for T3n SDK errors
333
+ */
334
+ declare class T3nError extends Error {
335
+ readonly code?: string | undefined;
336
+ constructor(message: string, code?: string | undefined);
337
+ }
338
+ /**
339
+ * Error thrown when session is in invalid state for operation
340
+ */
341
+ declare class SessionStateError extends T3nError {
342
+ readonly currentState: string;
343
+ constructor(message: string, currentState: string);
344
+ }
345
+ /**
346
+ * Error thrown during authentication process
347
+ */
348
+ declare class AuthenticationError extends T3nError {
349
+ readonly authMethod?: string | undefined;
350
+ constructor(message: string, authMethod?: string | undefined);
351
+ }
352
+ /**
353
+ * Error thrown during handshake process
354
+ */
355
+ declare class HandshakeError extends T3nError {
356
+ constructor(message: string);
357
+ }
358
+ /**
359
+ * Error thrown during RPC communication.
360
+ *
361
+ * `message` is the human-readable error (preferring the server's
362
+ * `error.data.detail` when present, with the request id appended in
363
+ * `[<id>]` form so a UI that only surfaces `.message` still gives an
364
+ * operator something to grep). The structured fields below preserve
365
+ * the same info for callers that want to render or log them
366
+ * separately — e.g. a toast that shows `detail` but surfaces
367
+ * `requestId` in a "copy for support" affordance.
368
+ */
369
+ declare class RpcError extends T3nError {
370
+ readonly rpcMethod?: string | undefined;
371
+ readonly httpStatus?: number | undefined;
372
+ /** Server-attached detail (JSON-RPC `error.data.detail`). User-facing kinds
373
+ * carry the specific reason here; internal kinds omit it. */
374
+ readonly detail?: string | undefined;
375
+ /** Per-request correlation id (JSON-RPC `error.data.request_id`). */
376
+ readonly requestId?: string | undefined;
377
+ constructor(message: string, rpcMethod?: string | undefined, httpStatus?: number | undefined,
378
+ /** Server-attached detail (JSON-RPC `error.data.detail`). User-facing kinds
379
+ * carry the specific reason here; internal kinds omit it. */
380
+ detail?: string | undefined,
381
+ /** Per-request correlation id (JSON-RPC `error.data.request_id`). */
382
+ requestId?: string | undefined);
383
+ }
384
+ /**
385
+ * Error thrown when WASM operations fail
386
+ */
387
+ declare class WasmError extends T3nError {
388
+ readonly operation?: string | undefined;
389
+ readonly payload?: unknown | undefined;
390
+ constructor(message: string, operation?: string | undefined, payload?: unknown | undefined);
391
+ }
392
+ /**
393
+ * Decode WASM error message from comma-separated byte array format
394
+ * WASM errors often come as "83,101,114,100,101..." which represents ASCII bytes
395
+ *
396
+ * @param errorMessage - The error message string that may contain comma-separated bytes
397
+ * @returns Decoded error message if it was encoded, otherwise original message
398
+ */
399
+ declare function decodeWasmErrorMessage(errorMessage: string): string;
400
+ /**
401
+ * Extract and decode error from WASM ComponentError
402
+ *
403
+ * @param error - The error object from WASM
404
+ * @returns Decoded error message
405
+ */
406
+ declare function extractWasmError(error: unknown): string;
407
+
408
+ /**
409
+ * MAT-1374 — wire types for the explicit `otp-request` /
410
+ * `otp-verify` / slim `user-upsert` exports on `tee:user/contracts`
411
+ * (`tee:user@2.0.0`).
412
+ *
413
+ * Pre-2.0.0 the user contract dispatched OTP request and OTP verify
414
+ * implicitly from the input shape of a single `user-upsert` call.
415
+ * 2.0.0 splits that surface into three explicit functions; the
416
+ * shapes here mirror the Rust types in
417
+ * `node/tee_contracts/user/src/otp_types.rs` and
418
+ * `node/tee_contracts/user/src/upsert_types.rs`.
419
+ *
420
+ * Keep the two in sync — bytes flow directly from the contract
421
+ * through the JSON-RPC envelope into the SDK wrappers
422
+ * (`T3nClient.otpRequest`, `T3nClient.otpVerify`,
423
+ * `T3nClient.submitUserInput`).
424
+ */
425
+
426
+ /**
427
+ * OTP delivery channel. Matches the `OtpContactChannel` Rust enum
428
+ * with `serde(rename_all = "snake_case")`.
429
+ */
430
+ type OtpChannel = "email" | "sms";
431
+ /**
432
+ * Discriminated OTP contact target. Mirrors the Rust `OtpRequest` enum
433
+ * (`email_channel` / `sms_channel` on the wire). Exactly one branch is
434
+ * set — no parallel optional email + phone with a separate channel tag.
435
+ *
436
+ * The legacy `keys.generic_api.otp_channel` body shadow was removed
437
+ * in 2.0.0 — the contract rejects calls carrying it with a
438
+ * `legacy_field` error.
439
+ */
440
+ type OtpRequestInput = {
441
+ emailChannel: {
442
+ emailAddress: string;
443
+ };
444
+ } | {
445
+ smsChannel: {
446
+ phoneNumber: string;
447
+ };
448
+ };
449
+ /**
450
+ * Response returned by `otp-request`. Mirrors `OtpResponse` in
451
+ * `otp_types.rs`.
452
+ *
453
+ * - `contact` echoes the OTP destination so clients that race
454
+ * multiple channels can correlate replies.
455
+ * - `expiresAtSec` is the Unix-second TTL the host minted for the
456
+ * pending OTP. Absent in `skip_otp` test environments.
457
+ * - `status` is `"otp_pending"` on the happy path. `"otp_failed"`
458
+ * only appears on retry without a fresh request — usually you
459
+ * shouldn't see it on `otp-request`.
460
+ * - `txHash` is the host-enriched ledger ref for the OTP-pending
461
+ * write (the contract leaves it `null`; the host injects).
462
+ * - `isNewProfile` is `true` on a fresh DID's first OTP request —
463
+ * read this to detect "did the user just register".
464
+ */
465
+ interface OtpRequestResult {
466
+ contact: string;
467
+ channel: OtpChannel;
468
+ expiresAtSec?: number;
469
+ status?: string;
470
+ txHash?: string;
471
+ isNewProfile?: boolean;
472
+ }
473
+ /**
474
+ * Input to `T3nClient.otpVerify`. `otpCode` is mandatory; `request`
475
+ * repeats the same discriminated contact shape as {@link OtpRequestInput}.
476
+ */
477
+ interface OtpVerifyInput {
478
+ otpCode: string;
479
+ request: OtpRequestInput;
480
+ }
481
+ /**
482
+ * Suggestion surfaced when the bind-target contact already belongs
483
+ * to another DID. The contract refuses to silently steal the
484
+ * authenticator; the caller must resolve the merge (typically via
485
+ * `merge-profiles`) before re-attempting verify.
486
+ */
487
+ interface OtpMergeSuggestion {
488
+ existingDid: string;
489
+ currentDid: string;
490
+ email?: string;
491
+ phone?: string;
492
+ channel: OtpChannel;
493
+ }
494
+ /**
495
+ * Response returned by `otp-verify`. Mirrors
496
+ * `OtpVerifyResponse` in `otp_types.rs`.
497
+ *
498
+ * - `email` is populated on `channel = "email"` success;
499
+ * `phone` on `channel = "sms"` success. Mutually exclusive on
500
+ * the happy path.
501
+ * - `status` carries `"otp_failed"` / `"otp_expired"` on retry; the
502
+ * happy path leaves it `undefined`.
503
+ * - `mergeSuggestion` is set when the contact-to-bind is already
504
+ * owned by another DID. Wire it into your merge UX.
505
+ */
506
+ interface OtpVerifyResult {
507
+ txHash?: string;
508
+ did: string;
509
+ channel: OtpChannel;
510
+ email?: string;
511
+ phone?: string;
512
+ status?: string;
513
+ mergeSuggestion?: OtpMergeSuggestion;
514
+ isNewProfile?: boolean;
515
+ }
516
+ /**
517
+ * Level-1 user-input fields accepted by the slim `user-upsert`.
518
+ * The Rust contract validates these via `validate_profile`; the
519
+ * shape is intentionally open so callers can add provider-specific
520
+ * fields on top of the canonical six.
521
+ */
522
+ interface UserInputProfile {
523
+ firstName?: string;
524
+ lastName?: string;
525
+ email_address?: string;
526
+ phone_number?: string;
527
+ birthdate?: string;
528
+ nationality?: string;
529
+ [key: string]: unknown;
530
+ }
531
+ /**
532
+ * Input to `T3nClient.submitUserInput`. Maps onto the slim
533
+ * `user-upsert` request shape: `{ profile, organisation_did?,
534
+ * attestations?, keys? }`.
535
+ */
536
+ interface SubmitUserInputArgs {
537
+ profile: UserInputProfile;
538
+ organisationDid?: string;
539
+ attestations?: unknown;
540
+ keys?: Record<string, unknown>;
541
+ /**
542
+ * KYC webhook orphan-attestation flow. When set, the contract
543
+ * skips the email-not-verified gate and only records the
544
+ * attestation if the DID has no profile yet (T3-TS-026 §13).
545
+ * Most callers should leave this `undefined`.
546
+ */
547
+ requireExistingUser?: boolean;
548
+ }
549
+ /**
550
+ * Response shape returned by the slim `user-upsert`. Carries the
551
+ * post-merge tx hash plus the L1 merge diagnostics
552
+ * (`refusedFields`, `mergeSuggestion`) the pre-2.0.0 omnibus
553
+ * `UpsertResponse` already surfaced.
554
+ */
555
+ interface SubmitUserInputResult {
556
+ txHash?: string;
557
+ refusedFields?: string[];
558
+ mergeSuggestion?: OtpMergeSuggestion;
559
+ userFound?: boolean;
560
+ }
561
+ /**
562
+ * Discriminator for {@link UserUpsertError}. Mirrors the
563
+ * `UserUpsertError` Rust enum and its `<code>:<detail>` wire
564
+ * format (see `upsert_types.rs::UserUpsertError::code`). Branch
565
+ * with a `switch` over `kind`.
566
+ *
567
+ * - `EmailNotVerified` — the slim `user-upsert` was called against
568
+ * a DID that has no verified email and no proving authenticator.
569
+ * Run `otpRequest` + `otpVerify` first (or accept an
570
+ * OIDC/Email-authed session).
571
+ * - `LegacyField` — caller passed a pre-2.0.0 dispatch field
572
+ * (`otp_code` to a non-verify export, or
573
+ * `keys.generic_api.otp_channel` anywhere). Migrate the call
574
+ * site to the new explicit functions.
575
+ * - `UserNotFound` — `requireExistingUser` was set but no profile
576
+ * exists for the DID. The attestation is recorded for audit;
577
+ * no profile created.
578
+ */
579
+ type UserUpsertErrorKind = "EmailNotVerified" | "LegacyField" | "UserNotFound";
580
+ /**
581
+ * Typed wrapper for the `<code>:<detail>` errors the slim
582
+ * `user-upsert` and the OTP entry points emit. `kind` is the
583
+ * structured discriminator the SDK derives from the error code
584
+ * prefix; `code` and `detail` retain the wire components for
585
+ * fall-through logging.
586
+ *
587
+ * Throw site: `T3nClient.submitUserInput` (and friends) when the
588
+ * contract returns a string error matching the
589
+ * `<code>:<detail>` shape.
590
+ */
591
+ declare class UserUpsertError extends T3nError {
592
+ readonly kind: UserUpsertErrorKind | "Unknown";
593
+ readonly detail: string;
594
+ constructor(code: string, detail: string);
595
+ /**
596
+ * Try to parse a contract error string into a `UserUpsertError`.
597
+ * Returns `null` if `raw` doesn't match the `<code>:<detail>`
598
+ * shape — caller falls back to a generic error.
599
+ */
600
+ static fromWire(raw: string): UserUpsertError | null;
601
+ }
602
+
328
603
  /**
329
604
  * Public types export for T3n SDK
330
605
  */
@@ -515,86 +790,6 @@ interface T3nClientConfig {
515
790
  handlers?: GuestToHostHandlers;
516
791
  }
517
792
 
518
- /**
519
- * Error classes for T3n SDK
520
- */
521
- /**
522
- * Base error class for T3n SDK errors
523
- */
524
- declare class T3nError extends Error {
525
- readonly code?: string | undefined;
526
- constructor(message: string, code?: string | undefined);
527
- }
528
- /**
529
- * Error thrown when session is in invalid state for operation
530
- */
531
- declare class SessionStateError extends T3nError {
532
- readonly currentState: string;
533
- constructor(message: string, currentState: string);
534
- }
535
- /**
536
- * Error thrown during authentication process
537
- */
538
- declare class AuthenticationError extends T3nError {
539
- readonly authMethod?: string | undefined;
540
- constructor(message: string, authMethod?: string | undefined);
541
- }
542
- /**
543
- * Error thrown during handshake process
544
- */
545
- declare class HandshakeError extends T3nError {
546
- constructor(message: string);
547
- }
548
- /**
549
- * Error thrown during RPC communication.
550
- *
551
- * `message` is the human-readable error (preferring the server's
552
- * `error.data.detail` when present, with the request id appended in
553
- * `[<id>]` form so a UI that only surfaces `.message` still gives an
554
- * operator something to grep). The structured fields below preserve
555
- * the same info for callers that want to render or log them
556
- * separately — e.g. a toast that shows `detail` but surfaces
557
- * `requestId` in a "copy for support" affordance.
558
- */
559
- declare class RpcError extends T3nError {
560
- readonly rpcMethod?: string | undefined;
561
- readonly httpStatus?: number | undefined;
562
- /** Server-attached detail (JSON-RPC `error.data.detail`). User-facing kinds
563
- * carry the specific reason here; internal kinds omit it. */
564
- readonly detail?: string | undefined;
565
- /** Per-request correlation id (JSON-RPC `error.data.request_id`). */
566
- readonly requestId?: string | undefined;
567
- constructor(message: string, rpcMethod?: string | undefined, httpStatus?: number | undefined,
568
- /** Server-attached detail (JSON-RPC `error.data.detail`). User-facing kinds
569
- * carry the specific reason here; internal kinds omit it. */
570
- detail?: string | undefined,
571
- /** Per-request correlation id (JSON-RPC `error.data.request_id`). */
572
- requestId?: string | undefined);
573
- }
574
- /**
575
- * Error thrown when WASM operations fail
576
- */
577
- declare class WasmError extends T3nError {
578
- readonly operation?: string | undefined;
579
- readonly payload?: unknown | undefined;
580
- constructor(message: string, operation?: string | undefined, payload?: unknown | undefined);
581
- }
582
- /**
583
- * Decode WASM error message from comma-separated byte array format
584
- * WASM errors often come as "83,101,114,100,101..." which represents ASCII bytes
585
- *
586
- * @param errorMessage - The error message string that may contain comma-separated bytes
587
- * @returns Decoded error message if it was encoded, otherwise original message
588
- */
589
- declare function decodeWasmErrorMessage(errorMessage: string): string;
590
- /**
591
- * Extract and decode error from WASM ComponentError
592
- *
593
- * @param error - The error object from WASM
594
- * @returns Decoded error message
595
- */
596
- declare function extractWasmError(error: unknown): string;
597
-
598
793
  /**
599
794
  * Contract response decoding for {@link T3nClient.execute}.
600
795
  *
@@ -1030,6 +1225,116 @@ declare class T3nClient {
1030
1225
  * row exists yet), or if the response shape is unexpected.
1031
1226
  */
1032
1227
  kycStatus(providerId?: string): Promise<KycStatus>;
1228
+ /**
1229
+ * Dispatch a one-time code to the supplied contact via the host's
1230
+ * OTP provider. Backed by `tee:user/contracts::otp-request`.
1231
+ *
1232
+ * The contract persists the unverified contact in the channel's
1233
+ * pending slot (`pending_email` / `pending_phone`) and returns
1234
+ * `OtpRequestResult` with `status = "otp_pending"` (or `undefined`
1235
+ * when the node is configured with `skip_otp = true`). The next
1236
+ * step is {@link otpVerify} with the code the user typed.
1237
+ *
1238
+ * Behaviour notes:
1239
+ *
1240
+ * - Contact is a discriminated object: `emailChannel` or
1241
+ * `smsChannel` (mirrors Rust `OtpRequest`). The legacy
1242
+ * `keys.generic_api.otp_channel` body shadow is rejected by the
1243
+ * contract.
1244
+ * - SMS channel is gated on a verified email. Calling with
1245
+ * `channel = "sms"` against a DID that hasn't completed an
1246
+ * email roundtrip first throws.
1247
+ * - On a fresh DID's first call, `result.isNewProfile === true`.
1248
+ * Use this signal — not [[otpVerify]]'s response — for "did the
1249
+ * user just register" gating.
1250
+ *
1251
+ * @throws {RpcError} if the node rejects the action; the message
1252
+ * carries the contract-side detail (e.g. sequential-flow guard
1253
+ * when the email is missing).
1254
+ */
1255
+ otpRequest(input: OtpRequestInput): Promise<OtpRequestResult>;
1256
+ /**
1257
+ * Redeem a one-time code and bind the contact to the
1258
+ * authenticated DID. Backed by
1259
+ * `tee:user/contracts::otp-verify`.
1260
+ *
1261
+ * On success the contract promotes the pending contact back to
1262
+ * the canonical slot, writes the AUTH_MAP / DIDS_MAP /
1263
+ * USER_AUTHS_MAP authenticator entries, stamps
1264
+ * `verified_contacts.{email,phone}`, and mints the ambient
1265
+ * `t3n.personal.contact.1` ownership VC when both contacts are
1266
+ * now verified.
1267
+ *
1268
+ * If the contact is already owned by a different DID the
1269
+ * contract refuses to silently reparent the authenticator and
1270
+ * surfaces a `mergeSuggestion` instead — the caller resolves the
1271
+ * merge (typically via {@link mergeProfiles}) and re-attempts.
1272
+ *
1273
+ * On a wrong / expired code the contract returns the result with
1274
+ * `status = "otp_failed"` or `"otp_expired"` — no exception is
1275
+ * thrown, the error is surfaced as data so the UI can stay on
1276
+ * the verify screen and let the user retry.
1277
+ *
1278
+ * @throws {RpcError} if the node rejects the action outright
1279
+ * (network / decode / bad input shape). Branch on
1280
+ * `result.status` for retryable OTP failures.
1281
+ */
1282
+ otpVerify(input: OtpVerifyInput): Promise<OtpVerifyResult>;
1283
+ /**
1284
+ * Submit Level-1 user-input fields to the slim
1285
+ * `tee:user/contracts::user-upsert`. The contract merges the
1286
+ * supplied profile fields, validates them, mints
1287
+ * `t3n.user-input.kyc.1` once every Level-1 field is present, and
1288
+ * commits the write.
1289
+ *
1290
+ * **Pre-condition** (MAT-1374): the DID must already have a
1291
+ * verified email — either because {@link otpVerify} bound one or
1292
+ * because the session carries a proving authenticator (OIDC /
1293
+ * Email auth). Calls without proof are rejected with
1294
+ * {@link UserUpsertError} `kind = "EmailNotVerified"`. The
1295
+ * recommended UX is "request OTP -> verify OTP -> submit user
1296
+ * input" (or use {@link runOtpThenUserInput} which chains all
1297
+ * three).
1298
+ *
1299
+ * The KYC webhook orphan-attestation flow stays here: when
1300
+ * `requireExistingUser` is set, the contract identifies the user
1301
+ * by DID (vendorData) instead of email and the gate is bypassed.
1302
+ *
1303
+ * @throws {UserUpsertError} when the contract returns a typed
1304
+ * error (`email_not_verified`, `legacy_field`, `user_not_found`).
1305
+ * Branch on `err.kind`.
1306
+ * @throws {RpcError} for non-typed transport / decode failures.
1307
+ */
1308
+ submitUserInput(input: SubmitUserInputArgs): Promise<SubmitUserInputResult>;
1309
+ /**
1310
+ * Convenience helper: run the explicit OTP roundtrip and submit
1311
+ * the slim user-upsert in one call.
1312
+ *
1313
+ * The caller supplies a `getOtpCode(contact, channel)` callback
1314
+ * the SDK invokes between request and verify — this is where
1315
+ * a UI prompts the user to type the code that arrived on email
1316
+ * / SMS. Throw from the callback to abort the flow; the helper
1317
+ * does not retry on its own.
1318
+ *
1319
+ * Returns the slim `submitUserInput` result on success. Throws
1320
+ * {@link UserUpsertError} or `RpcError` on the same conditions
1321
+ * as the underlying wrappers.
1322
+ *
1323
+ * Optional, opt-in path — the recommended default is to call
1324
+ * {@link otpRequest}, {@link otpVerify}, and
1325
+ * {@link submitUserInput} explicitly so the application owns the
1326
+ * flow.
1327
+ */
1328
+ runOtpThenUserInput(args: {
1329
+ channel: OtpChannel;
1330
+ emailAddress?: string;
1331
+ phoneNumber?: string;
1332
+ profile: SubmitUserInputArgs["profile"];
1333
+ organisationDid?: string;
1334
+ attestations?: unknown;
1335
+ keys?: Record<string, unknown>;
1336
+ getOtpCode: (contact: string, channel: OtpChannel) => Promise<string> | string;
1337
+ }): Promise<SubmitUserInputResult>;
1033
1338
  /**
1034
1339
  * Poll `kyc-status` until a terminal status arrives or the
1035
1340
  * configured timeout elapses.
@@ -1202,6 +1507,211 @@ declare function createRandomHandler(): GuestToHostHandler;
1202
1507
  */
1203
1508
  declare function createDefaultHandlers(baseUrl: string): GuestToHostHandlers;
1204
1509
 
1510
+ /**
1511
+ * User-to-Agent Delegation (T3-TS-030).
1512
+ *
1513
+ * TypeScript reference implementation of the delegation credential and
1514
+ * envelope shapes defined in `node/tee_contracts/delegation-types`.
1515
+ *
1516
+ * The wire shape is byte-for-byte identical to the Rust source — pinned
1517
+ * by the KAT fixtures under `tests/kat/`. Specifically:
1518
+ *
1519
+ * - `not_before_secs` / `not_after_secs` / `batch_cap_cents` are
1520
+ * emitted as **JSON strings** (e.g. `"1700086400"`) so JS Number
1521
+ * precision never enters the canonicalisation surface.
1522
+ * - `nonce` (16 B), `vc_id` (16 B), `request_hash` (32 B),
1523
+ * `agent_pubkey` (33 B compressed secp256k1), `user_sig`,
1524
+ * `agent_sig` are emitted as **base64url-no-pad** strings.
1525
+ * - `org_did` / `user_did` are emitted as `did:t3n:<40-hex>` (the
1526
+ * `CompactDid` `Display` form).
1527
+ *
1528
+ * Canonicalisation uses the npm `canonicalize` package (RFC 8785 JCS).
1529
+ * Cryptography uses `@noble/curves` (secp256k1) and `@noble/hashes`
1530
+ * (sha256, keccak_256).
1531
+ */
1532
+ /** Domain tag carried in `DelegationCredential.v`. */
1533
+ declare const DELEGATION_CREDENTIAL_DOMAIN: "ot3.delegation/1";
1534
+ /** Domain tag prepended to the agent-side pre-image. */
1535
+ declare const DELEGATION_INVOCATION_DOMAIN: "ot3.invocation/1";
1536
+ declare const VC_ID_LEN = 16;
1537
+ declare const NONCE_LEN = 16;
1538
+ declare const REQUEST_HASH_LEN = 32;
1539
+ declare const AGENT_PUBKEY_LEN = 33;
1540
+ declare const ETH_SIG_LEN = 65;
1541
+ /** User-to-agent delegation credential body. */
1542
+ interface DelegationCredential {
1543
+ /** Domain tag, must equal {@link DELEGATION_CREDENTIAL_DOMAIN}. */
1544
+ v: string;
1545
+ /** `did:t3n:<40-hex>` user DID. */
1546
+ user_did: string;
1547
+ /** 33-byte compressed secp256k1 public key the agent uses per call. */
1548
+ agent_pubkey: Uint8Array;
1549
+ /** `did:t3n:<40-hex>` org DID. */
1550
+ org_did: string;
1551
+ /** Contract id, e.g. `"tee:payroll"`. */
1552
+ contract: string;
1553
+ /** Function name, e.g. `"run-payroll"`. */
1554
+ function: string;
1555
+ /** Org-data scopes the contract may read on this user's behalf. */
1556
+ scopes: string[];
1557
+ /** Flat key-value labels checked against the org grant. */
1558
+ metadata: Record<string, string>;
1559
+ /** Inclusive lower bound of the validity window (unix seconds). */
1560
+ not_before_secs: bigint;
1561
+ /** Inclusive upper bound of the validity window (unix seconds). */
1562
+ not_after_secs: bigint;
1563
+ /** Random 16-byte credential id. */
1564
+ vc_id: Uint8Array;
1565
+ }
1566
+ /** Envelope wrapping a contract-specific request body. */
1567
+ interface DelegationEnvelope {
1568
+ /** RFC 8785 JCS bytes of the credential, exactly as signed. */
1569
+ credential_jcs: Uint8Array;
1570
+ /** 65-byte EIP-191 signature over `credential_jcs`. */
1571
+ user_sig: Uint8Array;
1572
+ /** Per-call agent signature over the invocation pre-image. */
1573
+ agent_sig: Uint8Array;
1574
+ /** 16-byte agent-generated per-call nonce. */
1575
+ nonce: Uint8Array;
1576
+ /** SHA-256 of the canonical request body. */
1577
+ request_hash: Uint8Array;
1578
+ }
1579
+ /** Payroll-specific request body wrapped by a delegation envelope. */
1580
+ interface PayrollRunRequest {
1581
+ /** `did:t3n:<40-hex>` org id. */
1582
+ org_id: string;
1583
+ cycle_id: string;
1584
+ pay_period_start: string;
1585
+ pay_period_end: string;
1586
+ /** Total cap on the run's net disbursement, in cents. */
1587
+ batch_cap_cents: bigint;
1588
+ /** `employee_id` → previous-cycle baseline net disbursement, cents (decimal string). */
1589
+ historical_baselines: Record<string, string>;
1590
+ }
1591
+ /** Convenience wrapper paired with the matching delegation envelope. */
1592
+ interface PayrollInvocation {
1593
+ envelope: DelegationEnvelope;
1594
+ request: PayrollRunRequest;
1595
+ }
1596
+ /** Response from `tee:delegation.sign`. */
1597
+ interface SignDelegationResponse {
1598
+ credential_jcs: Uint8Array;
1599
+ user_sig: Uint8Array;
1600
+ }
1601
+ declare function b64uEncode(input: Uint8Array): string;
1602
+ /**
1603
+ * Encode raw bytes to base64url-no-pad (RFC 4648 §5 with padding
1604
+ * dropped). The same encoding T3-TS-030 wire-shape uses for binary
1605
+ * fields (`agent_pubkey`, `vc_id`, `nonce`, `agent_sig`, `user_sig`,
1606
+ * `request_hash`, `credential_jcs`).
1607
+ *
1608
+ * Public API since v2.2: callers building `PayrollInvocation` JSON
1609
+ * for the wire (e.g. the t3n-mcp `runPayroll` handler) need this
1610
+ * encoder to match the contract's deserializer.
1611
+ */
1612
+ declare function b64uEncodeBytes(input: Uint8Array): string;
1613
+ /**
1614
+ * Decode a base64url-no-pad string. Strict — rejects standard-alphabet
1615
+ * `+` / `/` and any padding `=`.
1616
+ */
1617
+ declare function b64uDecodeStrict(input: string): Uint8Array;
1618
+ /** @internal — preserved alias for in-tree callers. Prefer
1619
+ * {@link b64uEncodeBytes} / {@link b64uDecodeStrict}. */
1620
+ declare const _b64uEncode: typeof b64uEncode;
1621
+ /** Build a `did:t3n:<40-hex>` from raw 20 bytes. */
1622
+ declare function compactDidFromBytes(bytes: Uint8Array): string;
1623
+ /**
1624
+ * Canonicalise a {@link DelegationCredential} to RFC 8785 JCS bytes.
1625
+ *
1626
+ * Output is byte-identical to the Rust `canonicalise_credential` in
1627
+ * `delegation-types` (pinned by `tests/kat/credential.json`).
1628
+ */
1629
+ declare function canonicaliseCredential(credential: DelegationCredential): Uint8Array;
1630
+ /** Canonicalise an arbitrary {@link PayrollRunRequest} to JCS bytes. */
1631
+ declare function canonicaliseRequest(request: PayrollRunRequest): Uint8Array;
1632
+ /** SHA-256 of the canonicalised request body. */
1633
+ declare function requestHash(request: PayrollRunRequest): Uint8Array;
1634
+ /**
1635
+ * Build the agent-side pre-image bytes:
1636
+ * `utf8(DELEGATION_INVOCATION_DOMAIN) || vc_id || nonce || request_hash`.
1637
+ *
1638
+ * SHA-256 of these bytes is what the agent's secp256k1 signature is
1639
+ * verified against.
1640
+ */
1641
+ declare function buildInvocationPreimage(vcId: Uint8Array, nonce: Uint8Array, reqHash: Uint8Array): Uint8Array;
1642
+ /** Options for {@link buildDelegationCredential}. */
1643
+ interface BuildDelegationCredentialOpts {
1644
+ user_did: string;
1645
+ agent_pubkey: Uint8Array;
1646
+ org_did: string;
1647
+ contract: string;
1648
+ function: string;
1649
+ scopes?: string[];
1650
+ metadata?: Record<string, string>;
1651
+ not_before_secs: bigint | number;
1652
+ not_after_secs: bigint | number;
1653
+ vc_id: Uint8Array;
1654
+ }
1655
+ /**
1656
+ * Construct a {@link DelegationCredential} and validate body-level
1657
+ * invariants (domain, lengths, validity window). Throws on the same
1658
+ * conditions the Rust `validate_credential_body` rejects.
1659
+ */
1660
+ declare function buildDelegationCredential(opts: BuildDelegationCredentialOpts): DelegationCredential;
1661
+ /**
1662
+ * Body-level validation matching `delegation-types::validate_credential_body`,
1663
+ * minus the `now`/`max_validity_secs` checks (which are caller-supplied).
1664
+ * Throws with a message identifying the offending invariant.
1665
+ */
1666
+ declare function validateCredentialBody(c: DelegationCredential): void;
1667
+ /** Compute the EIP-191 "personal_sign" digest of a message. */
1668
+ declare function eip191Digest(msg: Uint8Array): Uint8Array;
1669
+ /**
1670
+ * EIP-191 sign `jcs` under `secret`, returning a 65-byte signature
1671
+ * (`r || s || v`, with `v` in 27/28 — Ethereum convention) and the
1672
+ * recovered 20-byte ETH address.
1673
+ */
1674
+ declare function signCredential(jcs: Uint8Array, secret: Uint8Array): {
1675
+ sig: Uint8Array;
1676
+ addr: Uint8Array;
1677
+ };
1678
+ /**
1679
+ * Recover the 20-byte ETH address that signed `msg` under EIP-191.
1680
+ * Mirrors `delegation-types::eth_recover_eip191`.
1681
+ *
1682
+ * **Signature malleability — accepted by design.** This routine does
1683
+ * not enforce low-s. EIP-2 mandates low-s for *transaction* signatures,
1684
+ * but EIP-191 / `personal_sign` has no such requirement, and ethers.js
1685
+ * / MetaMask / web3.js produce both shapes. Two distinct `(r, s)` and
1686
+ * `(r, n − s)` pairs verify under the same recovered address — replay
1687
+ * protection comes from the envelope's `request_hash` + `nonce`, not
1688
+ * from byte-uniqueness of the signature.
1689
+ */
1690
+ declare function ethRecoverEip191(msg: Uint8Array, sig: Uint8Array): Uint8Array;
1691
+ /**
1692
+ * Sign the agent-side invocation pre-image. The signature is raw
1693
+ * compact ECDSA (64 bytes) over `sha256(preimage)` — what
1694
+ * `delegation-types::verify_agent_sig` accepts as the 64-byte form.
1695
+ */
1696
+ declare function signAgentInvocation(preimage: Uint8Array, secret: Uint8Array): Uint8Array;
1697
+ /** Options for {@link buildPayrollInvocation}. */
1698
+ interface BuildPayrollInvocationOpts {
1699
+ credentialJcs: Uint8Array;
1700
+ userSig: Uint8Array;
1701
+ /** Credential's `vc_id` — needed for the agent pre-image. */
1702
+ vcId: Uint8Array;
1703
+ nonce: Uint8Array;
1704
+ request: PayrollRunRequest;
1705
+ agentSecret: Uint8Array;
1706
+ }
1707
+ /**
1708
+ * Assemble a complete {@link PayrollInvocation} (envelope + request)
1709
+ * given a user-signed credential and a per-call agent secret. Computes
1710
+ * `request_hash` from the canonical request bytes and produces an
1711
+ * `agent_sig` over `sha256(invocation_preimage)`.
1712
+ */
1713
+ declare function buildPayrollInvocation(opts: BuildPayrollInvocationOpts): PayrollInvocation;
1714
+
1205
1715
  /**
1206
1716
  * Cryptographic utilities for T3n SDK
1207
1717
  *
@@ -1423,5 +1933,5 @@ declare function clearKeyCache(): void;
1423
1933
  */
1424
1934
  declare function loadConfig(baseUrl?: string): SdkConfig;
1425
1935
 
1426
- export { AuthMethod, AuthenticationError, ContractResponseError, DEFAULT_KYC_POLL_CADENCE, HandshakeError, HttpTransport, KycStatusTimeoutError, LogLevel, MockTransport, NODE_URLS, RpcError, SessionStateError, SessionStatus, T3nClient, T3nError, TERMINAL_KYC_STATUSES, WasmError, bytesToString, clearKeyCache, createDefaultHandlers, createEthAuthInput, createLogger, createMlKemPublicKeyHandler, createOidcAuthInput, createRandomHandler, decodeWasmErrorMessage, eth_get_address, extractWasmError, fetchDkgAttestation, fetchMlKemPublicKey, generateRandomString, generateUUID, getEnvironment, getEnvironmentName, getGlobalLogLevel, getLogger, getNodeUrl, getScriptVersion, loadConfig, loadWasmComponent, metamask_get_address, metamask_sign, parseContractResponse, redactSecrets, redactSecretsFromJson, setEnvironment, setGlobalLogLevel, setNodeUrl, stringToBytes, validateConfig, verifyDkgAttestation, verifyTdxQuote };
1427
- export type { AuthInput, ClientAuth, ClientHandshake, ConfigValidationResult, ContractResponseSchema, Did, DkgAttestation, DkgVerifyResult, Environment, EthAuthInput, GuestToHostHandler, GuestToHostHandlers, HandshakeResult, JsonRpcRequest, JsonRpcResponse, KycPollCadence, KycPollOptions, KycStatus, KycStatusKind, Logger, OidcAuthInput, OidcCredentials, PeerQuoteResult, QuoteVerifyResult, SdkConfig, SessionCrypto, SessionId, T3nClientConfig, Transport, WasmComponent, WasmNextResult };
1936
+ export { AGENT_PUBKEY_LEN, AuthMethod, AuthenticationError, ContractResponseError, DEFAULT_KYC_POLL_CADENCE, DELEGATION_CREDENTIAL_DOMAIN, DELEGATION_INVOCATION_DOMAIN, ETH_SIG_LEN, HandshakeError, HttpTransport, KycStatusTimeoutError, LogLevel, MockTransport, NODE_URLS, NONCE_LEN, REQUEST_HASH_LEN, RpcError, SessionStateError, SessionStatus, T3nClient, T3nError, TERMINAL_KYC_STATUSES, UserUpsertError, VC_ID_LEN, WasmError, _b64uEncode, b64uDecodeStrict, b64uEncodeBytes, buildDelegationCredential, buildInvocationPreimage, buildPayrollInvocation, bytesToString, canonicaliseCredential, canonicaliseRequest, clearKeyCache, compactDidFromBytes, createDefaultHandlers, createEthAuthInput, createLogger, createMlKemPublicKeyHandler, createOidcAuthInput, createRandomHandler, decodeWasmErrorMessage, eip191Digest, ethRecoverEip191, eth_get_address, extractWasmError, fetchDkgAttestation, fetchMlKemPublicKey, generateRandomString, generateUUID, getEnvironment, getEnvironmentName, getGlobalLogLevel, getLogger, getNodeUrl, getScriptVersion, loadConfig, loadWasmComponent, metamask_get_address, metamask_sign, parseContractResponse, redactSecrets, redactSecretsFromJson, requestHash, setEnvironment, setGlobalLogLevel, setNodeUrl, signAgentInvocation, signCredential, stringToBytes, validateConfig, validateCredentialBody, verifyDkgAttestation, verifyTdxQuote };
1937
+ export type { AuthInput, BuildDelegationCredentialOpts, BuildPayrollInvocationOpts, ClientAuth, ClientHandshake, ConfigValidationResult, ContractResponseSchema, DelegationCredential, DelegationEnvelope, Did, DkgAttestation, DkgVerifyResult, Environment, EthAuthInput, GuestToHostHandler, GuestToHostHandlers, HandshakeResult, JsonRpcRequest, JsonRpcResponse, KycPollCadence, KycPollOptions, KycStatus, KycStatusKind, Logger, OidcAuthInput, OidcCredentials, OtpChannel, OtpMergeSuggestion, OtpRequestInput, OtpRequestResult, OtpVerifyInput, OtpVerifyResult, PayrollInvocation, PayrollRunRequest, PeerQuoteResult, QuoteVerifyResult, SdkConfig, SessionCrypto, SessionId, SignDelegationResponse, SubmitUserInputArgs, SubmitUserInputResult, T3nClientConfig, Transport, UserInputProfile, UserUpsertErrorKind, WasmComponent, WasmNextResult };