@capxul/sdk 0.2.0-alpha.3 → 0.2.0-alpha.5

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.
Files changed (45) hide show
  1. package/README.md +4 -266
  2. package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs +113 -0
  3. package/dist/InMemoryAuthCacheAdapter-BK-B_ERB.mjs.map +1 -0
  4. package/dist/index.d.mts +1263 -0
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/index.mjs +4740 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/node/index.d.mts +55 -0
  9. package/dist/node/index.d.mts.map +1 -0
  10. package/dist/node/index.mjs +159 -0
  11. package/dist/node/index.mjs.map +1 -0
  12. package/dist/ports/safe-deployment.d.mts +2 -0
  13. package/dist/ports/safe-deployment.mjs +38 -0
  14. package/dist/ports/safe-deployment.mjs.map +1 -0
  15. package/dist/safe-deployment-Vni46k3t.d.mts +137 -0
  16. package/dist/safe-deployment-Vni46k3t.d.mts.map +1 -0
  17. package/dist/signer-oaYGfjDe.d.mts +142 -0
  18. package/dist/signer-oaYGfjDe.d.mts.map +1 -0
  19. package/package.json +37 -71
  20. package/CHANGELOG.md +0 -256
  21. package/LICENSE +0 -44
  22. package/dist/client-B_Z3ThFO.d.cts +0 -1484
  23. package/dist/client-pMBRFcsz.d.ts +0 -1484
  24. package/dist/client.cjs +0 -4324
  25. package/dist/client.d.cts +0 -6
  26. package/dist/client.d.ts +0 -6
  27. package/dist/client.js +0 -4322
  28. package/dist/errors-CwhCWGxm.d.ts +0 -70
  29. package/dist/errors-rqxuUhQP.d.cts +0 -70
  30. package/dist/errors.cjs +0 -35
  31. package/dist/errors.d.cts +0 -2
  32. package/dist/errors.d.ts +0 -2
  33. package/dist/errors.js +0 -31
  34. package/dist/index.cjs +0 -4626
  35. package/dist/index.d.cts +0 -571
  36. package/dist/index.d.ts +0 -571
  37. package/dist/index.js +0 -4585
  38. package/dist/next-action-CTGl8wpy.d.cts +0 -177
  39. package/dist/next-action-CTGl8wpy.d.ts +0 -177
  40. package/dist/types-Brucpq0Z.d.cts +0 -1191
  41. package/dist/types-V_D7qjxY.d.ts +0 -1191
  42. package/dist/webhooks.cjs +0 -118
  43. package/dist/webhooks.d.cts +0 -33
  44. package/dist/webhooks.d.ts +0 -33
  45. package/dist/webhooks.js +0 -116
