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