@capxul/sdk 0.1.0-alpha.0 → 0.1.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -67,12 +67,681 @@ function fromConvexError(error) {
67
67
  });
68
68
  }
69
69
 
70
+ // ../observability/src/try-catch.ts
71
+ async function tryCatch(promise) {
72
+ try {
73
+ return [null, await promise];
74
+ } catch (e) {
75
+ return [e instanceof Error ? e : new Error(String(e)), null];
76
+ }
77
+ }
78
+
79
+ // ../observability/src/debug-log.ts
80
+ function isDevelopmentBuild() {
81
+ if (typeof process === "undefined") {
82
+ return false;
83
+ }
84
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
85
+ }
86
+ function debugLog(line) {
87
+ if (!isDevelopmentBuild()) return;
88
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
89
+ console.info(line);
90
+ return;
91
+ }
92
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
93
+ process.stderr.write(`${line}
94
+ `);
95
+ }
96
+ }
97
+ function formatDebugValue(value) {
98
+ if (value === void 0 || value === "") return "";
99
+ if (typeof value === "string") return value;
100
+ try {
101
+ return JSON.stringify(value);
102
+ } catch {
103
+ return String(value);
104
+ }
105
+ }
106
+ function track(...args) {
107
+ const [name, props] = args;
108
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
109
+ }
110
+ function formatDebugValue2(value) {
111
+ if (value === void 0 || value === "") return "";
112
+ if (typeof value === "string") return value;
113
+ try {
114
+ return JSON.stringify(value);
115
+ } catch {
116
+ return String(value);
117
+ }
118
+ }
119
+ function identify(userId, traits) {
120
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
121
+ }
122
+
123
+ // ../platform-kernel/src/ids.ts
124
+ function makePrefixedIdConstructor(prefix, fieldName) {
125
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
126
+ return (raw) => {
127
+ if (typeof raw !== "string" || !re.test(raw)) {
128
+ throw new Error(
129
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
130
+ );
131
+ }
132
+ return raw;
133
+ };
134
+ }
135
+ var toOperationId = makePrefixedIdConstructor(
136
+ "op",
137
+ "operationId"
138
+ );
139
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
140
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
141
+ var toSubAccountId = makePrefixedIdConstructor(
142
+ "sub",
143
+ "subAccountId"
144
+ );
145
+
146
+ // src/core/external-accounts.ts
147
+ function brandExternalAccount(raw) {
148
+ return {
149
+ ...raw,
150
+ id: toExternalAccountId(raw.id),
151
+ operation: {
152
+ id: toOperationId(raw.operation.id),
153
+ status: raw.operation.status,
154
+ correlationId: toCorrelationId(raw.operation.correlationId)
155
+ }
156
+ };
157
+ }
158
+ function createExternalAccountsClient(config = {}) {
159
+ return {
160
+ retrieve: async (externalAccountId) => {
161
+ if (!config.data) {
162
+ return stub(
163
+ "externalAccounts.retrieve"
164
+ );
165
+ }
166
+ const [err, raw] = await tryCatch(
167
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
168
+ externalAccountId
169
+ })
170
+ );
171
+ if (err) {
172
+ return [
173
+ fromConvexError(err),
174
+ null
175
+ ];
176
+ }
177
+ if (!raw) {
178
+ return [
179
+ new CapxulError({
180
+ code: "NOT_FOUND",
181
+ message: `external_account ${externalAccountId} not found`
182
+ }),
183
+ null
184
+ ];
185
+ }
186
+ return [null, brandExternalAccount(raw)];
187
+ },
188
+ remove: async (externalAccountId) => {
189
+ if (!config.data) {
190
+ return stub("externalAccounts.remove");
191
+ }
192
+ const [err] = await tryCatch(
193
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
194
+ externalAccountId
195
+ })
196
+ );
197
+ if (err) {
198
+ return [
199
+ fromConvexError(err),
200
+ null
201
+ ];
202
+ }
203
+ return [null, void 0];
204
+ }
205
+ };
206
+ }
207
+
208
+ // src/core/sub-accounts.ts
209
+ function malformedWireError(reason, raw) {
210
+ return new CapxulError({
211
+ code: "PROVIDER_ERROR",
212
+ message: `convex brandSubAccount failed: ${reason}`,
213
+ details: {
214
+ provider: "convex",
215
+ operation: "brandSubAccount",
216
+ reason,
217
+ // PII discipline (`.claude/rules/posthog.md`): user-authored
218
+ // strings on the wire (`name`, `purpose`) are customer-confidential
219
+ // — sub-account names like "Q3 Acquisition Reserve" or
220
+ // "Vendor ABC payments" must not flow into telemetry. We emit a
221
+ // structural keys-only sample via a strict ALLOWLIST so any future
222
+ // wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
223
+ // by construction rather than leaked through a denylist gap.
224
+ sample: safeSampleShape(raw)
225
+ }
226
+ });
227
+ }
228
+ function safeSampleShape(raw) {
229
+ if (raw === null || typeof raw !== "object") {
230
+ return { type: typeof raw };
231
+ }
232
+ const r = raw;
233
+ const balance = r.balance;
234
+ return {
235
+ object: typeof r.object === "string" ? r.object : typeof r.object,
236
+ idPresent: typeof r.id === "string" && r.id.length > 0,
237
+ // First 4 chars only — enough to distinguish "sub_" prefixed IDs
238
+ // from accidental other resource IDs without leaking the full ID.
239
+ idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
240
+ parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
241
+ status: r.status,
242
+ hasName: typeof r.name === "string",
243
+ hasPurpose: r.purpose !== void 0,
244
+ balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
245
+ createdAtType: typeof r.createdAt,
246
+ updatedAtType: typeof r.updatedAt
247
+ };
248
+ }
249
+ function isMoneyShape(v) {
250
+ if (typeof v !== "object" || v === null) return false;
251
+ const m = v;
252
+ return typeof m.value === "string" && typeof m.currency === "string";
253
+ }
254
+ function isParentShape(v) {
255
+ if (typeof v !== "object" || v === null) return false;
256
+ const p = v;
257
+ return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
258
+ }
259
+ function isFiniteNonNegativeInteger(v) {
260
+ return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
261
+ }
262
+ function validateWireSubAccount(raw) {
263
+ if (typeof raw !== "object" || raw === null) {
264
+ return { ok: false, reason: "not an object" };
265
+ }
266
+ const r = raw;
267
+ if (r.object !== "sub_account") {
268
+ return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
269
+ }
270
+ if (typeof r.id !== "string" || r.id.length === 0) {
271
+ return { ok: false, reason: "id must be a non-empty string" };
272
+ }
273
+ if (!isParentShape(r.parent)) {
274
+ return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
275
+ }
276
+ if (typeof r.name !== "string") {
277
+ return { ok: false, reason: "name must be a string" };
278
+ }
279
+ if (r.purpose !== void 0 && typeof r.purpose !== "string") {
280
+ return { ok: false, reason: "purpose must be a string when present" };
281
+ }
282
+ if (r.status !== "active" && r.status !== "archived") {
283
+ return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
284
+ }
285
+ if (!isMoneyShape(r.balance)) {
286
+ return { ok: false, reason: "balance must be { value: string, currency: string }" };
287
+ }
288
+ if (!isFiniteNonNegativeInteger(r.createdAt)) {
289
+ return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
290
+ }
291
+ if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
292
+ return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
293
+ }
294
+ return { ok: true, value: r };
295
+ }
296
+ function brandSubAccount(raw) {
297
+ const result = validateWireSubAccount(raw);
298
+ if (!result.ok) {
299
+ throw malformedWireError(result.reason, raw);
300
+ }
301
+ const wire = result.value;
302
+ return {
303
+ object: wire.object,
304
+ id: toSubAccountId(wire.id),
305
+ parent: wire.parent,
306
+ name: wire.name,
307
+ ...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
308
+ status: wire.status,
309
+ balance: wire.balance,
310
+ createdAt: new Date(wire.createdAt).toISOString()
311
+ };
312
+ }
313
+ function tryBrandSubAccount(raw) {
314
+ try {
315
+ return [null, brandSubAccount(raw)];
316
+ } catch (err) {
317
+ if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
318
+ return [err, null];
319
+ }
320
+ return [
321
+ malformedWireError(
322
+ err instanceof Error ? err.message : String(err),
323
+ raw
324
+ ),
325
+ null
326
+ ];
327
+ }
328
+ }
329
+ function createSubAccountsClient(config = {}) {
330
+ return {
331
+ retrieve: async (subAccountId) => {
332
+ if (!config.data) {
333
+ return stub("subAccounts.retrieve");
334
+ }
335
+ const [err, raw] = await tryCatch(
336
+ config.data.query(api.subAccounts.queries.retrieve, {
337
+ subAccountId
338
+ })
339
+ );
340
+ if (err) {
341
+ return [
342
+ fromConvexError(err),
343
+ null
344
+ ];
345
+ }
346
+ if (!raw) {
347
+ return [
348
+ new CapxulError({
349
+ code: "NOT_FOUND",
350
+ message: `sub_account ${subAccountId} not found`
351
+ }),
352
+ null
353
+ ];
354
+ }
355
+ const [brandErr, branded] = tryBrandSubAccount(raw);
356
+ if (brandErr) {
357
+ return [brandErr, null];
358
+ }
359
+ return [null, branded];
360
+ },
361
+ remove: async (subAccountId) => {
362
+ if (!config.data) {
363
+ return stub("subAccounts.remove");
364
+ }
365
+ const [err, raw] = await tryCatch(
366
+ config.data.mutation(api.subAccounts.mutations.archive, {
367
+ subAccountId
368
+ })
369
+ );
370
+ if (err) {
371
+ return [
372
+ fromConvexError(err),
373
+ null
374
+ ];
375
+ }
376
+ if (!raw) {
377
+ return [
378
+ new CapxulError({
379
+ code: "NOT_FOUND",
380
+ message: `sub_account ${subAccountId} not found`
381
+ }),
382
+ null
383
+ ];
384
+ }
385
+ const [brandErr, branded] = tryBrandSubAccount(raw);
386
+ if (brandErr) {
387
+ return [brandErr, null];
388
+ }
389
+ return [null, branded];
390
+ }
391
+ };
392
+ }
393
+
70
394
  // src/core/accounts.ts