package/dist/client.js DELETED
@@ -1,4322 +0,0 @@
1
- import { componentsGeneric, anyApi } from 'convex/server';
2
- import { toSafeSmartAccount } from 'permissionless/accounts';
3
- import { getAddress, parseUnits, createPublicClient, http, encodeFunctionData } from 'viem';
4
- import { entryPoint07Address, createPaymasterClient, createBundlerClient } from 'viem/account-abstraction';
5
- import { baseSepolia } from 'viem/chains';
6
- import { deriveSafeAddress as deriveSafeAddress$1, defaultSafeDeriveConfig } from '@repo/safe-derive';
7
- import { ConvexHttpClient } from 'convex/browser';
8
- import { setup, fromPromise, assign } from 'xstate';
9
-
10
- // src/_generated/api.js
11
- var api = anyApi;
12
- componentsGeneric();
13
-
14
- // src/errors.ts
15
- var CapxulError = class extends Error {
16
- code;
17
- details;
18
- operationId;
19
- correlationId;
20
- retryable;
21
- constructor(init) {
22
- super(
23
- init.message,
24
- init.cause !== void 0 ? { cause: init.cause } : void 0
25
- );
26
- this.name = "CapxulError";
27
- this.code = init.code;
28
- this.details = init.details;
29
- this.operationId = init.operationId;
30
- this.correlationId = init.correlationId;
31
- this.retryable = init.retryable;
32
- }
33
- };
34
- function notImplemented(method) {
35
- return new CapxulError({
36
- code: "NOT_IMPLEMENTED",
37
- message: `${method} is not yet implemented in @capxul/sdk (Slice C scaffold).`
38
- });
39
- }
40
- function stub(method) {
41
- return [notImplemented(method), null];
42
- }
43
-
44
- // src/internal/convex-error.ts
45
- function isConvexClientError(error) {
46
- if (typeof error !== "object" || error === null || !("data" in error)) {
47
- return false;
48
- }
49
- const data = error.data;
50
- return typeof data === "object" && data !== null;
51
- }
52
- function fromConvexError(error) {
53
- if (error instanceof CapxulError) {
54
- return error;
55
- }
56
- if (isConvexClientError(error)) {
57
- return new CapxulError({
58
- code: error.data.code ?? "UNKNOWN",
59
- message: error.data.message ?? error.message,
60
- details: error.data.details,
61
- correlationId: error.data.correlationId,
62
- cause: error
63
- });
64
- }
65
- return new CapxulError({
66
- code: "UNKNOWN",
67
- message: error instanceof Error ? error.message : String(error),
68
- cause: error
69
- });
70
- }
71
-
72
- // ../observability/src/try-catch.ts
73
- async function tryCatch(promise) {
74
- try {
75
- return [null, await promise];
76
- } catch (e) {
77
- return [e instanceof Error ? e : new Error(String(e)), null];
78
- }
79
- }
80
-
81
- // ../observability/src/debug-log.ts
82
- function isDevelopmentBuild() {
83
- if (typeof process === "undefined") {
84
- return false;
85
- }
86
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
87
- }
88
- function debugLog(line) {
89
- if (!isDevelopmentBuild()) return;
90
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
91
- console.info(line);
92
- return;
93
- }
94
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
95
- process.stderr.write(`${line}
96
- `);
97
- }
98
- }
99
- function formatDebugValue(value) {
100
- if (value === void 0 || value === "") return "";
101
- if (typeof value === "string") return value;
102
- try {
103
- return JSON.stringify(value);
104
- } catch {
105
- return String(value);
106
- }
107
- }
108
- function track(...args) {
109
- const [name, props] = args;
110
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
111
- }
112
- function formatDebugValue2(value) {
113
- if (value === void 0 || value === "") return "";
114
- if (typeof value === "string") return value;
115
- try {
116
- return JSON.stringify(value);
117
- } catch {
118
- return String(value);
119
- }
120
- }
121
- function identify(userId, traits) {
122
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
123
- }
124
-
125
- // ../config/src/chain.ts
126
- var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
127
- var TEST_USDC_ADDRESS = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
128
- var CAPXUL_API_BASE_URL = "https://elated-oyster-269.convex.site";
129
-
130
- // ../config/src/timing.ts
131
- var FLOW_INVOKE_TIMEOUT_MS = 3e4;
132
-
133
- // ../config/src/errors.ts
134
- var CapxulError2 = class extends Error {
135
- code;
136
- details;
137
- correlationId;
138
- layer;
139
- constructor(code, message, options) {
140
- super(message, options?.cause ? { cause: options.cause } : void 0);
141
- this.code = code;
142
- this.details = options?.details;
143
- this.correlationId = options?.correlationId;
144
- this.layer = options?.layer;
145
- }
146
- };
147
- var Errors = {
148
- notAuthenticated: () => new CapxulError2("NOT_AUTHENTICATED", "Not authenticated"),
149
- profileNotFound: (authUserId) => new CapxulError2("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`),
150
- smartAccountMissing: (authUserId) => new CapxulError2("SMART_ACCOUNT_MISSING", `Smart account not provisioned for user ${authUserId}`),
151
- envMissing: (name) => new CapxulError2("ENV_MISSING", `Environment variable ${name} not configured`),
152
- openfortApi: (operation, cause) => new CapxulError2(
153
- "PROVIDER_ERROR",
154
- `Openfort ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
155
- { cause, details: { provider: "openfort", operation } }
156
- ),
157
- shieldApi: (status, detail) => new CapxulError2(
158
- "PROVIDER_ERROR",
159
- `Shield API error (${status}): ${detail}`,
160
- { details: { provider: "shield", status } }
161
- ),
162
- providerError: (provider, operation, cause) => (
163
- // Public `message` is redacted to a fixed shape so provider-side
164
- // exception text never leaks to the client. The original `cause`
165
- // is preserved on `Error.cause` for server-side debugging via
166
- // observability sinks (Sentry, console traces).
167
- new CapxulError2(
168
- "PROVIDER_ERROR",
169
- `Provider error: ${provider} ${operation}`,
170
- { cause, details: { provider, operation } }
171
- )
172
- ),
173
- invalidInput: (field, reason) => new CapxulError2(
174
- "INVALID_INPUT",
175
- `Invalid ${field}: ${reason}`,
176
- { details: { field, reason } }
177
- ),
178
- playerNotFound: (playerId) => new CapxulError2(
179
- "PLAYER_NOT_FOUND",
180
- playerId ? `Openfort player ${playerId} not found` : "Openfort player not found"
181
- ),
182
- accountNotFound: (accountId) => new CapxulError2(
183
- "ACCOUNT_NOT_FOUND",
184
- accountId ? `Openfort account ${accountId} not found` : "Openfort smart account not found"
185
- ),
186
- invalidRecipient: (reason) => new CapxulError2("INVALID_RECIPIENT", reason),
187
- permissionDenied: (reason = "Permission denied") => new CapxulError2("PERMISSION_DENIED", reason),
188
- notFound: (resource, id) => new CapxulError2(
189
- "NOT_FOUND",
190
- id ? `${resource} ${id} not found` : `${resource} not found`
191
- ),
192
- idempotencyConflict: (details) => new CapxulError2(
193
- "IDEMPOTENCY_CONFLICT",
194
- "Idempotency key was already used for a different request",
195
- { details }
196
- ),
197
- emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
198
- details
199
- }),
200
- rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
201
- details: { ...details }
202
- }),
203
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
204
- /**
205
- * Verification gate. Surfaced when a request hits a verification
206
- * boundary the actor cannot cross under their current state. Two
207
- * variants share this code:
208
- *
209
- * - Rail gate (Withdrawals v1 W2, #465): the resolved
210
- * `external_account.kind` routes to a withdrawal rail (e.g.
211
- * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
212
- * `details.rail` + `details.currentKind`.
213
- * - KYC tier gate (legacy / future): the actor's KYC tier is below
214
- * the required tier. Carries `details.requiredTier`.
215
- *
216
- * Code is shared because both expose the same UX shape ("you cannot
217
- * proceed until verification advances"); the `details.*` keys
218
- * differentiate the route.
219
- */
220
- verificationRequired: (details) => {
221
- const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
222
- return new CapxulError2("VERIFICATION_REQUIRED", message, {
223
- details: { ...details }
224
- });
225
- }
226
- };
227
-
228
- // ../config/src/safe.ts
229
- var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
230
- var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
231
- var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
232
- var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
233
-
234
- // ../config/src/org-roles.ts
235
- function roleKeyFromLabel(label) {
236
- const bytes = new TextEncoder().encode(label);
237
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
238
- return "0x" + hex.padEnd(64, "0");
239
- }
240
- roleKeyFromLabel("OWNER");
241
- roleKeyFromLabel("FINANCE_MANAGER");
242
- roleKeyFromLabel("PAYMENTS_OPERATOR");
243
- async function buildSafeAccount(signer, chain) {
244
- try {
245
- const publicClient = createPublicClient({
246
- chain: baseSepolia,
247
- transport: http(chain.rpcUrl)
248
- });
249
- return await toSafeSmartAccount({
250
- client: publicClient,
251
- entryPoint: { address: entryPoint07Address, version: "0.7" },
252
- version: "1.4.1",
253
- owners: [signer],
254
- saltNonce: computeSaltNonce(signer.address),
255
- safeSingletonAddress: SAFE_L2_SINGLETON,
256
- safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
257
- safeModuleSetupAddress: SAFE_MODULE_SETUP,
258
- safe4337ModuleAddress: SAFE_4337_MODULE,
259
- safeModules: [],
260
- setupTransactions: []
261
- });
262
- } catch (cause) {
263
- throw new CapxulError({
264
- code: "NETWORK_ERROR",
265
- message: cause instanceof Error ? cause.message : String(cause),
266
- cause,
267
- details: { chainId: chain.chainId }
268
- });
269
- }
270
- }
271
- function computeSaltNonce(ownerAddress) {
272
- return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
273
- }
274
- function deriveSafeAddress(signerAddress) {
275
- return deriveSafeAddress$1(signerAddress, defaultSafeDeriveConfig);
276
- }
277
-
278
- // ../platform-kernel/src/ids.ts
279
- function makePrefixedIdConstructor(prefix, fieldName) {
280
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
281
- return (raw) => {
282
- if (typeof raw !== "string" || !re.test(raw)) {
283
- throw new Error(
284
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
285
- );
286
- }
287
- return raw;
288
- };
289
- }
290
- var toSafeId = makePrefixedIdConstructor(
291
- "safe",
292
- "safeId"
293
- );
294
- var toTreasuryId = makePrefixedIdConstructor(
295
- "try",
296
- "treasuryId"
297
- );
298
- var toOperationId = makePrefixedIdConstructor(
299
- "op",
300
- "operationId"
301
- );
302
- var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
303
- var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
304
- var toSubAccountId = makePrefixedIdConstructor(
305
- "sub",
306
- "subAccountId"
307
- );
308
-
309
- // src/core/external-accounts.ts
310
- function brandExternalAccount(raw) {
311
- return {
312
- ...raw,
313
- id: toExternalAccountId(raw.id),
314
- operation: {
315
- id: toOperationId(raw.operation.id),
316
- status: raw.operation.status,
317
- correlationId: toCorrelationId(raw.operation.correlationId)
318
- }
319
- };
320
- }
321
- function createExternalAccountsClient(config = {}) {
322
- return {
323
- retrieve: async (externalAccountId) => {
324
- if (!config._data) {
325
- return stub(
326
- "externalAccounts.retrieve"
327
- );
328
- }
329
- const [err, raw] = await tryCatch(
330
- config._data.query(api.externalAccounts.queries.retrievePersonal, {
331
- externalAccountId
332
- })
333
- );
334
- if (err) {
335
- return [
336
- fromConvexError(err),
337
- null
338
- ];
339
- }
340
- if (!raw) {
341
- return [
342
- new CapxulError({
343
- code: "NOT_FOUND",
344
- message: `external_account ${externalAccountId} not found`
345
- }),
346
- null
347
- ];
348
- }
349
- return [null, brandExternalAccount(raw)];
350
- },
351
- remove: async (externalAccountId) => {
352
- if (!config._data) {
353
- return stub("externalAccounts.remove");
354
- }
355
- const [err] = await tryCatch(
356
- config._data.mutation(api.externalAccounts.mutations.removePersonal, {
357
- externalAccountId
358
- })
359
- );
360
- if (err) {
361
- return [
362
- fromConvexError(err),
363
- null
364
- ];
365
- }
366
- return [null, void 0];
367
- }
368
- };
369
- }
370
-
371
- // src/core/sub-accounts.ts
372
- function malformedWireError(reason, raw) {
373
- return new CapxulError({
374
- code: "PROVIDER_ERROR",
375
- message: `convex brandSubAccount failed: ${reason}`,
376
- details: {
377
- provider: "convex",
378
- operation: "brandSubAccount",
379
- reason,
380
- // PII discipline (`.claude/rules/posthog.md`): user-authored
381
- // strings on the wire (`name`, `purpose`) are customer-confidential
382
- // — sub-account names like "Q3 Acquisition Reserve" or
383
- // "Vendor ABC payments" must not flow into telemetry. We emit a
384
- // structural keys-only sample via a strict ALLOWLIST so any future
385
- // wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
386
- // by construction rather than leaked through a denylist gap.
387
- sample: safeSampleShape(raw)
388
- }
389
- });
390
- }
391
- function safeSampleShape(raw) {
392
- if (raw === null || typeof raw !== "object") {
393
- return { type: typeof raw };
394
- }
395
- const r = raw;
396
- const balance = r.balance;
397
- return {
398
- object: typeof r.object === "string" ? r.object : typeof r.object,
399
- idPresent: typeof r.id === "string" && r.id.length > 0,
400
- // First 4 chars only — enough to distinguish "sub_" prefixed IDs
401
- // from accidental other resource IDs without leaking the full ID.
402
- idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
403
- parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
404
- status: r.status,
405
- hasName: typeof r.name === "string",
406
- hasPurpose: r.purpose !== void 0,
407
- balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
408
- createdAtType: typeof r.createdAt,
409
- updatedAtType: typeof r.updatedAt
410
- };
411
- }
412
- function isMoneyShape(v) {
413
- if (typeof v !== "object" || v === null) return false;
414
- const m = v;
415
- return typeof m.value === "string" && typeof m.currency === "string";
416
- }
417
- function isParentShape(v) {
418
- if (typeof v !== "object" || v === null) return false;
419
- const p = v;
420
- return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
421
- }
422
- function isFiniteNonNegativeInteger(v) {
423
- return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
424
- }
425
- function validateWireSubAccount(raw) {
426
- if (typeof raw !== "object" || raw === null) {
427
- return { ok: false, reason: "not an object" };
428
- }
429
- const r = raw;
430
- if (r.object !== "sub_account") {
431
- return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
432
- }
433
- if (typeof r.id !== "string" || r.id.length === 0) {
434
- return { ok: false, reason: "id must be a non-empty string" };
435
- }
436
- if (!isParentShape(r.parent)) {
437
- return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
438
- }
439
- if (typeof r.name !== "string") {
440
- return { ok: false, reason: "name must be a string" };
441
- }
442
- if (r.purpose !== void 0 && typeof r.purpose !== "string") {
443
- return { ok: false, reason: "purpose must be a string when present" };
444
- }
445
- if (r.status !== "active" && r.status !== "archived") {
446
- return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
447
- }
448
- if (!isMoneyShape(r.balance)) {
449
- return { ok: false, reason: "balance must be { value: string, currency: string }" };
450
- }
451
- if (!isFiniteNonNegativeInteger(r.createdAt)) {
452
- return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
453
- }
454
- if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
455
- return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
456
- }
457
- return { ok: true, value: r };
458
- }
459
- function brandSubAccount(raw) {
460
- const result = validateWireSubAccount(raw);
461
- if (!result.ok) {
462
- throw malformedWireError(result.reason, raw);
463
- }
464
- const wire = result.value;
465
- return {
466
- object: wire.object,
467
- id: toSubAccountId(wire.id),
468
- parent: wire.parent,
469
- name: wire.name,
470
- ...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
471
- status: wire.status,
472
- balance: wire.balance,
473
- createdAt: new Date(wire.createdAt).toISOString()
474
- };
475
- }
476
- function tryBrandSubAccount(raw) {
477
- try {
478
- return [null, brandSubAccount(raw)];
479
- } catch (err) {
480
- if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
481
- return [err, null];
482
- }
483
- return [
484
- malformedWireError(
485
- err instanceof Error ? err.message : String(err),
486
- raw
487
- ),
488
- null
489
- ];
490
- }
491
- }
492
- function createSubAccountsClient(config = {}) {
493
- return {
494
- retrieve: async (subAccountId) => {
495
- if (!config._data) {
496
- return stub("subAccounts.retrieve");
497
- }
498
- const [err, raw] = await tryCatch(
499
- config._data.query(api.subAccounts.queries.retrieve, {
500
- subAccountId
501
- })
502
- );
503
- if (err) {
504
- return [
505
- fromConvexError(err),
506
- null
507
- ];
508
- }
509
- if (!raw) {
510
- return [
511
- new CapxulError({
512
- code: "NOT_FOUND",
513
- message: `sub_account ${subAccountId} not found`
514
- }),
515
- null
516
- ];
517
- }
518
- const [brandErr, branded] = tryBrandSubAccount(raw);
519
- if (brandErr) {
520
- return [brandErr, null];
521
- }
522
- return [null, branded];
523
- },
524
- remove: async (subAccountId) => {
525
- if (!config._data) {
526
- return stub("subAccounts.remove");
527
- }
528
- const [err, raw] = await tryCatch(
529
- config._data.mutation(api.subAccounts.mutations.archive, {
530
- subAccountId
531
- })
532
- );
533
- if (err) {
534
- return [
535
- fromConvexError(err),
536
- null
537
- ];
538
- }
539
- if (!raw) {
540
- return [
541
- new CapxulError({
542
- code: "NOT_FOUND",
543
- message: `sub_account ${subAccountId} not found`
544
- }),
545
- null
546
- ];
547
- }
548
- const [brandErr, branded] = tryBrandSubAccount(raw);
549
- if (brandErr) {
550
- return [brandErr, null];
551
- }
552
- return [null, branded];
553
- }
554
- };
555
- }
556
-
557
- // src/core/accounts.ts
558
- function createAccountExternalAccountsClient(config) {
559
- return {
560
- create: async (input) => {
561
- if (!config._data) {
562
- return stub(
563
- "accounts.externalAccounts.create"
564
- );
565
- }
566
- const [err, raw] = await tryCatch(
567
- config._data.mutation(
568
- api.externalAccounts.mutations.createPersonal,
569
- {
570
- kind: input.kind,
571
- label: input.label,
572
- address: input.address,
573
- iban: input.iban,
574
- bic: input.bic,
575
- accountHolder: input.accountHolder,
576
- network: input.network,
577
- panToken: input.panToken,
578
- last4: input.last4
579
- }
580
- )
581
- );
582
- if (err) {
583
- return [
584
- fromConvexError(err),
585
- null
586
- ];
587
- }
588
- if (!raw) {
589
- return [
590
- new CapxulError({
591
- code: "NOT_FOUND",
592
- message: "external_account creation returned no resource"
593
- }),
594
- null
595
- ];
596
- }
597
- return [
598
- null,
599
- brandExternalAccount(
600
- raw
601
- )
602
- ];
603
- },
604
- list: async (input) => {
605
- if (!config._data) {
606
- return stub(
607
- "accounts.externalAccounts.list"
608
- );
609
- }
610
- const [err, result] = await tryCatch(
611
- config._data.query(api.externalAccounts.queries.listPersonal, {
612
- limit: input.limit,
613
- cursor: input.cursor
614
- })
615
- );
616
- if (err) {
617
- return [fromConvexError(err), null];
618
- }
619
- const branded = result.data.map(
620
- (row) => brandExternalAccount(
621
- row
622
- )
623
- );
624
- return [
625
- null,
626
- {
627
- object: "list",
628
- data: branded,
629
- page: result.page
630
- }
631
- ];
632
- },
633
- retrieve: async (externalAccountId) => {
634
- if (!config._data) {
635
- return stub(
636
- "accounts.externalAccounts.retrieve"
637
- );
638
- }
639
- const [err, raw] = await tryCatch(
640
- config._data.query(api.externalAccounts.queries.retrievePersonal, {
641
- externalAccountId
642
- })
643
- );
644
- if (err) {
645
- return [
646
- fromConvexError(err),
647
- null
648
- ];
649
- }
650
- if (!raw) {
651
- return [
652
- new CapxulError({
653
- code: "NOT_FOUND",
654
- message: `external_account ${externalAccountId} not found`
655
- }),
656
- null
657
- ];
658
- }
659
- return [
660
- null,
661
- brandExternalAccount(
662
- raw
663
- )
664
- ];
665
- },
666
- remove: async (externalAccountId) => {
667
- if (!config._data) {
668
- return stub("accounts.externalAccounts.remove");
669
- }
670
- const [err] = await tryCatch(
671
- config._data.mutation(api.externalAccounts.mutations.removePersonal, {
672
- externalAccountId
673
- })
674
- );
675
- if (err) {
676
- return [
677
- fromConvexError(err),
678
- null
679
- ];
680
- }
681
- return [null, void 0];
682
- }
683
- };
684
- }
685
- function createAccountSubAccountsClient(config) {
686
- return {
687
- create: async (input) => {
688
- if (!config._data) {
689
- return stub(
690
- "accounts.subAccounts.create"
691
- );
692
- }
693
- const [err, raw] = await tryCatch(
694
- config._data.mutation(api.subAccounts.mutations.create, {
695
- parent: { kind: "account", id: input.accountId },
696
- name: input.name,
697
- purpose: input.purpose
698
- })
699
- );
700
- if (err) {
701
- return [
702
- fromConvexError(err),
703
- null
704
- ];
705
- }
706
- if (!raw) {
707
- return [
708
- new CapxulError({
709
- code: "NOT_FOUND",
710
- message: "sub_account creation returned no resource"
711
- }),
712
- null
713
- ];
714
- }
715
- const [brandErr, branded] = tryBrandSubAccount(raw);
716
- if (brandErr) {
717
- return [
718
- brandErr,
719
- null
720
- ];
721
- }
722
- return [null, branded];
723
- },
724
- list: async (input) => {
725
- if (!config._data) {
726
- return stub(
727
- "accounts.subAccounts.list"
728
- );
729
- }
730
- const [err, rows] = await tryCatch(
731
- config._data.query(api.subAccounts.queries.listByAccount, {
732
- accountId: input.accountId
733
- })
734
- );
735
- if (err) {
736
- return [
737
- fromConvexError(err),
738
- null
739
- ];
740
- }
741
- const branded = [];
742
- for (const row of rows) {
743
- const [brandErr, value] = tryBrandSubAccount(row);
744
- if (brandErr) {
745
- return [
746
- brandErr,
747
- null
748
- ];
749
- }
750
- branded.push(value);
751
- }
752
- return [
753
- null,
754
- {
755
- object: "list",
756
- data: branded,
757
- page: { hasMore: false }
758
- }
759
- ];
760
- },
761
- retrieve: async (subAccountId) => {
762
- if (!config._data) {
763
- return stub(
764
- "accounts.subAccounts.retrieve"
765
- );
766
- }
767
- const [err, raw] = await tryCatch(
768
- config._data.query(api.subAccounts.queries.retrieve, {
769
- subAccountId
770
- })
771
- );
772
- if (err) {
773
- return [
774
- fromConvexError(err),
775
- null
776
- ];
777
- }
778
- if (!raw) {
779
- return [
780
- new CapxulError({
781
- code: "NOT_FOUND",
782
- message: `sub_account ${subAccountId} not found`
783
- }),
784
- null
785
- ];
786
- }
787
- const [brandErr, branded] = tryBrandSubAccount(raw);
788
- if (brandErr) {
789
- return [
790
- brandErr,
791
- null
792
- ];
793
- }
794
- return [null, branded];
795
- },
796
- remove: async (subAccountId) => {
797
- if (!config._data) {
798
- return stub(
799
- "accounts.subAccounts.remove"
800
- );
801
- }
802
- const [err, raw] = await tryCatch(
803
- config._data.mutation(api.subAccounts.mutations.archive, {
804
- subAccountId
805
- })
806
- );
807
- if (err) {
808
- return [
809
- fromConvexError(err),
810
- null
811
- ];
812
- }
813
- if (!raw) {
814
- return [
815
- new CapxulError({
816
- code: "NOT_FOUND",
817
- message: `sub_account ${subAccountId} not found`
818
- }),
819
- null
820
- ];
821
- }
822
- const [brandErr, branded] = tryBrandSubAccount(raw);
823
- if (brandErr) {
824
- return [
825
- brandErr,
826
- null
827
- ];
828
- }
829
- return [null, branded];
830
- }
831
- };
832
- }
833
- function createAccountsClient(config = {}) {
834
- return {
835
- retrieve: async (accountId) => {
836
- if (!config._data) {
837
- return stub("accounts.retrieve");
838
- }
839
- try {
840
- const account = await config._data.query(
841
- api.openfort.queries.getMyAccount,
842
- {}
843
- );
844
- if (account.id !== accountId) {
845
- return [
846
- new CapxulError({
847
- code: "PERMISSION_DENIED",
848
- message: "accounts.retrieve currently supports the authenticated caller's own account only.",
849
- details: {
850
- requestedAccountId: accountId,
851
- authenticatedAccountId: account.id
852
- }
853
- }),
854
- null
855
- ];
856
- }
857
- return [null, account];
858
- } catch (cause) {
859
- return [fromConvexError(cause), null];
860
- }
861
- },
862
- lookup: async () => stub("accounts.lookup"),
863
- update: async (input) => {
864
- if (!config._data) {
865
- return stub("accounts.update");
866
- }
867
- if (input.countryCode !== void 0) {
868
- return [
869
- new CapxulError({
870
- code: "INVALID_INPUT",
871
- message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
872
- details: { field: "countryCode" }
873
- }),
874
- null
875
- ];
876
- }
877
- try {
878
- const current = await config._data.query(
879
- api.openfort.queries.getMyAccount,
880
- {}
881
- );
882
- if (current.id !== input.accountId) {
883
- return [
884
- new CapxulError({
885
- code: "PERMISSION_DENIED",
886
- message: "accounts.update currently supports the authenticated caller's own account only.",
887
- details: {
888
- requestedAccountId: input.accountId,
889
- authenticatedAccountId: current.id
890
- }
891
- }),
892
- null
893
- ];
894
- }
895
- await config._data.mutation(api.openfort.mutations.updateProfile, {
896
- displayName: input.name,
897
- username: input.username
898
- });
899
- const updated = await config._data.query(
900
- api.openfort.queries.getMyAccount,
901
- {}
902
- );
903
- return [null, updated];
904
- } catch (cause) {
905
- return [fromConvexError(cause), null];
906
- }
907
- },
908
- provisionPersonal: async (input) => {
909
- if (!config._data) {
910
- return stub(
911
- "accounts.provisionPersonal"
912
- );
913
- }
914
- if (input.signerProvider.kind !== "local-private-key") {
915
- return [
916
- new CapxulError({
917
- code: "INVALID_INPUT",
918
- message: "accounts.provisionPersonal currently supports local-private-key signer providers only."
919
- }),
920
- null
921
- ];
922
- }
923
- try {
924
- await config._data.mutation(
925
- api.safe.mutations.provisionLocalPersonalAccount,
926
- {
927
- displayName: input.displayName,
928
- username: input.username,
929
- countryCode: input.countryCode,
930
- eoaAddress: input.signerProvider.signerAddress,
931
- safeAddress: deriveSafeAddress(input.signerProvider.signerAddress)
932
- }
933
- );
934
- const account = await config._data.query(
935
- api.openfort.queries.getMyAccount,
936
- {}
937
- );
938
- if (!account) {
939
- return [
940
- new CapxulError({
941
- code: "NOT_FOUND",
942
- message: "accounts.provisionPersonal completed but no account resource was readable."
943
- }),
944
- null
945
- ];
946
- }
947
- return [null, account];
948
- } catch (cause) {
949
- return [
950
- fromConvexError(cause),
951
- null
952
- ];
953
- }
954
- },
955
- safes: {
956
- retrieve: async (safeId) => {
957
- if (!config._data) {
958
- return stub("accounts.safes.retrieve");
959
- }
960
- try {
961
- const safe = await config._data.query(
962
- api.safe.queries.retrieveAccountSafe,
963
- { safeId }
964
- );
965
- if (!safe) {
966
- return [
967
- new CapxulError({
968
- code: "NOT_FOUND",
969
- message: `safe ${safeId} not found`
970
- }),
971
- null
972
- ];
973
- }
974
- return [null, safe];
975
- } catch (cause) {
976
- return [
977
- fromConvexError(cause),
978
- null
979
- ];
980
- }
981
- }
982
- },
983
- kycProfiles: {
984
- create: async () => stub("accounts.kycProfiles.create"),
985
- retrieve: async () => stub("accounts.kycProfiles.retrieve")
986
- },
987
- externalAccounts: createAccountExternalAccountsClient(config),
988
- subAccounts: createAccountSubAccountsClient(config),
989
- balanceLedger: {
990
- list: async (input) => {
991
- if (!config._data) {
992
- return stub(
993
- "accounts.balanceLedger.list"
994
- );
995
- }
996
- try {
997
- const accountId = input.accountId.replace(/^acct_/, "");
998
- const page = await config._data.query(
999
- api.balanceLedger.queries.listForAccount,
1000
- { accountId, limit: input.limit, cursor: input.cursor }
1001
- );
1002
- return [null, page];
1003
- } catch (cause) {
1004
- return [fromConvexError(cause), null];
1005
- }
1006
- },
1007
- retrieve: async (entryId) => {
1008
- if (!config._data) {
1009
- return stub(
1010
- "accounts.balanceLedger.retrieve"
1011
- );
1012
- }
1013
- try {
1014
- const entry = await config._data.query(
1015
- api.balanceLedger.queries.retrieve,
1016
- { entryId }
1017
- );
1018
- if (!entry) {
1019
- return [
1020
- new CapxulError({
1021
- code: "NOT_FOUND",
1022
- message: `balance_ledger_entry ${entryId} not found`
1023
- }),
1024
- null
1025
- ];
1026
- }
1027
- return [null, entry];
1028
- } catch (cause) {
1029
- return [fromConvexError(cause), null];
1030
- }
1031
- }
1032
- }
1033
- };
1034
- }
1035
-
1036
- // src/core/api-keys.ts
1037
- function createApiKeysClient() {
1038
- return {
1039
- create: async () => stub("apiKeys.create"),
1040
- retrieve: async () => stub("apiKeys.retrieve"),
1041
- list: async () => stub("apiKeys.list"),
1042
- revoke: async () => stub("apiKeys.revoke")
1043
- };
1044
- }
1045
- function createDefaultDataClient(convexUrl, jwt) {
1046
- const client = new ConvexHttpClient(convexUrl);
1047
- client.setAuth(jwt);
1048
- return client;
1049
- }
1050
-
1051
- // src/transport.ts
1052
- function makeHttpTransport(config) {
1053
- switch (config.mode) {
1054
- case "build-time-urls":
1055
- return makeBuildTimeUrlsTransport(config);
1056
- case "publishable-key":
1057
- return makePublishableKeyTransport(config);
1058
- default:
1059
- return assertNever(config);
1060
- }
1061
- }
1062
- function createLifecycle(initial) {
1063
- let state = initial;
1064
- const listeners = /* @__PURE__ */ new Set();
1065
- return {
1066
- getState: () => state,
1067
- setState: (next) => {
1068
- if (Object.is(state, next)) return;
1069
- state = next;
1070
- for (const listener of listeners) listener();
1071
- },
1072
- subscribe: (listener) => {
1073
- listeners.add(listener);
1074
- return () => {
1075
- listeners.delete(listener);
1076
- };
1077
- }
1078
- };
1079
- }
1080
- function makeBuildTimeUrlsTransport(config) {
1081
- if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
1082
- throw invalidConfigError(
1083
- "authBaseUrl",
1084
- "build-time-urls transport requires a non-empty authBaseUrl."
1085
- );
1086
- }
1087
- if (!config.convexUrl || config.convexUrl.trim().length === 0) {
1088
- throw invalidConfigError(
1089
- "convexUrl",
1090
- "build-time-urls transport requires a non-empty convexUrl."
1091
- );
1092
- }
1093
- const authBaseUrl = stripTrailingSlash(config.authBaseUrl);
1094
- const convexUrl = config.convexUrl;
1095
- const fetchImpl = config.fetchImpl ?? globalThis.fetch;
1096
- const runtime = { authBaseUrl, convexUrl };
1097
- const lifecycle = createLifecycle({ status: "ready", runtime });
1098
- let dataClient = null;
1099
- return {
1100
- authBaseUrl,
1101
- convexUrl,
1102
- ensureRuntime: async () => runtime,
1103
- fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
1104
- getState: lifecycle.getState,
1105
- subscribe: lifecycle.subscribe,
1106
- getDataClient: () => dataClient,
1107
- markAuthenticated: ({ dataClient: nextDataClient }) => {
1108
- if (nextDataClient !== void 0) dataClient = nextDataClient;
1109
- lifecycle.setState({ status: "authenticated", runtime });
1110
- },
1111
- clearAuth: () => {
1112
- dataClient = null;
1113
- lifecycle.setState({ status: "ready", runtime });
1114
- }
1115
- };
1116
- }
1117
- function makePublishableKeyTransport(config) {
1118
- const publishableKey = config.publishableKey?.trim();
1119
- if (!publishableKey) {
1120
- throw invalidConfigError(
1121
- "publishableKey",
1122
- "publishable-key transport requires a non-empty publishableKey."
1123
- );
1124
- }
1125
- const fetchImpl = config.fetchImpl ?? globalThis.fetch;
1126
- const bootstrapUrl = normalizeBootstrapUrl(
1127
- config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
1128
- );
1129
- let authBaseUrl = "";
1130
- let convexUrl = "";
1131
- let bootstrapPromise = null;
1132
- let dataClient = null;
1133
- const lifecycle = createLifecycle({ status: "idle" });
1134
- async function ensureBootstrap() {
1135
- if (bootstrapPromise) return await bootstrapPromise;
1136
- lifecycle.setState({ status: "bootstrapping" });
1137
- const attempt = (async () => {
1138
- const response = await fetchImpl(bootstrapUrl, {
1139
- method: "POST",
1140
- headers: { "content-type": "application/json" },
1141
- body: JSON.stringify({ publishableKey })
1142
- });
1143
- if (!response.ok) {
1144
- throw await bootstrapResponseError(response, bootstrapUrl);
1145
- }
1146
- const body = await readBootstrapSuccessBody(response);
1147
- if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
1148
- throw bootstrapContractError(
1149
- "authBaseUrl",
1150
- "/v1/client/bootstrap returned no authBaseUrl."
1151
- );
1152
- }
1153
- if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
1154
- throw bootstrapContractError(
1155
- "convexUrl",
1156
- "/v1/client/bootstrap returned no convexUrl."
1157
- );
1158
- }
1159
- authBaseUrl = stripTrailingSlash(body.authBaseUrl);
1160
- convexUrl = body.convexUrl;
1161
- const runtime = { authBaseUrl, convexUrl };
1162
- lifecycle.setState({ status: "ready", runtime });
1163
- return runtime;
1164
- })();
1165
- bootstrapPromise = attempt.catch((err) => {
1166
- const error = normalizeBootstrapThrownError(err);
1167
- bootstrapPromise = null;
1168
- lifecycle.setState({
1169
- status: "error",
1170
- error
1171
- });
1172
- throw error;
1173
- });
1174
- return await bootstrapPromise;
1175
- }
1176
- return {
1177
- get authBaseUrl() {
1178
- return authBaseUrl;
1179
- },
1180
- get convexUrl() {
1181
- return convexUrl;
1182
- },
1183
- ensureRuntime: ensureBootstrap,
1184
- fetch: async (path, init) => {
1185
- const resolved = await ensureBootstrap();
1186
- return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
1187
- },
1188
- getState: lifecycle.getState,
1189
- subscribe: lifecycle.subscribe,
1190
- getDataClient: () => dataClient,
1191
- markAuthenticated: ({ dataClient: nextDataClient }) => {
1192
- const current = lifecycle.getState();
1193
- if (current.status !== "ready" && current.status !== "authenticated") {
1194
- throw internalTransportError(
1195
- `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
1196
- );
1197
- }
1198
- if (nextDataClient !== void 0) dataClient = nextDataClient;
1199
- lifecycle.setState({
1200
- status: "authenticated",
1201
- runtime: current.runtime
1202
- });
1203
- },
1204
- clearAuth: () => {
1205
- const current = lifecycle.getState();
1206
- dataClient = null;
1207
- if (current.status === "authenticated") {
1208
- lifecycle.setState({ status: "ready", runtime: current.runtime });
1209
- }
1210
- }
1211
- };
1212
- }
1213
- function stripTrailingSlash(url) {
1214
- return url.replace(/\/+$/, "");
1215
- }
1216
- function normalizeBootstrapUrl(url) {
1217
- const normalized = stripTrailingSlash(url.trim());
1218
- if (!isAbsoluteHttpUrl(normalized)) {
1219
- throw invalidConfigError(
1220
- "bootstrapUrl",
1221
- "publishable-key transport requires an absolute http(s) bootstrapUrl."
1222
- );
1223
- }
1224
- return normalized;
1225
- }
1226
- function isAbsoluteHttpUrl(url) {
1227
- try {
1228
- const parsed = new URL(url);
1229
- return parsed.protocol === "http:" || parsed.protocol === "https:";
1230
- } catch {
1231
- return false;
1232
- }
1233
- }
1234
- function resolveUrl(authBaseUrl, path) {
1235
- if (path.startsWith("http://") || path.startsWith("https://")) {
1236
- return path;
1237
- }
1238
- return `${authBaseUrl}${path}`;
1239
- }
1240
- function assertNever(value) {
1241
- throw internalTransportError(
1242
- `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
1243
- );
1244
- }
1245
- function invalidConfigError(field, reason) {
1246
- return new CapxulError({
1247
- code: "INVALID_INPUT",
1248
- message: `Invalid ${field}: ${reason}`,
1249
- details: { source: "sdk-config", field, reason }
1250
- });
1251
- }
1252
- function bootstrapContractError(field, message) {
1253
- return new CapxulError({
1254
- code: "INVALID_INPUT",
1255
- message,
1256
- details: {
1257
- source: "backend-bootstrap",
1258
- phase: "publishable-key-bootstrap",
1259
- field,
1260
- reason: message
1261
- }
1262
- });
1263
- }
1264
- function internalTransportError(reason) {
1265
- return new CapxulError({
1266
- code: "INTERNAL_ERROR",
1267
- message: `Internal error: ${reason}`,
1268
- details: { source: "sdk-transport", reason }
1269
- });
1270
- }
1271
- async function bootstrapResponseError(response, bootstrapUrl) {
1272
- const envelope = await readBootstrapErrorEnvelope(response);
1273
- const wireCode = readNonEmptyString(envelope?.error?.code);
1274
- const normalized = normalizeBootstrapErrorCode(wireCode);
1275
- const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
1276
- const backendDetails = readRecord(envelope?.error?.details);
1277
- return new CapxulError({
1278
- code: normalized.code,
1279
- message,
1280
- details: {
1281
- ...backendDetails,
1282
- source: "backend-bootstrap",
1283
- phase: "publishable-key-bootstrap",
1284
- httpStatus: response.status,
1285
- ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
1286
- },
1287
- operationId: readNonEmptyString(envelope?.error?.operationId),
1288
- correlationId: readNonEmptyString(envelope?.error?.correlationId),
1289
- retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
1290
- });
1291
- }
1292
- async function readBootstrapErrorEnvelope(response) {
1293
- try {
1294
- const parsed = await response.json();
1295
- return typeof parsed === "object" && parsed !== null ? parsed : null;
1296
- } catch {
1297
- return null;
1298
- }
1299
- }
1300
- async function readBootstrapSuccessBody(response) {
1301
- try {
1302
- const parsed = await response.json();
1303
- return typeof parsed === "object" && parsed !== null ? parsed : {};
1304
- } catch {
1305
- throw bootstrapContractError(
1306
- "body",
1307
- "/v1/client/bootstrap returned invalid JSON."
1308
- );
1309
- }
1310
- }
1311
- function normalizeBootstrapThrownError(error) {
1312
- if (error instanceof CapxulError) return error;
1313
- return new CapxulError({
1314
- code: "NETWORK_ERROR",
1315
- message: "Publishable-key bootstrap network failure.",
1316
- cause: error,
1317
- details: {
1318
- source: "bootstrap-network",
1319
- phase: "publishable-key-bootstrap"
1320
- }
1321
- });
1322
- }
1323
- function normalizeBootstrapErrorCode(wireCode) {
1324
- if (wireCode === "INTERNAL_SERVER_ERROR") {
1325
- return { code: "INTERNAL_ERROR", wireCode };
1326
- }
1327
- if (wireCode && isCapxulErrorCode(wireCode)) {
1328
- return { code: wireCode };
1329
- }
1330
- return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
1331
- }
1332
- var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
1333
- "NOT_AUTHENTICATED",
1334
- "EMAIL_DELIVERY_FAILED",
1335
- "PROFILE_NOT_FOUND",
1336
- "SMART_ACCOUNT_MISSING",
1337
- "PLAYER_NOT_FOUND",
1338
- "ACCOUNT_NOT_FOUND",
1339
- "PROVIDER_ERROR",
1340
- "INVALID_INPUT",
1341
- "ENV_MISSING",
1342
- "NOT_IMPLEMENTED",
1343
- "VERIFICATION_REQUIRED",
1344
- "INSUFFICIENT_BALANCE",
1345
- "INVALID_RECIPIENT",
1346
- "TRANSACTION_FAILED",
1347
- "RATE_LIMITED",
1348
- "NETWORK_ERROR",
1349
- "UNKNOWN",
1350
- "PERMISSION_DENIED",
1351
- "API_KEY_INVALID",
1352
- "API_KEY_EXPIRED",
1353
- "IDEMPOTENCY_CONFLICT",
1354
- "NOT_FOUND",
1355
- "OPERATION_CANCELED",
1356
- "OPERATION_TIMEOUT",
1357
- "ACTION_REQUIRED",
1358
- "KYC_REQUIRED",
1359
- "POLICY_DENIED",
1360
- "SAFE_NOT_READY",
1361
- "PROVIDER_UNAVAILABLE",
1362
- "PROVIDER_REJECTED",
1363
- "RECONCILIATION_FAILED",
1364
- "INTERNAL_ERROR",
1365
- "QUOTE_EXPIRED",
1366
- "QUOTE_NOT_FOUND"
1367
- ]);
1368
- function isCapxulErrorCode(value) {
1369
- return CAPXUL_ERROR_CODES.has(value);
1370
- }
1371
- function readRecord(value) {
1372
- return typeof value === "object" && value !== null ? value : null;
1373
- }
1374
- function readNonEmptyString(value) {
1375
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1376
- }
1377
-
1378
- // src/core/auth.ts
1379
- function createAuthClient(config = {}) {
1380
- let dataClient = config._data ?? null;
1381
- const sessionStore = config.auth?.sessionStore ?? createMemorySessionStore();
1382
- const getTransport = createTransportProvider(config);
1383
- return {
1384
- sendOtp: async (input, options) => {
1385
- const transport = getTransport();
1386
- if (!transport) {
1387
- return stub("auth.sendOtp");
1388
- }
1389
- return await postBetterAuth(
1390
- transport,
1391
- "/email-otp/send-verification-otp",
1392
- { email: input.email, type: "sign-in" },
1393
- "EMAIL_DELIVERY_FAILED",
1394
- options?.signal
1395
- );
1396
- },
1397
- verifyOtp: async (input, options) => {
1398
- const transport = getTransport();
1399
- if (!transport) {
1400
- return stub("auth.verifyOtp");
1401
- }
1402
- const [signInError, signIn] = await postBetterAuth(
1403
- transport,
1404
- "/sign-in/email-otp",
1405
- { email: input.email, otp: input.otp },
1406
- "NOT_AUTHENTICATED",
1407
- options?.signal
1408
- );
1409
- if (signInError) return [signInError, null];
1410
- if (!signIn?.token || !signIn.user?.id) {
1411
- return [
1412
- new CapxulError({
1413
- code: "NOT_AUTHENTICATED",
1414
- message: "BetterAuth did not return a usable session."
1415
- }),
1416
- null
1417
- ];
1418
- }
1419
- const [convexError, convexJwt] = await exchangeConvexToken(
1420
- transport,
1421
- config,
1422
- signIn.token,
1423
- options?.signal
1424
- );
1425
- if (convexError) return [convexError, null];
1426
- const session = {
1427
- authUserId: signIn.user.id,
1428
- email: signIn.user.email,
1429
- token: signIn.token,
1430
- convexJwt,
1431
- expiresAt: new Date(
1432
- Date.now() + 30 * 24 * 60 * 60 * 1e3
1433
- ).toISOString()
1434
- };
1435
- sessionStore.set(session);
1436
- if (!dataClient) {
1437
- try {
1438
- const convexUrl = transport.convexUrl;
1439
- if (!convexUrl || !session.convexJwt) {
1440
- return [
1441
- new CapxulError({
1442
- code: "NETWORK_ERROR",
1443
- message: "Cannot create data client: missing convex URL or JWT."
1444
- }),
1445
- null
1446
- ];
1447
- }
1448
- dataClient = createDefaultDataClient(convexUrl, session.convexJwt);
1449
- mutableConfig(config)._data = dataClient;
1450
- } catch (cause) {
1451
- return [
1452
- new CapxulError({
1453
- code: "NETWORK_ERROR",
1454
- message: "Authenticated data client creation failed.",
1455
- cause
1456
- }),
1457
- null
1458
- ];
1459
- }
1460
- } else {
1461
- const injected = dataClient;
1462
- if (typeof injected.refreshAuth === "function") {
1463
- injected.refreshAuth();
1464
- } else if (typeof injected.setAuth === "function" && session.convexJwt) {
1465
- injected.setAuth(session.convexJwt);
1466
- }
1467
- }
1468
- transport.markAuthenticated({ dataClient });
1469
- if (!dataClient) {
1470
- return [
1471
- new CapxulError({
1472
- code: "NOT_AUTHENTICATED",
1473
- message: "Auth bootstrap requires an authenticated Convex data client."
1474
- }),
1475
- null
1476
- ];
1477
- }
1478
- try {
1479
- const resolution = await dataClient.mutation(
1480
- api.authBootstrap.resolveAfterOtp,
1481
- {
1482
- email: session.email,
1483
- sessionToken: session.token
1484
- }
1485
- );
1486
- if (resolution.kind === "existing_member") {
1487
- return [null, { ...resolution, session }];
1488
- }
1489
- return [null, { ...resolution, session }];
1490
- } catch (cause) {
1491
- return [fromConvexError(cause), null];
1492
- }
1493
- },
1494
- completeBootstrap: async (input) => {
1495
- const session = sessionStore.get();
1496
- const data = dataClient ?? config._data;
1497
- if (!session || !data) {
1498
- return [
1499
- new CapxulError({
1500
- code: "INVALID_INPUT",
1501
- message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
1502
- }),
1503
- null
1504
- ];
1505
- }
1506
- const signerAddress = config.signer?.address;
1507
- if (!signerAddress) {
1508
- return [
1509
- new CapxulError({
1510
- code: "INVALID_INPUT",
1511
- message: "completeBootstrap requires a signer to be configured on the client."
1512
- }),
1513
- null
1514
- ];
1515
- }
1516
- try {
1517
- const safeAddress = deriveSafeAddress(signerAddress);
1518
- const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1519
- bootstrapToken: input.bootstrapToken,
1520
- sessionToken: session.token,
1521
- username: input.username,
1522
- displayName: input.displayName,
1523
- countryCode: input.countryCode,
1524
- signerProvider: {
1525
- kind: "local-private-key",
1526
- signerAddress,
1527
- safeAddress
1528
- }
1529
- });
1530
- return [null, { kind: "authenticated", session, ...result }];
1531
- } catch (cause) {
1532
- return [
1533
- fromConvexError(cause),
1534
- null
1535
- ];
1536
- }
1537
- },
1538
- getSession: async () => [null, sessionStore.get()],
1539
- signOut: async () => {
1540
- sessionStore.clear();
1541
- dataClient = null;
1542
- mutableConfig(config)._data = void 0;
1543
- const transport = getTransport();
1544
- transport?.clearAuth();
1545
- return [null, void 0];
1546
- },
1547
- serviceTokenMint: async () => stub("auth.serviceTokenMint"),
1548
- getDataClient: () => dataClient
1549
- };
1550
- }
1551
- function createMemorySessionStore() {
1552
- let current = null;
1553
- return {
1554
- get: () => current,
1555
- set: (session) => {
1556
- current = session;
1557
- },
1558
- clear: () => {
1559
- current = null;
1560
- }
1561
- };
1562
- }
1563
- function createTransportProvider(config) {
1564
- let cached = config._transport ?? null;
1565
- return () => {
1566
- if (cached) return cached;
1567
- const baseUrl = config.auth?.baseUrl;
1568
- if (baseUrl) {
1569
- cached = makeHttpTransport({
1570
- mode: "build-time-urls",
1571
- authBaseUrl: betterAuthRoot(baseUrl),
1572
- convexUrl: baseUrl,
1573
- fetchImpl: config.fetch
1574
- });
1575
- return cached;
1576
- }
1577
- if (!config.publishableKey) return null;
1578
- cached = makeHttpTransport({
1579
- mode: "publishable-key",
1580
- publishableKey: config.publishableKey,
1581
- fetchImpl: config.fetch
1582
- });
1583
- return cached;
1584
- };
1585
- }
1586
- function betterAuthRoot(rawBaseUrl) {
1587
- const trimmed = rawBaseUrl.replace(/\/+$/, "");
1588
- return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
1589
- }
1590
- async function postBetterAuth(transport, path, body, code, signal) {
1591
- try {
1592
- const response = await transport.fetch(path, {
1593
- method: "POST",
1594
- headers: { "content-type": "application/json" },
1595
- body: JSON.stringify(body),
1596
- signal
1597
- });
1598
- const text = await response.text();
1599
- if (!response.ok) {
1600
- const parsedError = parseBetterAuthError(text);
1601
- return [
1602
- new CapxulError({
1603
- code: parsedError.code ?? code,
1604
- message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1605
- details: parsedError.details,
1606
- retryable: parsedError.retryable
1607
- }),
1608
- null
1609
- ];
1610
- }
1611
- return [null, text ? JSON.parse(text) : void 0];
1612
- } catch (cause) {
1613
- if (cause instanceof CapxulError) {
1614
- return [cause, null];
1615
- }
1616
- return [
1617
- new CapxulError({
1618
- code: "NETWORK_ERROR",
1619
- message: `BetterAuth ${path} network failure.`,
1620
- cause
1621
- }),
1622
- null
1623
- ];
1624
- }
1625
- }
1626
- function parseBetterAuthError(text) {
1627
- if (!text.trim()) {
1628
- return {};
1629
- }
1630
- try {
1631
- const body = JSON.parse(text);
1632
- if (!body || typeof body !== "object") {
1633
- return {};
1634
- }
1635
- const record = body;
1636
- const nested = record.error && typeof record.error === "object" ? record.error : record;
1637
- const code = typeof nested.code === "string" ? nested.code : void 0;
1638
- const message = typeof nested.message === "string" ? nested.message : void 0;
1639
- const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1640
- const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1641
- const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1642
- return {
1643
- code: isCapxulErrorCode2(code) ? code : void 0,
1644
- message,
1645
- details,
1646
- correlationId,
1647
- retryable
1648
- };
1649
- } catch {
1650
- return {};
1651
- }
1652
- }
1653
- function isCapxulErrorCode2(code) {
1654
- return code === "NOT_AUTHENTICATED" || code === "EMAIL_DELIVERY_FAILED" || code === "INVALID_INPUT" || code === "RATE_LIMITED" || code === "NETWORK_ERROR" || code === "API_KEY_INVALID" || code === "API_KEY_EXPIRED" || code === "INTERNAL_ERROR" || code === "ENV_MISSING" || code === "UNKNOWN" || code === "PROVIDER_ERROR";
1655
- }
1656
- async function exchangeConvexToken(transport, config, token, signal) {
1657
- const path = config.auth?.convexTokenUrl ?? "/convex/token";
1658
- try {
1659
- const response = await transport.fetch(path, {
1660
- headers: { authorization: `Bearer ${token}` },
1661
- signal
1662
- });
1663
- if (!response.ok) {
1664
- return [
1665
- new CapxulError({
1666
- code: "NOT_AUTHENTICATED",
1667
- message: `Convex token exchange failed with HTTP ${response.status}.`
1668
- }),
1669
- null
1670
- ];
1671
- }
1672
- const body = await response.json();
1673
- if (typeof body.token !== "string") {
1674
- return [
1675
- new CapxulError({
1676
- code: "NOT_AUTHENTICATED",
1677
- message: "Convex token exchange returned no token."
1678
- }),
1679
- null
1680
- ];
1681
- }
1682
- return [null, body.token];
1683
- } catch (cause) {
1684
- if (cause instanceof CapxulError) {
1685
- return [cause, null];
1686
- }
1687
- return [
1688
- new CapxulError({
1689
- code: "NETWORK_ERROR",
1690
- message: "Convex token exchange network failure.",
1691
- cause
1692
- }),
1693
- null
1694
- ];
1695
- }
1696
- }
1697
- function mutableConfig(config) {
1698
- return config;
1699
- }
1700
-
1701
- // src/core/documents.ts
1702
- function createDocumentsClient() {
1703
- return {
1704
- create: async () => stub("documents.create"),
1705
- retrieve: async () => stub("documents.retrieve"),
1706
- list: async () => stub("documents.list"),
1707
- cancel: async () => stub("documents.cancel")
1708
- };
1709
- }
1710
- function createOrgDocumentsClient() {
1711
- return {
1712
- create: async () => stub("organizations.documents.create"),
1713
- retrieve: async () => stub("organizations.documents.retrieve"),
1714
- list: async () => stub("organizations.documents.list"),
1715
- cancel: async () => stub("organizations.documents.cancel")
1716
- };
1717
- }
1718
-
1719
- // src/core/me.ts
1720
- function createMeClient(config = {}) {
1721
- return {
1722
- get: async () => {
1723
- if (!config._data) {
1724
- return stub("me.get");
1725
- }
1726
- try {
1727
- const account = await config._data.query(
1728
- api.openfort.queries.getMyAccount,
1729
- {}
1730
- );
1731
- return [null, account];
1732
- } catch (cause) {
1733
- return [fromConvexError(cause), null];
1734
- }
1735
- },
1736
- update: async (input) => {
1737
- if (!config._data) {
1738
- return stub("me.update");
1739
- }
1740
- if (input.countryCode !== void 0) {
1741
- return [
1742
- new CapxulError({
1743
- code: "INVALID_INPUT",
1744
- message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
1745
- details: { field: "countryCode" }
1746
- }),
1747
- null
1748
- ];
1749
- }
1750
- try {
1751
- await config._data.mutation(api.openfort.mutations.updateProfile, {
1752
- displayName: input.name,
1753
- username: input.username
1754
- });
1755
- const account = await config._data.query(
1756
- api.openfort.queries.getMyAccount,
1757
- {}
1758
- );
1759
- return [null, account];
1760
- } catch (cause) {
1761
- return [fromConvexError(cause), null];
1762
- }
1763
- }
1764
- };
1765
- }
1766
-
1767
- // src/core/operations.ts
1768
- function createOperationsClient(config = {}) {
1769
- const retrieve = async (operationId) => {
1770
- if (!config._data) {
1771
- return stub("operations.retrieve");
1772
- }
1773
- try {
1774
- const operation = await config._data.query(api.operations.queries.retrieve, {
1775
- operationId
1776
- });
1777
- if (!operation) {
1778
- return [new CapxulError({
1779
- code: "NOT_FOUND",
1780
- message: `operation ${operationId} not found`
1781
- }), null];
1782
- }
1783
- return [null, operation];
1784
- } catch (cause) {
1785
- return [fromConvexError(cause), null];
1786
- }
1787
- };
1788
- return {
1789
- retrieve,
1790
- wait: async (operationId, input = {}) => {
1791
- if (!config._data) {
1792
- return stub("operations.wait");
1793
- }
1794
- const until = new Set(
1795
- input.until ?? ["succeeded", "failed", "canceled", "indexed"]
1796
- );
1797
- const timeoutMs = (input.timeoutSeconds ?? 60) * 1e3;
1798
- const pollIntervalMs = input.pollIntervalMs ?? 1e3;
1799
- const deadline = Date.now() + timeoutMs;
1800
- while (Date.now() <= deadline) {
1801
- const [error, operation] = await retrieve(operationId);
1802
- if (error) {
1803
- return [error, null];
1804
- }
1805
- if (until.has(operation.status)) {
1806
- return [null, operation];
1807
- }
1808
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1809
- }
1810
- return [new CapxulError({
1811
- code: "OPERATION_TIMEOUT",
1812
- message: `operation ${operationId} did not reach a terminal state before the timeout`,
1813
- details: {
1814
- operationId,
1815
- timeoutSeconds: input.timeoutSeconds ?? 60,
1816
- pollIntervalMs
1817
- }
1818
- }), null];
1819
- }
1820
- };
1821
- }
1822
- function toTokenUnits(value, decimals = 6) {
1823
- return parseUnits(value, decimals);
1824
- }
1825
-
1826
- // src/core/token-registry.ts
1827
- function resolvePaymentToken(currency) {
1828
- const normalized = currency.trim().toUpperCase();
1829
- if (normalized === "USD" || normalized === "USDC") {
1830
- return {
1831
- address: TEST_USDC_ADDRESS.toLowerCase(),
1832
- decimals: 6,
1833
- symbol: "USDC"
1834
- };
1835
- }
1836
- throw new CapxulError({
1837
- code: "NOT_IMPLEMENTED",
1838
- message: `Currency ${currency} is not yet supported by the token registry.`,
1839
- details: { currency: normalized }
1840
- });
1841
- }
1842
- function createCapxulBundler(config) {
1843
- const paymaster = createPaymasterClient({
1844
- transport: http(config.rpcUrl)
1845
- });
1846
- return createBundlerClient({
1847
- chain: baseSepolia,
1848
- transport: http(config.rpcUrl),
1849
- paymaster,
1850
- paymasterContext: { policyId: config.gasPolicyId }
1851
- });
1852
- }
1853
- var CAPXUL_PAYMENTS_SEND_ABI = [
1854
- {
1855
- name: "send",
1856
- type: "function",
1857
- stateMutability: "nonpayable",
1858
- inputs: [
1859
- { name: "token", type: "address" },
1860
- { name: "recipient", type: "address" },
1861
- { name: "amount", type: "uint256" },
1862
- { name: "documentHash", type: "bytes32" },
1863
- { name: "paymentType", type: "uint8" }
1864
- ],
1865
- outputs: []
1866
- }
1867
- ];
1868
- var ERC20_APPROVE_ABI = [
1869
- {
1870
- name: "approve",
1871
- type: "function",
1872
- stateMutability: "nonpayable",
1873
- inputs: [
1874
- { name: "spender", type: "address" },
1875
- { name: "amount", type: "uint256" }
1876
- ],
1877
- outputs: [{ name: "", type: "bool" }]
1878
- }
1879
- ];
1880
- function encodeOwnerTransferCalls(params) {
1881
- const documentHash = params.documentHash ?? "0x" + "0".repeat(64);
1882
- const paymentType = params.paymentType ?? 0;
1883
- const approve = encodeFunctionData({
1884
- abi: ERC20_APPROVE_ABI,
1885
- functionName: "approve",
1886
- args: [CAPXUL_PAYMENTS_ADDRESS, params.amount]
1887
- });
1888
- const send = encodeFunctionData({
1889
- abi: CAPXUL_PAYMENTS_SEND_ABI,
1890
- functionName: "send",
1891
- args: [
1892
- params.tokenAddress,
1893
- params.recipientAddress,
1894
- params.amount,
1895
- documentHash,
1896
- paymentType
1897
- ]
1898
- });
1899
- return [
1900
- {
1901
- to: params.tokenAddress.toLowerCase(),
1902
- data: approve,
1903
- value: 0n
1904
- },
1905
- {
1906
- to: CAPXUL_PAYMENTS_ADDRESS,
1907
- data: send,
1908
- value: 0n
1909
- }
1910
- ];
1911
- }
1912
-
1913
- // src/internal/safe/operations.ts
1914
- var USER_OP_RECEIPT_TIMEOUT_MS = 12e4;
1915
- async function transferAsOwner(config, params) {
1916
- try {
1917
- const safeAccount = await buildSafeAccount(config.signer, config.signing);
1918
- const bundler = createCapxulBundler(config.signing);
1919
- const calls = encodeOwnerTransferCalls(params);
1920
- const userOpHash = await bundler.sendUserOperation({
1921
- account: safeAccount,
1922
- calls
1923
- });
1924
- const receipt = await bundler.waitForUserOperationReceipt({
1925
- hash: userOpHash,
1926
- timeout: USER_OP_RECEIPT_TIMEOUT_MS
1927
- });
1928
- return {
1929
- txHash: receipt.receipt.transactionHash,
1930
- userOpHash,
1931
- blockNumber: Number(receipt.receipt.blockNumber),
1932
- success: receipt.success,
1933
- logs: receipt.receipt.logs
1934
- };
1935
- } catch (cause) {
1936
- throw new CapxulError({
1937
- code: "NETWORK_ERROR",
1938
- message: cause instanceof Error ? cause.message : String(cause),
1939
- cause
1940
- });
1941
- }
1942
- }
1943
-
1944
- // src/core/payments.ts
1945
- function createPaymentsClient(config = {}) {
1946
- return {
1947
- create: async (input) => {
1948
- if (!config._data || !config.signer || !config.signing) {
1949
- return stub("payments.create");
1950
- }
1951
- let created = null;
1952
- let submitted = null;
1953
- try {
1954
- created = await config._data.mutation(api.payments.mutations.create, {
1955
- to: input.to,
1956
- amount: input.amount,
1957
- reference: input.reference,
1958
- idempotencyKey: input.idempotencyKey,
1959
- source: input.source
1960
- });
1961
- if (!created) {
1962
- return [
1963
- new CapxulError({
1964
- code: "NETWORK_ERROR",
1965
- message: "payments.create returned no payment resource"
1966
- }),
1967
- null
1968
- ];
1969
- }
1970
- if (created.status !== "processing" || created.operation.status !== "processing") {
1971
- return [null, created];
1972
- }
1973
- const currentSigner = await config._data.query(
1974
- api.safe.queries.getMySignerAddress,
1975
- {}
1976
- );
1977
- if (!currentSigner?.address) {
1978
- throw new CapxulError({
1979
- code: "PERMISSION_DENIED",
1980
- message: "No signer is registered for the authenticated account.",
1981
- details: { paymentId: created.id }
1982
- });
1983
- }
1984
- if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1985
- throw new CapxulError({
1986
- code: "PERMISSION_DENIED",
1987
- message: "Configured signer does not match the authenticated account signer.",
1988
- details: {
1989
- paymentId: created.id,
1990
- expectedSignerAddress: currentSigner.address,
1991
- actualSignerAddress: config.signer.address
1992
- }
1993
- });
1994
- }
1995
- const submission = await config._data.query(
1996
- api.payments.queries.prepareSubmission,
1997
- {
1998
- paymentId: created.id
1999
- }
2000
- );
2001
- if (!submission?.recipientAddress) {
2002
- throw new CapxulError({
2003
- code: "NETWORK_ERROR",
2004
- message: "payments.prepareSubmission returned no recipient address.",
2005
- details: { paymentId: created.id }
2006
- });
2007
- }
2008
- const token = resolvePaymentToken(submission.amount.currency);
2009
- const transfer = await transferAsOwner(
2010
- {
2011
- signer: config.signer,
2012
- signing: config.signing
2013
- },
2014
- {
2015
- tokenAddress: token.address,
2016
- recipientAddress: submission.recipientAddress,
2017
- amount: toTokenUnits(submission.amount.value, token.decimals)
2018
- }
2019
- );
2020
- if (!transfer.success) {
2021
- throw new CapxulError({
2022
- code: "NETWORK_ERROR",
2023
- message: "Bundler submission did not succeed.",
2024
- details: {
2025
- paymentId: created.id,
2026
- txHash: transfer.txHash,
2027
- userOpHash: transfer.userOpHash
2028
- }
2029
- });
2030
- }
2031
- submitted = {
2032
- txHash: transfer.txHash,
2033
- userOpHash: transfer.userOpHash
2034
- };
2035
- await config._data.mutation(api.payments.mutations.recordSubmitted, {
2036
- paymentId: created.id,
2037
- txHash: transfer.txHash,
2038
- userOpHash: transfer.userOpHash,
2039
- source: "sdk"
2040
- });
2041
- return [null, created];
2042
- } catch (cause) {
2043
- const error = mapCreateError(fromConvexError(cause));
2044
- if (created?.id && created.status === "processing" && !submitted) {
2045
- await bestEffortMarkFailed({ _data: config._data }, created.id, error);
2046
- }
2047
- if (submitted && created?.id) {
2048
- return [
2049
- new CapxulError({
2050
- code: "NETWORK_ERROR",
2051
- message: "Payment was submitted on-chain, but backend submission tracking failed.",
2052
- cause,
2053
- details: {
2054
- paymentId: created.id,
2055
- txHash: submitted.txHash,
2056
- userOpHash: submitted.userOpHash
2057
- }
2058
- }),
2059
- null
2060
- ];
2061
- }
2062
- return [error, null];
2063
- }
2064
- },
2065
- retrieve: async (paymentId) => {
2066
- if (!config._data) {
2067
- return stub("payments.retrieve");
2068
- }
2069
- try {
2070
- const payment = await config._data.query(
2071
- api.payments.queries.retrieve,
2072
- {
2073
- paymentId
2074
- }
2075
- );
2076
- if (!payment) {
2077
- return [
2078
- new CapxulError({
2079
- code: "NOT_FOUND",
2080
- message: `payment ${paymentId} not found`
2081
- }),
2082
- null
2083
- ];
2084
- }
2085
- return [null, payment];
2086
- } catch (cause) {
2087
- return [fromConvexError(cause), null];
2088
- }
2089
- },
2090
- list: async (input) => {
2091
- if (!config._data) {
2092
- return stub("payments.list");
2093
- }
2094
- try {
2095
- const page = await config._data.query(api.payments.queries.list, {
2096
- limit: input?.limit,
2097
- cursor: input?.cursor
2098
- });
2099
- return [null, page];
2100
- } catch (cause) {
2101
- return [fromConvexError(cause), null];
2102
- }
2103
- }
2104
- };
2105
- }
2106
- function createOrgPaymentsClient() {
2107
- return {
2108
- create: async () => stub(
2109
- "organizations.payments.create"
2110
- ),
2111
- retrieve: async () => stub("organizations.payments.retrieve"),
2112
- list: async () => stub("organizations.payments.list")
2113
- };
2114
- }
2115
- async function bestEffortMarkFailed(config, paymentId, error) {
2116
- try {
2117
- await config._data.mutation(api.payments.mutations.markFailed, {
2118
- paymentId,
2119
- errorCode: error.code,
2120
- errorMessage: error.message,
2121
- source: "sdk"
2122
- });
2123
- } catch {
2124
- }
2125
- }
2126
- function mapCreateError(error) {
2127
- switch (error.code) {
2128
- case "NOT_AUTHENTICATED":
2129
- case "PERMISSION_DENIED":
2130
- case "INVALID_INPUT":
2131
- case "INVALID_RECIPIENT":
2132
- case "INSUFFICIENT_BALANCE":
2133
- case "IDEMPOTENCY_CONFLICT":
2134
- case "RATE_LIMITED":
2135
- case "NETWORK_ERROR":
2136
- return error;
2137
- default:
2138
- return new CapxulError({
2139
- code: "NETWORK_ERROR",
2140
- message: error.message,
2141
- cause: error,
2142
- details: error.details,
2143
- operationId: error.operationId,
2144
- correlationId: error.correlationId,
2145
- retryable: error.retryable
2146
- });
2147
- }
2148
- }
2149
-
2150
- // src/core/transfers.ts
2151
- function createTransfersClient() {
2152
- return {
2153
- create: async () => stub("transfers.create"),
2154
- retrieve: async () => stub("transfers.retrieve"),
2155
- list: async () => stub("transfers.list"),
2156
- confirm: async () => stub("transfers.confirm"),
2157
- cancel: async () => stub("transfers.cancel")
2158
- };
2159
- }
2160
- function createOrgTransfersClient() {
2161
- return {
2162
- create: async () => stub(
2163
- "organizations.transfers.create"
2164
- ),
2165
- retrieve: async () => stub("organizations.transfers.retrieve"),
2166
- list: async () => stub("organizations.transfers.list"),
2167
- confirm: async () => stub("organizations.transfers.confirm"),
2168
- cancel: async () => stub("organizations.transfers.cancel")
2169
- };
2170
- }
2171
- function createWithdrawalsClient(config = {}) {
2172
- return {
2173
- create: async (input) => {
2174
- if (!config._data) {
2175
- return stub("withdrawals.create");
2176
- }
2177
- const [createErr, createdRaw] = await tryCatch(
2178
- config._data.mutation(api.withdrawals.mutations.create, {
2179
- amount: input.amount,
2180
- destination: {
2181
- externalAccountId: input.destination.externalAccountId
2182
- },
2183
- source: input.source,
2184
- reference: input.reference,
2185
- idempotencyKey: input.idempotencyKey
2186
- })
2187
- );
2188
- if (createErr) {
2189
- return [mapCreateError2(fromConvexError(createErr)), null];
2190
- }
2191
- const created = createdRaw;
2192
- if (!created) {
2193
- return [
2194
- new CapxulError({
2195
- code: "NETWORK_ERROR",
2196
- message: "withdrawals.create returned no withdrawal resource"
2197
- }),
2198
- null
2199
- ];
2200
- }
2201
- if (created.status !== "processing" || created.operation.status !== "processing") {
2202
- return [null, created];
2203
- }
2204
- if (!config.signer || !config.signing) {
2205
- return [null, created];
2206
- }
2207
- const [signerErr, currentSigner] = await tryCatch(
2208
- config._data.query(api.safe.queries.getMySignerAddress, {})
2209
- );
2210
- if (signerErr) {
2211
- return await handleSubmissionFailure(
2212
- { _data: config._data },
2213
- created.id,
2214
- mapCreateError2(fromConvexError(signerErr))
2215
- );
2216
- }
2217
- if (!currentSigner?.address) {
2218
- return await handleSubmissionFailure(
2219
- { _data: config._data },
2220
- created.id,
2221
- new CapxulError({
2222
- code: "PERMISSION_DENIED",
2223
- message: "No signer is registered for the authenticated account.",
2224
- details: { withdrawalId: created.id }
2225
- })
2226
- );
2227
- }
2228
- if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
2229
- return await handleSubmissionFailure(
2230
- { _data: config._data },
2231
- created.id,
2232
- new CapxulError({
2233
- code: "PERMISSION_DENIED",
2234
- message: "Configured signer does not match the authenticated account signer.",
2235
- details: {
2236
- withdrawalId: created.id,
2237
- expectedSignerAddress: currentSigner.address,
2238
- actualSignerAddress: config.signer.address
2239
- }
2240
- })
2241
- );
2242
- }
2243
- const [prepErr, submission] = await tryCatch(
2244
- config._data.query(api.withdrawals.queries.prepareSubmission, {
2245
- withdrawalId: created.id
2246
- })
2247
- );
2248
- if (prepErr) {
2249
- return await handleSubmissionFailure(
2250
- { _data: config._data },
2251
- created.id,
2252
- mapCreateError2(fromConvexError(prepErr))
2253
- );
2254
- }
2255
- const destinationAddress = submission?.destinationAddress;
2256
- if (!submission || !destinationAddress) {
2257
- return await handleSubmissionFailure(
2258
- { _data: config._data },
2259
- created.id,
2260
- new CapxulError({
2261
- code: "NETWORK_ERROR",
2262
- message: "withdrawals.prepareSubmission returned no destination.",
2263
- details: { withdrawalId: created.id }
2264
- })
2265
- );
2266
- }
2267
- const token = resolvePaymentToken(submission.amount.currency);
2268
- const [transferErr, transferOk] = await tryCatch(
2269
- transferAsOwner(
2270
- {
2271
- signer: config.signer,
2272
- signing: config.signing
2273
- },
2274
- {
2275
- tokenAddress: token.address,
2276
- recipientAddress: destinationAddress,
2277
- amount: toTokenUnits(submission.amount.value, token.decimals)
2278
- }
2279
- )
2280
- );
2281
- if (transferErr) {
2282
- return await handleSubmissionFailure(
2283
- { _data: config._data },
2284
- created.id,
2285
- mapCreateError2(fromConvexError(transferErr))
2286
- );
2287
- }
2288
- if (!transferOk.success) {
2289
- return await handleSubmissionFailure(
2290
- { _data: config._data },
2291
- created.id,
2292
- new CapxulError({
2293
- code: "NETWORK_ERROR",
2294
- message: "Bundler submission did not succeed.",
2295
- details: {
2296
- withdrawalId: created.id,
2297
- txHash: transferOk.txHash,
2298
- userOpHash: transferOk.userOpHash
2299
- }
2300
- })
2301
- );
2302
- }
2303
- const [recordErr] = await tryCatch(
2304
- config._data.mutation(api.withdrawals.mutations.recordSubmitted, {
2305
- withdrawalId: created.id,
2306
- txHash: transferOk.txHash,
2307
- userOpHash: transferOk.userOpHash
2308
- })
2309
- );
2310
- if (recordErr) {
2311
- return [
2312
- new CapxulError({
2313
- code: "NETWORK_ERROR",
2314
- message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
2315
- cause: recordErr,
2316
- details: {
2317
- withdrawalId: created.id,
2318
- txHash: transferOk.txHash,
2319
- userOpHash: transferOk.userOpHash
2320
- }
2321
- }),
2322
- null
2323
- ];
2324
- }
2325
- return [null, created];
2326
- },
2327
- retrieve: async (withdrawalId) => {
2328
- if (!config._data) {
2329
- return stub("withdrawals.retrieve");
2330
- }
2331
- const [err, raw] = await tryCatch(
2332
- config._data.query(api.withdrawals.queries.retrieve, { withdrawalId })
2333
- );
2334
- if (err) {
2335
- return [fromConvexError(err), null];
2336
- }
2337
- const withdrawal = raw;
2338
- if (!withdrawal) {
2339
- return [
2340
- new CapxulError({
2341
- code: "NOT_FOUND",
2342
- message: `withdrawal ${withdrawalId} not found`
2343
- }),
2344
- null
2345
- ];
2346
- }
2347
- return [null, withdrawal];
2348
- },
2349
- list: async (input) => {
2350
- if (!config._data) {
2351
- return stub("withdrawals.list");
2352
- }
2353
- const [err, raw] = await tryCatch(
2354
- config._data.query(api.withdrawals.queries.list, {
2355
- limit: input?.limit,
2356
- cursor: input?.cursor
2357
- })
2358
- );
2359
- if (err) {
2360
- return [fromConvexError(err), null];
2361
- }
2362
- return [null, raw];
2363
- },
2364
- recordCompleted: async (input) => {
2365
- if (!config._data) {
2366
- return stub(
2367
- "withdrawals.recordCompleted"
2368
- );
2369
- }
2370
- const [err] = await tryCatch(
2371
- config._data.mutation(api.withdrawals.mutations.recordCompleted, {
2372
- withdrawalId: input.withdrawalId,
2373
- txHash: input.txHash
2374
- })
2375
- );
2376
- if (err) {
2377
- return [
2378
- mapRecordCompletedError(fromConvexError(err)),
2379
- null
2380
- ];
2381
- }
2382
- return [null, null];
2383
- }
2384
- };
2385
- }
2386
- function createOrgWithdrawalsClient(config = {}) {
2387
- return {
2388
- /**
2389
- * Org-scope create (Withdrawals v1 W2, #465).
2390
- *
2391
- * D6 — returns the `processing` row only. No `transferAsOwner`
2392
- * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
2393
- * orchestration ships in W3+.
2394
- */
2395
- create: async (input) => {
2396
- if (!config._data) {
2397
- return stub(
2398
- "organizations.withdrawals.create"
2399
- );
2400
- }
2401
- const [err, raw] = await tryCatch(
2402
- config._data.mutation(api.withdrawals.mutations.createOrg, {
2403
- organizationId: input.organizationId,
2404
- amount: input.amount,
2405
- destination: {
2406
- externalAccountId: input.destination.externalAccountId
2407
- },
2408
- source: input.source,
2409
- reference: input.reference,
2410
- idempotencyKey: input.idempotencyKey
2411
- })
2412
- );
2413
- if (err) {
2414
- return [mapCreateError2(fromConvexError(err)), null];
2415
- }
2416
- const created = raw;
2417
- if (!created) {
2418
- return [
2419
- new CapxulError({
2420
- code: "NETWORK_ERROR",
2421
- message: "organizations.withdrawals.create returned no withdrawal resource"
2422
- }),
2423
- null
2424
- ];
2425
- }
2426
- return [null, created];
2427
- },
2428
- retrieve: async (input) => {
2429
- if (!config._data) {
2430
- return stub(
2431
- "organizations.withdrawals.retrieve"
2432
- );
2433
- }
2434
- const [err, raw] = await tryCatch(
2435
- config._data.query(api.withdrawals.queries.retrieve, {
2436
- withdrawalId: input.withdrawalId
2437
- })
2438
- );
2439
- if (err) {
2440
- return [fromConvexError(err), null];
2441
- }
2442
- const withdrawal = raw;
2443
- if (!withdrawal) {
2444
- return [
2445
- new CapxulError({
2446
- code: "NOT_FOUND",
2447
- message: `withdrawal ${input.withdrawalId} not found`
2448
- }),
2449
- null
2450
- ];
2451
- }
2452
- const ownerCheck = withdrawal.owner;
2453
- if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
2454
- return [
2455
- new CapxulError({
2456
- code: "NOT_FOUND",
2457
- message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
2458
- }),
2459
- null
2460
- ];
2461
- }
2462
- return [null, withdrawal];
2463
- },
2464
- list: async (input) => {
2465
- if (!config._data) {
2466
- return stub(
2467
- "organizations.withdrawals.list"
2468
- );
2469
- }
2470
- const [err, raw] = await tryCatch(
2471
- config._data.query(api.withdrawals.queries.listOrg, {
2472
- organizationId: input.organizationId,
2473
- limit: input.limit,
2474
- cursor: input.cursor
2475
- })
2476
- );
2477
- if (err) {
2478
- return [fromConvexError(err), null];
2479
- }
2480
- return [null, raw];
2481
- }
2482
- };
2483
- }
2484
- async function handleSubmissionFailure(config, withdrawalId, error) {
2485
- await bestEffortMarkFailed2(config, withdrawalId, error);
2486
- return [error, null];
2487
- }
2488
- async function bestEffortMarkFailed2(config, withdrawalId, error) {
2489
- await tryCatch(
2490
- config._data.mutation(api.withdrawals.mutations.markFailed, {
2491
- withdrawalId,
2492
- errorCode: error.code,
2493
- errorMessage: error.message
2494
- })
2495
- );
2496
- }
2497
- function mapCreateError2(error) {
2498
- switch (error.code) {
2499
- case "NOT_AUTHENTICATED":
2500
- case "PERMISSION_DENIED":
2501
- case "INVALID_INPUT":
2502
- case "INSUFFICIENT_BALANCE":
2503
- case "IDEMPOTENCY_CONFLICT":
2504
- case "KYC_REQUIRED":
2505
- case "POLICY_DENIED":
2506
- case "RATE_LIMITED":
2507
- case "NETWORK_ERROR":
2508
- case "NOT_FOUND":
2509
- case "VERIFICATION_REQUIRED":
2510
- return error;
2511
- default:
2512
- return new CapxulError({
2513
- code: "NETWORK_ERROR",
2514
- message: error.message,
2515
- cause: error,
2516
- details: error.details,
2517
- operationId: error.operationId,
2518
- correlationId: error.correlationId,
2519
- retryable: error.retryable
2520
- });
2521
- }
2522
- }
2523
- function mapRecordCompletedError(error) {
2524
- switch (error.code) {
2525
- case "NOT_AUTHENTICATED":
2526
- case "PERMISSION_DENIED":
2527
- case "INVALID_INPUT":
2528
- case "NOT_FOUND":
2529
- case "NETWORK_ERROR":
2530
- case "INTERNAL_ERROR":
2531
- return error;
2532
- default:
2533
- return new CapxulError({
2534
- code: "NETWORK_ERROR",
2535
- message: error.message,
2536
- cause: error,
2537
- details: error.details,
2538
- operationId: error.operationId,
2539
- correlationId: error.correlationId,
2540
- retryable: error.retryable
2541
- });
2542
- }
2543
- }
2544
-
2545
- // src/core/webhook-endpoints.ts
2546
- function createWebhookEndpointsClient() {
2547
- return {
2548
- create: async () => stub(
2549
- "webhookEndpoints.create"
2550
- ),
2551
- retrieve: async () => stub("webhookEndpoints.retrieve"),
2552
- list: async () => stub("webhookEndpoints.list"),
2553
- remove: async () => stub("webhookEndpoints.remove")
2554
- };
2555
- }
2556
-
2557
- // src/core/webhook-events.ts
2558
- function createWebhookEventsClient() {
2559
- return {
2560
- retrieve: async () => stub("webhookEvents.retrieve"),
2561
- list: async () => stub("webhookEvents.list")
2562
- };
2563
- }
2564
-
2565
- // src/core/organizations.ts
2566
- function createOrgExternalAccountsClient(config) {
2567
- return {
2568
- create: async (input) => {
2569
- if (!config._data) {
2570
- return stub(
2571
- "organizations.externalAccounts.create"
2572
- );
2573
- }
2574
- const [err, raw] = await tryCatch(
2575
- config._data.mutation(api.externalAccounts.mutations.createOrg, {
2576
- organizationId: input.organizationId,
2577
- kind: input.kind,
2578
- label: input.label,
2579
- address: input.address,
2580
- iban: input.iban,
2581
- bic: input.bic,
2582
- accountHolder: input.accountHolder,
2583
- network: input.network,
2584
- panToken: input.panToken,
2585
- last4: input.last4
2586
- })
2587
- );
2588
- if (err) {
2589
- return [fromConvexError(err), null];
2590
- }
2591
- if (!raw) {
2592
- return [
2593
- new CapxulError({
2594
- code: "NOT_FOUND",
2595
- message: "external_account creation returned no resource"
2596
- }),
2597
- null
2598
- ];
2599
- }
2600
- return [null, brandExternalAccount(raw)];
2601
- },
2602
- list: async (input) => {
2603
- if (!config._data) {
2604
- return stub(
2605
- "organizations.externalAccounts.list"
2606
- );
2607
- }
2608
- const [err, result] = await tryCatch(
2609
- config._data.query(api.externalAccounts.queries.listOrg, {
2610
- organizationId: input.organizationId,
2611
- limit: input.limit,
2612
- cursor: input.cursor
2613
- })
2614
- );
2615
- if (err) {
2616
- return [fromConvexError(err), null];
2617
- }
2618
- const branded = result.data.map(
2619
- (row) => brandExternalAccount(row)
2620
- );
2621
- return [
2622
- null,
2623
- {
2624
- object: "list",
2625
- data: branded,
2626
- page: result.page
2627
- }
2628
- ];
2629
- },
2630
- retrieve: async (input) => {
2631
- if (!config._data) {
2632
- return stub(
2633
- "organizations.externalAccounts.retrieve"
2634
- );
2635
- }
2636
- const [err, raw] = await tryCatch(
2637
- config._data.query(api.externalAccounts.queries.retrieveOrg, {
2638
- organizationId: input.organizationId,
2639
- externalAccountId: input.externalAccountId
2640
- })
2641
- );
2642
- if (err) {
2643
- return [fromConvexError(err), null];
2644
- }
2645
- if (!raw) {
2646
- return [
2647
- new CapxulError({
2648
- code: "NOT_FOUND",
2649
- message: `external_account ${input.externalAccountId} not found`
2650
- }),
2651
- null
2652
- ];
2653
- }
2654
- return [null, brandExternalAccount(raw)];
2655
- },
2656
- remove: async (input) => {
2657
- if (!config._data) {
2658
- return stub("organizations.externalAccounts.remove");
2659
- }
2660
- const [err] = await tryCatch(
2661
- config._data.mutation(api.externalAccounts.mutations.removeOrg, {
2662
- organizationId: input.organizationId,
2663
- externalAccountId: input.externalAccountId
2664
- })
2665
- );
2666
- if (err) {
2667
- return [fromConvexError(err), null];
2668
- }
2669
- return [null, void 0];
2670
- }
2671
- };
2672
- }
2673
- function createOrgSubAccountsClient(config) {
2674
- return {
2675
- create: async (input) => {
2676
- if (!config._data) {
2677
- return stub(
2678
- "organizations.subAccounts.create"
2679
- );
2680
- }
2681
- const [err, raw] = await tryCatch(
2682
- config._data.mutation(api.subAccounts.mutations.create, {
2683
- parent: {
2684
- kind: "organization",
2685
- id: input.organizationId
2686
- },
2687
- name: input.name,
2688
- purpose: input.purpose
2689
- })
2690
- );
2691
- if (err) {
2692
- return [
2693
- fromConvexError(err),
2694
- null
2695
- ];
2696
- }
2697
- if (!raw) {
2698
- return [
2699
- new CapxulError({
2700
- code: "NOT_FOUND",
2701
- message: "sub_account creation returned no resource"
2702
- }),
2703
- null
2704
- ];
2705
- }
2706
- const [brandErr, branded] = tryBrandSubAccount(raw);
2707
- if (brandErr) {
2708
- return [brandErr, null];
2709
- }
2710
- return [null, branded];
2711
- },
2712
- list: async (input) => {
2713
- if (!config._data) {
2714
- return stub(
2715
- "organizations.subAccounts.list"
2716
- );
2717
- }
2718
- const [err, rows] = await tryCatch(
2719
- config._data.query(api.subAccounts.queries.listByOrganization, {
2720
- organizationId: input.organizationId
2721
- })
2722
- );
2723
- if (err) {
2724
- return [
2725
- fromConvexError(err),
2726
- null
2727
- ];
2728
- }
2729
- const branded = [];
2730
- for (const row of rows) {
2731
- const [brandErr, value] = tryBrandSubAccount(row);
2732
- if (brandErr) {
2733
- return [brandErr, null];
2734
- }
2735
- branded.push(value);
2736
- }
2737
- return [
2738
- null,
2739
- {
2740
- object: "list",
2741
- data: branded,
2742
- page: { hasMore: false }
2743
- }
2744
- ];
2745
- },
2746
- retrieve: async (input) => {
2747
- if (!config._data) {
2748
- return stub(
2749
- "organizations.subAccounts.retrieve"
2750
- );
2751
- }
2752
- const [err, raw] = await tryCatch(
2753
- config._data.query(api.subAccounts.queries.retrieve, {
2754
- subAccountId: input.subAccountId
2755
- })
2756
- );
2757
- if (err) {
2758
- return [
2759
- fromConvexError(err),
2760
- null
2761
- ];
2762
- }
2763
- if (!raw) {
2764
- return [
2765
- new CapxulError({
2766
- code: "NOT_FOUND",
2767
- message: `sub_account ${input.subAccountId} not found`
2768
- }),
2769
- null
2770
- ];
2771
- }
2772
- const [brandErr, branded] = tryBrandSubAccount(raw);
2773
- if (brandErr) {
2774
- return [
2775
- brandErr,
2776
- null
2777
- ];
2778
- }
2779
- return [null, branded];
2780
- },
2781
- remove: async (input) => {
2782
- if (!config._data) {
2783
- return stub(
2784
- "organizations.subAccounts.remove"
2785
- );
2786
- }
2787
- const [err, raw] = await tryCatch(
2788
- config._data.mutation(api.subAccounts.mutations.archive, {
2789
- subAccountId: input.subAccountId
2790
- })
2791
- );
2792
- if (err) {
2793
- return [
2794
- fromConvexError(err),
2795
- null
2796
- ];
2797
- }
2798
- if (!raw) {
2799
- return [
2800
- new CapxulError({
2801
- code: "NOT_FOUND",
2802
- message: `sub_account ${input.subAccountId} not found`
2803
- }),
2804
- null
2805
- ];
2806
- }
2807
- const [brandErr, branded] = tryBrandSubAccount(raw);
2808
- if (brandErr) {
2809
- return [
2810
- brandErr,
2811
- null
2812
- ];
2813
- }
2814
- return [null, branded];
2815
- }
2816
- };
2817
- }
2818
- function createOrganizationsClient(config = {}) {
2819
- return {
2820
- create: async (input) => {
2821
- if (!config._data) {
2822
- return stub("organizations.create");
2823
- }
2824
- if (input.country !== void 0) {
2825
- return [
2826
- new CapxulError({
2827
- code: "INVALID_INPUT",
2828
- message: "organizations.create does not support country yet \u2014 backend mutation only accepts name.",
2829
- details: { field: "country" }
2830
- }),
2831
- null
2832
- ];
2833
- }
2834
- try {
2835
- const orgId = await config._data.mutation(api.org.mutations.create, {
2836
- name: input.name
2837
- });
2838
- const org = await config._data.query(api.org.queries.retrieve, {
2839
- orgId
2840
- });
2841
- if (!org) {
2842
- return [
2843
- new CapxulError({
2844
- code: "NETWORK_ERROR",
2845
- message: "organization created but could not be retrieved"
2846
- }),
2847
- null
2848
- ];
2849
- }
2850
- return [null, org];
2851
- } catch (cause) {
2852
- return [fromConvexError(cause), null];
2853
- }
2854
- },
2855
- retrieve: async (organizationId) => {
2856
- if (!config._data) {
2857
- return stub("organizations.retrieve");
2858
- }
2859
- try {
2860
- const orgId = organizationId.replace(/^org_/, "");
2861
- const org = await config._data.query(api.org.queries.retrieve, {
2862
- orgId
2863
- });
2864
- if (!org) {
2865
- return [
2866
- new CapxulError({
2867
- code: "NOT_FOUND",
2868
- message: `organization ${organizationId} not found`
2869
- }),
2870
- null
2871
- ];
2872
- }
2873
- return [null, org];
2874
- } catch (cause) {
2875
- return [fromConvexError(cause), null];
2876
- }
2877
- },
2878
- list: async (input) => {
2879
- if (!config._data) {
2880
- return stub("organizations.list");
2881
- }
2882
- try {
2883
- const page = await config._data.query(api.org.queries.list, {
2884
- limit: input?.limit,
2885
- cursor: input?.cursor
2886
- });
2887
- const result = {
2888
- object: "list",
2889
- data: page.data,
2890
- page: {
2891
- hasMore: page.hasMore,
2892
- cursor: page.nextCursor
2893
- }
2894
- };
2895
- return [null, result];
2896
- } catch (cause) {
2897
- return [fromConvexError(cause), null];
2898
- }
2899
- },
2900
- update: async (input) => {
2901
- if (!config._data) {
2902
- return stub("organizations.update");
2903
- }
2904
- try {
2905
- const orgId = input.organizationId.replace(/^org_/, "");
2906
- const org = await config._data.mutation(api.org.mutations.update, {
2907
- orgId,
2908
- name: input.name
2909
- });
2910
- if (!org) {
2911
- return [
2912
- new CapxulError({
2913
- code: "NOT_FOUND",
2914
- message: `organization ${input.organizationId} not found`
2915
- }),
2916
- null
2917
- ];
2918
- }
2919
- return [null, org];
2920
- } catch (cause) {
2921
- return [fromConvexError(cause), null];
2922
- }
2923
- },
2924
- safes: {
2925
- retrieve: async (input) => {
2926
- if (!config._data) {
2927
- return stub("organizations.safes.retrieve");
2928
- }
2929
- try {
2930
- const safe = await config._data.query(
2931
- api.safe.queries.retrieveOrganizationSafe,
2932
- input
2933
- );
2934
- if (!safe) {
2935
- return [
2936
- new CapxulError({
2937
- code: "NOT_FOUND",
2938
- message: `safe ${input.safeId} not found`
2939
- }),
2940
- null
2941
- ];
2942
- }
2943
- return [null, safe];
2944
- } catch (cause) {
2945
- return [
2946
- fromConvexError(cause),
2947
- null
2948
- ];
2949
- }
2950
- }
2951
- },
2952
- treasury: {
2953
- retrieve: async (organizationId) => {
2954
- if (!config._data) {
2955
- return stub(
2956
- "organizations.treasury.retrieve"
2957
- );
2958
- }
2959
- try {
2960
- const orgId = organizationId.replace(/^org_/, "");
2961
- const raw = await config._data.query(
2962
- api.safe.queries.getOrgTreasuryBalance,
2963
- { orgId }
2964
- );
2965
- if (!raw) {
2966
- return [
2967
- new CapxulError({
2968
- code: "NOT_FOUND",
2969
- message: `treasury for organization ${organizationId} not found`
2970
- }),
2971
- null
2972
- ];
2973
- }
2974
- const treasury = {
2975
- object: "treasury",
2976
- id: toTreasuryId(`try_${orgId}`),
2977
- organizationId,
2978
- status: "active",
2979
- safeId: toSafeId(
2980
- `safe_${raw.safeAddress.replace(/^0x/, "").toLowerCase()}`
2981
- ),
2982
- totalBalance: { value: "0", currency: "USD" },
2983
- positions: raw.tokens.map((t) => ({
2984
- symbol: t.symbol,
2985
- contractAddress: t.tokenAddress,
2986
- amount: t.balance
2987
- })),
2988
- asOf: raw.lastUpdatedAt ? new Date(raw.lastUpdatedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
2989
- };
2990
- return [null, treasury];
2991
- } catch (cause) {
2992
- return [
2993
- fromConvexError(cause),
2994
- null
2995
- ];
2996
- }
2997
- }
2998
- },
2999
- members: {
3000
- list: async (input) => {
3001
- if (!config._data?.action) {
3002
- return stub("organizations.members.list");
3003
- }
3004
- try {
3005
- const orgId = input.organizationId.replace(/^org_/, "");
3006
- const page = await config._data.action(api.org.actions.membersList, {
3007
- organizationId: orgId,
3008
- status: input.status,
3009
- limit: input.limit,
3010
- cursor: input.cursor
3011
- });
3012
- return [null, page];
3013
- } catch (cause) {
3014
- return [fromConvexError(cause), null];
3015
- }
3016
- },
3017
- retrieve: async (input) => {
3018
- if (!config._data?.action) {
3019
- return stub("organizations.members.retrieve");
3020
- }
3021
- try {
3022
- const orgId = input.organizationId.replace(/^org_/, "");
3023
- const memberId = input.memberId.replace(/^mb_/, "");
3024
- const member = await config._data.action(
3025
- api.org.actions.retrieveMember,
3026
- {
3027
- organizationId: orgId,
3028
- memberId
3029
- }
3030
- );
3031
- return [null, member];
3032
- } catch (cause) {
3033
- return [fromConvexError(cause), null];
3034
- }
3035
- },
3036
- invite: async (input) => {
3037
- if (!config._data?.action) {
3038
- return stub("organizations.members.invite");
3039
- }
3040
- try {
3041
- const orgId = input.organizationId.replace(/^org_/, "");
3042
- const result = await config._data.action(
3043
- api.org.actions.inviteMember,
3044
- {
3045
- organizationId: orgId,
3046
- email: input.email,
3047
- role: input.role
3048
- }
3049
- );
3050
- return [null, result];
3051
- } catch (cause) {
3052
- return [fromConvexError(cause), null];
3053
- }
3054
- },
3055
- accept: async (input) => {
3056
- if (!config._data?.action) {
3057
- return stub("organizations.members.accept");
3058
- }
3059
- try {
3060
- const member = await config._data.action(
3061
- api.org.actions.acceptInvitation,
3062
- { token: input.token }
3063
- );
3064
- return [null, member];
3065
- } catch (cause) {
3066
- return [fromConvexError(cause), null];
3067
- }
3068
- },
3069
- updateRole: async (input) => {
3070
- if (!config._data?.action) {
3071
- return stub("organizations.members.updateRole");
3072
- }
3073
- try {
3074
- const orgId = input.organizationId.replace(/^org_/, "");
3075
- const memberId = input.memberId.replace(/^mb_/, "");
3076
- const member = await config._data.action(
3077
- api.org.actions.updateMemberRole,
3078
- {
3079
- organizationId: orgId,
3080
- memberId,
3081
- role: input.role
3082
- }
3083
- );
3084
- return [null, member];
3085
- } catch (cause) {
3086
- return [fromConvexError(cause), null];
3087
- }
3088
- },
3089
- revoke: async (input) => {
3090
- if (!config._data?.action) {
3091
- return stub("organizations.members.revoke");
3092
- }
3093
- try {
3094
- const orgId = input.organizationId.replace(/^org_/, "");
3095
- const memberId = input.memberId.replace(/^mb_/, "");
3096
- const member = await config._data.action(
3097
- api.org.actions.revokeMember,
3098
- {
3099
- organizationId: orgId,
3100
- memberId
3101
- }
3102
- );
3103
- return [null, member];
3104
- } catch (cause) {
3105
- return [fromConvexError(cause), null];
3106
- }
3107
- },
3108
- remove: async (input) => {
3109
- if (!config._data?.action) {
3110
- return stub("organizations.members.remove");
3111
- }
3112
- try {
3113
- const orgId = input.organizationId.replace(/^org_/, "");
3114
- const memberId = input.memberId.replace(/^mb_/, "");
3115
- await config._data.action(api.org.actions.removeMember, {
3116
- organizationId: orgId,
3117
- memberId
3118
- });
3119
- return [null, void 0];
3120
- } catch (cause) {
3121
- return [fromConvexError(cause), null];
3122
- }
3123
- },
3124
- resend: async (input) => {
3125
- if (!config._data?.action) {
3126
- return stub("organizations.members.resend");
3127
- }
3128
- try {
3129
- const orgId = input.organizationId.replace(/^org_/, "");
3130
- const memberId = input.memberId.replace(/^mb_/, "");
3131
- const result = await config._data.action(
3132
- api.org.actions.resendInvitation,
3133
- {
3134
- organizationId: orgId,
3135
- memberId
3136
- }
3137
- );
3138
- return [null, result];
3139
- } catch (cause) {
3140
- return [fromConvexError(cause), null];
3141
- }
3142
- }
3143
- },
3144
- apiKeys: createApiKeysClient(),
3145
- subAccounts: createOrgSubAccountsClient(config),
3146
- externalAccounts: createOrgExternalAccountsClient(config),
3147
- balanceLedger: {
3148
- list: async (input) => {
3149
- if (!config._data) {
3150
- return stub(
3151
- "organizations.balanceLedger.list"
3152
- );
3153
- }
3154
- try {
3155
- const orgId = input.organizationId.replace(/^org_/, "");
3156
- const page = await config._data.query(
3157
- api.balanceLedger.queries.listForOrg,
3158
- { orgId, limit: input.limit, cursor: input.cursor }
3159
- );
3160
- return [null, page];
3161
- } catch (cause) {
3162
- return [fromConvexError(cause), null];
3163
- }
3164
- },
3165
- retrieve: async (input) => {
3166
- if (!config._data) {
3167
- return stub(
3168
- "organizations.balanceLedger.retrieve"
3169
- );
3170
- }
3171
- try {
3172
- const entry = await config._data.query(
3173
- api.balanceLedger.queries.retrieve,
3174
- { entryId: input.entryId }
3175
- );
3176
- if (!entry) {
3177
- return [
3178
- new CapxulError({
3179
- code: "NOT_FOUND",
3180
- message: `balance_ledger_entry ${input.entryId} not found`
3181
- }),
3182
- null
3183
- ];
3184
- }
3185
- return [null, entry];
3186
- } catch (cause) {
3187
- return [fromConvexError(cause), null];
3188
- }
3189
- }
3190
- },
3191
- payments: createOrgPaymentsClient(),
3192
- transfers: createOrgTransfersClient(),
3193
- withdrawals: createOrgWithdrawalsClient(config),
3194
- documents: createOrgDocumentsClient(),
3195
- webhookEndpoints: createWebhookEndpointsClient(),
3196
- webhookEvents: createWebhookEventsClient()
3197
- };
3198
- }
3199
-
3200
- // src/core/token-transfers.ts
3201
- var toTokenTransferId = (raw) => {
3202
- if (typeof raw !== "string" || raw.length === 0) {
3203
- throw new Error(
3204
- `Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
3205
- );
3206
- }
3207
- return raw;
3208
- };
3209
- function brandRow(row) {
3210
- return {
3211
- ...row,
3212
- id: toTokenTransferId(row.id)
3213
- };
3214
- }
3215
- function createTokenTransfersClient(config = {}) {
3216
- return {
3217
- list: async (input) => {
3218
- if (!config._data) {
3219
- return stub("tokenTransfers.list");
3220
- }
3221
- try {
3222
- const raw = await config._data.query(
3223
- api.tokenTransfers.queries.list,
3224
- {
3225
- limit: input?.limit,
3226
- cursor: input?.cursor,
3227
- direction: input?.direction
3228
- }
3229
- );
3230
- if (!raw) {
3231
- return [
3232
- new CapxulError({
3233
- code: "NOT_AUTHENTICATED",
3234
- message: "tokenTransfers.list requires an authenticated session."
3235
- }),
3236
- null
3237
- ];
3238
- }
3239
- return [
3240
- null,
3241
- {
3242
- object: "list",
3243
- data: raw.items.map(brandRow),
3244
- page: {
3245
- hasMore: raw.hasMore,
3246
- nextCursor: raw.nextCursor
3247
- },
3248
- displayCurrency: raw.displayCurrency
3249
- }
3250
- ];
3251
- } catch (cause) {
3252
- return [fromConvexError(cause), null];
3253
- }
3254
- },
3255
- retrieve: async (input) => {
3256
- if (!config._data) {
3257
- return stub("tokenTransfers.retrieve");
3258
- }
3259
- try {
3260
- const raw = await config._data.query(
3261
- api.tokenTransfers.queries.getByTxLogIndex,
3262
- {
3263
- txHash: input.txHash,
3264
- logIndex: input.logIndex,
3265
- chainId: input.chainId
3266
- }
3267
- );
3268
- if (!raw) {
3269
- return [
3270
- new CapxulError({
3271
- code: "NOT_FOUND",
3272
- message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
3273
- details: {
3274
- txHash: input.txHash,
3275
- logIndex: input.logIndex,
3276
- chainId: input.chainId
3277
- }
3278
- }),
3279
- null
3280
- ];
3281
- }
3282
- return [null, brandRow(raw)];
3283
- } catch (cause) {
3284
- return [fromConvexError(cause), null];
3285
- }
3286
- }
3287
- };
3288
- }
3289
-
3290
- // src/core/virtual-accounts.ts
3291
- function createVirtualAccountsClient() {
3292
- return {
3293
- create: async () => stub("virtualAccounts.create"),
3294
- retrieve: async () => stub("virtualAccounts.retrieve"),
3295
- list: async () => stub("virtualAccounts.list"),
3296
- remove: async () => stub("virtualAccounts.remove")
3297
- };
3298
- }
3299
-
3300
- // src/core/virtual-cards.ts
3301
- function createVirtualCardsClient() {
3302
- return {
3303
- create: async () => stub("virtualCards.create"),
3304
- retrieve: async () => stub("virtualCards.retrieve"),
3305
- list: async () => stub("virtualCards.list"),
3306
- freeze: async () => stub("virtualCards.freeze"),
3307
- unfreeze: async () => stub("virtualCards.unfreeze"),
3308
- cancel: async () => stub("virtualCards.cancel")
3309
- };
3310
- }
3311
- function createAuthFlowMachine(client) {
3312
- return setup({
3313
- types: {},
3314
- actors: {
3315
- // XState v5's `fromPromise` injects an `AbortSignal` that aborts
3316
- // when the actor is stopped (parent transition fires, machine is
3317
- // disposed, etc.). Plumbing it through `client.auth.sendOtp` /
3318
- // `client.auth.verifyOtp` makes the in-flight HTTP request
3319
- // cancellable: stale responses can't race a state machine
3320
- // that's already moved on. See PR #406 S5.
3321
- sendOtp: fromPromise(async ({ input, signal }) => {
3322
- const [error] = await client.auth.sendOtp(
3323
- { email: input.email },
3324
- { signal }
3325
- );
3326
- if (error) throw error;
3327
- }),
3328
- verifyOtp: fromPromise(
3329
- async ({ input, signal }) => {
3330
- const [error, result] = await client.auth.verifyOtp(
3331
- {
3332
- email: input.email,
3333
- otp: input.code
3334
- },
3335
- { signal }
3336
- );
3337
- if (error) throw error;
3338
- if (result.kind === "bootstrap_required") {
3339
- throw new CapxulError({
3340
- code: "ACTION_REQUIRED",
3341
- message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
3342
- details: { reason: result.reason }
3343
- });
3344
- }
3345
- return result.session;
3346
- }
3347
- ),
3348
- signOut: fromPromise(async () => {
3349
- const [error] = await client.auth.signOut();
3350
- if (error) throw error;
3351
- })
3352
- },
3353
- actions: {
3354
- trackOtpRequested: ({ context }) => {
3355
- if (!context.email) return;
3356
- track("auth_otp_requested", {
3357
- email_domain: emailDomain(context.email)
3358
- });
3359
- },
3360
- trackOtpFailed: ({ event }) => {
3361
- const error = errorFromEvent(event);
3362
- track("auth_failed", {
3363
- auth_type: "email_otp",
3364
- reason: error.code
3365
- });
3366
- },
3367
- trackTimeoutFailed: () => {
3368
- track("auth_failed", {
3369
- auth_type: "email_otp",
3370
- reason: "timeout"
3371
- });
3372
- },
3373
- trackVerified: () => {
3374
- track("auth_verified", { auth_type: "email_otp" });
3375
- },
3376
- identifyAndTrack: ({ context }) => {
3377
- if (!context.session) return;
3378
- identify(context.session.authUserId, {
3379
- email_domain: emailDomain(context.session.email)
3380
- });
3381
- track("auth_identified", {
3382
- email_domain: emailDomain(context.session.email)
3383
- });
3384
- },
3385
- trackSignedOut: () => {
3386
- track("auth_signed_out");
3387
- }
3388
- }
3389
- }).createMachine({
3390
- id: "auth",
3391
- initial: "idle",
3392
- context: { email: null, session: null, error: null },
3393
- states: {
3394
- idle: {
3395
- on: {
3396
- REQUEST_OTP: {
3397
- target: "sending_otp",
3398
- actions: assign({
3399
- email: ({ event }) => event.email,
3400
- error: () => null
3401
- })
3402
- }
3403
- }
3404
- },
3405
- sending_otp: {
3406
- invoke: {
3407
- src: "sendOtp",
3408
- input: ({ context }) => ({ email: requireEmail(context) }),
3409
- onDone: {
3410
- target: "otp_requested",
3411
- actions: ["trackOtpRequested"]
3412
- },
3413
- onError: {
3414
- target: "error",
3415
- actions: [
3416
- assign({ error: ({ event }) => errorFromEvent(event) }),
3417
- "trackOtpFailed"
3418
- ]
3419
- }
3420
- },
3421
- after: {
3422
- [FLOW_INVOKE_TIMEOUT_MS]: {
3423
- target: "error",
3424
- actions: [
3425
- assign({
3426
- error: () => timeoutError("sending_otp")
3427
- }),
3428
- "trackTimeoutFailed"
3429
- ]
3430
- }
3431
- }
3432
- },
3433
- otp_requested: {
3434
- on: {
3435
- VERIFY: { target: "verifying" },
3436
- RESET: {
3437
- target: "idle",
3438
- actions: assign({ email: () => null, error: () => null })
3439
- }
3440
- }
3441
- },
3442
- verifying: {
3443
- invoke: {
3444
- src: "verifyOtp",
3445
- input: ({ context, event }) => ({
3446
- email: requireEmail(context),
3447
- code: requireCodeFromEvent(event)
3448
- }),
3449
- onDone: {
3450
- target: "authenticated",
3451
- actions: [
3452
- // Scrub the duplicate `context.email` (input value
3453
- // captured during sendOtp) since the verified
3454
- // `session.email` is now the canonical source
3455
- // post-authentication. The session's email is
3456
- // intentionally retained — it's the auth result, not
3457
- // lingering input. See PR #406 S2.
3458
- assign({
3459
- session: ({ event }) => event.output,
3460
- email: () => null
3461
- }),
3462
- "trackVerified",
3463
- "identifyAndTrack"
3464
- ]
3465
- },
3466
- onError: {
3467
- target: "error",
3468
- actions: [
3469
- assign({ error: ({ event }) => errorFromEvent(event) }),
3470
- "trackOtpFailed"
3471
- ]
3472
- }
3473
- },
3474
- after: {
3475
- [FLOW_INVOKE_TIMEOUT_MS]: {
3476
- target: "error",
3477
- actions: [
3478
- assign({
3479
- error: () => timeoutError("verifying")
3480
- }),
3481
- "trackTimeoutFailed"
3482
- ]
3483
- }
3484
- }
3485
- },
3486
- authenticated: {
3487
- on: {
3488
- SIGN_OUT: { target: "signing_out" }
3489
- }
3490
- },
3491
- signing_out: {
3492
- invoke: {
3493
- src: "signOut",
3494
- onDone: {
3495
- target: "idle",
3496
- actions: [
3497
- assign({
3498
- session: () => null,
3499
- email: () => null,
3500
- error: () => null
3501
- }),
3502
- "trackSignedOut"
3503
- ]
3504
- },
3505
- onError: {
3506
- target: "error",
3507
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
3508
- }
3509
- }
3510
- },
3511
- error: {
3512
- on: {
3513
- RESET: {
3514
- target: "idle",
3515
- actions: assign({ error: () => null })
3516
- }
3517
- }
3518
- }
3519
- }
3520
- });
3521
- }
3522
- function requireEmail(context) {
3523
- if (!context.email) {
3524
- throw Errors.invalidInput(
3525
- "email",
3526
- "Auth flow advanced without an email captured in context."
3527
- );
3528
- }
3529
- return context.email;
3530
- }
3531
- function requireCodeFromEvent(event) {
3532
- if (event.type !== "VERIFY") {
3533
- throw Errors.invalidInput(
3534
- "code",
3535
- `Auth flow's verifying state requires a VERIFY event, got ${event.type}.`
3536
- );
3537
- }
3538
- return event.code;
3539
- }
3540
- function errorFromEvent(event) {
3541
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3542
- if (cause instanceof CapxulError) {
3543
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3544
- return new CapxulError({
3545
- code: cause.code,
3546
- message: redactEmail(cause.message),
3547
- cause,
3548
- details: cause.details,
3549
- operationId: cause.operationId,
3550
- correlationId: cause.correlationId,
3551
- retryable: cause.retryable
3552
- });
3553
- }
3554
- if (cause instanceof CapxulError2) {
3555
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3556
- return new CapxulError2(cause.code, redactEmail(cause.message), {
3557
- cause,
3558
- details: cause.details,
3559
- correlationId: cause.correlationId,
3560
- layer: cause.layer
3561
- });
3562
- }
3563
- return Errors.providerError("auth", "flow", redactCauseEmail(cause));
3564
- }
3565
- var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
3566
- var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
3567
- function redactEmail(message) {
3568
- return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
3569
- }
3570
- function redactCauseEmail(cause) {
3571
- if (cause instanceof Error) {
3572
- if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
3573
- const redacted = new Error(redactEmail(cause.message));
3574
- redacted.cause = cause;
3575
- return redacted;
3576
- }
3577
- if (typeof cause === "string") {
3578
- return redactEmail(cause);
3579
- }
3580
- return cause;
3581
- }
3582
- function timeoutError(state) {
3583
- return Errors.providerError(
3584
- "auth",
3585
- "flow",
3586
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3587
- );
3588
- }
3589
- function emailDomain(email) {
3590
- const domain = email.split("@")[1]?.trim().toLowerCase();
3591
- return domain || "unknown";
3592
- }
3593
- var initialContext = {
3594
- email: null,
3595
- code: null,
3596
- username: null,
3597
- bootstrapToken: null,
3598
- bootstrapReason: null,
3599
- session: null,
3600
- account: null,
3601
- safe: null,
3602
- error: null
3603
- };
3604
- function createAuthBootstrapFlowMachine(client) {
3605
- return setup({
3606
- types: {},
3607
- actors: {
3608
- sendOtp: fromPromise(async ({ input, signal }) => {
3609
- const [error] = await client.auth.sendOtp(
3610
- { email: input.email },
3611
- { signal }
3612
- );
3613
- if (error) throw error;
3614
- }),
3615
- verifyOtp: fromPromise(
3616
- async ({ input, signal }) => {
3617
- const [error, result] = await client.auth.verifyOtp(
3618
- { email: input.email, otp: input.code },
3619
- { signal }
3620
- );
3621
- if (error) throw error;
3622
- return result;
3623
- }
3624
- ),
3625
- completeBootstrap: fromPromise(async ({ input }) => {
3626
- const [error, result] = await client.auth.completeBootstrap(input);
3627
- if (error) throw error;
3628
- return result;
3629
- }),
3630
- signOut: fromPromise(async () => {
3631
- const [error] = await client.auth.signOut();
3632
- if (error) throw error;
3633
- })
3634
- },
3635
- actions: {
3636
- trackOtpRequested: ({ context }) => {
3637
- if (!context.email) return;
3638
- track("auth_otp_requested", {
3639
- email_domain: emailDomain2(context.email)
3640
- });
3641
- },
3642
- trackFailed: ({ event }) => {
3643
- track("auth_failed", {
3644
- auth_type: "email_otp",
3645
- reason: errorFromEvent2(event).code
3646
- });
3647
- },
3648
- trackTimeoutFailed: () => {
3649
- track("auth_failed", {
3650
- auth_type: "email_otp",
3651
- reason: "timeout"
3652
- });
3653
- },
3654
- trackVerified: () => {
3655
- track("auth_verified", { auth_type: "email_otp" });
3656
- },
3657
- trackBootstrapRequired: ({ context }) => {
3658
- track("auth_verified", {
3659
- auth_type: "email_otp",
3660
- auth_mode: context.bootstrapReason ?? "bootstrap_required"
3661
- });
3662
- },
3663
- identifyAndTrack: ({ context }) => {
3664
- if (!context.session) return;
3665
- identify(context.session.authUserId, {
3666
- email_domain: emailDomain2(context.session.email)
3667
- });
3668
- track("auth_identified", {
3669
- email_domain: emailDomain2(context.session.email)
3670
- });
3671
- },
3672
- trackSignedOut: () => {
3673
- track("auth_signed_out");
3674
- }
3675
- }
3676
- }).createMachine({
3677
- id: "authBootstrap",
3678
- initial: "email",
3679
- context: initialContext,
3680
- states: {
3681
- email: {
3682
- on: {
3683
- ENTER_EMAIL: {
3684
- actions: assign({
3685
- email: ({ event }) => event.email,
3686
- error: () => null
3687
- })
3688
- },
3689
- REQUEST_OTP: { target: "sending_otp" }
3690
- }
3691
- },
3692
- sending_otp: {
3693
- invoke: {
3694
- src: "sendOtp",
3695
- input: ({ context }) => ({ email: requireEmail2(context) }),
3696
- onDone: { target: "otp_requested", actions: "trackOtpRequested" },
3697
- onError: {
3698
- target: "otp_requested",
3699
- actions: [
3700
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3701
- "trackFailed"
3702
- ]
3703
- }
3704
- },
3705
- after: {
3706
- [FLOW_INVOKE_TIMEOUT_MS]: {
3707
- target: "otp_requested",
3708
- actions: [
3709
- assign({ error: () => timeoutError2("sending_otp") }),
3710
- "trackTimeoutFailed"
3711
- ]
3712
- }
3713
- }
3714
- },
3715
- otp_requested: {
3716
- on: {
3717
- ENTER_OTP: {
3718
- actions: assign({
3719
- code: ({ event }) => event.code,
3720
- error: () => null
3721
- })
3722
- },
3723
- VERIFY_OTP: { target: "verifying_otp" },
3724
- BACK: { target: "email" },
3725
- RESET: { target: "email", actions: assign(() => initialContext) }
3726
- }
3727
- },
3728
- verifying_otp: {
3729
- invoke: {
3730
- src: "verifyOtp",
3731
- input: ({ context }) => ({
3732
- email: requireEmail2(context),
3733
- code: requireCode(context)
3734
- }),
3735
- onDone: [
3736
- {
3737
- guard: ({ event }) => event.output.kind === "existing_member",
3738
- target: "authenticated",
3739
- actions: [
3740
- assign({
3741
- session: ({ event }) => event.output.session,
3742
- account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
3743
- username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
3744
- safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
3745
- email: () => null,
3746
- error: () => null
3747
- }),
3748
- "trackVerified",
3749
- "identifyAndTrack"
3750
- ]
3751
- },
3752
- {
3753
- target: "bootstrap_required",
3754
- actions: [
3755
- assign({
3756
- session: ({ event }) => event.output.session,
3757
- bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
3758
- bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
3759
- username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
3760
- email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
3761
- error: () => null
3762
- }),
3763
- "trackVerified",
3764
- "trackBootstrapRequired"
3765
- ]
3766
- }
3767
- ],
3768
- onError: {
3769
- target: "otp_requested",
3770
- actions: [
3771
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3772
- "trackFailed"
3773
- ]
3774
- }
3775
- },
3776
- after: {
3777
- [FLOW_INVOKE_TIMEOUT_MS]: {
3778
- target: "otp_requested",
3779
- actions: [
3780
- assign({ error: () => timeoutError2("verifying_otp") }),
3781
- "trackTimeoutFailed"
3782
- ]
3783
- }
3784
- }
3785
- },
3786
- bootstrap_required: {
3787
- on: {
3788
- ENTER_USERNAME: {
3789
- actions: assign({
3790
- username: ({ event }) => event.username,
3791
- error: () => null
3792
- })
3793
- },
3794
- COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3795
- BACK: { target: "otp_requested" },
3796
- RESET: { target: "email", actions: assign(() => initialContext) }
3797
- }
3798
- },
3799
- completing_bootstrap: {
3800
- invoke: {
3801
- src: "completeBootstrap",
3802
- input: ({ context }) => ({
3803
- bootstrapToken: requireBootstrapToken(context),
3804
- username: requireUsername(context)
3805
- }),
3806
- onDone: {
3807
- target: "authenticated",
3808
- actions: [
3809
- assign({
3810
- session: ({ event }) => event.output.session,
3811
- account: ({ event }) => event.output.account,
3812
- username: ({ event }) => event.output.username,
3813
- safe: ({ event }) => event.output.safe,
3814
- bootstrapToken: () => null,
3815
- bootstrapReason: () => null,
3816
- email: () => null,
3817
- error: () => null
3818
- }),
3819
- "identifyAndTrack"
3820
- ]
3821
- },
3822
- onError: {
3823
- target: "bootstrap_required",
3824
- actions: [
3825
- assign({ error: ({ event }) => errorFromEvent2(event) }),
3826
- "trackFailed"
3827
- ]
3828
- }
3829
- },
3830
- after: {
3831
- [FLOW_INVOKE_TIMEOUT_MS]: {
3832
- target: "bootstrap_required",
3833
- actions: [
3834
- assign({ error: () => timeoutError2("completing_bootstrap") }),
3835
- "trackTimeoutFailed"
3836
- ]
3837
- }
3838
- }
3839
- },
3840
- authenticated: {
3841
- on: {
3842
- SIGN_OUT: { target: "signing_out" }
3843
- }
3844
- },
3845
- signing_out: {
3846
- invoke: {
3847
- src: "signOut",
3848
- onDone: {
3849
- target: "email",
3850
- actions: [
3851
- assign(() => initialContext),
3852
- "trackSignedOut"
3853
- ]
3854
- },
3855
- onError: {
3856
- target: "error",
3857
- actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3858
- }
3859
- }
3860
- },
3861
- error: {
3862
- on: {
3863
- RESET: { target: "email", actions: assign(() => initialContext) }
3864
- }
3865
- }
3866
- }
3867
- });
3868
- }
3869
- function requireEmail2(context) {
3870
- if (!context.email) {
3871
- throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3872
- }
3873
- return context.email;
3874
- }
3875
- function requireCode(context) {
3876
- if (!context.code) {
3877
- throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3878
- }
3879
- return context.code;
3880
- }
3881
- function requireBootstrapToken(context) {
3882
- if (!context.bootstrapToken) {
3883
- throw Errors.invalidInput(
3884
- "bootstrapToken",
3885
- "Auth bootstrap requires a continuation token."
3886
- );
3887
- }
3888
- return context.bootstrapToken;
3889
- }
3890
- function requireUsername(context) {
3891
- if (!context.username) {
3892
- throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3893
- }
3894
- return context.username;
3895
- }
3896
- function errorFromEvent2(event) {
3897
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3898
- if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3899
- return cause;
3900
- }
3901
- return Errors.providerError("auth", "bootstrap", cause);
3902
- }
3903
- function timeoutError2(state) {
3904
- return Errors.providerError(
3905
- "auth",
3906
- "bootstrap",
3907
- new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3908
- );
3909
- }
3910
- function emailDomain2(email) {
3911
- const domain = email.split("@")[1]?.trim().toLowerCase();
3912
- return domain || "unknown";
3913
- }
3914
- function createProvisioningMachine(client) {
3915
- return setup({
3916
- types: {},
3917
- actors: {
3918
- provisionPersonal: fromPromise(async ({ input }) => {
3919
- const [error, account] = await client.accounts.provisionPersonal(
3920
- input.input
3921
- );
3922
- if (error) throw error;
3923
- return account;
3924
- })
3925
- },
3926
- guards: {
3927
- hasInput: ({ context }) => context.input !== null
3928
- },
3929
- actions: {
3930
- trackWalletCreated: ({ context }) => {
3931
- const provider = context.input?.signerProvider;
3932
- if (!provider) return;
3933
- track("provisioning_wallet_created", {
3934
- eoa_address: provider.signerAddress
3935
- });
3936
- },
3937
- trackSafeCreated: ({ context }) => {
3938
- const provider = context.input?.signerProvider;
3939
- if (!provider) return;
3940
- track("provisioning_safe_created", {
3941
- safe_address: deriveSafeAddress(provider.signerAddress)
3942
- });
3943
- }
3944
- }
3945
- }).createMachine({
3946
- id: "provisioning",
3947
- initial: "starting",
3948
- context: ({ input }) => ({
3949
- input: input?.input ?? null,
3950
- account: null,
3951
- error: null
3952
- }),
3953
- states: {
3954
- // Transient routing state: skip `idle` when input was provided
3955
- // at creation time (the invoked-by-parent path).
3956
- starting: {
3957
- always: [
3958
- { guard: "hasInput", target: "running" },
3959
- { target: "idle" }
3960
- ]
3961
- },
3962
- idle: {
3963
- on: {
3964
- START: {
3965
- target: "running",
3966
- actions: assign({
3967
- input: ({ event }) => event.input,
3968
- account: () => null,
3969
- error: () => null
3970
- })
3971
- }
3972
- }
3973
- },
3974
- running: {
3975
- invoke: {
3976
- src: "provisionPersonal",
3977
- input: ({ context }) => ({
3978
- input: requireProvisionInput(context)
3979
- }),
3980
- onDone: {
3981
- target: "done",
3982
- actions: assign({ account: ({ event }) => event.output })
3983
- },
3984
- onError: {
3985
- target: "error",
3986
- actions: assign({ error: ({ event }) => errorFromEvent3(event) })
3987
- }
3988
- },
3989
- after: {
3990
- [FLOW_INVOKE_TIMEOUT_MS]: {
3991
- target: "error",
3992
- actions: assign({ error: () => timeoutError3() })
3993
- }
3994
- }
3995
- },
3996
- done: {
3997
- type: "final",
3998
- entry: ["trackWalletCreated", "trackSafeCreated"]
3999
- },
4000
- error: {
4001
- type: "final"
4002
- }
4003
- },
4004
- /**
4005
- * Root-level output mapper — fires when the machine reaches any
4006
- * top-level `final` state (`done` or `error`). The parent receives
4007
- * this payload on its `onDone` transition and branches via guards
4008
- * on `event.output.error`.
4009
- */
4010
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
4011
- });
4012
- }
4013
- function requireProvisionInput(context) {
4014
- if (!context.input) {
4015
- throw Errors.invalidInput(
4016
- "input",
4017
- "Provisioning flow advanced to running without input captured in context."
4018
- );
4019
- }
4020
- return context.input;
4021
- }
4022
- function errorFromEvent3(event) {
4023
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4024
- if (cause instanceof CapxulError) return cause;
4025
- if (cause instanceof CapxulError2) return cause;
4026
- return Errors.providerError("provisioning", "flow", cause);
4027
- }
4028
- function timeoutError3() {
4029
- return Errors.providerError(
4030
- "provisioning",
4031
- "flow",
4032
- new Error(`timeout: running exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
4033
- );
4034
- }
4035
-
4036
- // src/flows/onboarding.ts
4037
- function createOnboardingFlowMachine(client) {
4038
- const provisioningMachine = createProvisioningMachine(client);
4039
- return setup({
4040
- types: {},
4041
- actors: {
4042
- provisioningMachine
4043
- },
4044
- guards: {
4045
- isOrg: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind !== "account",
4046
- missingSigner: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind === "account" && !event.input.signerProvider,
4047
- childReportedError: ({ event }) => {
4048
- if (typeof event !== "object" || event === null || !("output" in event)) {
4049
- return false;
4050
- }
4051
- const output = event.output;
4052
- return typeof output === "object" && output !== null && "error" in output && output.error != null;
4053
- }
4054
- },
4055
- actions: {
4056
- // Always-on for any START_PROVISIONING transition.
4057
- assignOperationIdAndInput: assign({
4058
- operationId: () => generateOperationId(),
4059
- input: ({ event }) => event.type === "START_PROVISIONING" ? event.input : null,
4060
- error: () => null,
4061
- account: () => null
4062
- }),
4063
- // Pre-actor rejection branches stamp the synchronous error.
4064
- assignOrgNotImplemented: assign({
4065
- error: () => new CapxulError({
4066
- code: "NOT_IMPLEMENTED",
4067
- message: "useOnboardingFlow organization onboarding is not implemented in this local-private-key slice."
4068
- })
4069
- }),
4070
- assignMissingSigner: assign({
4071
- error: () => new CapxulError({
4072
- code: "INVALID_INPUT",
4073
- message: "useOnboardingFlow requires a signerProvider for account onboarding."
4074
- })
4075
- }),
4076
- // Telemetry actions — order matters; the source fires the
4077
- // "submitted" track BEFORE the rejection track on validation
4078
- // errors, and the happy path follows the documented sequence.
4079
- trackOrgSubmitted: ({ event }) => {
4080
- if (event.type !== "START_PROVISIONING") return;
4081
- track("onboarding_org_submitted", {
4082
- country: event.input.country ?? "unknown"
4083
- });
4084
- },
4085
- trackPersonalSubmitted: ({ event }) => {
4086
- if (event.type !== "START_PROVISIONING") return;
4087
- track("onboarding_personal_submitted", {
4088
- country: event.input.country ?? "unknown",
4089
- wallet_count: event.input.signerProvider ? 1 : 0
4090
- });
4091
- },
4092
- trackOrgNotImplementedError: () => {
4093
- track("onboarding_wallet_error", {
4094
- step: "profile",
4095
- reason: "organization_not_implemented"
4096
- });
4097
- },
4098
- trackMissingSignerError: () => {
4099
- track("onboarding_wallet_error", {
4100
- step: "profile",
4101
- reason: "missing_signer_provider"
4102
- });
4103
- },
4104
- trackWalletCreating: () => {
4105
- track("onboarding_wallet_creating");
4106
- },
4107
- trackOnboardingCompleted: () => {
4108
- track("onboarding_completed", { account_type: "personal" });
4109
- },
4110
- trackProvisioningFailed: ({ context }) => {
4111
- const code = context.error?.code ?? "UNKNOWN";
4112
- track("onboarding_wallet_error", {
4113
- step: "provision_personal",
4114
- reason: code
4115
- });
4116
- },
4117
- assignChildReportedError: assign({
4118
- error: ({ event }) => extractChildErrorOrFallback(event)
4119
- }),
4120
- assignChildThrown: assign({
4121
- error: ({ event }) => errorFromEvent4(event)
4122
- }),
4123
- assignAccountFromChild: assign({
4124
- account: ({ event }) => extractChildAccountOrNull(event)
4125
- }),
4126
- resetContext: assign({
4127
- input: () => null,
4128
- operationId: () => null,
4129
- account: () => null,
4130
- error: () => null
4131
- })
4132
- }
4133
- }).createMachine({
4134
- id: "onboarding",
4135
- initial: "profile",
4136
- context: {
4137
- input: null,
4138
- operationId: null,
4139
- account: null,
4140
- error: null
4141
- },
4142
- states: {
4143
- profile: {
4144
- on: {
4145
- START_PROVISIONING: [
4146
- {
4147
- guard: "isOrg",
4148
- target: "error",
4149
- actions: [
4150
- "assignOperationIdAndInput",
4151
- "trackOrgSubmitted",
4152
- "assignOrgNotImplemented",
4153
- "trackOrgNotImplementedError"
4154
- ]
4155
- },
4156
- {
4157
- guard: "missingSigner",
4158
- target: "error",
4159
- actions: [
4160
- "assignOperationIdAndInput",
4161
- "trackPersonalSubmitted",
4162
- "assignMissingSigner",
4163
- "trackMissingSignerError"
4164
- ]
4165
- },
4166
- {
4167
- target: "provisioning",
4168
- actions: [
4169
- "assignOperationIdAndInput",
4170
- "trackPersonalSubmitted"
4171
- ]
4172
- }
4173
- ]
4174
- }
4175
- },
4176
- provisioning: {
4177
- entry: ["trackWalletCreating"],
4178
- invoke: {
4179
- src: "provisioningMachine",
4180
- input: ({ context }) => ({
4181
- input: requireProvisionInput2(context)
4182
- }),
4183
- // The child machine reaches a top-level `final` state for
4184
- // both success and failure, so the parent's `onDone` fires
4185
- // in both cases. We branch via a guard on
4186
- // `event.output.error`. The child machine has its own
4187
- // `FLOW_INVOKE_TIMEOUT_MS` timer that ends in a final
4188
- // `error` state on timeout — that signal flows back through
4189
- // `onDone` + the `childReportedError` guard. The previous
4190
- // duplicate parent `after: FLOW_INVOKE_TIMEOUT_MS` was
4191
- // removed in PR #406 (X1+G3) so the child's `output.error`
4192
- // is the single source of provisioning failure.
4193
- onDone: [
4194
- {
4195
- guard: "childReportedError",
4196
- target: "error",
4197
- actions: [
4198
- "assignChildReportedError",
4199
- "trackProvisioningFailed"
4200
- ]
4201
- },
4202
- {
4203
- target: "complete",
4204
- actions: [
4205
- "assignAccountFromChild",
4206
- "trackOnboardingCompleted"
4207
- ]
4208
- }
4209
- ],
4210
- // `onError` is the safety net for an unexpected throw from
4211
- // inside the child machine itself (not the spawned actor's
4212
- // `error` final state, which goes through `onDone`). In
4213
- // normal flow this never fires.
4214
- onError: {
4215
- target: "error",
4216
- actions: ["assignChildThrown", "trackProvisioningFailed"]
4217
- }
4218
- }
4219
- },
4220
- // TODO(stack-1): wire `action_required` once the KYC gate /
4221
- // async-resume `NextAction` path is lifted from
4222
- // `useOperation(operationId)`. PROCEED → provisioning re-enters
4223
- // the actor with the resumed input. Currently unreachable from
4224
- // any transition; declared for parity with the public type.
4225
- action_required: {
4226
- on: {
4227
- PROCEED: { target: "provisioning" },
4228
- RESET: { target: "profile", actions: "resetContext" }
4229
- }
4230
- },
4231
- complete: {
4232
- on: {
4233
- RESET: { target: "profile", actions: "resetContext" }
4234
- }
4235
- },
4236
- error: {
4237
- on: {
4238
- RESET: { target: "profile", actions: "resetContext" }
4239
- }
4240
- }
4241
- }
4242
- });
4243
- }
4244
- function generateOperationId() {
4245
- return toOperationId(`op_onboarding_local_${Date.now().toString(36)}`);
4246
- }
4247
- function requireProvisionInput2(context) {
4248
- if (!context.input || !context.input.signerProvider) {
4249
- throw Errors.invalidInput(
4250
- "signerProvider",
4251
- "Onboarding flow advanced to provisioning without a signerProvider in context."
4252
- );
4253
- }
4254
- return {
4255
- displayName: context.input.displayName,
4256
- username: context.input.username,
4257
- countryCode: context.input.country,
4258
- signerProvider: context.input.signerProvider
4259
- };
4260
- }
4261
- function extractChildOutput(event) {
4262
- if (typeof event !== "object" || event === null || !("output" in event)) {
4263
- return null;
4264
- }
4265
- const output = event.output;
4266
- if (typeof output !== "object" || output === null) return null;
4267
- return output;
4268
- }
4269
- function extractChildErrorOrFallback(event) {
4270
- const output = extractChildOutput(event);
4271
- if (output && "error" in output && output.error) return output.error;
4272
- return Errors.providerError(
4273
- "onboarding",
4274
- "flow",
4275
- new Error("provisioning child reported error without payload")
4276
- );
4277
- }
4278
- function extractChildAccountOrNull(event) {
4279
- const output = extractChildOutput(event);
4280
- if (output && "account" in output && output.account) return output.account;
4281
- return null;
4282
- }
4283
- function errorFromEvent4(event) {
4284
- const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
4285
- if (cause instanceof CapxulError) return cause;
4286
- if (cause instanceof CapxulError2) return cause;
4287
- return Errors.providerError("onboarding", "flow", cause);
4288
- }
4289
-
4290
- // src/client.ts
4291
- function createCapxulClient(config = {}) {
4292
- const clientWithoutFlows = {
4293
- id: crypto.randomUUID(),
4294
- auth: createAuthClient(config),
4295
- me: createMeClient(config),
4296
- accounts: createAccountsClient(config),
4297
- organizations: createOrganizationsClient(config),
4298
- payments: createPaymentsClient(config),
4299
- transfers: createTransfersClient(),
4300
- tokenTransfers: createTokenTransfersClient(config),
4301
- withdrawals: createWithdrawalsClient(config),
4302
- documents: createDocumentsClient(),
4303
- subAccounts: createSubAccountsClient(config),
4304
- virtualAccounts: createVirtualAccountsClient(),
4305
- virtualCards: createVirtualCardsClient(),
4306
- externalAccounts: createExternalAccountsClient(config),
4307
- operations: createOperationsClient(config),
4308
- webhookEndpoints: createWebhookEndpointsClient(),
4309
- webhookEvents: createWebhookEventsClient(),
4310
- apiKeys: createApiKeysClient()
4311
- };
4312
- const client = clientWithoutFlows;
4313
- client.flows = {
4314
- auth: () => createAuthFlowMachine(client),
4315
- authBootstrap: () => createAuthBootstrapFlowMachine(client),
4316
- onboarding: () => createOnboardingFlowMachine(client),
4317
- provisioning: () => createProvisioningMachine(client)
4318
- };
4319
- return client;
4320
- }
4321
-
4322
- export { createCapxulClient };