@trusset/sdk 0.1.0 → 0.5.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.mjs CHANGED
@@ -1,38 +1,56 @@
1
- // src/utils/errors.ts
2
- var TrussetError = class _TrussetError extends Error {
3
- constructor(message, code, statusCode, requestId) {
4
- super(message);
5
- this.name = "TrussetError";
6
- this.code = code;
7
- this.statusCode = statusCode;
8
- this.requestId = requestId ?? null;
9
- Object.setPrototypeOf(this, _TrussetError.prototype);
10
- }
11
- };
12
- var AuthError = class _AuthError extends TrussetError {
13
- constructor(message, requestId) {
14
- super(message, "AUTH_FAILED", 401, requestId);
15
- this.name = "AuthError";
16
- Object.setPrototypeOf(this, _AuthError.prototype);
17
- }
18
- };
19
- var ValidationError = class _ValidationError extends TrussetError {
20
- constructor(message) {
21
- super(message, "VALIDATION_ERROR", 400);
22
- this.name = "ValidationError";
23
- Object.setPrototypeOf(this, _ValidationError.prototype);
24
- }
25
- };
26
- var NetworkError = class _NetworkError extends TrussetError {
27
- constructor(message) {
28
- super(message, "NETWORK_ERROR", 0);
29
- this.name = "NetworkError";
30
- Object.setPrototypeOf(this, _NetworkError.prototype);
31
- }
32
- };
1
+ import {
2
+ CustomerIdLinks,
3
+ CustomerIdentity,
4
+ CustomerManagement,
5
+ CustomerSync,
6
+ CustomerVerificationProfiles,
7
+ Customers,
8
+ manifestToVerifyInput
9
+ } from "./chunk-OQG3PEF3.mjs";
10
+ import {
11
+ Lending,
12
+ LendingAccessLists,
13
+ LendingAssetData,
14
+ LendingAuctions,
15
+ LendingBots,
16
+ LendingDocuments,
17
+ LendingFees,
18
+ LendingHooks,
19
+ LendingInsurance,
20
+ LendingLiquidations,
21
+ LendingLiquidity,
22
+ LendingLoans,
23
+ LendingMarkets,
24
+ LendingOperators,
25
+ LendingOracle,
26
+ LendingPledges,
27
+ LendingRates,
28
+ LendingTerms,
29
+ LendingVaults
30
+ } from "./chunk-4MNJZAXE.mjs";
31
+ import {
32
+ Tokens
33
+ } from "./chunk-C7CANC53.mjs";
34
+ import {
35
+ LendingContext
36
+ } from "./chunk-DQCX2GYT.mjs";
37
+ import {
38
+ AuthError,
39
+ ConfirmationError,
40
+ NetworkError,
41
+ TrussetError,
42
+ ValidationError
43
+ } from "./chunk-3YF42MHV.mjs";
44
+
45
+ // src/version.ts
46
+ var SDK_VERSION = "0.5.0";
33
47
 
34
48
  // src/utils/http.ts
35
49
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
50
+ var CONFIRM_RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
51
+ var MAX_RETRY_AFTER_MS = 6e4;
52
+ var NOT_FOUND_DELAYS_MS = [3e3, 6e3, 12e3];
53
+ var EMPTY_METADATA = { requestId: "", timestamp: "" };
36
54
  var silentLogger = {
37
55
  debug() {
38
56
  },
@@ -50,24 +68,40 @@ var HttpClient = class {
50
68
  apiKey: config.apiKey,
51
69
  timeoutMs: config.timeoutMs ?? 3e4,
52
70
  maxRetries: config.maxRetries ?? 3,
53
- logger: config.logger ?? silentLogger
71
+ logger: config.logger ?? silentLogger,
72
+ fetch: config.fetch ?? ((input, init) => fetch(input, init))
54
73
  };
55
74
  }