395
+ function createAccountExternalAccountsClient(config) {
396
+ return {
397
+ create: async (input) => {
398
+ if (!config.data) {
399
+ return stub(
400
+ "accounts.externalAccounts.create"
401
+ );
402
+ }
403
+ const [err, raw] = await tryCatch(
404
+ config.data.mutation(
405
+ api.externalAccounts.mutations.createPersonal,
406
+ {
407
+ kind: input.kind,
408
+ label: input.label,
409
+ address: input.address,
410
+ iban: input.iban,
411
+ bic: input.bic,
412
+ accountHolder: input.accountHolder,
413
+ network: input.network,
414
+ panToken: input.panToken,
415
+ last4: input.last4
416
+ }
417
+ )
418
+ );
419
+ if (err) {
420
+ return [
421
+ fromConvexError(err),
422
+ null
423
+ ];
424
+ }
425
+ if (!raw) {
426
+ return [
427
+ new CapxulError({
428
+ code: "NOT_FOUND",
429
+ message: "external_account creation returned no resource"
430
+ }),
431
+ null
432
+ ];
433
+ }
434
+ return [
435
+ null,
436
+ brandExternalAccount(
437
+ raw
438
+ )
439
+ ];
440
+ },
441
+ list: async (input) => {
442
+ if (!config.data) {
443
+ return stub(
444
+ "accounts.externalAccounts.list"
445
+ );
446
+ }
447
+ const [err, result] = await tryCatch(
448
+ config.data.query(api.externalAccounts.queries.listPersonal, {
449
+ limit: input.limit,
450
+ cursor: input.cursor
451
+ })
452
+ );
453
+ if (err) {
454
+ return [fromConvexError(err), null];
455
+ }
456
+ const branded = result.data.map(
457
+ (row) => brandExternalAccount(
458
+ row
459
+ )
460
+ );
461
+ return [
462
+ null,
463
+ {
464
+ object: "list",
465
+ data: branded,
466
+ page: result.page
467
+ }
468
+ ];
469
+ },
470
+ retrieve: async (externalAccountId) => {
471
+ if (!config.data) {
472
+ return stub(
473
+ "accounts.externalAccounts.retrieve"
474
+ );
475
+ }
476
+ const [err, raw] = await tryCatch(
477
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
478
+ externalAccountId
479
+ })
480
+ );
481
+ if (err) {
482
+ return [
483
+ fromConvexError(err),
484
+ null
485
+ ];
486
+ }
487
+ if (!raw) {
488
+ return [
489
+ new CapxulError({
490
+ code: "NOT_FOUND",
491
+ message: `external_account ${externalAccountId} not found`
492
+ }),
493
+ null
494
+ ];
495
+ }
496
+ return [
497
+ null,
498
+ brandExternalAccount(
499
+ raw
500
+ )
501
+ ];
502
+ },
503
+ remove: async (externalAccountId) => {
504
+ if (!config.data) {
505
+ return stub("accounts.externalAccounts.remove");
506
+ }
507
+ const [err] = await tryCatch(
508
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
509
+ externalAccountId
510
+ })
511
+ );
512
+ if (err) {
513
+ return [
514
+ fromConvexError(err),
515
+ null
516
+ ];
517
+ }
518
+ return [null, void 0];
519
+ }
520
+ };
521
+ }
522
+ function createAccountSubAccountsClient(config) {
523
+ return {
524
+ create: async (input) => {
525
+ if (!config.data) {
526
+ return stub(
527
+ "accounts.subAccounts.create"
528
+ );
529
+ }
530
+ const [err, raw] = await tryCatch(
531
+ config.data.mutation(api.subAccounts.mutations.create, {
532
+ parent: { kind: "account", id: input.accountId },
533
+ name: input.name,
534
+ purpose: input.purpose
535
+ })
536
+ );
537
+ if (err) {
538
+ return [
539
+ fromConvexError(err),
540
+ null
541
+ ];
542
+ }
543
+ if (!raw) {
544
+ return [
545
+ new CapxulError({
546
+ code: "NOT_FOUND",
547
+ message: "sub_account creation returned no resource"
548
+ }),
549
+ null
550
+ ];
551
+ }
552
+ const [brandErr, branded] = tryBrandSubAccount(raw);
553
+ if (brandErr) {
554
+ return [
555
+ brandErr,
556
+ null
557
+ ];
558
+ }
559
+ return [null, branded];
560
+ },
561
+ list: async (input) => {
562
+ if (!config.data) {
563
+ return stub(
564
+ "accounts.subAccounts.list"
565
+ );
566
+ }
567
+ const [err, rows] = await tryCatch(
568
+ config.data.query(api.subAccounts.queries.listByAccount, {
569
+ accountId: input.accountId
570
+ })
571
+ );
572
+ if (err) {
573
+ return [
574
+ fromConvexError(err),
575
+ null
576
+ ];
577
+ }
578
+ const branded = [];
579
+ for (const row of rows) {
580
+ const [brandErr, value] = tryBrandSubAccount(row);
581
+ if (brandErr) {
582
+ return [
583
+ brandErr,
584
+ null
585
+ ];
586
+ }
587
+ branded.push(value);
588
+ }
589
+ return [
590
+ null,
591
+ {
592
+ object: "list",
593
+ data: branded,
594
+ page: { hasMore: false }
595
+ }
596
+ ];
597
+ },
598
+ retrieve: async (subAccountId) => {
599
+ if (!config.data) {
600
+ return stub(
601
+ "accounts.subAccounts.retrieve"
602
+ );
603
+ }
604
+ const [err, raw] = await tryCatch(
605
+ config.data.query(api.subAccounts.queries.retrieve, {
606
+ subAccountId
607
+ })
608
+ );
609
+ if (err) {
610
+ return [
611
+ fromConvexError(err),
612
+ null
613
+ ];
614
+ }
615
+ if (!raw) {
616
+ return [
617
+ new CapxulError({
618
+ code: "NOT_FOUND",
619
+ message: `sub_account ${subAccountId} not found`
620
+ }),
621
+ null
622
+ ];
623
+ }
624
+ const [brandErr, branded] = tryBrandSubAccount(raw);
625
+ if (brandErr) {
626
+ return [
627
+ brandErr,
628
+ null
629
+ ];
630
+ }
631
+ return [null, branded];
632
+ },
633
+ remove: async (subAccountId) => {
634
+ if (!config.data) {
635
+ return stub(
636
+ "accounts.subAccounts.remove"
637
+ );
638
+ }
639
+ const [err, raw] = await tryCatch(
640
+ config.data.mutation(api.subAccounts.mutations.archive, {
641
+ subAccountId
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: `sub_account ${subAccountId} not found`
655
+ }),
656
+ null
657
+ ];
658
+ }
659
+ const [brandErr, branded] = tryBrandSubAccount(raw);
660
+ if (brandErr) {
661
+ return [
662
+ brandErr,
663
+ null
664
+ ];
665
+ }
666
+ return [null, branded];
667
+ }
668
+ };
669
+ }
71
670
  function createAccountsClient(config = {}) {
72
671
  return {
73
- retrieve: async () => stub("accounts.retrieve"),
672
+ retrieve: async (accountId) => {
673
+ if (!config.data) {
674
+ return stub("accounts.retrieve");
675
+ }
676
+ try {
677
+ const account = await config.data.query(
678
+ api.openfort.queries.getMyAccount,
679
+ {}
680
+ );
681
+ if (account.id !== accountId) {
682
+ return [
683
+ new CapxulError({
684
+ code: "PERMISSION_DENIED",
685
+ message: "accounts.retrieve currently supports the authenticated caller's own account only.",
686
+ details: {
687
+ requestedAccountId: accountId,
688
+ authenticatedAccountId: account.id
689
+ }
690
+ }),
691
+ null
692
+ ];
693
+ }
694
+ return [null, account];
695
+ } catch (cause) {
696
+ return [fromConvexError(cause), null];
697
+ }
698
+ },
74
699
  lookup: async () => stub("accounts.lookup"),
75
- update: async () => stub("accounts.update"),
700
+ update: async (input) => {
701
+ if (!config.data) {
702
+ return stub("accounts.update");
703
+ }
704
+ if (input.countryCode !== void 0) {
705
+ return [
706
+ new CapxulError({
707
+ code: "INVALID_INPUT",
708
+ message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
709
+ details: { field: "countryCode" }
710
+ }),
711
+ null
712
+ ];
713
+ }
714
+ try {
715
+ const current = await config.data.query(
716
+ api.openfort.queries.getMyAccount,
717
+ {}
718
+ );
719
+ if (current.id !== input.accountId) {
720
+ return [
721
+ new CapxulError({
722
+ code: "PERMISSION_DENIED",
723
+ message: "accounts.update currently supports the authenticated caller's own account only.",
724
+ details: {
725
+ requestedAccountId: input.accountId,
726
+ authenticatedAccountId: current.id
727
+ }
728
+ }),
729
+ null
730
+ ];
731
+ }
732
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
733
+ displayName: input.name,
734
+ username: input.username
735
+ });
736
+ const updated = await config.data.query(
737
+ api.openfort.queries.getMyAccount,
738
+ {}
739
+ );
740
+ return [null, updated];
741
+ } catch (cause) {
742
+ return [fromConvexError(cause), null];
743
+ }
744
+ },
76
745
  provisionPersonal: async (input) => {
77
746
  if (!config.data) {
78
747
  return stub(
@@ -152,24 +821,8 @@ function createAccountsClient(config = {}) {
152
821
  create: async () => stub("accounts.kycProfiles.create"),
153
822
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
154
823
  },
155
- externalAccounts: {
156
- create: async () => stub(
157
- "accounts.externalAccounts.create"
158
- ),
159
- list: async () => stub(
160
- "accounts.externalAccounts.list"
161
- ),
162
- retrieve: async () => stub(
163
- "accounts.externalAccounts.retrieve"
164
- ),
165
- remove: async () => stub("accounts.externalAccounts.remove")
166
- },
167
- subAccounts: {
168
- create: async () => stub("accounts.subAccounts.create"),
169
- list: async () => stub("accounts.subAccounts.list"),
170
- retrieve: async () => stub("accounts.subAccounts.retrieve"),
171
- remove: async () => stub("accounts.subAccounts.remove")
172
- },
824
+ externalAccounts: createAccountExternalAccountsClient(config),
825
+ subAccounts: createAccountSubAccountsClient(config),
173
826
  balanceLedger: {
174
827
  list: async () => stub(
175
828
  "accounts.balanceLedger.list"
@@ -228,10 +881,16 @@ var Errors = {
228
881
  `Shield API error (${status}): ${detail}`,
229
882
  { details: { provider: "shield", status } }
230
883
  ),
231
- providerError: (provider, operation, cause) => new CapxulError2(
232
- "PROVIDER_ERROR",
233
- `${provider} ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
234
- { cause, details: { provider, operation } }
884
+ providerError: (provider, operation, cause) => (
885
+ // Public `message` is redacted to a fixed shape so provider-side
886
+ // exception text never leaks to the client. The original `cause`
887
+ // is preserved on `Error.cause` for server-side debugging via
888
+ // observability sinks (Sentry, console traces).
889
+ new CapxulError2(
890
+ "PROVIDER_ERROR",
891
+ `Provider error: ${provider} ${operation}`,
892
+ { cause, details: { provider, operation } }
893
+ )
235
894
  ),
236
895
  invalidInput: (field, reason) => new CapxulError2(
237
896
  "INVALID_INPUT",
@@ -257,8 +916,35 @@ var Errors = {
257
916
  "Idempotency key was already used for a different request",
258
917
  { details }
259
918
  ),
260
- emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
261
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
919
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
920
+ details
921
+ }),
922
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
923
+ details: { ...details }
924
+ }),
925
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
926
+ /**
927
+ * Verification gate. Surfaced when a request hits a verification
928
+ * boundary the actor cannot cross under their current state. Two
929
+ * variants share this code:
930
+ *
931
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
932
+ * `external_account.kind` routes to a withdrawal rail (e.g.
933
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
934
+ * `details.rail` + `details.currentKind`.
935
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
936
+ * the required tier. Carries `details.requiredTier`.
937
+ *
938
+ * Code is shared because both expose the same UX shape ("you cannot
939
+ * proceed until verification advances"); the `details.*` keys
940
+ * differentiate the route.
941
+ */
942
+ verificationRequired: (details) => {
943
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
944
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
945
+ details: { ...details }
946
+ });
947
+ }
262
948
  };
263
949
 
264
950
  // ../config/src/safe.ts
@@ -308,13 +994,13 @@ function createLifecycle(initial) {
308
994
  }
309
995
  function makeBuildTimeUrlsTransport(config) {
310
996
  if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
311
- throw Errors.invalidInput(
997
+ throw invalidConfigError(
312
998
  "authBaseUrl",
313
999
  "build-time-urls transport requires a non-empty authBaseUrl."
314
1000
  );
315
1001
  }
316
1002
  if (!config.convexUrl || config.convexUrl.trim().length === 0) {
317
- throw Errors.invalidInput(
1003
+ throw invalidConfigError(
318
1004
  "convexUrl",
319
1005
  "build-time-urls transport requires a non-empty convexUrl."
320
1006
  );
@@ -328,6 +1014,7 @@ function makeBuildTimeUrlsTransport(config) {
328
1014
  return {
329
1015
  authBaseUrl,
330
1016
  convexUrl,
1017
+ ensureRuntime: async () => runtime,
331
1018
  fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
332
1019
  getState: lifecycle.getState,
333
1020
  subscribe: lifecycle.subscribe,
@@ -343,14 +1030,15 @@ function makeBuildTimeUrlsTransport(config) {
343
1030
  };
344
1031
  }
345
1032
  function makePublishableKeyTransport(config) {
346
- if (!config.publishableKey || config.publishableKey.trim().length === 0) {
347
- throw Errors.invalidInput(
1033
+ const publishableKey = config.publishableKey?.trim();
1034
+ if (!publishableKey) {
1035
+ throw invalidConfigError(
348
1036
  "publishableKey",
349
1037
  "publishable-key transport requires a non-empty publishableKey."
350
1038
  );
351
1039
  }
352
1040
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
353
- const bootstrapUrl = stripTrailingSlash(
1041
+ const bootstrapUrl = normalizeBootstrapUrl(
354
1042
  config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
355
1043
  );
356
1044
  let authBaseUrl = "";
@@ -365,23 +1053,20 @@ function makePublishableKeyTransport(config) {
365
1053
  const response = await fetchImpl(bootstrapUrl, {
366
1054
  method: "POST",
367
1055
  headers: { "content-type": "application/json" },
368
- body: JSON.stringify({ publishableKey: config.publishableKey })
1056
+ body: JSON.stringify({ publishableKey })
369
1057
  });
370
1058
  if (!response.ok) {
371
- throw Errors.invalidInput(
372
- "publishableKey",
373
- `${bootstrapUrl} failed with HTTP ${response.status}.`
374
- );
1059
+ throw await bootstrapResponseError(response, bootstrapUrl);
375
1060
  }
376
- const body = await response.json();
1061
+ const body = await readBootstrapSuccessBody(response);
377
1062
  if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
378
- throw Errors.invalidInput(
1063
+ throw bootstrapContractError(
379
1064
  "authBaseUrl",
380
1065
  "/v1/client/bootstrap returned no authBaseUrl."
381
1066
  );
382
1067
  }
383
1068
  if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
384
- throw Errors.invalidInput(
1069
+ throw bootstrapContractError(
385
1070
  "convexUrl",
386
1071
  "/v1/client/bootstrap returned no convexUrl."
387
1072
  );
@@ -393,16 +1078,13 @@ function makePublishableKeyTransport(config) {
393
1078
  return runtime;
394
1079
  })();
395
1080
  bootstrapPromise = attempt.catch((err) => {
1081
+ const error = normalizeBootstrapThrownError(err);
396
1082
  bootstrapPromise = null;
397
1083
  lifecycle.setState({
398
1084
  status: "error",
399
- error: err instanceof CapxulError ? err : new CapxulError({
400
- code: "UNKNOWN",
401
- message: "Bootstrap failed without a typed CapxulError.",
402
- cause: err
403
- })
1085
+ error
404
1086
  });
405
- throw err;
1087
+ throw error;
406
1088
  });
407
1089
  return await bootstrapPromise;
408
1090
  }
@@ -413,6 +1095,7 @@ function makePublishableKeyTransport(config) {
413
1095
  get convexUrl() {
414
1096
  return convexUrl;
415
1097
  },
1098
+ ensureRuntime: ensureBootstrap,
416
1099
  fetch: async (path, init) => {
417
1100
  const resolved = await ensureBootstrap();
418
1101
  return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
@@ -423,7 +1106,7 @@ function makePublishableKeyTransport(config) {
423
1106
  markAuthenticated: ({ dataClient: nextDataClient }) => {
424
1107
  const current = lifecycle.getState();
425
1108
  if (current.status !== "ready" && current.status !== "authenticated") {
426
- throw Errors.internalError(
1109
+ throw internalTransportError(
427
1110
  `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
428
1111
  );
429
1112
  }
@@ -445,6 +1128,24 @@ function makePublishableKeyTransport(config) {
445
1128
  function stripTrailingSlash(url) {
446
1129
  return url.replace(/\/+$/, "");
447
1130
  }
1131
+ function normalizeBootstrapUrl(url) {
1132
+ const normalized = stripTrailingSlash(url.trim());
1133
+ if (!isAbsoluteHttpUrl(normalized)) {
1134
+ throw invalidConfigError(
1135
+ "bootstrapUrl",
1136
+ "publishable-key transport requires an absolute http(s) bootstrapUrl."
1137
+ );
1138
+ }
1139
+ return normalized;
1140
+ }
1141
+ function isAbsoluteHttpUrl(url) {
1142
+ try {
1143
+ const parsed = new URL(url);
1144
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
1145
+ } catch {
1146
+ return false;
1147
+ }
1148
+ }
448
1149
  function resolveUrl(authBaseUrl, path) {
449
1150
  if (path.startsWith("http://") || path.startsWith("https://")) {
450
1151
  return path;
@@ -452,10 +1153,142 @@ function resolveUrl(authBaseUrl, path) {
452
1153
  return `${authBaseUrl}${path}`;
453
1154
  }
454
1155
  function assertNever(value) {
455
- throw Errors.internalError(
1156
+ throw internalTransportError(
456
1157
  `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
457
1158
  );
458
1159
  }
1160
+ function invalidConfigError(field, reason) {
1161
+ return new CapxulError({
1162
+ code: "INVALID_INPUT",
1163
+ message: `Invalid ${field}: ${reason}`,
1164
+ details: { source: "sdk-config", field, reason }
1165
+ });
1166
+ }
1167
+ function bootstrapContractError(field, message) {
1168
+ return new CapxulError({
1169
+ code: "INVALID_INPUT",
1170
+ message,
1171
+ details: {
1172
+ source: "backend-bootstrap",
1173
+ phase: "publishable-key-bootstrap",
1174
+ field,
1175
+ reason: message
1176
+ }
1177
+ });
1178
+ }
1179
+ function internalTransportError(reason) {
1180
+ return new CapxulError({
1181
+ code: "INTERNAL_ERROR",
1182
+ message: `Internal error: ${reason}`,
1183
+ details: { source: "sdk-transport", reason }
1184
+ });
1185
+ }
1186
+ async function bootstrapResponseError(response, bootstrapUrl) {
1187
+ const envelope = await readBootstrapErrorEnvelope(response);
1188
+ const wireCode = readNonEmptyString(envelope?.error?.code);
1189
+ const normalized = normalizeBootstrapErrorCode(wireCode);
1190
+ const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
1191
+ const backendDetails = readRecord(envelope?.error?.details);
1192
+ return new CapxulError({
1193
+ code: normalized.code,
1194
+ message,
1195
+ details: {
1196
+ ...backendDetails,
1197
+ source: "backend-bootstrap",
1198
+ phase: "publishable-key-bootstrap",
1199
+ httpStatus: response.status,
1200
+ ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
1201
+ },
1202
+ operationId: readNonEmptyString(envelope?.error?.operationId),
1203
+ correlationId: readNonEmptyString(envelope?.error?.correlationId),
1204
+ retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
1205
+ });
1206
+ }
1207
+ async function readBootstrapErrorEnvelope(response) {
1208
+ try {
1209
+ const parsed = await response.json();
1210
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
1211
+ } catch {
1212
+ return null;
1213
+ }
1214
+ }
1215
+ async function readBootstrapSuccessBody(response) {
1216
+ try {
1217
+ const parsed = await response.json();
1218
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
1219
+ } catch {
1220
+ throw bootstrapContractError(
1221
+ "body",
1222
+ "/v1/client/bootstrap returned invalid JSON."
1223
+ );
1224
+ }
1225
+ }
1226
+ function normalizeBootstrapThrownError(error) {
1227
+ if (error instanceof CapxulError) return error;
1228
+ return new CapxulError({
1229
+ code: "NETWORK_ERROR",
1230
+ message: "Publishable-key bootstrap network failure.",
1231
+ cause: error,
1232
+ details: {
1233
+ source: "bootstrap-network",
1234
+ phase: "publishable-key-bootstrap"
1235
+ }
1236
+ });
1237
+ }
1238
+ function normalizeBootstrapErrorCode(wireCode) {
1239
+ if (wireCode === "INTERNAL_SERVER_ERROR") {
1240
+ return { code: "INTERNAL_ERROR", wireCode };
1241
+ }
1242
+ if (wireCode && isCapxulErrorCode(wireCode)) {
1243
+ return { code: wireCode };
1244
+ }
1245
+ return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
1246
+ }
1247
+ var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
1248
+ "NOT_AUTHENTICATED",
1249
+ "EMAIL_DELIVERY_FAILED",
1250
+ "PROFILE_NOT_FOUND",
1251
+ "SMART_ACCOUNT_MISSING",
1252
+ "PLAYER_NOT_FOUND",
1253
+ "ACCOUNT_NOT_FOUND",
1254
+ "PROVIDER_ERROR",
1255
+ "INVALID_INPUT",
1256
+ "ENV_MISSING",
1257
+ "NOT_IMPLEMENTED",
1258
+ "VERIFICATION_REQUIRED",
1259
+ "INSUFFICIENT_BALANCE",
1260
+ "INVALID_RECIPIENT",
1261
+ "TRANSACTION_FAILED",
1262
+ "RATE_LIMITED",
1263
+ "NETWORK_ERROR",
1264
+ "UNKNOWN",
1265
+ "PERMISSION_DENIED",
1266
+ "API_KEY_INVALID",
1267
+ "API_KEY_EXPIRED",
1268
+ "IDEMPOTENCY_CONFLICT",
1269
+ "NOT_FOUND",
1270
+ "OPERATION_CANCELED",
1271
+ "OPERATION_TIMEOUT",
1272
+ "ACTION_REQUIRED",
1273
+ "KYC_REQUIRED",
1274
+ "POLICY_DENIED",
1275
+ "SAFE_NOT_READY",
1276
+ "PROVIDER_UNAVAILABLE",
1277
+ "PROVIDER_REJECTED",
1278
+ "RECONCILIATION_FAILED",
1279
+ "INTERNAL_ERROR",
1280
+ "QUOTE_EXPIRED",
1281
+ "QUOTE_NOT_FOUND"
1282
+ ]);
1283
+ function isCapxulErrorCode(value) {
1284
+ return CAPXUL_ERROR_CODES.has(value);
1285
+ }
1286
+ function readRecord(value) {
1287
+ return typeof value === "object" && value !== null ? value : null;
1288
+ }
1289
+ function readNonEmptyString(value) {
1290
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1291
+ }
459
1292
 
460
1293
  // src/core/auth.ts
461
1294
  function createAuthClient(config = {}) {
@@ -510,13 +1343,16 @@ function createAuthClient(config = {}) {
510
1343
  email: signIn.user.email,
511
1344
  token: signIn.token,
512
1345
  convexJwt,
513
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
1346
+ expiresAt: new Date(
1347
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
1348
+ ).toISOString()
514
1349
  };
515
1350
  sessionStore.set(session);
516
1351
  if (config.auth?.createDataClient) {
517
1352
  try {
518
1353
  dataClient = await config.auth.createDataClient(session);
519
1354
  mutableConfig(config).data = dataClient;
1355
+ transport.markAuthenticated({ dataClient });
520
1356
  } catch (cause) {
521
1357
  return [
522
1358
  new CapxulError({
@@ -528,13 +1364,76 @@ function createAuthClient(config = {}) {
528
1364
  ];
529
1365
  }
530
1366
  }
531
- return [null, session];
1367
+ if (!dataClient) {
1368
+ return [
1369
+ new CapxulError({
1370
+ code: "NOT_AUTHENTICATED",
1371
+ message: "Auth bootstrap requires an authenticated Convex data client."
1372
+ }),
1373
+ null
1374
+ ];
1375
+ }
1376
+ try {
1377
+ const resolution = await dataClient.mutation(
1378
+ api.authBootstrap.resolveAfterOtp,
1379
+ {
1380
+ email: session.email,
1381
+ sessionToken: session.token
1382
+ }
1383
+ );
1384
+ if (resolution.kind === "existing_member") {
1385
+ return [null, { ...resolution, session }];
1386
+ }
1387
+ return [null, { ...resolution, session }];
1388
+ } catch (cause) {
1389
+ return [fromConvexError(cause), null];
1390
+ }
1391
+ },
1392
+ completeBootstrap: async (input) => {
1393
+ const session = sessionStore.get();
1394
+ const data = dataClient ?? config.data;
1395
+ if (!session || !data) {
1396
+ return [
1397
+ new CapxulError({
1398
+ code: "INVALID_INPUT",
1399
+ message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
1400
+ }),
1401
+ null
1402
+ ];
1403
+ }
1404
+ if (input.signerProvider.kind !== "local-private-key") {
1405
+ return [
1406
+ new CapxulError({
1407
+ code: "INVALID_INPUT",
1408
+ message: "completeBootstrap currently supports local-private-key signer providers only."
1409
+ }),
1410
+ null
1411
+ ];
1412
+ }
1413
+ try {
1414
+ const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1415
+ bootstrapToken: input.bootstrapToken,
1416
+ sessionToken: session.token,
1417
+ username: input.username,
1418
+ displayName: input.displayName,
1419
+ countryCode: input.countryCode,
1420
+ signerProvider: input.signerProvider
1421
+ });
1422
+ return [null, { kind: "authenticated", session, ...result }];
1423
+ } catch (cause) {
1424
+ return [
1425
+ fromConvexError(cause),
1426
+ null
1427
+ ];
1428
+ }
532
1429
  },
533
1430
  getSession: async () => [null, sessionStore.get()],
534
1431
  signOut: async () => {
535
1432
  sessionStore.clear();
536
1433
  dataClient = null;
537
1434
  mutableConfig(config).data = void 0;
1435
+ const transport = getTransport();
1436
+ transport?.clearAuth();
538
1437
  return [null, void 0];
539
1438
  },
540
1439
  serviceTokenMint: async () => stub("auth.serviceTokenMint"),
@@ -588,16 +1487,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
588
1487
  body: JSON.stringify(body),
589
1488
  signal
590
1489
  });
1490
+ const text = await response.text();
591
1491
  if (!response.ok) {
1492
+ const parsedError = parseBetterAuthError(text);
592
1493
  return [
593
1494
  new CapxulError({
594
- code,
595
- message: `BetterAuth ${path} failed with HTTP ${response.status}.`
1495
+ code: parsedError.code ?? code,
1496
+ message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1497
+ details: parsedError.details,
1498
+ retryable: parsedError.retryable
596
1499
  }),
597
1500
  null
598
1501
  ];
599
1502
  }
600
- const text = await response.text();
601
1503
  return [null, text ? JSON.parse(text) : void 0];
602
1504
  } catch (cause) {
603
1505
  return [
@@ -610,6 +1512,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
610
1512
  ];
611
1513
  }
612
1514
  }
1515
+ function parseBetterAuthError(text) {
1516
+ if (!text.trim()) {
1517
+ return {};
1518
+ }
1519
+ try {
1520
+ const body = JSON.parse(text);
1521
+ if (!body || typeof body !== "object") {
1522
+ return {};
1523
+ }
1524
+ const record = body;
1525
+ const nested = record.error && typeof record.error === "object" ? record.error : record;
1526
+ const code = typeof nested.code === "string" ? nested.code : void 0;
1527
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1528
+ const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1529
+ const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1530
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1531
+ return {
1532
+ code: isCapxulErrorCode2(code) ? code : void 0,
1533
+ message,
1534
+ details,
1535
+ correlationId,
1536
+ retryable
1537
+ };
1538
+ } catch {
1539
+ return {};
1540
+ }
1541
+ }
1542
+ function isCapxulErrorCode2(code) {
1543
+ 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";
1544
+ }
613
1545
  async function exchangeConvexToken(transport, config, token, signal) {
614
1546
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
615
1547
  try {
@@ -670,22 +1602,42 @@ function createOrgDocumentsClient() {
670
1602
  };
671
1603
  }
672
1604
 
673
- // src/core/external-accounts.ts
674
- function createExternalAccountsClient() {
675
- return {
676
- retrieve: async () => stub("externalAccounts.retrieve"),
677
- remove: async () => stub("externalAccounts.remove")
678
- };
679
- }
680
-
681
1605
  // src/core/me.ts
682
1606
  function createMeClient(config = {}) {
683
1607
  return {
684
1608
  get: async () => {
685
1609
  if (!config.data) {
686
- return stub("me.get");
1610
+ return stub("me.get");
1611
+ }
1612
+ try {
1613
+ const account = await config.data.query(
1614
+ api.openfort.queries.getMyAccount,
1615
+ {}
1616
+ );
1617
+ return [null, account];
1618
+ } catch (cause) {
1619
+ return [fromConvexError(cause), null];
1620
+ }
1621
+ },
1622
+ update: async (input) => {
1623
+ if (!config.data) {
1624
+ return stub("me.update");
1625
+ }
1626
+ if (input.countryCode !== void 0) {
1627
+ return [
1628
+ new CapxulError({
1629
+ code: "INVALID_INPUT",
1630
+ message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
1631
+ details: { field: "countryCode" }
1632
+ }),
1633
+ null
1634
+ ];
687
1635
  }
688
1636
  try {
1637
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
1638
+ displayName: input.name,
1639
+ username: input.username
1640
+ });
689
1641
  const account = await config.data.query(
690
1642
  api.openfort.queries.getMyAccount,
691
1643
  {}
@@ -694,8 +1646,7 @@ function createMeClient(config = {}) {
694
1646
  } catch (cause) {
695
1647
  return [fromConvexError(cause), null];
696
1648
  }
697
- },
698
- update: async () => stub("me.update")
1649
+ }
699
1650
  };
700
1651
  }
701
1652
 
@@ -1033,36 +1984,430 @@ function createPaymentsClient(config = {}) {
1033
1984
  list: async () => stub("payments.list")
1034
1985
  };
1035
1986
  }
1036
- function createOrgPaymentsClient() {
1037
- return {
1038
- create: async () => stub(
1039
- "organizations.payments.create"
1040
- ),
1041
- retrieve: async () => stub("organizations.payments.retrieve"),
1042
- list: async () => stub("organizations.payments.list")
1043
- };
1987
+ function createOrgPaymentsClient() {
1988
+ return {
1989
+ create: async () => stub(
1990
+ "organizations.payments.create"
1991
+ ),
1992
+ retrieve: async () => stub("organizations.payments.retrieve"),
1993
+ list: async () => stub("organizations.payments.list")
1994
+ };
1995
+ }
1996
+ async function bestEffortMarkFailed(config, paymentId, error) {
1997
+ try {
1998
+ await config.data.mutation(api.payments.mutations.markFailed, {
1999
+ paymentId,
2000
+ errorCode: error.code,
2001
+ errorMessage: error.message,
2002
+ source: "sdk"
2003
+ });
2004
+ } catch {
2005
+ }
2006
+ }
2007
+ function mapCreateError(error) {
2008
+ switch (error.code) {
2009
+ case "NOT_AUTHENTICATED":
2010
+ case "PERMISSION_DENIED":
2011
+ case "INVALID_INPUT":
2012
+ case "INVALID_RECIPIENT":
2013
+ case "INSUFFICIENT_BALANCE":
2014
+ case "IDEMPOTENCY_CONFLICT":
2015
+ case "RATE_LIMITED":
2016
+ case "NETWORK_ERROR":
2017
+ return error;
2018
+ default:
2019
+ return new CapxulError({
2020
+ code: "NETWORK_ERROR",
2021
+ message: error.message,
2022
+ cause: error,
2023
+ details: error.details,
2024
+ operationId: error.operationId,
2025
+ correlationId: error.correlationId,
2026
+ retryable: error.retryable
2027
+ });
2028
+ }
2029
+ }
2030
+
2031
+ // src/core/transfers.ts
2032
+ function createTransfersClient() {
2033
+ return {
2034
+ create: async () => stub("transfers.create"),
2035
+ retrieve: async () => stub("transfers.retrieve"),
2036
+ list: async () => stub("transfers.list"),
2037
+ confirm: async () => stub("transfers.confirm"),
2038
+ cancel: async () => stub("transfers.cancel")
2039
+ };
2040
+ }
2041
+ function createOrgTransfersClient() {
2042
+ return {
2043
+ create: async () => stub(
2044
+ "organizations.transfers.create"
2045
+ ),
2046
+ retrieve: async () => stub("organizations.transfers.retrieve"),
2047
+ list: async () => stub("organizations.transfers.list"),
2048
+ confirm: async () => stub("organizations.transfers.confirm"),
2049
+ cancel: async () => stub("organizations.transfers.cancel")
2050
+ };
2051
+ }
2052
+ function createWithdrawalsClient(config = {}) {
2053
+ return {
2054
+ create: async (input) => {
2055
+ if (!config.data) {
2056
+ return stub("withdrawals.create");
2057
+ }
2058
+ const [createErr, createdRaw] = await tryCatch(
2059
+ config.data.mutation(api.withdrawals.mutations.create, {
2060
+ amount: input.amount,
2061
+ destination: {
2062
+ externalAccountId: input.destination.externalAccountId
2063
+ },
2064
+ source: input.source,
2065
+ reference: input.reference,
2066
+ idempotencyKey: input.idempotencyKey
2067
+ })
2068
+ );
2069
+ if (createErr) {
2070
+ return [mapCreateError2(fromConvexError(createErr)), null];
2071
+ }
2072
+ const created = createdRaw;
2073
+ if (!created) {
2074
+ return [
2075
+ new CapxulError({
2076
+ code: "NETWORK_ERROR",
2077
+ message: "withdrawals.create returned no withdrawal resource"
2078
+ }),
2079
+ null
2080
+ ];
2081
+ }
2082
+ if (created.status !== "processing" || created.operation.status !== "processing") {
2083
+ return [null, created];
2084
+ }
2085
+ if (!config.signer || !config.signing) {
2086
+ return [null, created];
2087
+ }
2088
+ const [signerErr, currentSigner] = await tryCatch(
2089
+ config.data.query(api.safe.queries.getMySignerAddress, {})
2090
+ );
2091
+ if (signerErr) {
2092
+ return await handleSubmissionFailure(
2093
+ { data: config.data },
2094
+ created.id,
2095
+ mapCreateError2(fromConvexError(signerErr))
2096
+ );
2097
+ }
2098
+ if (!currentSigner?.address) {
2099
+ return await handleSubmissionFailure(
2100
+ { data: config.data },
2101
+ created.id,
2102
+ new CapxulError({
2103
+ code: "PERMISSION_DENIED",
2104
+ message: "No signer is registered for the authenticated account.",
2105
+ details: { withdrawalId: created.id }
2106
+ })
2107
+ );
2108
+ }
2109
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
2110
+ return await handleSubmissionFailure(
2111
+ { data: config.data },
2112
+ created.id,
2113
+ new CapxulError({
2114
+ code: "PERMISSION_DENIED",
2115
+ message: "Configured signer does not match the authenticated account signer.",
2116
+ details: {
2117
+ withdrawalId: created.id,
2118
+ expectedSignerAddress: currentSigner.address,
2119
+ actualSignerAddress: config.signer.address
2120
+ }
2121
+ })
2122
+ );
2123
+ }
2124
+ const [prepErr, submission] = await tryCatch(
2125
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
2126
+ withdrawalId: created.id
2127
+ })
2128
+ );
2129
+ if (prepErr) {
2130
+ return await handleSubmissionFailure(
2131
+ { data: config.data },
2132
+ created.id,
2133
+ mapCreateError2(fromConvexError(prepErr))
2134
+ );
2135
+ }
2136
+ const destinationAddress = submission?.destinationAddress;
2137
+ if (!submission || !destinationAddress) {
2138
+ return await handleSubmissionFailure(
2139
+ { data: config.data },
2140
+ created.id,
2141
+ new CapxulError({
2142
+ code: "NETWORK_ERROR",
2143
+ message: "withdrawals.prepareSubmission returned no destination.",
2144
+ details: { withdrawalId: created.id }
2145
+ })
2146
+ );
2147
+ }
2148
+ const [transferErr, transferOk] = await tryCatch(
2149
+ transferAsOwner(
2150
+ {
2151
+ signer: config.signer,
2152
+ signing: config.signing
2153
+ },
2154
+ {
2155
+ tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
2156
+ recipientAddress: destinationAddress,
2157
+ amount: toTokenUnits(submission.amount.value, 6)
2158
+ }
2159
+ )
2160
+ );
2161
+ if (transferErr) {
2162
+ return await handleSubmissionFailure(
2163
+ { data: config.data },
2164
+ created.id,
2165
+ mapCreateError2(fromConvexError(transferErr))
2166
+ );
2167
+ }
2168
+ if (!transferOk.success) {
2169
+ return await handleSubmissionFailure(
2170
+ { data: config.data },
2171
+ created.id,
2172
+ new CapxulError({
2173
+ code: "NETWORK_ERROR",
2174
+ message: "Bundler submission did not succeed.",
2175
+ details: {
2176
+ withdrawalId: created.id,
2177
+ txHash: transferOk.txHash,
2178
+ userOpHash: transferOk.userOpHash
2179
+ }
2180
+ })
2181
+ );
2182
+ }
2183
+ const [recordErr] = await tryCatch(
2184
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
2185
+ withdrawalId: created.id,
2186
+ txHash: transferOk.txHash,
2187
+ userOpHash: transferOk.userOpHash
2188
+ })
2189
+ );
2190
+ if (recordErr) {
2191
+ return [
2192
+ new CapxulError({
2193
+ code: "NETWORK_ERROR",
2194
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
2195
+ cause: recordErr,
2196
+ details: {
2197
+ withdrawalId: created.id,
2198
+ txHash: transferOk.txHash,
2199
+ userOpHash: transferOk.userOpHash
2200
+ }
2201
+ }),
2202
+ null
2203
+ ];
2204
+ }
2205
+ return [null, created];
2206
+ },
2207
+ retrieve: async (withdrawalId) => {
2208
+ if (!config.data) {
2209
+ return stub("withdrawals.retrieve");
2210
+ }
2211
+ const [err, raw] = await tryCatch(
2212
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
2213
+ );
2214
+ if (err) {
2215
+ return [fromConvexError(err), null];
2216
+ }
2217
+ const withdrawal = raw;
2218
+ if (!withdrawal) {
2219
+ return [
2220
+ new CapxulError({
2221
+ code: "NOT_FOUND",
2222
+ message: `withdrawal ${withdrawalId} not found`
2223
+ }),
2224
+ null
2225
+ ];
2226
+ }
2227
+ return [null, withdrawal];
2228
+ },
2229
+ list: async (input) => {
2230
+ if (!config.data) {
2231
+ return stub("withdrawals.list");
2232
+ }
2233
+ const [err, raw] = await tryCatch(
2234
+ config.data.query(api.withdrawals.queries.list, {
2235
+ limit: input?.limit,
2236
+ cursor: input?.cursor
2237
+ })
2238
+ );
2239
+ if (err) {
2240
+ return [fromConvexError(err), null];
2241
+ }
2242
+ return [null, raw];
2243
+ },
2244
+ recordCompleted: async (input) => {
2245
+ if (!config.data) {
2246
+ return stub(
2247
+ "withdrawals.recordCompleted"
2248
+ );
2249
+ }
2250
+ const [err] = await tryCatch(
2251
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
2252
+ withdrawalId: input.withdrawalId,
2253
+ txHash: input.txHash
2254
+ })
2255
+ );
2256
+ if (err) {
2257
+ return [
2258
+ mapRecordCompletedError(fromConvexError(err)),
2259
+ null
2260
+ ];
2261
+ }
2262
+ return [null, null];
2263
+ }
2264
+ };
2265
+ }
2266
+ function createOrgWithdrawalsClient(config = {}) {
2267
+ return {
2268
+ /**
2269
+ * Org-scope create (Withdrawals v1 W2, #465).
2270
+ *
2271
+ * D6 — returns the `processing` row only. No `transferAsOwner`
2272
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
2273
+ * orchestration ships in W3+.
2274
+ */
2275
+ create: async (input) => {
2276
+ if (!config.data) {
2277
+ return stub(
2278
+ "organizations.withdrawals.create"
2279
+ );
2280
+ }
2281
+ const [err, raw] = await tryCatch(
2282
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
2283
+ organizationId: input.organizationId,
2284
+ amount: input.amount,
2285
+ destination: {
2286
+ externalAccountId: input.destination.externalAccountId
2287
+ },
2288
+ source: input.source,
2289
+ reference: input.reference,
2290
+ idempotencyKey: input.idempotencyKey
2291
+ })
2292
+ );
2293
+ if (err) {
2294
+ return [mapCreateError2(fromConvexError(err)), null];
2295
+ }
2296
+ const created = raw;
2297
+ if (!created) {
2298
+ return [
2299
+ new CapxulError({
2300
+ code: "NETWORK_ERROR",
2301
+ message: "organizations.withdrawals.create returned no withdrawal resource"
2302
+ }),
2303
+ null
2304
+ ];
2305
+ }
2306
+ return [null, created];
2307
+ },
2308
+ retrieve: async (input) => {
2309
+ if (!config.data) {
2310
+ return stub(
2311
+ "organizations.withdrawals.retrieve"
2312
+ );
2313
+ }
2314
+ const [err, raw] = await tryCatch(
2315
+ config.data.query(api.withdrawals.queries.retrieve, {
2316
+ withdrawalId: input.withdrawalId
2317
+ })
2318
+ );
2319
+ if (err) {
2320
+ return [fromConvexError(err), null];
2321
+ }
2322
+ const withdrawal = raw;
2323
+ if (!withdrawal) {
2324
+ return [
2325
+ new CapxulError({
2326
+ code: "NOT_FOUND",
2327
+ message: `withdrawal ${input.withdrawalId} not found`
2328
+ }),
2329
+ null
2330
+ ];
2331
+ }
2332
+ const ownerCheck = withdrawal.owner;
2333
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
2334
+ return [
2335
+ new CapxulError({
2336
+ code: "NOT_FOUND",
2337
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
2338
+ }),
2339
+ null
2340
+ ];
2341
+ }
2342
+ return [null, withdrawal];
2343
+ },
2344
+ list: async (input) => {
2345
+ if (!config.data) {
2346
+ return stub(
2347
+ "organizations.withdrawals.list"
2348
+ );
2349
+ }
2350
+ const [err, raw] = await tryCatch(
2351
+ config.data.query(api.withdrawals.queries.listOrg, {
2352
+ organizationId: input.organizationId,
2353
+ limit: input.limit,
2354
+ cursor: input.cursor
2355
+ })
2356
+ );
2357
+ if (err) {
2358
+ return [fromConvexError(err), null];
2359
+ }
2360
+ return [null, raw];
2361
+ }
2362
+ };
2363
+ }
2364
+ async function handleSubmissionFailure(config, withdrawalId, error) {
2365
+ await bestEffortMarkFailed2(config, withdrawalId, error);
2366
+ return [error, null];
1044
2367
  }
1045
- async function bestEffortMarkFailed(config, paymentId, error) {
1046
- try {
1047
- await config.data.mutation(api.payments.mutations.markFailed, {
1048
- paymentId,
2368
+ async function bestEffortMarkFailed2(config, withdrawalId, error) {
2369
+ await tryCatch(
2370
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
2371
+ withdrawalId,
1049
2372
  errorCode: error.code,
1050
- errorMessage: error.message,
1051
- source: "sdk"
1052
- });
1053
- } catch {
1054
- }
2373
+ errorMessage: error.message
2374
+ })
2375
+ );
1055
2376
  }
1056
- function mapCreateError(error) {
2377
+ function mapCreateError2(error) {
1057
2378
  switch (error.code) {
1058
2379
  case "NOT_AUTHENTICATED":
1059
2380
  case "PERMISSION_DENIED":
1060
2381
  case "INVALID_INPUT":
1061
- case "INVALID_RECIPIENT":
1062
2382
  case "INSUFFICIENT_BALANCE":
1063
2383
  case "IDEMPOTENCY_CONFLICT":
2384
+ case "KYC_REQUIRED":
2385
+ case "POLICY_DENIED":
1064
2386
  case "RATE_LIMITED":
1065
2387
  case "NETWORK_ERROR":
2388
+ case "NOT_FOUND":
2389
+ case "VERIFICATION_REQUIRED":
2390
+ return error;
2391
+ default:
2392
+ return new CapxulError({
2393
+ code: "NETWORK_ERROR",
2394
+ message: error.message,
2395
+ cause: error,
2396
+ details: error.details,
2397
+ operationId: error.operationId,
2398
+ correlationId: error.correlationId,
2399
+ retryable: error.retryable
2400
+ });
2401
+ }
2402
+ }
2403
+ function mapRecordCompletedError(error) {
2404
+ switch (error.code) {
2405
+ case "NOT_AUTHENTICATED":
2406
+ case "PERMISSION_DENIED":
2407
+ case "INVALID_INPUT":
2408
+ case "NOT_FOUND":
2409
+ case "NETWORK_ERROR":
2410
+ case "INTERNAL_ERROR":
1066
2411
  return error;
1067
2412
  default:
1068
2413
  return new CapxulError({
@@ -1077,341 +2422,308 @@ function mapCreateError(error) {
1077
2422
  }
1078
2423
  }
1079
2424
 
1080
- // src/core/transfers.ts
1081
- function createTransfersClient() {
2425
+ // src/core/webhook-endpoints.ts
2426
+ function createWebhookEndpointsClient() {
1082
2427
  return {
1083
- create: async () => stub("transfers.create"),
1084
- retrieve: async () => stub("transfers.retrieve"),
1085
- list: async () => stub("transfers.list"),
1086
- confirm: async () => stub("transfers.confirm"),
1087
- cancel: async () => stub("transfers.cancel")
2428
+ create: async () => stub(
2429
+ "webhookEndpoints.create"
2430
+ ),
2431
+ retrieve: async () => stub("webhookEndpoints.retrieve"),
2432
+ list: async () => stub("webhookEndpoints.list"),
2433
+ remove: async () => stub("webhookEndpoints.remove")
1088
2434
  };
1089
2435
  }
1090
- function createOrgTransfersClient() {
2436
+
2437
+ // src/core/webhook-events.ts
2438
+ function createWebhookEventsClient() {
1091
2439
  return {
1092
- create: async () => stub(
1093
- "organizations.transfers.create"
1094
- ),
1095
- retrieve: async () => stub("organizations.transfers.retrieve"),
1096
- list: async () => stub("organizations.transfers.list"),
1097
- confirm: async () => stub("organizations.transfers.confirm"),
1098
- cancel: async () => stub("organizations.transfers.cancel")
2440
+ retrieve: async () => stub("webhookEvents.retrieve"),
2441
+ list: async () => stub("webhookEvents.list")
1099
2442
  };
1100
2443
  }
1101
- var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1102
- function createWithdrawalsClient(config = {}) {
2444
+
2445
+ // src/core/organizations.ts
2446
+ function createOrgExternalAccountsClient(config) {
1103
2447
  return {
1104
2448
  create: async (input) => {
1105
2449
  if (!config.data) {
1106
- return stub("withdrawals.create");
1107
- }
1108
- let created = null;
1109
- let submitted = null;
1110
- try {
1111
- created = await config.data.mutation(
1112
- api.withdrawals.mutations.create,
1113
- {
1114
- amount: input.amount,
1115
- destination: {
1116
- externalAccountId: input.destination.externalAccountId,
1117
- kind: input.destination.kind
1118
- },
1119
- source: input.source,
1120
- reference: input.reference,
1121
- idempotencyKey: input.idempotencyKey
1122
- }
1123
- );
1124
- if (!created) {
1125
- return [
1126
- new CapxulError({
1127
- code: "NETWORK_ERROR",
1128
- message: "withdrawals.create returned no withdrawal resource"
1129
- }),
1130
- null
1131
- ];
1132
- }
1133
- if (created.status !== "processing" || created.operation.status !== "processing") {
1134
- return [null, created];
1135
- }
1136
- if (input.destination.kind !== "evm") {
1137
- return [null, created];
1138
- }
1139
- if (!config.signer || !config.signing) {
1140
- return [null, created];
1141
- }
1142
- if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1143
- throw new CapxulError({
1144
- code: "INVALID_INPUT",
1145
- message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1146
- details: { field: "destination.externalAccountId" }
1147
- });
1148
- }
1149
- const currentSigner = await config.data.query(
1150
- api.safe.queries.getMySignerAddress,
1151
- {}
1152
- );
1153
- if (!currentSigner?.address) {
1154
- throw new CapxulError({
1155
- code: "PERMISSION_DENIED",
1156
- message: "No signer is registered for the authenticated account.",
1157
- details: { withdrawalId: created.id }
1158
- });
1159
- }
1160
- if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1161
- throw new CapxulError({
1162
- code: "PERMISSION_DENIED",
1163
- message: "Configured signer does not match the authenticated account signer.",
1164
- details: {
1165
- withdrawalId: created.id,
1166
- expectedSignerAddress: currentSigner.address,
1167
- actualSignerAddress: config.signer.address
1168
- }
1169
- });
1170
- }
1171
- const submission = await config.data.query(
1172
- api.withdrawals.queries.prepareSubmission,
1173
- { withdrawalId: created.id }
2450
+ return stub(
2451
+ "organizations.externalAccounts.create"
1174
2452
  );
1175
- if (!submission?.externalAccountId) {
1176
- throw new CapxulError({
1177
- code: "NETWORK_ERROR",
1178
- message: "withdrawals.prepareSubmission returned no destination.",
1179
- details: { withdrawalId: created.id }
1180
- });
1181
- }
1182
- const transfer = await transferAsOwner(
1183
- {
1184
- signer: config.signer,
1185
- signing: config.signing
1186
- },
1187
- {
1188
- tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1189
- recipientAddress: submission.externalAccountId,
1190
- amount: toTokenUnits(submission.amount.value, 6)
1191
- }
2453
+ }
2454
+ const [err, raw] = await tryCatch(
2455
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
2456
+ organizationId: input.organizationId,
2457
+ kind: input.kind,
2458
+ label: input.label,
2459
+ address: input.address,
2460
+ iban: input.iban,
2461
+ bic: input.bic,
2462
+ accountHolder: input.accountHolder,
2463
+ network: input.network,
2464
+ panToken: input.panToken,
2465
+ last4: input.last4
2466
+ })
2467
+ );
2468
+ if (err) {
2469
+ return [
2470
+ fromConvexError(err),
2471
+ null
2472
+ ];
2473
+ }
2474
+ if (!raw) {
2475
+ return [
2476
+ new CapxulError({
2477
+ code: "NOT_FOUND",
2478
+ message: "external_account creation returned no resource"
2479
+ }),
2480
+ null
2481
+ ];
2482
+ }
2483
+ return [
2484
+ null,
2485
+ brandExternalAccount(
2486
+ raw
2487
+ )
2488
+ ];
2489
+ },
2490
+ list: async (input) => {
2491
+ if (!config.data) {
2492
+ return stub(
2493
+ "organizations.externalAccounts.list"
1192
2494
  );
1193
- if (!transfer.success) {
1194
- throw new CapxulError({
1195
- code: "NETWORK_ERROR",
1196
- message: "Bundler submission did not succeed.",
1197
- details: {
1198
- withdrawalId: created.id,
1199
- txHash: transfer.txHash,
1200
- userOpHash: transfer.userOpHash
1201
- }
1202
- });
2495
+ }
2496
+ const [err, result] = await tryCatch(
2497
+ config.data.query(api.externalAccounts.queries.listOrg, {
2498
+ organizationId: input.organizationId,
2499
+ limit: input.limit,
2500
+ cursor: input.cursor
2501
+ })
2502
+ );
2503
+ if (err) {
2504
+ return [fromConvexError(err), null];
2505
+ }
2506
+ const branded = result.data.map(
2507
+ (row) => brandExternalAccount(
2508
+ row
2509
+ )
2510
+ );
2511
+ return [
2512
+ null,
2513
+ {
2514
+ object: "list",
2515
+ data: branded,
2516
+ page: result.page
1203
2517
  }
1204
- submitted = {
1205
- txHash: transfer.txHash,
1206
- userOpHash: transfer.userOpHash
1207
- };
1208
- await config.data.mutation(
1209
- api.withdrawals.mutations.recordSubmitted,
1210
- {
1211
- withdrawalId: created.id,
1212
- txHash: transfer.txHash,
1213
- userOpHash: transfer.userOpHash
1214
- }
2518
+ ];
2519
+ },
2520
+ retrieve: async (input) => {
2521
+ if (!config.data) {
2522
+ return stub(
2523
+ "organizations.externalAccounts.retrieve"
1215
2524
  );
1216
- return [null, created];
1217
- } catch (cause) {
1218
- const error = mapCreateError2(fromConvexError(cause));
1219
- if (created?.id && created.status === "processing" && !submitted) {
1220
- await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1221
- }
1222
- if (submitted && created?.id) {
1223
- return [
1224
- new CapxulError({
1225
- code: "NETWORK_ERROR",
1226
- message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1227
- cause,
1228
- details: {
1229
- withdrawalId: created.id,
1230
- txHash: submitted.txHash,
1231
- userOpHash: submitted.userOpHash
1232
- }
1233
- }),
1234
- null
1235
- ];
1236
- }
1237
- return [error, null];
1238
2525
  }
2526
+ const [err, raw] = await tryCatch(
2527
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
2528
+ organizationId: input.organizationId,
2529
+ externalAccountId: input.externalAccountId
2530
+ })
2531
+ );
2532
+ if (err) {
2533
+ return [
2534
+ fromConvexError(err),
2535
+ null
2536
+ ];
2537
+ }
2538
+ if (!raw) {
2539
+ return [
2540
+ new CapxulError({
2541
+ code: "NOT_FOUND",
2542
+ message: `external_account ${input.externalAccountId} not found`
2543
+ }),
2544
+ null
2545
+ ];
2546
+ }
2547
+ return [
2548
+ null,
2549
+ brandExternalAccount(
2550
+ raw
2551
+ )
2552
+ ];
1239
2553
  },
1240
- retrieve: async (withdrawalId) => {
2554
+ remove: async (input) => {
1241
2555
  if (!config.data) {
1242
- return stub("withdrawals.retrieve");
2556
+ return stub(
2557
+ "organizations.externalAccounts.remove"
2558
+ );
1243
2559
  }
1244
- try {
1245
- const withdrawal = await config.data.query(
1246
- api.withdrawals.queries.retrieve,
1247
- { withdrawalId }
2560
+ const [err] = await tryCatch(
2561
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2562
+ organizationId: input.organizationId,
2563
+ externalAccountId: input.externalAccountId
2564
+ })
2565
+ );
2566
+ if (err) {
2567
+ return [
2568
+ fromConvexError(err),
2569
+ null
2570
+ ];
2571
+ }
2572
+ return [null, void 0];
2573
+ }
2574
+ };
2575
+ }
2576
+ function createOrgSubAccountsClient(config) {
2577
+ return {
2578
+ create: async (input) => {
2579
+ if (!config.data) {
2580
+ return stub(
2581
+ "organizations.subAccounts.create"
1248
2582
  );
1249
- if (!withdrawal) {
1250
- return [
1251
- new CapxulError({
1252
- code: "NOT_FOUND",
1253
- message: `withdrawal ${withdrawalId} not found`
1254
- }),
1255
- null
1256
- ];
1257
- }
1258
- return [null, withdrawal];
1259
- } catch (cause) {
2583
+ }
2584
+ const [err, raw] = await tryCatch(
2585
+ config.data.mutation(api.subAccounts.mutations.create, {
2586
+ parent: {
2587
+ kind: "organization",
2588
+ id: input.organizationId
2589
+ },
2590
+ name: input.name,
2591
+ purpose: input.purpose
2592
+ })
2593
+ );
2594
+ if (err) {
2595
+ return [
2596
+ fromConvexError(err),
2597
+ null
2598
+ ];
2599
+ }
2600
+ if (!raw) {
2601
+ return [
2602
+ new CapxulError({
2603
+ code: "NOT_FOUND",
2604
+ message: "sub_account creation returned no resource"
2605
+ }),
2606
+ null
2607
+ ];
2608
+ }
2609
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2610
+ if (brandErr) {
1260
2611
  return [
1261
- fromConvexError(cause),
2612
+ brandErr,
1262
2613
  null
1263
2614
  ];
1264
2615
  }
2616
+ return [null, branded];
1265
2617
  },
1266
2618
  list: async (input) => {
1267
2619
  if (!config.data) {
1268
- return stub("withdrawals.list");
1269
- }
1270
- try {
1271
- const result = await config.data.query(
1272
- api.withdrawals.queries.list,
1273
- {
1274
- limit: input?.limit,
1275
- cursor: input?.cursor
1276
- }
2620
+ return stub(
2621
+ "organizations.subAccounts.list"
1277
2622
  );
1278
- return [null, result];
1279
- } catch (cause) {
2623
+ }
2624
+ const [err, rows] = await tryCatch(
2625
+ config.data.query(api.subAccounts.queries.listByOrganization, {
2626
+ organizationId: input.organizationId
2627
+ })
2628
+ );
2629
+ if (err) {
1280
2630
  return [
1281
- fromConvexError(cause),
2631
+ fromConvexError(err),
1282
2632
  null
1283
2633
  ];
1284
2634
  }
1285
- }
1286
- };
1287
- }
1288
- function createOrgWithdrawalsClient(config = {}) {
1289
- return {
1290
- // Slice 1 ships personal-scope only end-to-end; org-scope create
1291
- // remains stubbed pending org-scoped backend mutation. List + retrieve
1292
- // are wired through the org-aware query.
1293
- create: async () => stub(
1294
- "organizations.withdrawals.create"
1295
- ),
1296
- retrieve: async (input) => {
1297
- if (!config.data) {
1298
- return stub(
1299
- "organizations.withdrawals.retrieve"
1300
- );
1301
- }
1302
- try {
1303
- const withdrawal = await config.data.query(
1304
- api.withdrawals.queries.retrieve,
1305
- { withdrawalId: input.withdrawalId }
1306
- );
1307
- if (!withdrawal) {
2635
+ const branded = [];
2636
+ for (const row of rows) {
2637
+ const [brandErr, value] = tryBrandSubAccount(row);
2638
+ if (brandErr) {
1308
2639
  return [
1309
- new CapxulError({
1310
- code: "NOT_FOUND",
1311
- message: `withdrawal ${input.withdrawalId} not found`
1312
- }),
2640
+ brandErr,
1313
2641
  null
1314
2642
  ];
1315
2643
  }
1316
- const ownerCheck = withdrawal.owner;
1317
- if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1318
- return [
1319
- new CapxulError({
1320
- code: "NOT_FOUND",
1321
- message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1322
- }),
1323
- null
1324
- ];
2644
+ branded.push(value);
2645
+ }
2646
+ return [
2647
+ null,
2648
+ {
2649
+ object: "list",
2650
+ data: branded,
2651
+ page: { hasMore: false }
1325
2652
  }
1326
- return [null, withdrawal];
1327
- } catch (cause) {
2653
+ ];
2654
+ },
2655
+ retrieve: async (input) => {
2656
+ if (!config.data) {
2657
+ return stub(
2658
+ "organizations.subAccounts.retrieve"
2659
+ );
2660
+ }
2661
+ const [err, raw] = await tryCatch(
2662
+ config.data.query(api.subAccounts.queries.retrieve, {
2663
+ subAccountId: input.subAccountId
2664
+ })
2665
+ );
2666
+ if (err) {
1328
2667
  return [
1329
- fromConvexError(cause),
2668
+ fromConvexError(err),
2669
+ null
2670
+ ];
2671
+ }
2672
+ if (!raw) {
2673
+ return [
2674
+ new CapxulError({
2675
+ code: "NOT_FOUND",
2676
+ message: `sub_account ${input.subAccountId} not found`
2677
+ }),
2678
+ null
2679
+ ];
2680
+ }
2681
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2682
+ if (brandErr) {
2683
+ return [
2684
+ brandErr,
1330
2685
  null
1331
2686
  ];
1332
2687
  }
2688
+ return [null, branded];
1333
2689
  },
1334
- list: async (input) => {
2690
+ remove: async (input) => {
1335
2691
  if (!config.data) {
1336
2692
  return stub(
1337
- "organizations.withdrawals.list"
2693
+ "organizations.subAccounts.remove"
1338
2694
  );
1339
2695
  }
1340
- try {
1341
- const result = await config.data.query(
1342
- api.withdrawals.queries.listOrg,
1343
- {
1344
- organizationId: input.organizationId,
1345
- limit: input.limit,
1346
- cursor: input.cursor
1347
- }
1348
- );
1349
- return [null, result];
1350
- } catch (cause) {
2696
+ const [err, raw] = await tryCatch(
2697
+ config.data.mutation(api.subAccounts.mutations.archive, {
2698
+ subAccountId: input.subAccountId
2699
+ })
2700
+ );
2701
+ if (err) {
1351
2702
  return [
1352
- fromConvexError(cause),
2703
+ fromConvexError(err),
2704
+ null
2705
+ ];
2706
+ }
2707
+ if (!raw) {
2708
+ return [
2709
+ new CapxulError({
2710
+ code: "NOT_FOUND",
2711
+ message: `sub_account ${input.subAccountId} not found`
2712
+ }),
2713
+ null
2714
+ ];
2715
+ }
2716
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2717
+ if (brandErr) {
2718
+ return [
2719
+ brandErr,
1353
2720
  null
1354
2721
  ];
1355
2722
  }
2723
+ return [null, branded];
1356
2724
  }
1357
2725
  };
1358
2726
  }
1359
- async function bestEffortMarkFailed2(config, withdrawalId, error) {
1360
- try {
1361
- await config.data.mutation(api.withdrawals.mutations.markFailed, {
1362
- withdrawalId,
1363
- errorCode: error.code,
1364
- errorMessage: error.message
1365
- });
1366
- } catch {
1367
- }
1368
- }
1369
- function mapCreateError2(error) {
1370
- switch (error.code) {
1371
- case "NOT_AUTHENTICATED":
1372
- case "PERMISSION_DENIED":
1373
- case "INVALID_INPUT":
1374
- case "INSUFFICIENT_BALANCE":
1375
- case "IDEMPOTENCY_CONFLICT":
1376
- case "KYC_REQUIRED":
1377
- case "POLICY_DENIED":
1378
- case "RATE_LIMITED":
1379
- case "NETWORK_ERROR":
1380
- return error;
1381
- default:
1382
- return new CapxulError({
1383
- code: "NETWORK_ERROR",
1384
- message: error.message,
1385
- cause: error,
1386
- details: error.details,
1387
- operationId: error.operationId,
1388
- correlationId: error.correlationId,
1389
- retryable: error.retryable
1390
- });
1391
- }
1392
- }
1393
-
1394
- // src/core/webhook-endpoints.ts
1395
- function createWebhookEndpointsClient() {
1396
- return {
1397
- create: async () => stub(
1398
- "webhookEndpoints.create"
1399
- ),
1400
- retrieve: async () => stub("webhookEndpoints.retrieve"),
1401
- list: async () => stub("webhookEndpoints.list"),
1402
- remove: async () => stub("webhookEndpoints.remove")
1403
- };
1404
- }
1405
-
1406
- // src/core/webhook-events.ts
1407
- function createWebhookEventsClient() {
1408
- return {
1409
- retrieve: async () => stub("webhookEvents.retrieve"),
1410
- list: async () => stub("webhookEvents.list")
1411
- };
1412
- }
1413
-
1414
- // src/core/organizations.ts
1415
2727
  function createOrganizationsClient(config = {}) {
1416
2728
  return {
1417
2729
  create: async () => stub("organizations.create"),
@@ -1461,24 +2773,8 @@ function createOrganizationsClient(config = {}) {
1461
2773
  start: async () => stub("organizations.kybProfile.start"),
1462
2774
  retrieve: async () => stub("organizations.kybProfile.retrieve")
1463
2775
  },
1464
- subAccounts: {
1465
- create: async () => stub("organizations.subAccounts.create"),
1466
- list: async () => stub("organizations.subAccounts.list"),
1467
- retrieve: async () => stub("organizations.subAccounts.retrieve"),
1468
- remove: async () => stub("organizations.subAccounts.remove")
1469
- },
1470
- externalAccounts: {
1471
- create: async () => stub(
1472
- "organizations.externalAccounts.create"
1473
- ),
1474
- list: async () => stub(
1475
- "organizations.externalAccounts.list"
1476
- ),
1477
- retrieve: async () => stub(
1478
- "organizations.externalAccounts.retrieve"
1479
- ),
1480
- remove: async () => stub("organizations.externalAccounts.remove")
1481
- },
2776
+ subAccounts: createOrgSubAccountsClient(config),
2777
+ externalAccounts: createOrgExternalAccountsClient(config),
1482
2778
  balanceLedger: {
1483
2779
  list: async () => stub(
1484
2780
  "organizations.balanceLedger.list"
@@ -1496,78 +2792,116 @@ function createOrganizationsClient(config = {}) {
1496
2792
  };
1497
2793
  }
1498
2794
 
1499
- // src/core/sub-accounts.ts
1500
- function createSubAccountsClient() {
1501
- return {
1502
- retrieve: async () => stub("subAccounts.retrieve"),
1503
- remove: async () => stub("subAccounts.remove")
1504
- };
1505
- }
1506
-
1507
- // src/core/virtual-accounts.ts
1508
- function createVirtualAccountsClient() {
2795
+ // src/core/token-transfers.ts
2796
+ var toTokenTransferId = (raw) => {
2797
+ if (typeof raw !== "string" || raw.length === 0) {
2798
+ throw new Error(
2799
+ `Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
2800
+ );
2801
+ }
2802
+ return raw;
2803
+ };
2804
+ function brandRow(row) {
1509
2805
  return {
1510
- create: async () => stub("virtualAccounts.create"),
1511
- retrieve: async () => stub("virtualAccounts.retrieve"),
1512
- list: async () => stub("virtualAccounts.list"),
1513
- remove: async () => stub("virtualAccounts.remove")
2806
+ ...row,
2807
+ id: toTokenTransferId(row.id)
1514
2808
  };
1515
2809
  }
1516
-
1517
- // src/core/virtual-cards.ts
1518
- function createVirtualCardsClient() {
2810
+ function createTokenTransfersClient(config = {}) {
1519
2811
  return {
1520
- create: async () => stub("virtualCards.create"),
1521
- retrieve: async () => stub("virtualCards.retrieve"),
1522
- list: async () => stub("virtualCards.list"),
1523
- freeze: async () => stub("virtualCards.freeze"),
1524
- unfreeze: async () => stub("virtualCards.unfreeze"),
1525
- cancel: async () => stub("virtualCards.cancel")
2812
+ list: async (input) => {
2813
+ if (!config.data) {
2814
+ return stub("tokenTransfers.list");
2815
+ }
2816
+ try {
2817
+ const raw = await config.data.query(
2818
+ api.tokenTransfers.queries.list,
2819
+ {
2820
+ limit: input?.limit,
2821
+ cursor: input?.cursor,
2822
+ direction: input?.direction
2823
+ }
2824
+ );
2825
+ if (!raw) {
2826
+ return [
2827
+ new CapxulError({
2828
+ code: "NOT_AUTHENTICATED",
2829
+ message: "tokenTransfers.list requires an authenticated session."
2830
+ }),
2831
+ null
2832
+ ];
2833
+ }
2834
+ return [
2835
+ null,
2836
+ {
2837
+ object: "list",
2838
+ data: raw.items.map(brandRow),
2839
+ page: {
2840
+ hasMore: raw.hasMore,
2841
+ nextCursor: raw.nextCursor
2842
+ },
2843
+ displayCurrency: raw.displayCurrency
2844
+ }
2845
+ ];
2846
+ } catch (cause) {
2847
+ return [fromConvexError(cause), null];
2848
+ }
2849
+ },
2850
+ retrieve: async (input) => {
2851
+ if (!config.data) {
2852
+ return stub("tokenTransfers.retrieve");
2853
+ }
2854
+ try {
2855
+ const raw = await config.data.query(
2856
+ api.tokenTransfers.queries.getByTxLogIndex,
2857
+ {
2858
+ txHash: input.txHash,
2859
+ logIndex: input.logIndex,
2860
+ chainId: input.chainId
2861
+ }
2862
+ );
2863
+ if (!raw) {
2864
+ return [
2865
+ new CapxulError({
2866
+ code: "NOT_FOUND",
2867
+ message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
2868
+ details: {
2869
+ txHash: input.txHash,
2870
+ logIndex: input.logIndex,
2871
+ chainId: input.chainId
2872
+ }
2873
+ }),
2874
+ null
2875
+ ];
2876
+ }
2877
+ return [null, brandRow(raw)];
2878
+ } catch (cause) {
2879
+ return [fromConvexError(cause), null];
2880
+ }
2881
+ }
1526
2882
  };
1527
2883
  }
1528
-
1529
- // ../observability/src/debug-log.ts
1530
- function isDevelopmentBuild() {
1531
- if (typeof process === "undefined") {
1532
- return false;
1533
- }
1534
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1535
- }
1536
- function debugLog(line) {
1537
- if (!isDevelopmentBuild()) return;
1538
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1539
- console.info(line);
1540
- return;
1541
- }
1542
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1543
- process.stderr.write(`${line}
1544
- `);
1545
- }
1546
- }
1547
- function formatDebugValue(value) {
1548
- if (value === void 0 || value === "") return "";
1549
- if (typeof value === "string") return value;
1550
- try {
1551
- return JSON.stringify(value);
1552
- } catch {
1553
- return String(value);
1554
- }
1555
- }
1556
- function track(...args) {
1557
- const [name, props] = args;
1558
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1559
- }
1560
- function formatDebugValue2(value) {
1561
- if (value === void 0 || value === "") return "";
1562
- if (typeof value === "string") return value;
1563
- try {
1564
- return JSON.stringify(value);
1565
- } catch {
1566
- return String(value);
1567
- }
1568
- }
1569
- function identify(userId, traits) {
1570
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
2884
+
2885
+ // src/core/virtual-accounts.ts
2886
+ function createVirtualAccountsClient() {
2887
+ return {
2888
+ create: async () => stub("virtualAccounts.create"),
2889
+ retrieve: async () => stub("virtualAccounts.retrieve"),
2890
+ list: async () => stub("virtualAccounts.list"),
2891
+ remove: async () => stub("virtualAccounts.remove")
2892
+ };
2893
+ }
2894
+
2895
+ // src/core/virtual-cards.ts
2896
+ function createVirtualCardsClient() {
2897
+ return {
2898
+ create: async () => stub("virtualCards.create"),
2899
+ retrieve: async () => stub("virtualCards.retrieve"),
2900
+ list: async () => stub("virtualCards.list"),
2901
+ freeze: async () => stub("virtualCards.freeze"),
2902
+ unfreeze: async () => stub("virtualCards.unfreeze"),
2903
+ cancel: async () => stub("virtualCards.cancel")
2904
+ };
1571
2905
  }
1572
2906
  function createAuthFlowMachine(client) {
1573
2907
  return setup({
@@ -1588,7 +2922,7 @@ function createAuthFlowMachine(client) {
1588
2922
  }),
1589
2923
  verifyOtp: fromPromise(
1590
2924
  async ({ input, signal }) => {
1591
- const [error, session] = await client.auth.verifyOtp(
2925
+ const [error, result] = await client.auth.verifyOtp(
1592
2926
  {
1593
2927
  email: input.email,
1594
2928
  otp: input.code
@@ -1596,7 +2930,14 @@ function createAuthFlowMachine(client) {
1596
2930
  { signal }
1597
2931
  );
1598
2932
  if (error) throw error;
1599
- return session;
2933
+ if (result.kind === "bootstrap_required") {
2934
+ throw new CapxulError({
2935
+ code: "ACTION_REQUIRED",
2936
+ message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
2937
+ details: { reason: result.reason }
2938
+ });
2939
+ }
2940
+ return result.session;
1600
2941
  }
1601
2942
  ),
1602
2943
  signOut: fromPromise(async () => {
@@ -1844,23 +3185,345 @@ function emailDomain(email) {
1844
3185
  const domain = email.split("@")[1]?.trim().toLowerCase();
1845
3186
  return domain || "unknown";
1846
3187
  }
1847
-
1848
- // ../platform-kernel/src/ids.ts
1849
- function makePrefixedIdConstructor(prefix, fieldName) {
1850
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
1851
- return (raw) => {
1852
- if (typeof raw !== "string" || !re.test(raw)) {
1853
- throw new Error(
1854
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
1855
- );
3188
+ var initialContext = {
3189
+ email: null,
3190
+ code: null,
3191
+ username: null,
3192
+ signerProvider: null,
3193
+ bootstrapToken: null,
3194
+ bootstrapReason: null,
3195
+ session: null,
3196
+ account: null,
3197
+ safe: null,
3198
+ error: null
3199
+ };
3200
+ function createAuthBootstrapFlowMachine(client) {
3201
+ return setup({
3202
+ types: {},
3203
+ actors: {
3204
+ sendOtp: fromPromise(async ({ input, signal }) => {
3205
+ const [error] = await client.auth.sendOtp(
3206
+ { email: input.email },
3207
+ { signal }
3208
+ );
3209
+ if (error) throw error;
3210
+ }),
3211
+ verifyOtp: fromPromise(
3212
+ async ({ input, signal }) => {
3213
+ const [error, result] = await client.auth.verifyOtp(
3214
+ { email: input.email, otp: input.code },
3215
+ { signal }
3216
+ );
3217
+ if (error) throw error;
3218
+ return result;
3219
+ }
3220
+ ),
3221
+ completeBootstrap: fromPromise(async ({ input }) => {
3222
+ const [error, result] = await client.auth.completeBootstrap(input);
3223
+ if (error) throw error;
3224
+ return result;
3225
+ }),
3226
+ signOut: fromPromise(async () => {
3227
+ const [error] = await client.auth.signOut();
3228
+ if (error) throw error;
3229
+ })
3230
+ },
3231
+ actions: {
3232
+ trackOtpRequested: ({ context }) => {
3233
+ if (!context.email) return;
3234
+ track("auth_otp_requested", {
3235
+ email_domain: emailDomain2(context.email)
3236
+ });
3237
+ },
3238
+ trackFailed: ({ event }) => {
3239
+ track("auth_failed", {
3240
+ auth_type: "email_otp",
3241
+ reason: errorFromEvent2(event).code
3242
+ });
3243
+ },
3244
+ trackTimeoutFailed: () => {
3245
+ track("auth_failed", {
3246
+ auth_type: "email_otp",
3247
+ reason: "timeout"
3248
+ });
3249
+ },
3250
+ trackVerified: () => {
3251
+ track("auth_verified", { auth_type: "email_otp" });
3252
+ },
3253
+ trackBootstrapRequired: ({ context }) => {
3254
+ track("auth_verified", {
3255
+ auth_type: "email_otp",
3256
+ auth_mode: context.bootstrapReason ?? "bootstrap_required"
3257
+ });
3258
+ },
3259
+ identifyAndTrack: ({ context }) => {
3260
+ if (!context.session) return;
3261
+ identify(context.session.authUserId, {
3262
+ email_domain: emailDomain2(context.session.email)
3263
+ });
3264
+ track("auth_identified", {
3265
+ email_domain: emailDomain2(context.session.email)
3266
+ });
3267
+ },
3268
+ trackSignedOut: () => {
3269
+ track("auth_signed_out");
3270
+ }
1856
3271
  }
1857
- return raw;
1858
- };
3272
+ }).createMachine({
3273
+ id: "authBootstrap",
3274
+ initial: "email",
3275
+ context: initialContext,
3276
+ states: {
3277
+ email: {
3278
+ on: {
3279
+ ENTER_EMAIL: {
3280
+ actions: assign({
3281
+ email: ({ event }) => event.email,
3282
+ error: () => null
3283
+ })
3284
+ },
3285
+ REQUEST_OTP: { target: "sending_otp" }
3286
+ }
3287
+ },
3288
+ sending_otp: {
3289
+ invoke: {
3290
+ src: "sendOtp",
3291
+ input: ({ context }) => ({ email: requireEmail2(context) }),
3292
+ onDone: { target: "otp_requested", actions: "trackOtpRequested" },
3293
+ onError: {
3294
+ target: "otp_requested",
3295
+ actions: [
3296
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
3297
+ "trackFailed"
3298
+ ]
3299
+ }
3300
+ },
3301
+ after: {
3302
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3303
+ target: "otp_requested",
3304
+ actions: [
3305
+ assign({ error: () => timeoutError2("sending_otp") }),
3306
+ "trackTimeoutFailed"
3307
+ ]
3308
+ }
3309
+ }
3310
+ },
3311
+ otp_requested: {
3312
+ on: {
3313
+ ENTER_OTP: {
3314
+ actions: assign({
3315
+ code: ({ event }) => event.code,
3316
+ error: () => null
3317
+ })
3318
+ },
3319
+ VERIFY_OTP: { target: "verifying_otp" },
3320
+ BACK: { target: "email" },
3321
+ RESET: { target: "email", actions: assign(() => initialContext) }
3322
+ }
3323
+ },
3324
+ verifying_otp: {
3325
+ invoke: {
3326
+ src: "verifyOtp",
3327
+ input: ({ context }) => ({
3328
+ email: requireEmail2(context),
3329
+ code: requireCode(context)
3330
+ }),
3331
+ onDone: [
3332
+ {
3333
+ guard: ({ event }) => event.output.kind === "existing_member",
3334
+ target: "authenticated",
3335
+ actions: [
3336
+ assign({
3337
+ session: ({ event }) => event.output.session,
3338
+ account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
3339
+ username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
3340
+ safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
3341
+ email: () => null,
3342
+ error: () => null
3343
+ }),
3344
+ "trackVerified",
3345
+ "identifyAndTrack"
3346
+ ]
3347
+ },
3348
+ {
3349
+ target: "bootstrap_required",
3350
+ actions: [
3351
+ assign({
3352
+ session: ({ event }) => event.output.session,
3353
+ bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
3354
+ bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
3355
+ username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
3356
+ email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
3357
+ error: () => null
3358
+ }),
3359
+ "trackVerified",
3360
+ "trackBootstrapRequired"
3361
+ ]
3362
+ }
3363
+ ],
3364
+ onError: {
3365
+ target: "otp_requested",
3366
+ actions: [
3367
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
3368
+ "trackFailed"
3369
+ ]
3370
+ }
3371
+ },
3372
+ after: {
3373
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3374
+ target: "otp_requested",
3375
+ actions: [
3376
+ assign({ error: () => timeoutError2("verifying_otp") }),
3377
+ "trackTimeoutFailed"
3378
+ ]
3379
+ }
3380
+ }
3381
+ },
3382
+ bootstrap_required: {
3383
+ on: {
3384
+ ENTER_USERNAME: {
3385
+ actions: assign({
3386
+ username: ({ event }) => event.username,
3387
+ error: () => null
3388
+ })
3389
+ },
3390
+ ENTER_SIGNER_PROVIDER: {
3391
+ actions: assign({
3392
+ signerProvider: ({ event }) => event.signerProvider,
3393
+ error: () => null
3394
+ })
3395
+ },
3396
+ COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3397
+ BACK: { target: "otp_requested" },
3398
+ RESET: { target: "email", actions: assign(() => initialContext) }
3399
+ }
3400
+ },
3401
+ completing_bootstrap: {
3402
+ invoke: {
3403
+ src: "completeBootstrap",
3404
+ input: ({ context }) => ({
3405
+ bootstrapToken: requireBootstrapToken(context),
3406
+ username: requireUsername(context),
3407
+ signerProvider: requireSignerProvider(context)
3408
+ }),
3409
+ onDone: {
3410
+ target: "authenticated",
3411
+ actions: [
3412
+ assign({
3413
+ session: ({ event }) => event.output.session,
3414
+ account: ({ event }) => event.output.account,
3415
+ username: ({ event }) => event.output.username,
3416
+ safe: ({ event }) => event.output.safe,
3417
+ bootstrapToken: () => null,
3418
+ bootstrapReason: () => null,
3419
+ signerProvider: () => null,
3420
+ email: () => null,
3421
+ error: () => null
3422
+ }),
3423
+ "identifyAndTrack"
3424
+ ]
3425
+ },
3426
+ onError: {
3427
+ target: "bootstrap_required",
3428
+ actions: [
3429
+ assign({ error: ({ event }) => errorFromEvent2(event) }),
3430
+ "trackFailed"
3431
+ ]
3432
+ }
3433
+ },
3434
+ after: {
3435
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3436
+ target: "bootstrap_required",
3437
+ actions: [
3438
+ assign({ error: () => timeoutError2("completing_bootstrap") }),
3439
+ "trackTimeoutFailed"
3440
+ ]
3441
+ }
3442
+ }
3443
+ },
3444
+ authenticated: {
3445
+ on: {
3446
+ SIGN_OUT: { target: "signing_out" }
3447
+ }
3448
+ },
3449
+ signing_out: {
3450
+ invoke: {
3451
+ src: "signOut",
3452
+ onDone: {
3453
+ target: "email",
3454
+ actions: [
3455
+ assign(() => initialContext),
3456
+ "trackSignedOut"
3457
+ ]
3458
+ },
3459
+ onError: {
3460
+ target: "error",
3461
+ actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3462
+ }
3463
+ }
3464
+ },
3465
+ error: {
3466
+ on: {
3467
+ RESET: { target: "email", actions: assign(() => initialContext) }
3468
+ }
3469
+ }
3470
+ }
3471
+ });
3472
+ }
3473
+ function requireEmail2(context) {
3474
+ if (!context.email) {
3475
+ throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3476
+ }
3477
+ return context.email;
3478
+ }
3479
+ function requireCode(context) {
3480
+ if (!context.code) {
3481
+ throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3482
+ }
3483
+ return context.code;
3484
+ }
3485
+ function requireBootstrapToken(context) {
3486
+ if (!context.bootstrapToken) {
3487
+ throw Errors.invalidInput(
3488
+ "bootstrapToken",
3489
+ "Auth bootstrap requires a continuation token."
3490
+ );
3491
+ }
3492
+ return context.bootstrapToken;
3493
+ }
3494
+ function requireUsername(context) {
3495
+ if (!context.username) {
3496
+ throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3497
+ }
3498
+ return context.username;
3499
+ }
3500
+ function requireSignerProvider(context) {
3501
+ if (!context.signerProvider) {
3502
+ throw Errors.invalidInput(
3503
+ "signerProvider",
3504
+ "Auth bootstrap requires a signer provider."
3505
+ );
3506
+ }
3507
+ return context.signerProvider;
3508
+ }
3509
+ function errorFromEvent2(event) {
3510
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3511
+ if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3512
+ return cause;
3513
+ }
3514
+ return Errors.providerError("auth", "bootstrap", cause);
3515
+ }
3516
+ function timeoutError2(state) {
3517
+ return Errors.providerError(
3518
+ "auth",
3519
+ "bootstrap",
3520
+ new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3521
+ );
3522
+ }
3523
+ function emailDomain2(email) {
3524
+ const domain = email.split("@")[1]?.trim().toLowerCase();
3525
+ return domain || "unknown";
1859
3526
  }
1860
- var toOperationId = makePrefixedIdConstructor(
1861
- "op",
1862
- "operationId"
1863
- );
1864
3527
  function createProvisioningMachine(client) {
1865
3528
  return setup({
1866
3529
  types: {},
@@ -1933,13 +3596,13 @@ function createProvisioningMachine(client) {
1933
3596
  },
1934
3597
  onError: {
1935
3598
  target: "error",
1936
- actions: assign({ error: ({ event }) => errorFromEvent2(event) })
3599
+ actions: assign({ error: ({ event }) => errorFromEvent3(event) })
1937
3600
  }
1938
3601
  },
1939
3602
  after: {
1940
3603
  [FLOW_INVOKE_TIMEOUT_MS]: {
1941
3604
  target: "error",
1942
- actions: assign({ error: () => timeoutError2() })
3605
+ actions: assign({ error: () => timeoutError3() })
1943
3606
  }
1944
3607
  }
1945
3608
  },
@@ -1957,7 +3620,7 @@ function createProvisioningMachine(client) {
1957
3620
  * this payload on its `onDone` transition and branches via guards
1958
3621
  * on `event.output.error`.
1959
3622
  */
1960
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError2() }
3623
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
1961
3624
  });
1962
3625
  }
1963
3626
  function requireProvisionInput(context) {
@@ -1969,13 +3632,13 @@ function requireProvisionInput(context) {
1969
3632
  }
1970
3633
  return context.input;
1971
3634
  }
1972
- function errorFromEvent2(event) {
3635
+ function errorFromEvent3(event) {
1973
3636
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
1974
3637
  if (cause instanceof CapxulError) return cause;
1975
3638
  if (cause instanceof CapxulError2) return cause;
1976
3639
  return Errors.providerError("provisioning", "flow", cause);
1977
3640
  }
1978
- function timeoutError2() {
3641
+ function timeoutError3() {
1979
3642
  return Errors.providerError(
1980
3643
  "provisioning",
1981
3644
  "flow",
@@ -2068,7 +3731,7 @@ function createOnboardingFlowMachine(client) {
2068
3731
  error: ({ event }) => extractChildErrorOrFallback(event)
2069
3732
  }),
2070
3733
  assignChildThrown: assign({
2071
- error: ({ event }) => errorFromEvent3(event)
3734
+ error: ({ event }) => errorFromEvent4(event)
2072
3735
  }),
2073
3736
  assignAccountFromChild: assign({
2074
3737
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -2230,7 +3893,7 @@ function extractChildAccountOrNull(event) {
2230
3893
  if (output && "account" in output && output.account) return output.account;
2231
3894
  return null;
2232
3895
  }
2233
- function errorFromEvent3(event) {
3896
+ function errorFromEvent4(event) {
2234
3897
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2235
3898
  if (cause instanceof CapxulError) return cause;
2236
3899
  if (cause instanceof CapxulError2) return cause;
@@ -2247,12 +3910,13 @@ function createCapxulClient(config = {}) {
2247
3910
  organizations: createOrganizationsClient(config),
2248
3911
  payments: createPaymentsClient(config),
2249
3912
  transfers: createTransfersClient(),
3913
+ tokenTransfers: createTokenTransfersClient(config),
2250
3914
  withdrawals: createWithdrawalsClient(config),
2251
3915
  documents: createDocumentsClient(),
2252
- subAccounts: createSubAccountsClient(),
3916
+ subAccounts: createSubAccountsClient(config),
2253
3917
  virtualAccounts: createVirtualAccountsClient(),
2254
3918
  virtualCards: createVirtualCardsClient(),
2255
- externalAccounts: createExternalAccountsClient(),
3919
+ externalAccounts: createExternalAccountsClient(config),
2256
3920
  operations: createOperationsClient(config),
2257
3921
  webhookEndpoints: createWebhookEndpointsClient(),
2258
3922
  webhookEvents: createWebhookEventsClient(),
@@ -2261,6 +3925,7 @@ function createCapxulClient(config = {}) {
2261
3925
  const client = clientWithoutFlows;
2262
3926
  client.flows = {
2263
3927
  auth: () => createAuthFlowMachine(client),
3928
+ authBootstrap: () => createAuthBootstrapFlowMachine(client),
2264
3929
  onboarding: () => createOnboardingFlowMachine(client),
2265
3930
  provisioning: () => createProvisioningMachine(client)
2266
3931
  };