@terminal3/t3n-sdk 2.4.0 → 2.7.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.
@@ -196,6 +196,63 @@ export declare function ethRecoverEip191(msg: Uint8Array, sig: Uint8Array): Uint
196
196
  * `delegation-types::verify_agent_sig` accepts as the 64-byte form.
197
197
  */
198
198
  export declare function signAgentInvocation(preimage: Uint8Array, secret: Uint8Array): Uint8Array;
199
+ /**
200
+ * Options for {@link DelegationCustodialClient}.
201
+ */
202
+ export interface DelegationCustodialClientOpts {
203
+ /**
204
+ * Explicit semver string for the delegation contract (e.g. `"1.0.0"`).
205
+ * When omitted the client resolves `"latest"` via
206
+ * `GET /api/contracts/current?name=tee:delegation/contracts` (one
207
+ * request per client instance, cached in `getScriptVersion`).
208
+ */
209
+ scriptVersion?: string;
210
+ }
211
+ /**
212
+ * Result returned by {@link DelegationCustodialClient.signCustodial}.
213
+ */
214
+ export interface SignCustodialResult {
215
+ /** RFC 8785 JCS bytes of the credential, exactly as signed by the node. */
216
+ credentialJcs: Uint8Array;
217
+ /** 65-byte EIP-191 signature over `credentialJcs` produced by the TEE. */
218
+ userSig: Uint8Array;
219
+ }
220
+ /**
221
+ * Wraps the `tee:delegation/contracts::sign` function for OIDC users
222
+ * (or any user whose private key is held by the TEE rather than the
223
+ * browser).
224
+ *
225
+ * ETH-EOA users who hold their own key should call
226
+ * {@link signCredential} directly — no network round-trip required.
227
+ *
228
+ * The client must be constructed with an authenticated {@link T3nClient}
229
+ * instance and the node's base URL; `signCustodial` sends the credential
230
+ * body to the TEE and returns the signed bytes.
231
+ *
232
+ * Reference: `node/tests/harness/src/payroll_seed.rs` (the
233
+ * `tee:delegation.sign` invocation at line 550).
234
+ */
235
+ export declare class DelegationCustodialClient {
236
+ private readonly t3n;
237
+ private readonly baseUrl;
238
+ private readonly opts;
239
+ constructor(t3n: import("./t3n-client").T3nClient, baseUrl: string, opts?: DelegationCustodialClientOpts);
240
+ /**
241
+ * Request the TEE to sign a delegation credential on behalf of the
242
+ * authenticated user.
243
+ *
244
+ * The `body` is sent as-is as the `input.body` field of the
245
+ * `tee:delegation/contracts::sign` action. Use
246
+ * {@link buildDelegationCredential} + the wire-shape projection to
247
+ * produce the correct representation — binary fields (`agent_pubkey`,
248
+ * `vc_id`) must be base64url-no-pad strings, and `not_before_secs` /
249
+ * `not_after_secs` must be decimal strings.
250
+ *
251
+ * Returns `{ credentialJcs, userSig }` — both as `Uint8Array` — ready
252
+ * to be passed into {@link buildPayrollInvocation}.
253
+ */
254
+ signCustodial(body: Record<string, unknown>): Promise<SignCustodialResult>;
255
+ }
199
256
  /** Options for {@link buildPayrollInvocation}. */