56
- async get(path, params) {
57
- const url = this.buildUrl(path, params);
58
- return this.request("GET", url);
75
+ async get(path, params, options) {
76
+ const { data } = await this.requestEnvelope("GET", this.buildUrl(path, params), void 0, options);
77
+ return data;
78
+ }
79
+ /** GET that also returns the response metadata (used for list totals). */
80
+ async getWithMeta(path, params) {
81
+ return this.requestEnvelope("GET", this.buildUrl(path, params));
82
+ }
83
+ async getBinary(path, params, options) {
84
+ return this.requestBinary(this.buildUrl(path, params), options);
85
+ }
86
+ async post(path, body, options) {
87
+ const { data } = await this.requestEnvelope("POST", this.buildUrl(path), body, options);
88
+ return data;
89
+ }
90
+ async put(path, body, options) {
91
+ const { data } = await this.requestEnvelope("PUT", this.buildUrl(path), body, options);
92
+ return data;
59
93
  }
60
- async post(path, body) {
61
- const url = this.buildUrl(path);
62
- return this.request("POST", url, body);
94
+ async patch(path, body, options) {
95
+ const { data } = await this.requestEnvelope("PATCH", this.buildUrl(path), body, options);
96
+ return data;
63
97
  }
64
- async put(path, body) {
65
- const url = this.buildUrl(path);
66
- return this.request("PUT", url, body);
98
+ async delete(path, options) {
99
+ const { data } = await this.requestEnvelope("DELETE", this.buildUrl(path), void 0, options);
100
+ return data;
67
101
  }
68
- async delete(path) {
69
- const url = this.buildUrl(path);
70
- return this.request("DELETE", url);
102
+ async postForm(path, form, params, options) {
103
+ const { data } = await this.requestEnvelope("POST", this.buildUrl(path, params), form, options);
104
+ return data;
71
105
  }
72
106
  // -----------------------------------------------------------------------
73
107
  buildUrl(path, params) {
@@ -83,469 +117,175 @@ var HttpClient = class {
83
117
  }
84
118
  return url.toString();
85
119
  }
86
- async request(method, url, body) {
87
- let lastError = null;
88
- for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
89
- if (attempt > 0) {
90
- const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 1e4);
91
- this.config.logger.debug(`Retry ${attempt}/${this.config.maxRetries} after ${delay}ms`, { url });
120
+ retryClass(method, options) {
121
+ if (options?.retry) return options.retry;
122
+ return method === "GET" ? "read" : "write";
123
+ }
124
+ // Writes are never blindly retried: identity writes are two-phase
125
+ // (prepare calldata, then confirm a mined transaction), and retrying a
126
+ // confirm after an ambiguous failure (5xx, timeout, dropped connection)
127
+ // could record the same transaction twice. 429 is the exception - the
128
+ // server rejected the request before doing any work.
129
+ canRetry(kind, status) {
130
+ if (kind === "read") return status === void 0 || RETRYABLE_STATUS.has(status);
131
+ if (kind === "confirm") return status === void 0 || CONFIRM_RETRYABLE_STATUS.has(status);
132
+ return status === 429;
133
+ }
134
+ headers(jsonBody, accept) {
135
+ const headers = {
136
+ "x-api-key": this.config.apiKey,
137
+ "Accept": accept,
138
+ "User-Agent": `@trusset/sdk/${SDK_VERSION}`
139
+ };
140
+ if (jsonBody) headers["Content-Type"] = "application/json";
141
+ return headers;
142
+ }
143
+ async send(method, url, body, timeoutMs, accept) {
144
+ const controller = new AbortController();
145
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
146
+ const form = typeof FormData !== "undefined" && body instanceof FormData;
147
+ try {
148
+ return await this.config.fetch(url, {
149
+ method,
150
+ headers: this.headers(body !== void 0 && !form, accept),
151
+ body: body === void 0 ? void 0 : form ? body : JSON.stringify(body),
152
+ signal: controller.signal
153
+ });
154
+ } finally {
155
+ clearTimeout(timer);
156
+ }
157
+ }
158
+ async requestEnvelope(method, url, body, options) {
159
+ const kind = this.retryClass(method, options);
160
+ const timeoutMs = Math.max(this.config.timeoutMs, options?.timeoutMs ?? 0);
161
+ return this.withRetries(kind, url, timeoutMs, async () => {
162
+ const res = await this.send(method, url, body, timeoutMs, "application/json");
163
+ await this.throwIfFailed(res);
164
+ let json;
165
+ try {
166
+ json = await res.json();
167
+ } catch {
168
+ throw new TrussetError(
169
+ `Server returned a non-JSON response (status ${res.status})`,
170
+ "BAD_RESPONSE",
171
+ res.status
172
+ );
173
+ }
174
+ if (!json.success) {
175
+ throw toApiError(json, res, `Request failed with status ${res.status}`);
176
+ }
177
+ return { data: json.data, metadata: json.metadata ?? EMPTY_METADATA };
178
+ });
179
+ }
180
+ async requestBinary(url, options) {
181
+ const timeoutMs = Math.max(this.config.timeoutMs, options?.timeoutMs ?? 0);
182
+ return this.withRetries(this.retryClass("GET", options), url, timeoutMs, async () => {
183
+ const res = await this.send("GET", url, void 0, timeoutMs, "*/*");
184
+ await this.throwIfFailed(res);
185
+ const contentType = res.headers.get("content-type") ?? "application/octet-stream";
186
+ if (contentType.includes("application/json")) {
187
+ const json = await res.json().catch(() => null);
188
+ throw toApiError(json, res, `Expected a document but the server answered with JSON (status ${res.status})`);
189
+ }
190
+ const bytes = new Uint8Array(await res.arrayBuffer());
191
+ return { bytes, contentType, filename: filenameOf(res.headers.get("content-disposition")) };
192
+ });
193
+ }
194
+ async throwIfFailed(res) {
195
+ if (res.status === 401) {
196
+ const json = await res.json().catch(() => ({}));
197
+ throw new AuthError(
198
+ json.error?.message ?? "Authentication failed",
199
+ json.metadata?.requestId
200
+ );
201
+ }
202
+ if (res.status >= 400) {
203
+ const json = await res.json().catch(() => null);
204
+ throw toApiError(json, res, `Server returned ${res.status}`);
205
+ }
206
+ }
207
+ async withRetries(kind, url, timeoutMs, attempt) {
208
+ let retries = 0;
209
+ let notFoundRetries = 0;
210
+ let delay = 0;
211
+ while (true) {
212
+ if (delay > 0) {
213
+ this.config.logger.debug(`Retry after ${delay}ms`, { url });
92
214
  await sleep(delay);
93
215
  }
94
216
  try {
95
- const controller = new AbortController();
96
- const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
97
- const headers = {
98
- "x-api-key": this.config.apiKey,
99
- "Accept": "application/json",
100
- "User-Agent": "@trusset/sdk/0.1.0"
101
- };
102
- if (body !== void 0) {
103
- headers["Content-Type"] = "application/json";
104
- }
105
- const res = await fetch(url, {
106
- method,
107
- headers,
108
- body: body !== void 0 ? JSON.stringify(body) : void 0,
109
- signal: controller.signal
110
- });
111
- clearTimeout(timer);
112
- if (res.status === 401) {
113
- const json2 = await res.json().catch(() => ({}));
114
- throw new AuthError(
115
- json2.error?.message ?? "Authentication failed",
116
- json2.metadata?.requestId
117
- );
118
- }
119
- if (RETRYABLE_STATUS.has(res.status)) {
120
- lastError = new TrussetError(
121
- `Server returned ${res.status}`,
122
- "SERVER_ERROR",
123
- res.status
124
- );
125
- continue;
126
- }
127
- const json = await res.json();
128
- if (!json.success) {
129
- throw new TrussetError(
130
- json.error?.message ?? `Request failed with status ${res.status}`,
131
- json.error?.code ?? "REQUEST_FAILED",
132
- res.status,
133
- json.metadata?.requestId
134
- );
135
- }
136
- return json.data;
217
+ return await attempt();
137
218
  } catch (err) {
138
- if (err instanceof TrussetError) {
139
- if (!RETRYABLE_STATUS.has(err.statusCode)) throw err;
140
- lastError = err;
219
+ if (err instanceof AuthError) throw err;
220
+ if (err instanceof TrussetError && err.statusCode > 0) {
221
+ if (kind !== "read" && err.code === "TX_NOT_FOUND") {
222
+ if (notFoundRetries >= NOT_FOUND_DELAYS_MS.length) throw err;
223
+ delay = NOT_FOUND_DELAYS_MS[notFoundRetries];
224
+ notFoundRetries += 1;
225
+ continue;
226
+ }
227
+ if (!this.canRetry(kind, err.statusCode) || retries >= this.config.maxRetries) throw err;
228
+ retries += 1;
229
+ delay = err.statusCode === 429 && err.retryAfter !== null ? Math.min(err.retryAfter * 1e3, MAX_RETRY_AFTER_MS) : backoff(retries);
141
230
  continue;
142
231
  }
143
232
  if (err instanceof TypeError || err instanceof DOMException && err.name === "AbortError") {
144
- lastError = new NetworkError(
145
- err instanceof DOMException ? "Request timed out" : `Network error: ${err.message}`
233
+ const netError = new NetworkError(
234
+ err instanceof DOMException ? `Request timed out after ${timeoutMs}ms` : `Network error: ${err.message}`
146
235
  );
236
+ if (!this.canRetry(kind) || retries >= this.config.maxRetries) throw netError;
237
+ retries += 1;
238
+ delay = backoff(retries);
147
239
  continue;
148
240
  }
149
241
  throw err;
150
242
  }
151
243
  }
152
- throw lastError ?? new NetworkError("Request failed after retries");
153
244
  }
154
245
  };
155
- function sleep(ms) {
156
- return new Promise((resolve) => setTimeout(resolve, ms));
246
+ function toApiError(json, res, fallback) {
247
+ const error = json?.error ?? {};
248
+ const { code, message, details, ...rest } = error;
249
+ const metadata = json?.metadata ?? {};
250
+ return new TrussetError(
251
+ typeof message === "string" ? message : fallback,
252
+ typeof code === "string" ? code : res.status >= 500 ? "SERVER_ERROR" : "REQUEST_FAILED",
253
+ res.status,
254
+ typeof metadata.requestId === "string" ? metadata.requestId : void 0,
255
+ {
256
+ details,
257
+ data: Object.keys(rest).length ? rest : null,
258
+ retryAfter: res.status === 429 ? retryAfterOf(res, metadata) : null
259
+ }
260
+ );
157
261
  }
158
-
159
- // src/utils/validation.ts
160
- var ETH_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
161
- function requireString(value, field) {
162
- if (typeof value !== "string" || value.trim().length === 0) {
163
- throw new ValidationError(`${field} is required and must be a non-empty string`);
164
- }
165
- return value.trim();
262
+ function retryAfterOf(res, metadata) {
263
+ const header = res.headers.get("retry-after");
264
+ if (header !== null && /^\d+$/.test(header.trim())) return Number(header.trim());
265
+ if (typeof metadata.retryAfter === "number" && Number.isFinite(metadata.retryAfter)) return Math.max(0, metadata.retryAfter);
266
+ return null;
166
267
  }
167
- function requireAddress(value, field) {
168
- const s = requireString(value, field);
169
- if (!ETH_ADDRESS_RE.test(s)) {
170
- throw new ValidationError(`${field} must be a valid Ethereum address`);
171
- }
172
- return s;
268
+ function filenameOf(disposition) {
269
+ if (!disposition) return null;
270
+ const match = /filename="?([^";]+)"?/i.exec(disposition);
271
+ return match ? match[1] : null;
173
272
  }
174
- function optionalAddress(value, field) {
175
- if (value === void 0 || value === null || value === "") return void 0;
176
- return requireAddress(value, field);
273
+ function backoff(attempt) {
274
+ return Math.min(1e3 * Math.pow(2, attempt - 1), 1e4);
177
275
  }
178
- function requireCountry(value, field) {
179
- const s = requireString(value, field);
180
- if (s.length < 2 || s.length > 3) {
181
- throw new ValidationError(`${field} must be a 2-3 letter country code`);
182
- }
183
- return s.toUpperCase();
184
- }
185
- function clampInt(value, min, max, fallback) {
186
- if (value === void 0 || value === null) return fallback;
187
- const n = Math.floor(value);
188
- if (isNaN(n)) return fallback;
189
- return Math.max(min, Math.min(max, n));
276
+ function sleep(ms) {
277
+ return new Promise((resolve) => setTimeout(resolve, ms));
190
278
  }
191
279
 
192
- // src/modules/customers/management.ts
193
- var CustomerManagement = class {
194
- constructor(http) {
195
- this.http = http;
196
- }
197
- /**
198
- * List customers for the current instance.
199
- */
200
- async list(options = {}) {
201
- const params = {
202
- limit: clampInt(options.limit, 1, 200, 100),
203
- offset: clampInt(options.offset, 0, Number.MAX_SAFE_INTEGER, 0),
204
- includeArchived: options.includeArchived ? "true" : void 0,
205
- search: options.search || void 0
206
- };
207
- return this.http.get("/customers/api/manage", params);
208
- }
209
- /**
210
- * Get a customer by wallet address, ID, externalId, or referenceKey.
211
- */
212
- async get(identifier) {
213
- requireString(identifier, "identifier");
214
- return this.http.get(`/customers/api/manage/${encodeURIComponent(identifier)}`);
215
- }
216
- /**
217
- * Create a new customer record.
218
- */
219
- async create(input) {
220
- if (input.walletAddress) optionalAddress(input.walletAddress, "walletAddress");
221
- return this.http.post("/customers/api/manage", input);
222
- }
223
- /**
224
- * Update an existing customer.
225
- */
226
- async update(customerId, input) {
227
- requireString(customerId, "customerId");
228
- return this.http.put(`/customers/api/manage/${encodeURIComponent(customerId)}`, input);
229
- }
230
- /**
231
- * Archive (soft-delete) a customer.
232
- */
233
- async archive(customerId) {
234
- requireString(customerId, "customerId");
235
- return this.http.delete(`/customers/api/manage/${encodeURIComponent(customerId)}`);
236
- }
237
- /**
238
- * Link a wallet address to a customer created via referenceKey.
239
- */
240
- async linkWallet(input) {
241
- requireString(input.referenceKey, "referenceKey");
242
- requireAddress(input.walletAddress, "walletAddress");
243
- return this.http.post("/customers/api/manage/link-wallet", input);
244
- }
245
- /**
246
- * Batch-fetch country codes for a list of wallet addresses.
247
- */
248
- async batchGetCountries(addresses) {
249
- if (!Array.isArray(addresses) || addresses.length === 0) {
250
- return {};
251
- }
252
- return this.http.post("/customers/api/manage/countries/batch", { addresses });
253
- }
254
- // -----------------------------------------------------------------------
255
- // Convenience search helpers
256
- // -----------------------------------------------------------------------
257
- /**
258
- * Search customers by name substring.
259
- */
260
- async searchByName(name, options) {
261
- return this.list({ ...options, search: name });
262
- }
263
- /**
264
- * Find a customer by wallet address.
265
- */
266
- async searchByWallet(walletAddress) {
267
- requireAddress(walletAddress, "walletAddress");
268
- return this.get(walletAddress);
269
- }
270
- /**
271
- * Find a customer by external ID (your system's unique identifier).
272
- */
273
- async searchByExternalId(externalId) {
274
- requireString(externalId, "externalId");
275
- return this.get(externalId);
276
- }
277
- /**
278
- * Find a customer by reference key (for pre-wallet customers).
279
- */
280
- async searchByReference(referenceKey) {
281
- requireString(referenceKey, "referenceKey");
282
- return this.get(referenceKey);
283
- }
284
- };
285
-
286
- // src/modules/customers/identity.ts
287
- var CustomerIdentity = class {
288
- constructor(http) {
289
- this.http = http;
290
- }
291
- /**
292
- * Verify a customer's identity on-chain via the IdentityRegistry contract.
293
- * Requires a configured relayer on the instance.
294
- */
295
- async verify(input) {
296
- requireAddress(input.walletAddress, "walletAddress");
297
- requireCountry(input.country, "country");
298
- return this.http.post("/customers/api/identity/verify", input);
299
- }
300
- /**
301
- * Batch-verify up to 500 identities in a single transaction.
302
- * Requires a configured relayer.
303
- */
304
- async batchVerify(entries) {
305
- if (!Array.isArray(entries) || entries.length === 0 || entries.length > 500) {
306
- throw new ValidationError("Provide 1-500 entries for batch verification");
307
- }
308
- for (const e of entries) {
309
- requireAddress(e.walletAddress, "walletAddress");
310
- requireCountry(e.country, "country");
311
- }
312
- return this.http.post("/customers/api/identity/batch-verify", { entries });
313
- }
314
- /**
315
- * Revoke an identity on-chain. Requires a configured relayer.
316
- */
317
- async revoke(walletAddress) {
318
- requireAddress(walletAddress, "walletAddress");
319
- return this.http.post("/customers/api/identity/revoke", { walletAddress });
320
- }
321
- /**
322
- * Get combined on-chain and off-chain identity status.
323
- */
324
- async getStatus(walletAddress) {
325
- requireAddress(walletAddress, "walletAddress");
326
- return this.http.get(`/customers/api/identity/status/${walletAddress}`);
327
- }
328
- /**
329
- * Check whether a token transfer between two addresses is compliant.
330
- * Runs identity checks + all registered compliance modules.
331
- */
332
- async canTransfer(input) {
333
- requireAddress(input.from, "from");
334
- requireAddress(input.to, "to");
335
- return this.http.post("/customers/api/identity/can-transfer", input);
336
- }
337
- };
338
-
339
- // src/modules/customers/id-links.ts
340
- var CustomerIdLinks = class {
341
- constructor(http) {
342
- this.http = http;
343
- }
344
- /**
345
- * Create a KYC verification link to send to an investor.
346
- */
347
- async create(input) {
348
- if (!input.recipientEmail && !input.recipientWallet && !input.recipientPhone) {
349
- throw new ValidationError("At least one of recipientEmail, recipientWallet, or recipientPhone is required");
350
- }
351
- return this.http.post("/customers/api/id-links", input);
352
- }
353
- /**
354
- * List all verification links for the current instance.
355
- */
356
- async list(options = {}) {
357
- const params = {
358
- limit: clampInt(options.limit, 1, 200, 50),
359
- offset: clampInt(options.offset, 0, Number.MAX_SAFE_INTEGER, 0),
360
- status: options.status
361
- };
362
- return this.http.get("/customers/api/id-links", params);
363
- }
364
- /**
365
- * Revoke (expire) a verification link so it can no longer be used.
366
- */
367
- async revoke(linkId) {
368
- requireString(linkId, "linkId");
369
- await this.http.put(`/customers/api/id-links/${encodeURIComponent(linkId)}/revoke`);
370
- }
371
- /**
372
- * Resend a verification link via the specified channels.
373
- */
374
- async resend(linkId, channels) {
375
- requireString(linkId, "linkId");
376
- if (!Array.isArray(channels) || channels.length === 0) {
377
- throw new ValidationError("channels array is required (email and/or sms)");
378
- }
379
- return this.http.post(`/customers/api/id-links/${encodeURIComponent(linkId)}/resend`, { channels });
380
- }
381
- };
382
-
383
- // src/modules/customers/sync.ts
384
- var DEFAULT_BATCH_SIZE = 50;
385
- var MAX_BATCH_SIZE = 200;
386
- var MAX_VERIFY_BATCH = 500;
387
- var CustomerSync = class {
388
- constructor(http) {
389
- this.http = http;
390
- }
391
- /**
392
- * Import customer records from an external system.
393
- *
394
- * Each record is mapped by `externalId`. If a customer with that externalId
395
- * already exists, the `onConflict` strategy determines whether to skip or
396
- * update.
397
- *
398
- * When `verifyOnChain` is true, records that include a `walletAddress` and
399
- * `country` will be batch-verified on-chain after the database import.
400
- */
401
- async importRecords(records, options = {}) {
402
- if (!Array.isArray(records) || records.length === 0) {
403
- throw new ValidationError("records must be a non-empty array");
404
- }
405
- const batchSize = clampInt(options.batchSize, 1, MAX_BATCH_SIZE, DEFAULT_BATCH_SIZE);
406
- const onConflict = options.onConflict ?? "update";
407
- const result = { total: records.length, created: 0, updated: 0, skipped: 0, errors: [] };
408
- const verifyQueue = [];
409
- for (let i = 0; i < records.length; i += batchSize) {
410
- const batch = records.slice(i, i + batchSize);
411
- const batchResults = await Promise.allSettled(
412
- batch.map((record) => this.processRecord(record, onConflict))
413
- );
414
- for (let j = 0; j < batchResults.length; j++) {
415
- const settled = batchResults[j];
416
- const record = batch[j];
417
- if (settled.status === "fulfilled") {
418
- const outcome = settled.value;
419
- if (outcome.action === "created") result.created++;
420
- else if (outcome.action === "updated") result.updated++;
421
- else result.skipped++;
422
- if (options.verifyOnChain && record.walletAddress && record.country && outcome.action !== "skipped") {
423
- verifyQueue.push({
424
- walletAddress: record.walletAddress,
425
- country: record.country,
426
- investorType: record.investorType,
427
- softExpiryDays: options.verificationDefaults?.softExpiryDays,
428
- hardExpiryDays: options.verificationDefaults?.hardExpiryDays
429
- });
430
- }
431
- } else {
432
- const err = settled.reason;
433
- result.errors.push({
434
- externalId: record.externalId,
435
- code: err?.code ?? "UNKNOWN",
436
- message: err?.message ?? "Unknown error"
437
- });
438
- }
439
- }
440
- options.onProgress?.(Math.min(i + batchSize, records.length), records.length);
441
- }
442
- if (verifyQueue.length > 0) {
443
- await this.runBatchVerification(verifyQueue, result);
444
- }
445
- return result;
446
- }
447
- /**
448
- * Export all customers from Trusset as flat records.
449
- * Useful for syncing data back into your system.
450
- */
451
- async exportRecords(options = {}) {
452
- const allRecords = [];
453
- let offset = 0;
454
- const limit = 200;
455
- while (true) {
456
- const page = await this.http.get("/customers/api/manage", {
457
- limit,
458
- offset,
459
- includeArchived: options.includeArchived ? "true" : void 0
460
- });
461
- if (!page.customers || page.customers.length === 0) break;
462
- for (const c of page.customers) {
463
- allRecords.push({
464
- externalId: c.externalId ?? c.id,
465
- walletAddress: c.walletAddress ?? void 0,
466
- name: c.name ?? void 0,
467
- country: c.country ?? void 0,
468
- investorType: c.investorType ?? void 0,
469
- metadata: c.metadata ?? void 0
470
- });
471
- }
472
- offset += page.customers.length;
473
- if (offset >= page.total) break;
474
- }
475
- return allRecords;
476
- }
477
- // -----------------------------------------------------------------------
478
- // Internal
479
- // -----------------------------------------------------------------------
480
- async processRecord(record, onConflict) {
481
- requireString(record.externalId, "externalId");
482
- let existing = null;
483
- try {
484
- existing = await this.http.get(
485
- `/customers/api/manage/${encodeURIComponent(record.externalId)}`
486
- );
487
- } catch (err) {
488
- const code = err?.code;
489
- const statusCode = err?.statusCode;
490
- if (statusCode !== 404 && code !== "NOT_FOUND") throw err;
491
- }
492
- if (existing) {
493
- if (onConflict === "skip") {
494
- return { action: "skipped" };
495
- }
496
- await this.http.put(`/customers/api/manage/${encodeURIComponent(existing.id)}`, {
497
- name: record.name,
498
- country: record.country,
499
- investorType: record.investorType,
500
- walletAddress: record.walletAddress,
501
- metadata: record.metadata
502
- });
503
- return { action: "updated" };
504
- }
505
- await this.http.post("/customers/api/manage", {
506
- externalId: record.externalId,
507
- walletAddress: record.walletAddress,
508
- name: record.name,
509
- country: record.country,
510
- investorType: record.investorType,
511
- metadata: record.metadata
512
- });
513
- return { action: "created" };
514
- }
515
- async runBatchVerification(queue, result) {
516
- for (let i = 0; i < queue.length; i += MAX_VERIFY_BATCH) {
517
- const batch = queue.slice(i, i + MAX_VERIFY_BATCH);
518
- try {
519
- await this.http.post("/customers/api/identity/batch-verify", { entries: batch });
520
- } catch (err) {
521
- for (const entry of batch) {
522
- result.errors.push({
523
- externalId: entry.walletAddress,
524
- code: "VERIFY_FAILED",
525
- message: err?.message ?? "On-chain batch verification failed"
526
- });
527
- }
528
- }
529
- }
530
- }
531
- };
532
-
533
- // src/modules/customers/index.ts
534
- var Customers = class {
535
- constructor(http) {
536
- this.manage = new CustomerManagement(http);
537
- this.identity = new CustomerIdentity(http);
538
- this.idLinks = new CustomerIdLinks(http);
539
- this.sync = new CustomerSync(http);
540
- }
541
- };
542
-
543
280
  // src/client.ts
544
281
  var TrussetClient = class {
545
282
  constructor(config) {
546
283
  if (!config.apiKey || typeof config.apiKey !== "string" || config.apiKey.length < 20) {
547
284
  throw new ValidationError("A valid apiKey is required");
548
285
  }
286
+ if (!config.apiKey.startsWith("trusset_")) {
287
+ throw new ValidationError("apiKey must be a Trusset instance API key (they start with 'trusset_')");
288
+ }
549
289
  this.http = new HttpClient({
550
290
  apiKey: config.apiKey,
551
291
  baseUrl: config.baseUrl,
@@ -553,18 +293,44 @@ var TrussetClient = class {
553
293
  maxRetries: config.maxRetries,
554
294
  logger: config.logger
555
295
  });
556
- this.customers = new Customers(this.http);
296
+ const context = new LendingContext(this.http);
297
+ this.customers = new Customers(this.http, context);
298
+ this.lending = new Lending(this.http, context);
299
+ this.tokens = new Tokens(context);
557
300
  }
558
301
  };
559
302
  export {
560
303
  AuthError,
304
+ ConfirmationError,
561
305
  CustomerIdLinks,
562
306
  CustomerIdentity,
563
307
  CustomerManagement,
564
308
  CustomerSync,
309
+ CustomerVerificationProfiles,
565
310
  Customers,
311
+ Lending,
312
+ LendingAccessLists,
313
+ LendingAssetData,
314
+ LendingAuctions,
315
+ LendingBots,
316
+ LendingDocuments,
317
+ LendingFees,
318
+ LendingHooks,
319
+ LendingInsurance,
320
+ LendingLiquidations,
321
+ LendingLiquidity,
322
+ LendingLoans,
323
+ LendingMarkets,
324
+ LendingOperators,
325
+ LendingOracle,
326
+ LendingPledges,
327
+ LendingRates,
328
+ LendingTerms,
329
+ LendingVaults,
566
330
  NetworkError,
331
+ Tokens,
567
332
  TrussetClient,
568
333
  TrussetError,
569
- ValidationError
334
+ ValidationError,
335
+ manifestToVerifyInput
570
336
  };