@terminal3/t3n-sdk 3.2.0 → 3.4.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/README.md +33 -796
- package/dist/index.d.ts +288 -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 -269
- 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
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* T3n Client - Main SDK class
|
|
3
|
-
*
|
|
4
|
-
* Provides a simple interface for establishing secure sessions with T3n nodes.
|
|
5
|
-
* All cryptographic complexity is handled in WASM components.
|
|
6
|
-
*/
|
|
7
|
-
import { T3nClientConfig } from "./config";
|
|
8
|
-
import { type ContractResponseSchema } from "./contract-response";
|
|
9
|
-
import { SessionId, Did, SessionStatus, AuthInput, EthAuthInput, HandshakeResult } from "../types";
|
|
10
|
-
import { KycPollOptions, KycStatus } from "../types/kyc";
|
|
11
|
-
import { OtpChannel, OtpRequestInput, OtpRequestResult, OtpVerifyInput, OtpVerifyResult, SubmitUserInputArgs, SubmitUserInputResult } from "../types/user";
|
|
12
|
-
import { GetUsageOptions, UsagePage } from "../types/token";
|
|
13
|
-
/**
|
|
14
|
-
* Main T3n SDK Client
|
|
15
|
-
*/
|
|
16
|
-
export declare class T3nClient {
|
|
17
|
-
private readonly config;
|
|
18
|
-
private readonly transport;
|
|
19
|
-
/**
|
|
20
|
-
* Resolved node base URL. Snapshotted in the constructor so the
|
|
21
|
-
* typed contract wrappers (`kycStatus`, `getSelfEthAddress`, …)
|
|
22
|
-
* can call `getScriptVersion()` against the same host the
|
|
23
|
-
* transport talks to. Used only by the `script_version: "latest"`
|
|
24
|
-
* resolution path in {@link executeUserContract}.
|
|
25
|
-
*/
|
|
26
|
-
private readonly effectiveBaseUrl;
|
|
27
|
-
/**
|
|
28
|
-
* Server-minted session ID, set by {@link handshake} from the
|
|
29
|
-
* `Session-Id` response header (pentest M-1 / MAT-983). `null`
|
|
30
|
-
* until the handshake completes. Client code cannot set it — the
|
|
31
|
-
* former `config.sessionId` hook was the session-fixation vector
|
|
32
|
-
* this fix closes.
|
|
33
|
-
*/
|
|
34
|
-
private sessionId;
|
|
35
|
-
/**
|
|
36
|
-
* Set by {@link sendRpcRequest} when an `auth.handshake` RPC is
|
|
37
|
-
* actually issued. Decouples the "flow completed without talking
|
|
38
|
-
* to a server" case (unit-test mocks that only exercise handler
|
|
39
|
-
* delegation) from the real "server must mint the id" invariant:
|
|
40
|
-
* we only enforce the mint requirement when a round-trip happened.
|
|
41
|
-
*/
|
|
42
|
-
private handshakeSentRpc;
|
|
43
|
-
private readonly logger;
|
|
44
|
-
private readonly encryption;
|
|
45
|
-
private status;
|
|
46
|
-
/**
|
|
47
|
-
* In-flight WASM state-machine bytes. Holds the opaque state
|
|
48
|
-
* returned by `flow[method].next()` between iterations of
|
|
49
|
-
* `runFlow`. Always cleared at the top of `runFlow` and again
|
|
50
|
-
* once `tryFinalize` has extracted the terminal payload — so
|
|
51
|
-
* outside of an active loop these slots are always `null`.
|
|
52
|
-
*/
|
|
53
|
-
private wasmState;
|
|
54
|
-
/**
|
|
55
|
-
* Terminal payloads produced by `flow[method].finish()`:
|
|
56
|
-
* - `handshake` → serialized session blob, used by
|
|
57
|
-
* `getSessionState()` for subsequent `session.encrypt` calls.
|
|
58
|
-
* - `auth` → serialized DID; the public `authenticate()` decodes
|
|
59
|
-
* it into `this.did` and the slot is otherwise unused.
|
|
60
|
-
* - `execute` → unused (executes return immediately to the caller).
|
|
61
|
-
*
|
|
62
|
-
* Stored in a dedicated field instead of reusing `wasmState`
|
|
63
|
-
* because the two meanings — "in-flight state machine" vs
|
|
64
|
-
* "finalized payload" — are semantically different and merging
|
|
65
|
-
* them invites the bug-class Devin flagged in PR #1140.
|
|
66
|
-
*/
|
|
67
|
-
private finalizedPayload;
|
|
68
|
-
private did;
|
|
69
|
-
private handshakeResult;
|
|
70
|
-
constructor(config: T3nClientConfig);
|
|
71
|
-
/**
|
|
72
|
-
* Start the handshake process with the T3n node
|
|
73
|
-
*/
|
|
74
|
-
handshake(): Promise<HandshakeResult>;
|
|
75
|
-
/**
|
|
76
|
-
* Authenticate with the T3n node.
|
|
77
|
-
*
|
|
78
|
-
* For OIDC, this runs a two-step nonce-bound flow:
|
|
79
|
-
* 1. Sends `InitOidcAuth` to server → receives session-binding nonce.
|
|
80
|
-
* 2. Calls `getIdToken(nonce)` callback so the app can include the
|
|
81
|
-
* nonce in the Google authorization URL.
|
|
82
|
-
* 3. Sends `SubmitIdToken` with the nonce-bearing token → receives DID.
|
|
83
|
-
*/
|
|
84
|
-
authenticate(authInput: AuthInput): Promise<Did>;
|
|
85
|
-
/**
|
|
86
|
-
* Add an Ethereum wallet as an additional authenticator on the
|
|
87
|
-
* already-authenticated account.
|
|
88
|
-
*
|
|
89
|
-
* Proves control of the wallet via SIWE and links it to the session's
|
|
90
|
-
* EXISTING DID (resolved server-side from the session) — it does NOT
|
|
91
|
-
* mint a new DID and does NOT change the session: `this.did` and
|
|
92
|
-
* `this.status` are left untouched, and no new cookie is issued.
|
|
93
|
-
*
|
|
94
|
-
* Requires a completed {@link authenticate} first (e.g. an OIDC
|
|
95
|
-
* login). Only Ethereum is supported — OIDC/email identities are
|
|
96
|
-
* established at login, not added afterwards.
|
|
97
|
-
*
|
|
98
|
-
* Reuses the same client-side eth state machine as login, but posts
|
|
99
|
-
* to the authenticated `auth.add-authenticator` route. If the wallet
|
|
100
|
-
* is already linked to a different account, the server rejects with a
|
|
101
|
-
* typed `eth_auth_map_conflict` error (resolve via account merge).
|
|
102
|
-
*
|
|
103
|
-
* @returns the existing DID the wallet was linked to.
|
|
104
|
-
*/
|
|
105
|
-
addAuthMethod(authInput: EthAuthInput): Promise<Did>;
|
|
106
|
-
/**
|
|
107
|
-
* OIDC two-step authentication with session-binding nonce.
|
|
108
|
-
*
|
|
109
|
-
* Bypasses the WASM client state machine and makes two encrypted
|
|
110
|
-
* RPC calls directly:
|
|
111
|
-
* 1. `InitOidcAuth { provider }` → server generates nonce → returns
|
|
112
|
-
* `ProvideNonce { nonce }`.
|
|
113
|
-
* 2. App calls `getIdToken(nonce)` to obtain a nonce-bound `id_token`.
|
|
114
|
-
* 3. `SubmitIdToken { id_token }` → server verifies token + nonce →
|
|
115
|
-
* returns `Finish { did }`.
|
|
116
|
-
*/
|
|
117
|
-
private authenticateOidc;
|
|
118
|
-
/**
|
|
119
|
-
* Execute an action on the T3n node.
|
|
120
|
-
*
|
|
121
|
-
* Returns the JSON-stringified contract response. Most callers should
|
|
122
|
-
* prefer {@link executeAndDecode}, which JSON-parses the response and
|
|
123
|
-
* optionally validates it with a schema.
|
|
124
|
-
*/
|
|
125
|
-
execute(payload: unknown): Promise<string>;
|
|
126
|
-
/**
|
|
127
|
-
* Fetch the caller's usage feed — balance plus a bounded slice of
|
|
128
|
-
* recent token-ledger entries (charges, mints, transfers), newest
|
|
129
|
-
* first. T3-TS-030 Phase 1D step A (`token.get-usage`).
|
|
130
|
-
*
|
|
131
|
-
* `limit` defaults to 50 server-side and clamps silently to
|
|
132
|
-
* `1..=200`. `afterSeq` is the inclusive upper-bound `seq_no`
|
|
133
|
-
* passed back from a previous page's `next_cursor` to walk older
|
|
134
|
-
* entries. `kinds` filters by `TokenTxKind`; an empty array is
|
|
135
|
-
* treated as "no filter".
|
|
136
|
-
*
|
|
137
|
-
* Unlike {@link execute}, the body of this RPC is plaintext —
|
|
138
|
-
* `token.get-usage` is a session-authed read endpoint and the
|
|
139
|
-
* server-side handler does not run the encryption layer.
|
|
140
|
-
*/
|
|
141
|
-
getUsage(opts?: GetUsageOptions): Promise<UsagePage>;
|
|
142
|
-
/**
|
|
143
|
-
* Execute an action with an attached binary blob using multipart RPC.
|
|
144
|
-
*/
|
|
145
|
-
executeWithBlob(payload: unknown, blob: Blob): Promise<string>;
|
|
146
|
-
/**
|
|
147
|
-
* Execute an action and JSON-decode the response.
|
|
148
|
-
*
|
|
149
|
-
* The server strips its internal `ContractResponse` wrapper at
|
|
150
|
-
* `node/app/src/services/wasm.rs` before sending — the string returned
|
|
151
|
-
* by {@link execute} is the contract's decoded return value directly
|
|
152
|
-
* (no `{response, otp_state}` envelope). This helper is a typed
|
|
153
|
-
* `JSON.parse` with optional schema validation so callers stop
|
|
154
|
-
* reimplementing it at every call site.
|
|
155
|
-
*
|
|
156
|
-
* @param payload - action payload, identical to {@link execute}
|
|
157
|
-
* @param schema - optional validator applied to the parsed value
|
|
158
|
-
* (e.g. a zod schema); anything exposing `.parse(value)` is accepted
|
|
159
|
-
* @returns the decoded response, typed as `T`
|
|
160
|
-
* @throws {ContractResponseError} when the response is not valid JSON
|
|
161
|
-
*/
|
|
162
|
-
executeAndDecode<T = unknown>(payload: unknown, schema?: ContractResponseSchema<T>): Promise<T>;
|
|
163
|
-
/**
|
|
164
|
-
* Build the canonical `ExecuteActionRequest` shape the server
|
|
165
|
-
* expects in `node/primitives/src/action.rs::ExecuteActionRequest`
|
|
166
|
-
* (`script_name`, `script_version`, `function_name`, `input`,
|
|
167
|
-
* optional `pii_did`) and dispatch it through {@link execute}.
|
|
168
|
-
*
|
|
169
|
-
* Two pieces of glue live here that every typed user-contract
|
|
170
|
-
* wrapper would otherwise duplicate:
|
|
171
|
-
*
|
|
172
|
-
* 1. **Field naming.** The server deserialises strictly into
|
|
173
|
-
* `script_name` / `script_version` / `function_name` —
|
|
174
|
-
* sending `contract` / `version` / `function` produces
|
|
175
|
-
* `Invalid action request: missing field …` 400s. Centralising
|
|
176
|
-
* the names here means every wrapper agrees with the server.
|
|
177
|
-
* 2. **`"latest"` resolution.** `script_version` is `SemVer` on
|
|
178
|
-
* the server, so a literal `"latest"` cannot be parsed. We
|
|
179
|
-
* fetch the registered current version via
|
|
180
|
-
* `GET /api/contracts/current?name=…` (cached per script name
|
|
181
|
-
* in `getScriptVersion`) and forward the resolved
|
|
182
|
-
* `MAJOR.MINOR.PATCH` string.
|
|
183
|
-
*
|
|
184
|
-
* Wrappers that need PII delegation can extend this helper
|
|
185
|
-
* later — current call sites are all SelfOnly so `pii_did` stays
|
|
186
|
-
* implicit.
|
|
187
|
-
*/
|
|
188
|
-
private executeUserContract;
|
|
189
|
-
/**
|
|
190
|
-
* Return the authenticated user's Ethereum address from their
|
|
191
|
-
* T3N-hosted per-user wallet, as a 0x-prefixed lowercase hex string.
|
|
192
|
-
* Returns `null` if the user has no wallet yet (pre-backfill edge
|
|
193
|
-
* case — `tee:user` has not yet been migrated for this DID).
|
|
194
|
-
*
|
|
195
|
-
* Backed by the `tee:user/get-self-eth-address` contract function,
|
|
196
|
-
* which delegates to the signing host's `get-user-eth-address`
|
|
197
|
-
* primitive (T3-TS-027 §7.1). The request carries no body; the
|
|
198
|
-
* authenticated user's DID is read from session context by the host.
|
|
199
|
-
*
|
|
200
|
-
* Requires the session to be Authenticated (same precondition as
|
|
201
|
-
* {@link execute}).
|
|
202
|
-
*
|
|
203
|
-
* @throws if unauthenticated, if the node rejects the action, or if
|
|
204
|
-
* the response is not a JSON string / `null`.
|
|
205
|
-
*/
|
|
206
|
-
getSelfEthAddress(): Promise<string | null>;
|
|
207
|
-
/**
|
|
208
|
-
* Enumerate every wallet the authenticated user currently controls
|
|
209
|
-
* under T3-TS-028 multi-wallet custody.
|
|
210
|
-
*
|
|
211
|
-
* `primary` is the identity-bearing wallet — same address
|
|
212
|
-
* {@link getSelfEthAddress} returns, same address the host signs
|
|
213
|
-
* with under `sign-as-user`. `secondary` is an insertion-ordered
|
|
214
|
-
* list of archival wallets absorbed through prior
|
|
215
|
-
* {@link mergeProfiles} calls (most recent last); these are signable
|
|
216
|
-
* only via an explicit sign-with-wallet flow — never ambient.
|
|
217
|
-
*
|
|
218
|
-
* Backed by `tee:user/list-user-wallets` which delegates to the
|
|
219
|
-
* signing host's `list-user-wallets` primitive. See T3-TS-028 §7.1.
|
|
220
|
-
*
|
|
221
|
-
* @throws if unauthenticated, if the node rejects the action, or if
|
|
222
|
-
* the response shape is unexpected.
|
|
223
|
-
*/
|
|
224
|
-
listUserWallets(): Promise<{
|
|
225
|
-
primary: string;
|
|
226
|
-
secondary: string[];
|
|
227
|
-
}>;
|
|
228
|
-
/**
|
|
229
|
-
* Return the ownership audit trail for a specific wallet address.
|
|
230
|
-
*
|
|
231
|
-
* The response is an array of `AuditEntry` objects (newest last)
|
|
232
|
-
* describing every DID that has owned the wallet: `Created` at DID
|
|
233
|
-
* creation, `MergedFrom { source_did }` for every merge that pulled
|
|
234
|
-
* this wallet onto a new owner, `Abandoned` if
|
|
235
|
-
* {@link removeUserWithWalletAbandonment} was called on the last
|
|
236
|
-
* owner. Public-safe data: DIDs + timestamps + reason codes only,
|
|
237
|
-
* no key material or PII. See T3-TS-028 §4.1.
|
|
238
|
-
*
|
|
239
|
-
* @param walletAddress 0x-prefixed 40-char hex Ethereum address.
|
|
240
|
-
* @throws if the node rejects the action or if the response is not
|
|
241
|
-
* a JSON array.
|
|
242
|
-
*/
|
|
243
|
-
getWalletHistory(walletAddress: string): Promise<unknown[]>;
|
|
244
|
-
/**
|
|
245
|
-
* Remove the authenticated user's account AND explicitly abandon
|
|
246
|
-
* any wallets. Writes `Abandoned` audit rows to wallet history,
|
|
247
|
-
* clears the DID's wallet index, then runs the usual user-removal
|
|
248
|
-
* cleanup (profile, authenticators, VCs, attribution).
|
|
249
|
-
*
|
|
250
|
-
* `wallet_secrets[*]` is NOT deleted — key material stays preserved
|
|
251
|
-
* in the TEE but becomes unreachable from any DID. This path
|
|
252
|
-
* exists as an opt-in alternative to {@link removeUser}, which
|
|
253
|
-
* refuses when the DID owns wallets. Callers must sweep funds
|
|
254
|
-
* off-chain BEFORE calling this if they want to retain access —
|
|
255
|
-
* the host does not reconcile balances. See T3-TS-028 §6.2.
|
|
256
|
-
*
|
|
257
|
-
* Requires the session to be Authenticated (SelfOnly delegation).
|
|
258
|
-
*
|
|
259
|
-
* @throws if unauthenticated, if the node rejects the action, or if
|
|
260
|
-
* the response cannot be decoded.
|
|
261
|
-
*/
|
|
262
|
-
removeUserWithWalletAbandonment(): Promise<unknown>;
|
|
263
|
-
/**
|
|
264
|
-
* One-shot poll of `tee:user/contracts::kyc-status`.
|
|
265
|
-
*
|
|
266
|
-
* Returns the current snapshot of the authenticated user's Level 2
|
|
267
|
-
* KYC state — see [[KycStatus]] for the four possible terminal /
|
|
268
|
-
* non-terminal values. The caller usually wants
|
|
269
|
-
* [[kycStatusPoll]] (which loops with §8.4 cadence until a
|
|
270
|
-
* terminal status arrives), but the bare snapshot is useful for
|
|
271
|
-
* pages that need to render the user's standing without waiting
|
|
272
|
-
* (e.g. account-status views, "did the user finish KYC last time
|
|
273
|
-
* they were here?" gates).
|
|
274
|
-
*
|
|
275
|
-
* Phase one default — `providerId` defaults to `"veriff"` (the
|
|
276
|
-
* contract applies the same default when the field is absent),
|
|
277
|
-
* so the most common call site is `await t3n.kycStatus()`.
|
|
278
|
-
*
|
|
279
|
-
* @param providerId optional provider id, mirrored on the wire as
|
|
280
|
-
* `input.provider_id`. Omit for phase-one MetaMask flows.
|
|
281
|
-
* @throws if unauthenticated, if the node rejects the action
|
|
282
|
-
* (e.g. `precondition_failed:` when no `create-kyc-provider-session`
|
|
283
|
-
* row exists yet), or if the response shape is unexpected.
|
|
284
|
-
*/
|
|
285
|
-
kycStatus(providerId?: string): Promise<KycStatus>;
|
|
286
|
-
/**
|
|
287
|
-
* Dispatch a one-time code to the supplied contact via the host's
|
|
288
|
-
* OTP provider. Backed by `tee:user/contracts::otp-request`.
|
|
289
|
-
*
|
|
290
|
-
* The contract persists the unverified contact in the channel's
|
|
291
|
-
* pending slot (`pending_email` / `pending_phone`) and returns
|
|
292
|
-
* `OtpRequestResult` with `status = "otp_pending"` (or `undefined`
|
|
293
|
-
* when the node is configured with `skip_otp = true`). The next
|
|
294
|
-
* step is {@link otpVerify} with the code the user typed.
|
|
295
|
-
*
|
|
296
|
-
* Behaviour notes:
|
|
297
|
-
*
|
|
298
|
-
* - Contact is a discriminated object: `emailChannel` or
|
|
299
|
-
* `smsChannel` (mirrors Rust `OtpRequest`). The legacy
|
|
300
|
-
* `keys.generic_api.otp_channel` body shadow is rejected by the
|
|
301
|
-
* contract.
|
|
302
|
-
* - SMS channel is gated on a verified email. Calling with
|
|
303
|
-
* `channel = "sms"` against a DID that hasn't completed an
|
|
304
|
-
* email roundtrip first throws.
|
|
305
|
-
* - On a fresh DID's first call, `result.isNewProfile === true`.
|
|
306
|
-
* Use this signal — not [[otpVerify]]'s response — for "did the
|
|
307
|
-
* user just register" gating.
|
|
308
|
-
*
|
|
309
|
-
* @throws {RpcError} if the node rejects the action; the message
|
|
310
|
-
* carries the contract-side detail (e.g. sequential-flow guard
|
|
311
|
-
* when the email is missing).
|
|
312
|
-
*/
|
|
313
|
-
otpRequest(input: OtpRequestInput): Promise<OtpRequestResult>;
|
|
314
|
-
/**
|
|
315
|
-
* Redeem a one-time code and bind the contact to the
|
|
316
|
-
* authenticated DID. Backed by
|
|
317
|
-
* `tee:user/contracts::otp-verify`.
|
|
318
|
-
*
|
|
319
|
-
* On success the contract promotes the pending contact back to
|
|
320
|
-
* the canonical slot, writes the AUTH_MAP / DIDS_MAP /
|
|
321
|
-
* USER_AUTHS_MAP authenticator entries, stamps
|
|
322
|
-
* `verified_contacts.{email,phone}`, and mints the ambient
|
|
323
|
-
* `t3n.personal.contact.1` ownership VC when both contacts are
|
|
324
|
-
* now verified.
|
|
325
|
-
*
|
|
326
|
-
* If the contact is already owned by a different DID the
|
|
327
|
-
* contract refuses to silently reparent the authenticator and
|
|
328
|
-
* surfaces a `mergeSuggestion` instead — the caller resolves the
|
|
329
|
-
* merge (typically via {@link mergeProfiles}) and re-attempts.
|
|
330
|
-
*
|
|
331
|
-
* On a wrong / expired code the contract returns the result with
|
|
332
|
-
* `status = "otp_failed"` or `"otp_expired"` — no exception is
|
|
333
|
-
* thrown, the error is surfaced as data so the UI can stay on
|
|
334
|
-
* the verify screen and let the user retry.
|
|
335
|
-
*
|
|
336
|
-
* @throws {RpcError} if the node rejects the action outright
|
|
337
|
-
* (network / decode / bad input shape). Branch on
|
|
338
|
-
* `result.status` for retryable OTP failures.
|
|
339
|
-
*/
|
|
340
|
-
otpVerify(input: OtpVerifyInput): Promise<OtpVerifyResult>;
|
|
341
|
-
/**
|
|
342
|
-
* Submit Level-1 user-input fields to the slim
|
|
343
|
-
* `tee:user/contracts::user-upsert`. The contract merges the
|
|
344
|
-
* supplied profile fields, validates them, mints
|
|
345
|
-
* `t3n.user-input.kyc.1` once every Level-1 field is present, and
|
|
346
|
-
* commits the write.
|
|
347
|
-
*
|
|
348
|
-
* **Pre-condition** (MAT-1374): the DID must already have a
|
|
349
|
-
* verified email — either because {@link otpVerify} bound one or
|
|
350
|
-
* because the session carries a proving authenticator (OIDC /
|
|
351
|
-
* Email auth). Calls without proof are rejected with
|
|
352
|
-
* {@link UserUpsertError} `kind = "EmailNotVerified"`. The
|
|
353
|
-
* recommended UX is "request OTP -> verify OTP -> submit user
|
|
354
|
-
* input" (or use {@link runOtpThenUserInput} which chains all
|
|
355
|
-
* three).
|
|
356
|
-
*
|
|
357
|
-
* The KYC webhook orphan-attestation flow stays here: when
|
|
358
|
-
* `requireExistingUser` is set, the contract identifies the user
|
|
359
|
-
* by DID (vendorData) instead of email and the gate is bypassed.
|
|
360
|
-
*
|
|
361
|
-
* @throws {UserUpsertError} when the contract returns a typed
|
|
362
|
-
* error (`email_not_verified`, `legacy_field`, `user_not_found`).
|
|
363
|
-
* Branch on `err.kind`.
|
|
364
|
-
* @throws {RpcError} for non-typed transport / decode failures.
|
|
365
|
-
*/
|
|
366
|
-
submitUserInput(input: SubmitUserInputArgs): Promise<SubmitUserInputResult>;
|
|
367
|
-
/**
|
|
368
|
-
* Convenience helper: run the explicit OTP roundtrip and submit
|
|
369
|
-
* the slim user-upsert in one call.
|
|
370
|
-
*
|
|
371
|
-
* The caller supplies a `getOtpCode(contact, channel)` callback
|
|
372
|
-
* the SDK invokes between request and verify — this is where
|
|
373
|
-
* a UI prompts the user to type the code that arrived on email
|
|
374
|
-
* / SMS. Throw from the callback to abort the flow; the helper
|
|
375
|
-
* does not retry on its own.
|
|
376
|
-
*
|
|
377
|
-
* Returns the slim `submitUserInput` result on success. Throws
|
|
378
|
-
* {@link UserUpsertError} or `RpcError` on the same conditions
|
|
379
|
-
* as the underlying wrappers.
|
|
380
|
-
*
|
|
381
|
-
* Optional, opt-in path — the recommended default is to call
|
|
382
|
-
* {@link otpRequest}, {@link otpVerify}, and
|
|
383
|
-
* {@link submitUserInput} explicitly so the application owns the
|
|
384
|
-
* flow.
|
|
385
|
-
*/
|
|
386
|
-
runOtpThenUserInput(args: {
|
|
387
|
-
channel: OtpChannel;
|
|
388
|
-
emailAddress?: string;
|
|
389
|
-
phoneNumber?: string;
|
|
390
|
-
profile: SubmitUserInputArgs["profile"];
|
|
391
|
-
organisationDid?: string;
|
|
392
|
-
attestations?: unknown;
|
|
393
|
-
keys?: Record<string, unknown>;
|
|
394
|
-
/**
|
|
395
|
-
* MAT-1618 testnet self-admit, propagated to the inner
|
|
396
|
-
* {@link submitUserInput} call. Inspect `result.tenantAdmit` for
|
|
397
|
-
* the outcome.
|
|
398
|
-
*/
|
|
399
|
-
becomeDevTenant?: boolean;
|
|
400
|
-
getOtpCode: (contact: string, channel: OtpChannel) => Promise<string> | string;
|
|
401
|
-
}): Promise<SubmitUserInputResult>;
|
|
402
|
-
/**
|
|
403
|
-
* Poll `kyc-status` until a terminal status arrives or the
|
|
404
|
-
* configured timeout elapses.
|
|
405
|
-
*
|
|
406
|
-
* Cadence defaults to T3-TS-026 §8.4: 2-second interval for the
|
|
407
|
-
* first 30 seconds, then 5 seconds, with a 5-minute hard cap.
|
|
408
|
-
* Override via `opts.cadence` if you need different numbers
|
|
409
|
-
* (e.g. tests that don't want to wait the full 30 seconds before
|
|
410
|
-
* the slow window kicks in). [[KycStatusTimeoutError]] is thrown
|
|
411
|
-
* if the timeout elapses without reaching a terminal state.
|
|
412
|
-
*
|
|
413
|
-
* `opts.signal` cancels the loop synchronously — the helper
|
|
414
|
-
* stops sleeping and rejects with the abort reason; an
|
|
415
|
-
* already-in-flight `kycStatus()` request is allowed to settle
|
|
416
|
-
* but its result is discarded.
|
|
417
|
-
*
|
|
418
|
-
* `opts.onUpdate` fires once per snapshot, including
|
|
419
|
-
* intermediate `pending` ones. Errors thrown from the callback
|
|
420
|
-
* are swallowed so a misbehaving UI handler can't strand the
|
|
421
|
-
* poll loop.
|
|
422
|
-
*
|
|
423
|
-
* @throws [[KycStatusTimeoutError]] when the cadence's
|
|
424
|
-
* `timeoutMs` elapses without a terminal status.
|
|
425
|
-
* @throws the abort reason when `opts.signal` is aborted.
|
|
426
|
-
* @throws the underlying RPC error when the contract or
|
|
427
|
-
* transport raises (e.g. session expiry mid-poll).
|
|
428
|
-
*/
|
|
429
|
-
kycStatusPoll(opts?: KycPollOptions): Promise<KycStatus>;
|
|
430
|
-
/**
|
|
431
|
-
* The server-minted session ID once handshake has completed, or
|
|
432
|
-
* `null` beforehand (pentest M-1 / MAT-983).
|
|
433
|
-
*/
|
|
434
|
-
getSessionId(): SessionId | null;
|
|
435
|
-
getStatus(): SessionStatus;
|
|
436
|
-
getDid(): Did | null;
|
|
437
|
-
getLastSetCookie(): string | null;
|
|
438
|
-
getLastResponseHeaders(): Record<string, string>;
|
|
439
|
-
isAuthenticated(): boolean;
|
|
440
|
-
/**
|
|
441
|
-
* Run a WASM state machine flow to completion.
|
|
442
|
-
*
|
|
443
|
-
* Clears both `wasmState[method]` and `finalizedPayload[method]`
|
|
444
|
-
* at entry so a flow that previously threw partway (e.g. an RPC
|
|
445
|
-
* error) starts from a clean slate on retry. Without the reset,
|
|
446
|
-
* stale state from the failed attempt leaks into the new flow
|
|
447
|
-
* and `tryFinalize` may either spuriously succeed or run `next()`
|
|
448
|
-
* against a state that no longer matches the action we're sending.
|
|
449
|
-
*
|
|
450
|
-
* The `tryFinalize`-then-`next` order is load-bearing: the loop's
|
|
451
|
-
* exit condition fires *after* the previous iteration's
|
|
452
|
-
* `handleWasmRequest` has flushed the outbound peer reply, so
|
|
453
|
-
* every state-machine emission reaches the wire before we extract
|
|
454
|
-
* the final payload.
|
|
455
|
-
*/
|
|
456
|
-
private runFlow;
|
|
457
|
-
/**
|
|
458
|
-
* Try to finalize the current flow. Returns the finish() payload
|
|
459
|
-
* (a serialized Session for handshake, a serialized DID for auth)
|
|
460
|
-
* or `null` if the state machine has not reached its terminal phase
|
|
461
|
-
* yet.
|
|
462
|
-
*
|
|
463
|
-
* The "not yet finalized" case is the loop's signal to keep
|
|
464
|
-
* iterating, not a real error. Any *other* failure must propagate
|
|
465
|
-
* so callers see real WASM errors instead of silent retries that
|
|
466
|
-
* spin forever.
|
|
467
|
-
*
|
|
468
|
-
* The terminal payload is stored in `finalizedPayload[method]`
|
|
469
|
-
* (a separate field from `wasmState[method]`) so the in-flight
|
|
470
|
-
* state-machine bytes and the finalized session/DID bytes never
|
|
471
|
-
* occupy the same slot. `getSessionState()` reads from
|
|
472
|
-
* `finalizedPayload.handshake`.
|
|
473
|
-
*/
|
|
474
|
-
private tryFinalize;
|
|
475
|
-
/**
|
|
476
|
-
* Handle a WASM request based on its type
|
|
477
|
-
*/
|
|
478
|
-
private handleWasmRequest;
|
|
479
|
-
/**
|
|
480
|
-
* Handle a send-remote request by calling the RPC endpoint
|
|
481
|
-
*/
|
|
482
|
-
private handleSendRemote;
|
|
483
|
-
private captureHandshakeResult;
|
|
484
|
-
/**
|
|
485
|
-
* Handle a guest-to-host request using configured handlers
|
|
486
|
-
*/
|
|
487
|
-
private handleGuestToHost;
|
|
488
|
-
/**
|
|
489
|
-
* Send an RPC request with automatic encryption/decryption
|
|
490
|
-
*/
|
|
491
|
-
private sendRpcRequest;
|
|
492
|
-
/**
|
|
493
|
-
* Send a session-authenticated JSON-RPC request with plaintext
|
|
494
|
-
* params/result (no SessionEncryption layer). Used for tenant-facing
|
|
495
|
-
* read endpoints (`token.get-balance`, `token.get-usage`) where the
|
|
496
|
-
* server-side handler reads the JSON envelope directly.
|
|
497
|
-
*
|
|
498
|
-
* Auto-attaches the `Session-Id` header; surfaces JSON-RPC errors
|
|
499
|
-
* the same way as {@link sendRpcRequest} (typed
|
|
500
|
-
* `InsufficientCreditError` when applicable, `RpcError` otherwise).
|
|
501
|
-
* Returns the parsed `result` field — typically a JSON object the
|
|
502
|
-
* caller will narrow to the endpoint's response type.
|
|
503
|
-
*/
|
|
504
|
-
private sendUnencryptedSessionRpc;
|
|
505
|
-
/**
|
|
506
|
-
* Inspect a JSON-RPC response for an `error` field and throw the
|
|
507
|
-
* appropriate typed exception. No-op when `response.error` is
|
|
508
|
-
* absent. Shared by every RPC path so wire-shape changes in the
|
|
509
|
-
* server's error envelope (typed-error subclasses, request-id
|
|
510
|
-
* placement, detail formatting) only need to land in one place.
|
|
511
|
-
*
|
|
512
|
-
* JSON-RPC `error.message` is the generic category string
|
|
513
|
-
* ("Invalid params", "Internal error", …). The node attaches the
|
|
514
|
-
* actionable text and per-request correlation id in `error.data`
|
|
515
|
-
* — see `node/api/src/responses/rpc.rs::from_service_error`.
|
|
516
|
-
* Extract both so callers (and toasts that only render `.message`)
|
|
517
|
-
* get the real reason plus an id an operator can grep in
|
|
518
|
-
* `api::error` logs.
|
|
519
|
-
*
|
|
520
|
-
* T3-TS-030 chargepoint denials surface as a typed
|
|
521
|
-
* `InsufficientCreditError` so frontends can branch on
|
|
522
|
-
* `instanceof` to render an "out of credit" UI without
|
|
523
|
-
* prefix-matching the message themselves. Wire format is pinned
|
|
524
|
-
* by `api/src/error.rs::service_insufficient_credit_wire_format_is_stable`.
|
|
525
|
-
*/
|
|
526
|
-
private throwIfRpcError;
|
|
527
|
-
private sendMultipartRpcRequest;
|
|
528
|
-
/**
|
|
529
|
-
* Capture the server-minted `Session-Id` from the last handshake
|
|
530
|
-
* response headers (pentest M-1 / MAT-983). Validates shape so a
|
|
531
|
-
* broken or MITM'd response fails loudly instead of leaving a
|
|
532
|
-
* garbage value in the client. Idempotent: only the first valid
|
|
533
|
-
* mint per session is honoured — subsequent handshake RPC legs
|
|
534
|
-
* (none exist today, but the state-machine loop can iterate) do
|
|
535
|
-
* not overwrite an already-set value.
|
|
536
|
-
*/
|
|
537
|
-
private captureMintedSessionId;
|
|
538
|
-
/**
|
|
539
|
-
* Get the finalized session blob (for `session.encrypt` calls).
|
|
540
|
-
* Populated by `tryFinalize` once the handshake state machine
|
|
541
|
-
* reaches its terminal phase.
|
|
542
|
-
*/
|
|
543
|
-
private getSessionState;
|
|
544
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transport layer for T3n SDK
|
|
3
|
-
*
|
|
4
|
-
* Abstracts the communication with T3n nodes, allowing for different
|
|
5
|
-
* implementations (HTTP, WebSocket, Mock) and easy testing.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* JSON-RPC request structure
|
|
9
|
-
*/
|
|
10
|
-
export interface JsonRpcRequest {
|
|
11
|
-
jsonrpc: "2.0";
|
|
12
|
-
method: string;
|
|
13
|
-
params: unknown;
|
|
14
|
-
id: string | number;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* JSON-RPC response structure
|
|
18
|
-
*/
|
|
19
|
-
export interface JsonRpcResponse {
|
|
20
|
-
jsonrpc: "2.0";
|
|
21
|
-
result?: unknown;
|
|
22
|
-
error?: {
|
|
23
|
-
code: number;
|
|
24
|
-
message: string;
|
|
25
|
-
data?: unknown;
|
|
26
|
-
};
|
|
27
|
-
id: string | number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Transport interface for sending requests to T3n nodes
|
|
31
|
-
*/
|
|
32
|
-
export interface Transport {
|
|
33
|
-
/**
|
|
34
|
-
* Send a JSON-RPC request to the T3n node
|
|
35
|
-
* @param request - The JSON-RPC request to send
|
|
36
|
-
* @param headers - Additional headers to include
|
|
37
|
-
* @returns Promise that resolves to the JSON-RPC response
|
|
38
|
-
*/
|
|
39
|
-
send(request: JsonRpcRequest, headers: Record<string, string>): Promise<JsonRpcResponse>;
|
|
40
|
-
/**
|
|
41
|
-
* Send a JSON-RPC request with an attached binary blob (multipart/form-data).
|
|
42
|
-
* Part 1 (name=jsonrpc): JSON-RPC envelope.
|
|
43
|
-
* Part 2 (name=blob): raw binary bytes (e.g. WASM bytecode).
|
|
44
|
-
*/
|
|
45
|
-
sendMultipart?(request: JsonRpcRequest, headers: Record<string, string>, blob: Blob): Promise<JsonRpcResponse>;
|
|
46
|
-
/**
|
|
47
|
-
* Optional accessor for the latest Set-Cookie header value.
|
|
48
|
-
* (Useful in Node.js demos/tests; browsers block HttpOnly cookies.)
|
|
49
|
-
*/
|
|
50
|
-
getLastSetCookie?(): string | null;
|
|
51
|
-
/**
|
|
52
|
-
* Optional accessor for the last response headers (debugging).
|
|
53
|
-
*/
|
|
54
|
-
getLastResponseHeaders?(): Record<string, string>;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* HTTP transport implementation using fetch
|
|
58
|
-
*/
|
|
59
|
-
export declare class HttpTransport implements Transport {
|
|
60
|
-
private baseUrl;
|
|
61
|
-
private timeout;
|
|
62
|
-
private lastSetCookie;
|
|
63
|
-
private lastResponseHeaders;
|
|
64
|
-
constructor(baseUrl: string, timeout?: number);
|
|
65
|
-
getLastSetCookie(): string | null;
|
|
66
|
-
getLastResponseHeaders(): Record<string, string>;
|
|
67
|
-
send(request: JsonRpcRequest, headers: Record<string, string>): Promise<JsonRpcResponse>;
|
|
68
|
-
sendMultipart(request: JsonRpcRequest, headers: Record<string, string>, blob: Blob): Promise<JsonRpcResponse>;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Mock transport for testing
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* const mockTransport = new MockTransport();
|
|
76
|
-
* mockTransport.mockResponse("auth.handshake", { result: "..." });
|
|
77
|
-
*
|
|
78
|
-
* const client = new T3nClient({
|
|
79
|
-
* transport: mockTransport,
|
|
80
|
-
* wasmComponent: mockWasm,
|
|
81
|
-
* });
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export declare class MockTransport implements Transport {
|
|
85
|
-
private responses;
|
|
86
|
-
private responseHeaders;
|
|
87
|
-
private lastResponseHeaders;
|
|
88
|
-
private requests;
|
|
89
|
-
private multipartRequests;
|
|
90
|
-
/**
|
|
91
|
-
* Mock a response for a specific method
|
|
92
|
-
*/
|
|
93
|
-
mockResponse(method: string, response: Partial<JsonRpcResponse>): void;
|
|
94
|
-
/**
|
|
95
|
-
* Mock response headers for a specific method. Used by tests to
|
|
96
|
-
* simulate the server-minted `Session-Id` header the SDK picks up
|
|
97
|
-
* from the `auth.handshake` response (MAT-983). Unset methods
|
|
98
|
-
* default to no headers.
|
|
99
|
-
*/
|
|
100
|
-
mockResponseHeaders(method: string, headers: Record<string, string>): void;
|
|
101
|
-
/**
|
|
102
|
-
* Mock an error response for a specific method
|
|
103
|
-
*/
|
|
104
|
-
mockError(method: string, code: number, message: string, data?: unknown): void;
|
|
105
|
-
getLastResponseHeaders(): Record<string, string>;
|
|
106
|
-
/**
|
|
107
|
-
* Get all requests that were sent
|
|
108
|
-
*/
|
|
109
|
-
getRequests(): Array<{
|
|
110
|
-
request: JsonRpcRequest;
|
|
111
|
-
headers: Record<string, string>;
|
|
112
|
-
}>;
|
|
113
|
-
/**
|
|
114
|
-
* Get requests for a specific method
|
|
115
|
-
*/
|
|
116
|
-
getRequestsForMethod(method: string): Array<{
|
|
117
|
-
request: JsonRpcRequest;
|
|
118
|
-
headers: Record<string, string>;
|
|
119
|
-
}>;
|
|
120
|
-
getMultipartRequests(): Array<{
|
|
121
|
-
request: JsonRpcRequest;
|
|
122
|
-
headers: Record<string, string>;
|
|
123
|
-
blob: Blob;
|
|
124
|
-
}>;
|
|
125
|
-
/**
|
|
126
|
-
* Clear all recorded requests
|
|
127
|
-
*/
|
|
128
|
-
clearRequests(): void;
|
|
129
|
-
sendMultipart(request: JsonRpcRequest, headers: Record<string, string>, blob: Blob): Promise<JsonRpcResponse>;
|
|
130
|
-
send(request: JsonRpcRequest, headers: Record<string, string>): Promise<JsonRpcResponse>;
|
|
131
|
-
}
|