200
257
  export interface BuildPayrollInvocationOpts {
201
258
  credentialJcs: Uint8Array;
@@ -10,3 +10,4 @@ export * from "./actions";
10
10
  export * from "./request-parser";
11
11
  export * from "./contract-response";
12
12
  export * from "./delegation";
13
+ export * from "./org-data";
@@ -0,0 +1,195 @@
1
+ /**
2
+ * OrgDataClient — typed wrapper over the existing authenticated
3
+ * `/api/rpc` + `action.execute` pipeline.
4
+ *
5
+ * Unlike the removed direct `/api/user-contract/*` transport, this
6
+ * client reuses Trinity's normal session-backed ETH auth flow:
7
+ *
8
+ * 1. `auth.handshake`
9
+ * 2. `auth.authenticate`
10
+ * 3. `action.execute`
11
+ *
12
+ * The class keeps its public constructor stable for callers that
13
+ * already have an ETH secret key and expected DID, but internally it
14
+ * owns a lazily-authenticated `T3nClient` instance rather than
15
+ * constructing one-shot signed HTTP envelopes per call.
16
+ */
17
+ import type { Transport } from "./transport";
18
+ import { T3nClient } from "./t3n-client";
19
+ import type { WasmComponent } from "../wasm";
20
+ import { type GuestToHostHandlers } from "../types";
21
+ import type { OrgContractGrants, OrgPolicyMeta, OrgWriters, DataListResponse, DataGetResponse, MutationResponse, UserGrant } from "../types/org-data";
22
+ declare function executeOrgDataAction(client: T3nClient, baseUrl: string, functionName: string, args: Record<string, unknown>): Promise<unknown>;
23
+ export interface CreatePolicyInput {
24
+ orgDid: string;
25
+ initialAdminDid: string;
26
+ maxAdmins?: number;
27
+ }
28
+ export interface UpdateMetaInput {
29
+ orgDid: string;
30
+ admins: string[];
31
+ maxAdmins?: number;
32
+ }
33
+ export interface SetWritersInput {
34
+ orgDid: string;
35
+ scope: string;
36
+ writers: string[];
37
+ }
38
+ export interface SetGrantsInput {
39
+ orgDid: string;
40
+ contractId: string;
41
+ grants: UserGrant[];
42
+ }
43
+ export interface DeleteGrantsInput {
44
+ orgDid: string;
45
+ contractId: string;
46
+ }
47
+ export interface WriteDataInput {
48
+ orgDid: string;
49
+ scope: string;
50
+ payloadHex: string;
51
+ /** Explicit entry ID (32 hex chars). When present, enables idempotent upsert. */
52
+ entryId?: string;
53
+ /** Client-supplied monotonic counter for ID derivation when `entryId` is absent. */
54
+ clientSeqNo?: number;
55
+ }
56
+ export interface DeleteDataInput {
57
+ orgDid: string;
58
+ scope: string;
59
+ /** Hex-encoded entry ID (32 hex chars). */
60
+ entryId: string;
61
+ }
62
+ export interface DeleteScopeInput {
63
+ orgDid: string;
64
+ scope: string;
65
+ }
66
+ export interface PolicyGetInput {
67
+ orgDid: string;
68
+ }
69
+ export interface WritersGetInput {
70
+ orgDid: string;
71
+ scope: string;
72
+ }
73
+ export interface GrantsGetInput {
74
+ orgDid: string;
75
+ contractId: string;
76
+ }
77
+ export interface DataListInput {
78
+ orgDid: string;
79
+ scope: string;
80
+ offset?: number;
81
+ limit?: number;
82
+ }
83
+ export interface DataGetInput {
84
+ orgDid: string;
85
+ scope: string;
86
+ /** Hex-encoded entry ID (32 hex chars). */
87
+ entryId: string;
88
+ }
89
+ export interface ExecuteOrgDataActionOptions {
90
+ /**
91
+ * Deprecated. The direct signed-envelope transport used this as the
92
+ * envelope expiry window; the session-backed RPC path ignores it.
93
+ */
94
+ ttlSecs?: number;
95
+ }
96
+ /**
97
+ * Options used when constructing an {@link OrgDataClient}.
98
+ */
99
+ export interface OrgDataClientOptions extends ExecuteOrgDataActionOptions {
100
+ /** Optional preloaded WASM component for tests or shared callers. */
101
+ wasmComponent?: WasmComponent;
102
+ /** Optional transport override, primarily for tests. */
103
+ transport?: Transport;
104
+ /**
105
+ * Optional handler overrides. If `EthSign` is omitted, the client
106
+ * uses the supplied `ethSecret` to satisfy Trinity's existing ETH
107
+ * auth challenge flow automatically.
108
+ */
109
+ handlers?: GuestToHostHandlers;
110
+ }
111
+ /**
112
+ * Client for session-authenticated org-data contract execution.
113
+ *
114
+ * Constructed with the node's base URL, the caller's 32-byte ETH secret
115
+ * key, and the caller's DID (`did:t3n:<40-hex>`). The first method call
116
+ * lazily creates a `T3nClient`, completes ETH session auth, verifies that
117
+ * the authenticated DID matches `userDid`, and then reuses that session for
118
+ * subsequent contract calls.
119
+ */
120
+ export declare class OrgDataClient {
121
+ private readonly baseUrl;
122
+ private readonly ethSecret;
123
+ private readonly userDid;
124
+ private readonly opts;
125
+ private clientPromise;
126
+ constructor(baseUrl: string, ethSecret: Uint8Array, userDid: string, opts?: OrgDataClientOptions);
127
+ private getAuthenticatedClient;
128
+ private initialiseClient;
129
+ private call;
130
+ /**
131
+ * Initialise the data-tier policy for an existing organisation.
132
+ *
133
+ * The calling user must be named as `initialAdminDid`. New orgs created
134
+ * after the org-data contract was deployed have their policy seeded
135
+ * automatically by the organisation contract; call this only for orgs
136
+ * that pre-date the contract deployment.
137
+ */
138
+ createPolicy(input: CreatePolicyInput): Promise<MutationResponse>;
139
+ /**
140
+ * Replace the admin list and/or `max_admins` cap on an existing policy.
141
+ *
142
+ * The calling user cannot remove themselves when they are the sole
143
+ * remaining admin; another admin must be added first.
144
+ */
145
+ updateMeta(input: UpdateMetaInput): Promise<MutationResponse>;
146
+ /**
147
+ * Full replacement of the writer list for a data scope.
148
+ *
149
+ * Passing an empty list removes the entry (no writers allowed).
150
+ * Scope names are canonicalised to lowercase before storage.
151
+ */
152
+ setWriters(input: SetWritersInput): Promise<MutationResponse>;
153
+ /**
154
+ * Full replacement of the user-grant list for a contract.
155
+ *
156
+ * Passing an empty list removes the entry.
157
+ */
158
+ setGrants(input: SetGrantsInput): Promise<MutationResponse>;
159
+ /**
160
+ * Delete the grant entry for a contract entirely.
161
+ */
162
+ deleteGrants(input: DeleteGrantsInput): Promise<MutationResponse>;
163
+ /**
164
+ * Write a data entry to the org's scope.
165
+ *
166
+ * When `entryId` is supplied, the call is an idempotent upsert.
167
+ * When absent, `clientSeqNo` is required and the entry ID is derived
168
+ * via SHA-256 from `(org_did, scope, writer_did, client_seq_no)`.
169
+ */
170
+ writeData(input: WriteDataInput): Promise<MutationResponse>;
171
+ /** Delete a single data entry by entry ID. */
172
+ deleteData(input: DeleteDataInput): Promise<MutationResponse>;
173
+ /**
174
+ * Bulk-delete all entries in a scope.
175
+ *
176
+ * Requires admin access (unlike `deleteData` which requires writer access).
177
+ */
178
+ deleteScope(input: DeleteScopeInput): Promise<MutationResponse>;
179
+ /** Read the policy metadata for an org (admin-only). */
180
+ policyGet(input: PolicyGetInput): Promise<OrgPolicyMeta>;
181
+ /** Read the writer list for a scope (admin-only). */
182
+ writersGet(input: WritersGetInput): Promise<OrgWriters>;
183
+ /** Read the grant list for a contract (admin-only). */
184
+ grantsGet(input: GrantsGetInput): Promise<OrgContractGrants>;
185
+ /**
186
+ * List entry IDs for a scope (admin-only), paginated.
187
+ *
188
+ * Pass `offset` from the previous response's `next_offset` to fetch
189
+ * the next page.
190
+ */
191
+ dataList(input: DataListInput): Promise<DataListResponse>;
192
+ /** Retrieve a single data entry by entry ID (admin-only). */
193
+ dataGet(input: DataGetInput): Promise<DataGetResponse>;
194
+ }
195
+ export { executeOrgDataAction };
@@ -20,6 +20,11 @@ export type { KycStatus, KycStatusKind, KycPollOptions, KycPollCadence, } from "
20
20
  export { DEFAULT_KYC_POLL_CADENCE, TERMINAL_KYC_STATUSES, KycStatusTimeoutError, } from "./types/kyc";
21
21
  export type { OtpChannel, OtpRequestInput, OtpRequestResult, OtpVerifyInput, OtpVerifyResult, OtpMergeSuggestion, UserInputProfile, SubmitUserInputArgs, SubmitUserInputResult, UserUpsertErrorKind, } from "./types/user";
22
22
  export { UserUpsertError } from "./types/user";
23
+ export { OrgDataClient } from "./client/org-data";
24
+ export type { OrgDataClientOptions, CreatePolicyInput, UpdateMetaInput, SetWritersInput, SetGrantsInput, DeleteGrantsInput, WriteDataInput, DeleteDataInput, DeleteScopeInput, PolicyGetInput, WritersGetInput, GrantsGetInput, DataListInput, DataGetInput, ExecuteOrgDataActionOptions, } from "./client/org-data";
25
+ export type { OrgDataActionWire, OrgPolicyMeta, OrgWriters, OrgContractGrants, UserGrant, EmployeeRecord, EmploymentStatus, ResidencyCategory, AgeBand, ExpenseClaim, MutationResponse, DataListResponse, DataGetResponse, } from "./types/org-data";
26
+ export { DelegationCustodialClient } from "./client/delegation";
27
+ export type { DelegationCustodialClientOpts, SignCustodialResult, } from "./client/delegation";
23
28
  export { DELEGATION_CREDENTIAL_DOMAIN, DELEGATION_INVOCATION_DOMAIN, VC_ID_LEN, NONCE_LEN, REQUEST_HASH_LEN, AGENT_PUBKEY_LEN, ETH_SIG_LEN, buildDelegationCredential, validateCredentialBody, canonicaliseCredential, canonicaliseRequest, requestHash, buildInvocationPreimage, eip191Digest, signCredential, ethRecoverEip191, signAgentInvocation, buildPayrollInvocation, compactDidFromBytes, b64uEncodeBytes, b64uDecodeStrict, _b64uEncode, } from "./client/delegation";
24
29
  export type { DelegationCredential, DelegationEnvelope, PayrollRunRequest, PayrollInvocation, SignDelegationResponse, BuildDelegationCredentialOpts, BuildPayrollInvocationOpts, } from "./client/delegation";
25
30
  export { metamask_sign, metamask_get_address, eth_get_address, createDefaultHandlers, createMlKemPublicKeyHandler, createRandomHandler, } from "./client/handlers";
@@ -41,3 +41,4 @@ export interface GuestToHostHandlers {
41
41
  export * from "./session";
42
42
  export * from "./auth";
43
43
  export * from "./user";
44
+ export * from "./org-data";
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Org-data wire types mirroring the Rust contract shapes in
3
+ * `tee-contract-org-data` and `org-data-types`.
4
+ *
5
+ * Plain TypeScript interfaces (no zod) — the SDK does not use a
6
+ * validation library for domain types; see the existing `types/` files.
7
+ *
8
+ * Reference: `org-data-types/src/lib.rs` and
9
+ * `tee-contract-org-data/src/org_data.rs`.
10
+ */
11
+ /**
12
+ * Capability grant stored under `ORG_CONTRACT_GRANTS_MAP`.
13
+ *
14
+ * Mirrors `org_data_types::UserGrant`.
15
+ */
16
+ export interface UserGrant {
17
+ /** The user this grant applies to (`did:t3n:<40-hex>`). */
18
+ user_did: string;
19
+ /** WIT function names the user may invoke (e.g. `"run-payroll"`). */
20
+ functions: string[];
21
+ /** Data scope paths the user may access (e.g. `"payroll/employees"`). */
22
+ scopes: string[];
23
+ /**
24
+ * Optional key-value constraints that must match the request metadata
25
+ * exactly for every key present in this map.
26
+ */
27
+ constraints: Record<string, string>;
28
+ /** Unix timestamp (secs) after which this grant is expired. `null` means never expires. */
29
+ expires_at_secs: number | null;
30
+ }
31
+ /**
32
+ * Policy record for an organisation's data tier.
33
+ *
34
+ * Mirrors `org_data_types::OrgPolicyMeta`.
35
+ */
36
+ export interface OrgPolicyMeta {
37
+ /** DIDs (`did:t3n:<40-hex>`) of users authorised to manage policy and read data. */
38
+ admins: string[];
39
+ /** Maximum number of admins allowed for this org. */
40
+ max_admins: number;
41
+ /** Unix timestamp (secs) when the policy was first created. */
42
+ created_at_secs: number;
43
+ /** Unix timestamp (secs) of the most recent policy update. */
44
+ updated_at_secs: number;
45
+ }
46
+ export type EmploymentStatus = "Active" | "Terminated";
47
+ /** Singapore CPF residency categories. */
48
+ export type ResidencyCategory = "Citizen" | "Pr1" | "Pr2" | "PrThreePlus" | "Foreigner";
49
+ export type AgeBand = "Under35" | "Age35To45" | "Age45To50" | "Age50To55" | "Age55To60" | "Age60To65" | "Over65";
50
+ export interface ExpenseClaim {
51
+ claim_id: string;
52
+ amount_cents: number;
53
+ category: string;
54
+ description: string;
55
+ per_diem_days?: number;
56
+ }
57
+ /**
58
+ * Employee data row stored under `OrgData[org || "payroll/employees" || entry_id]`.
59
+ *
60
+ * Mirrors `tee-contract-payroll::types::EmployeeRecord`.
61
+ */
62
+ export interface EmployeeRecord {
63
+ employee_id: string;
64
+ employment_status: EmploymentStatus;
65
+ is_on_probation: boolean;
66
+ hire_date: string;
67
+ termination_date?: string;
68
+ /** Monthly gross base salary in integer cents SGD. */
69
+ base_salary_cents: number;
70
+ unpaid_leave_days: number;
71
+ working_days_in_period: number;
72
+ overtime_hours: number;
73
+ hourly_rate_cents: number;
74
+ residency: ResidencyCategory;
75
+ age_band: AgeBand;
76
+ expense_claims: ExpenseClaim[];
77
+ /** Opaque reference used by the service layer for disbursement. */
78
+ bank_account_ref: string;
79
+ bank_account_changed_recently: boolean;
80
+ }
81
+ /**
82
+ * Standard response returned by all policy write and data mutation operations.
83
+ *
84
+ * Mirrors `tee-contract-org-data::org_data::MutationResponse`.
85
+ */
86
+ export interface MutationResponse {
87
+ /** `"created"`, `"updated"`, or `"deleted"`. */
88
+ status: string;
89
+ /** Hex-encoded entry ID; present on data write/delete operations. */
90
+ entry_id?: string;
91
+ /** Whether the target key existed before deletion; present on single-entry deletes. */
92
+ deleted?: boolean;
93
+ /** Number of entries removed; present on `org-data-delete-scope`. */
94
+ deleted_entries?: number;
95
+ tx_hash: string | null;
96
+ }
97
+ /**
98
+ * Response type alias for org-writers-get.
99
+ *
100
+ * The wire body is `{ writers: string[] }` where each entry is
101
+ * `did:t3n:<40-hex>`.
102
+ */
103
+ export interface OrgWriters {
104
+ writers: string[];
105
+ }
106
+ /**
107
+ * Response type alias for org-grants-get.
108
+ *
109
+ * The wire body echoes the `contract_id` alongside the grant list.
110
+ */
111
+ export interface OrgContractGrants {
112
+ contract_id: string;
113
+ grants: UserGrant[];
114
+ }
115
+ /** Response for `org-data-list`. */
116
+ export interface DataListResponse {
117
+ /** Hex-encoded entry IDs for this page. */
118
+ entry_ids: string[];
119
+ /** Offset to pass for the next page. `null` when this is the last page. */
120
+ next_offset: number | null;
121
+ /** Total number of entries in the scope (across all pages). */
122
+ total: number;
123
+ }
124
+ /** Response for `org-data-get`. */
125
+ export interface DataGetResponse {
126
+ entry_id: string;
127
+ /** Hex-encoded raw payload bytes. */
128
+ payload_hex: string;
129
+ }
130
+ /**
131
+ * Legacy direct-route org-data envelope shape retained for compatibility.
132
+ *
133
+ * This mirrors the removed `/api/user-contract/execute` body format from
134
+ * the transitional transport. New callers should use `OrgDataClient`,
135
+ * which now dispatches through authenticated `/api/rpc` +
136
+ * `action.execute` instead.
137
+ */
138
+ export interface OrgDataActionWire {
139
+ nonce: string;
140
+ user_did: string;
141
+ authenticator_id: string;
142
+ contract_id: string;
143
+ function: string;
144
+ args_hash: string;
145
+ expires_at_secs: number;
146
+ signature: string;
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terminal3/t3n-sdk",
3
- "version": "2.4.0",
3
+ "version": "2.7.0",
4
4
  "type": "module",
5
5
  "description": "T3n TypeScript SDK - A minimal SDK that mirrors the server's RPC handler approach",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,9 @@
30
30
  "copy-wasm": "mkdir -p dist/wasm/generated && cp -r src/wasm/generated/* dist/wasm/generated/",
31
31
  "test": "vitest run",
32
32
  "test:watch": "vitest",
33
+ "e2e:agent": "tsx scripts/payroll-v2-agent-e2e.ts",
34
+ "e2e:member": "tsx scripts/payroll-v2-e2e.ts",
35
+ "store-token": "tsx scripts/payroll-v2-store-token.ts",
33
36
  "test:coverage": "vitest run --coverage",
34
37
  "lint": "eslint src --ext .ts,.tsx",
35
38
  "lint:fix": "eslint src --ext .ts,.tsx --fix",
@@ -105,13 +108,5 @@
105
108
  "@noble/hashes": "^2.2.0",
106
109
  "canonicalize": "^3.0.0",
107
110
  "ethers": "^6.16.0"
108
- },
109
- "pnpm": {
110
- "overrides": {
111
- "rollup": ">=4.59.0",
112
- "esbuild": ">=0.25.0",
113
- "minimatch": ">=9.0.7",
114
- "postcss": ">=8.5.10"
115
- }
116
111
  }
117
- }
112
+ }