@capxul/sdk 0.1.0-alpha.8 → 0.2.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,10 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var server = require('convex/server');
4
- var viem = require('viem');
5
4
  var accounts$1 = require('permissionless/accounts');
5
+ var viem = require('viem');
6
6
  var accountAbstraction = require('viem/account-abstraction');
7
7
  var chains = require('viem/chains');
8
+ var safeDerive = require('@repo/safe-derive');
9
+ var browser = require('convex/browser');
8
10
  var xstate = require('xstate');
9
11
  var accounts = require('viem/accounts');
10
12
 
@@ -123,6 +125,160 @@ function identify(userId, traits) {
123
125
  debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
124
126
  }
125
127
 
128
+ // ../config/src/chain.ts
129
+ var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
130
+ var TEST_USDC_ADDRESS = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
131
+ var CAPXUL_API_BASE_URL = "https://elated-oyster-269.convex.site";
132
+
133
+ // ../config/src/timing.ts
134
+ var FLOW_INVOKE_TIMEOUT_MS = 3e4;
135
+ var WEBHOOK_FRESHNESS_WINDOW_MS = 5 * 60 * 1e3;
136
+
137
+ // ../config/src/errors.ts
138
+ var CapxulError2 = class extends Error {
139
+ code;
140
+ details;
141
+ correlationId;
142
+ layer;
143
+ constructor(code, message, options) {
144
+ super(message, options?.cause ? { cause: options.cause } : void 0);
145
+ this.code = code;
146
+ this.details = options?.details;
147
+ this.correlationId = options?.correlationId;
148
+ this.layer = options?.layer;
149
+ }
150
+ };
151
+ var Errors = {
152
+ notAuthenticated: () => new CapxulError2("NOT_AUTHENTICATED", "Not authenticated"),
153
+ profileNotFound: (authUserId) => new CapxulError2("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`),
154
+ smartAccountMissing: (authUserId) => new CapxulError2("SMART_ACCOUNT_MISSING", `Smart account not provisioned for user ${authUserId}`),
155
+ envMissing: (name) => new CapxulError2("ENV_MISSING", `Environment variable ${name} not configured`),
156
+ openfortApi: (operation, cause) => new CapxulError2(
157
+ "PROVIDER_ERROR",
158
+ `Openfort ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
159
+ { cause, details: { provider: "openfort", operation } }
160
+ ),
161
+ shieldApi: (status, detail) => new CapxulError2(
162
+ "PROVIDER_ERROR",
163
+ `Shield API error (${status}): ${detail}`,
164
+ { details: { provider: "shield", status } }
165
+ ),
166
+ providerError: (provider, operation, cause) => (
167
+ // Public `message` is redacted to a fixed shape so provider-side
168
+ // exception text never leaks to the client. The original `cause`
169
+ // is preserved on `Error.cause` for server-side debugging via
170
+ // observability sinks (Sentry, console traces).
171
+ new CapxulError2(
172
+ "PROVIDER_ERROR",
173
+ `Provider error: ${provider} ${operation}`,
174
+ { cause, details: { provider, operation } }
175
+ )
176
+ ),
177
+ invalidInput: (field, reason) => new CapxulError2(
178
+ "INVALID_INPUT",
179
+ `Invalid ${field}: ${reason}`,
180
+ { details: { field, reason } }
181
+ ),
182
+ playerNotFound: (playerId) => new CapxulError2(
183
+ "PLAYER_NOT_FOUND",
184
+ playerId ? `Openfort player ${playerId} not found` : "Openfort player not found"
185
+ ),
186
+ accountNotFound: (accountId) => new CapxulError2(
187
+ "ACCOUNT_NOT_FOUND",
188
+ accountId ? `Openfort account ${accountId} not found` : "Openfort smart account not found"
189
+ ),
190
+ invalidRecipient: (reason) => new CapxulError2("INVALID_RECIPIENT", reason),
191
+ permissionDenied: (reason = "Permission denied") => new CapxulError2("PERMISSION_DENIED", reason),
192
+ notFound: (resource, id) => new CapxulError2(
193
+ "NOT_FOUND",
194
+ id ? `${resource} ${id} not found` : `${resource} not found`
195
+ ),
196
+ idempotencyConflict: (details) => new CapxulError2(
197
+ "IDEMPOTENCY_CONFLICT",
198
+ "Idempotency key was already used for a different request",
199
+ { details }
200
+ ),
201
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
202
+ details
203
+ }),
204
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
205
+ details: { ...details }
206
+ }),
207
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
208
+ /**
209
+ * Verification gate. Surfaced when a request hits a verification
210
+ * boundary the actor cannot cross under their current state. Two
211
+ * variants share this code:
212
+ *
213
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
214
+ * `external_account.kind` routes to a withdrawal rail (e.g.
215
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
216
+ * `details.rail` + `details.currentKind`.
217
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
218
+ * the required tier. Carries `details.requiredTier`.
219
+ *
220
+ * Code is shared because both expose the same UX shape ("you cannot
221
+ * proceed until verification advances"); the `details.*` keys
222
+ * differentiate the route.
223
+ */
224
+ verificationRequired: (details) => {
225
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
226
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
227
+ details: { ...details }
228
+ });
229
+ }
230
+ };
231
+
232
+ // ../config/src/safe.ts
233
+ var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
234
+ var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
235
+ var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
236
+ var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
237
+
238
+ // ../config/src/org-roles.ts
239
+ function roleKeyFromLabel(label) {
240
+ const bytes = new TextEncoder().encode(label);
241
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
242
+ return "0x" + hex.padEnd(64, "0");
243
+ }
244
+ roleKeyFromLabel("OWNER");
245
+ roleKeyFromLabel("FINANCE_MANAGER");
246
+ roleKeyFromLabel("PAYMENTS_OPERATOR");
247
+ async function buildSafeAccount(signer, chain) {
248
+ try {
249
+ const publicClient = viem.createPublicClient({
250
+ chain: chains.baseSepolia,
251
+ transport: viem.http(chain.rpcUrl)
252
+ });
253
+ return await accounts$1.toSafeSmartAccount({
254
+ client: publicClient,
255
+ entryPoint: { address: accountAbstraction.entryPoint07Address, version: "0.7" },
256
+ version: "1.4.1",
257
+ owners: [signer],
258
+ saltNonce: computeSaltNonce(signer.address),
259
+ safeSingletonAddress: SAFE_L2_SINGLETON,
260
+ safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
261
+ safeModuleSetupAddress: SAFE_MODULE_SETUP,
262
+ safe4337ModuleAddress: SAFE_4337_MODULE,
263
+ safeModules: [],
264
+ setupTransactions: []
265
+ });
266
+ } catch (cause) {
267
+ throw new CapxulError({
268
+ code: "NETWORK_ERROR",
269
+ message: cause instanceof Error ? cause.message : String(cause),
270
+ cause,
271
+ details: { chainId: chain.chainId }
272
+ });
273
+ }
274
+ }
275
+ function computeSaltNonce(ownerAddress) {
276
+ return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
277
+ }
278
+ function deriveSafeAddress(signerAddress) {
279
+ return safeDerive.deriveSafeAddress(signerAddress, safeDerive.defaultSafeDeriveConfig);
280
+ }
281
+
126
282
  // ../platform-kernel/src/ids.ts
127
283
  function makePrefixedIdConstructor(prefix, fieldName) {
128
284
  const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
@@ -141,7 +297,7 @@ var toAccountId = makePrefixedIdConstructor(
141
297
  );
142
298
  var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
143
299
  var toMemberId = makePrefixedIdConstructor(
144
- "mem",
300
+ "mb",
145
301
  "memberId"
146
302
  );
147
303
  var toSafeId = makePrefixedIdConstructor(
@@ -255,13 +411,13 @@ function brandExternalAccount(raw) {
255
411
  function createExternalAccountsClient(config = {}) {
256
412
  return {
257
413
  retrieve: async (externalAccountId) => {
258
- if (!config.data) {
414
+ if (!config._data) {
259
415
  return stub(
260
416
  "externalAccounts.retrieve"
261
417
  );
262
418
  }
263
419
  const [err, raw] = await tryCatch(
264
- config.data.query(api.externalAccounts.queries.retrievePersonal, {
420
+ config._data.query(api.externalAccounts.queries.retrievePersonal, {
265
421
  externalAccountId
266
422
  })
267
423
  );
@@ -283,11 +439,11 @@ function createExternalAccountsClient(config = {}) {
283
439
  return [null, brandExternalAccount(raw)];
284
440
  },
285
441
  remove: async (externalAccountId) => {
286
- if (!config.data) {
442
+ if (!config._data) {
287
443
  return stub("externalAccounts.remove");
288
444
  }
289
445
  const [err] = await tryCatch(
290
- config.data.mutation(api.externalAccounts.mutations.removePersonal, {
446
+ config._data.mutation(api.externalAccounts.mutations.removePersonal, {
291
447
  externalAccountId
292
448
  })
293
449
  );
@@ -302,17 +458,203 @@ function createExternalAccountsClient(config = {}) {
302
458
  };
303
459
  }
304
460
 
461
+ // src/core/sub-accounts.ts
462
+ function malformedWireError(reason, raw) {
463
+ return new CapxulError({
464
+ code: "PROVIDER_ERROR",
465
+ message: `convex brandSubAccount failed: ${reason}`,
466
+ details: {
467
+ provider: "convex",
468
+ operation: "brandSubAccount",
469
+ reason,
470
+ // PII discipline (`.claude/rules/posthog.md`): user-authored
471
+ // strings on the wire (`name`, `purpose`) are customer-confidential
472
+ // — sub-account names like "Q3 Acquisition Reserve" or
473
+ // "Vendor ABC payments" must not flow into telemetry. We emit a
474
+ // structural keys-only sample via a strict ALLOWLIST so any future
475
+ // wire-shape addition (e.g. `recipientEmail`, `tags`) is dropped
476
+ // by construction rather than leaked through a denylist gap.
477
+ sample: safeSampleShape(raw)
478
+ }
479
+ });
480
+ }
481
+ function safeSampleShape(raw) {
482
+ if (raw === null || typeof raw !== "object") {
483
+ return { type: typeof raw };
484
+ }
485
+ const r = raw;
486
+ const balance = r.balance;
487
+ return {
488
+ object: typeof r.object === "string" ? r.object : typeof r.object,
489
+ idPresent: typeof r.id === "string" && r.id.length > 0,
490
+ // First 4 chars only — enough to distinguish "sub_" prefixed IDs
491
+ // from accidental other resource IDs without leaking the full ID.
492
+ idPrefix: typeof r.id === "string" ? r.id.slice(0, 4) : void 0,
493
+ parentKind: r.parent !== null && typeof r.parent === "object" ? r.parent.kind : typeof r.parent,
494
+ status: r.status,
495
+ hasName: typeof r.name === "string",
496
+ hasPurpose: r.purpose !== void 0,
497
+ balanceShape: balance !== null && typeof balance === "object" ? Object.keys(balance).sort() : typeof balance,
498
+ createdAtType: typeof r.createdAt,
499
+ updatedAtType: typeof r.updatedAt
500
+ };
501
+ }
502
+ function isMoneyShape(v) {
503
+ if (typeof v !== "object" || v === null) return false;
504
+ const m = v;
505
+ return typeof m.value === "string" && typeof m.currency === "string";
506
+ }
507
+ function isParentShape(v) {
508
+ if (typeof v !== "object" || v === null) return false;
509
+ const p = v;
510
+ return (p.kind === "account" || p.kind === "organization") && typeof p.id === "string";
511
+ }
512
+ function isFiniteNonNegativeInteger(v) {
513
+ return typeof v === "number" && Number.isFinite(v) && Number.isInteger(v) && v >= 0;
514
+ }
515
+ function validateWireSubAccount(raw) {
516
+ if (typeof raw !== "object" || raw === null) {
517
+ return { ok: false, reason: "not an object" };
518
+ }
519
+ const r = raw;
520
+ if (r.object !== "sub_account") {
521
+ return { ok: false, reason: `object must be "sub_account" (got ${String(r.object)})` };
522
+ }
523
+ if (typeof r.id !== "string" || r.id.length === 0) {
524
+ return { ok: false, reason: "id must be a non-empty string" };
525
+ }
526
+ if (!isParentShape(r.parent)) {
527
+ return { ok: false, reason: "parent must be { kind: 'account' | 'organization', id: string }" };
528
+ }
529
+ if (typeof r.name !== "string") {
530
+ return { ok: false, reason: "name must be a string" };
531
+ }
532
+ if (r.purpose !== void 0 && typeof r.purpose !== "string") {
533
+ return { ok: false, reason: "purpose must be a string when present" };
534
+ }
535
+ if (r.status !== "active" && r.status !== "archived") {
536
+ return { ok: false, reason: `status must be "active" | "archived" (got ${String(r.status)})` };
537
+ }
538
+ if (!isMoneyShape(r.balance)) {
539
+ return { ok: false, reason: "balance must be { value: string, currency: string }" };
540
+ }
541
+ if (!isFiniteNonNegativeInteger(r.createdAt)) {
542
+ return { ok: false, reason: "createdAt must be a finite non-negative integer (epoch ms)" };
543
+ }
544
+ if (r.updatedAt !== void 0 && !isFiniteNonNegativeInteger(r.updatedAt)) {
545
+ return { ok: false, reason: "updatedAt must be a finite non-negative integer when present" };
546
+ }
547
+ return { ok: true, value: r };
548
+ }
549
+ function brandSubAccount(raw) {
550
+ const result = validateWireSubAccount(raw);
551
+ if (!result.ok) {
552
+ throw malformedWireError(result.reason, raw);
553
+ }
554
+ const wire = result.value;
555
+ return {
556
+ object: wire.object,
557
+ id: toSubAccountId(wire.id),
558
+ parent: wire.parent,
559
+ name: wire.name,
560
+ ...wire.purpose !== void 0 ? { purpose: wire.purpose } : {},
561
+ status: wire.status,
562
+ balance: wire.balance,
563
+ createdAt: new Date(wire.createdAt).toISOString()
564
+ };
565
+ }
566
+ function tryBrandSubAccount(raw) {
567
+ try {
568
+ return [null, brandSubAccount(raw)];
569
+ } catch (err) {
570
+ if (err instanceof CapxulError && err.code === "PROVIDER_ERROR") {
571
+ return [err, null];
572
+ }
573
+ return [
574
+ malformedWireError(
575
+ err instanceof Error ? err.message : String(err),
576
+ raw
577
+ ),
578
+ null
579
+ ];
580
+ }
581
+ }
582
+ function createSubAccountsClient(config = {}) {
583
+ return {
584
+ retrieve: async (subAccountId) => {
585
+ if (!config._data) {
586
+ return stub("subAccounts.retrieve");
587
+ }
588
+ const [err, raw] = await tryCatch(
589
+ config._data.query(api.subAccounts.queries.retrieve, {
590
+ subAccountId
591
+ })
592
+ );
593
+ if (err) {
594
+ return [
595
+ fromConvexError(err),
596
+ null
597
+ ];
598
+ }
599
+ if (!raw) {
600
+ return [
601
+ new CapxulError({
602
+ code: "NOT_FOUND",
603
+ message: `sub_account ${subAccountId} not found`
604
+ }),
605
+ null
606
+ ];
607
+ }
608
+ const [brandErr, branded] = tryBrandSubAccount(raw);
609
+ if (brandErr) {
610
+ return [brandErr, null];
611
+ }
612
+ return [null, branded];
613
+ },
614
+ remove: async (subAccountId) => {
615
+ if (!config._data) {
616
+ return stub("subAccounts.remove");
617
+ }
618
+ const [err, raw] = await tryCatch(
619
+ config._data.mutation(api.subAccounts.mutations.archive, {
620
+ subAccountId
621
+ })
622
+ );
623
+ if (err) {
624
+ return [
625
+ fromConvexError(err),
626
+ null
627
+ ];
628
+ }
629
+ if (!raw) {
630
+ return [
631
+ new CapxulError({
632
+ code: "NOT_FOUND",
633
+ message: `sub_account ${subAccountId} not found`
634
+ }),
635
+ null
636
+ ];
637
+ }
638
+ const [brandErr, branded] = tryBrandSubAccount(raw);
639
+ if (brandErr) {
640
+ return [brandErr, null];
641
+ }
642
+ return [null, branded];
643
+ }
644
+ };
645
+ }
646
+
305
647
  // src/core/accounts.ts
306
648
  function createAccountExternalAccountsClient(config) {
307
649
  return {
308
650
  create: async (input) => {
309
- if (!config.data) {
651
+ if (!config._data) {
310
652
  return stub(
311
653
  "accounts.externalAccounts.create"
312
654
  );
313
655
  }
314
656
  const [err, raw] = await tryCatch(
315
- config.data.mutation(
657
+ config._data.mutation(
316
658
  api.externalAccounts.mutations.createPersonal,
317
659
  {
318
660
  kind: input.kind,
@@ -350,13 +692,13 @@ function createAccountExternalAccountsClient(config) {
350
692
  ];
351
693
  },
352
694
  list: async (input) => {
353
- if (!config.data) {
695
+ if (!config._data) {
354
696
  return stub(
355
697
  "accounts.externalAccounts.list"
356
698
  );
357
699
  }
358
700
  const [err, result] = await tryCatch(
359
- config.data.query(api.externalAccounts.queries.listPersonal, {
701
+ config._data.query(api.externalAccounts.queries.listPersonal, {
360
702
  limit: input.limit,
361
703
  cursor: input.cursor
362
704
  })
@@ -379,13 +721,13 @@ function createAccountExternalAccountsClient(config) {
379
721
  ];
380
722
  },
381
723
  retrieve: async (externalAccountId) => {
382
- if (!config.data) {
724
+ if (!config._data) {
383
725
  return stub(
384
726
  "accounts.externalAccounts.retrieve"
385
727
  );
386
728
  }
387
729
  const [err, raw] = await tryCatch(
388
- config.data.query(api.externalAccounts.queries.retrievePersonal, {
730
+ config._data.query(api.externalAccounts.queries.retrievePersonal, {
389
731
  externalAccountId
390
732
  })
391
733
  );
@@ -412,11 +754,11 @@ function createAccountExternalAccountsClient(config) {
412
754
  ];
413
755
  },
414
756
  remove: async (externalAccountId) => {
415
- if (!config.data) {
757
+ if (!config._data) {
416
758
  return stub("accounts.externalAccounts.remove");
417
759
  }
418
760
  const [err] = await tryCatch(
419
- config.data.mutation(api.externalAccounts.mutations.removePersonal, {
761
+ config._data.mutation(api.externalAccounts.mutations.removePersonal, {
420
762
  externalAccountId
421
763
  })
422
764
  );
@@ -430,43 +772,191 @@ function createAccountExternalAccountsClient(config) {
430
772
  }
431
773
  };
432
774
  }
433
- function createAccountsClient(config = {}) {
775
+ function createAccountSubAccountsClient(config) {
434
776
  return {
435
- retrieve: async (accountId) => {
436
- if (!config.data) {
437
- return stub("accounts.retrieve");
438
- }
439
- try {
440
- const account = await config.data.query(
441
- api.openfort.queries.getMyAccount,
442
- {}
777
+ create: async (input) => {
778
+ if (!config._data) {
779
+ return stub(
780
+ "accounts.subAccounts.create"
443
781
  );
444
- if (account.id !== accountId) {
445
- return [
446
- new CapxulError({
447
- code: "PERMISSION_DENIED",
448
- message: "accounts.retrieve currently supports the authenticated caller's own account only.",
449
- details: {
450
- requestedAccountId: accountId,
451
- authenticatedAccountId: account.id
452
- }
453
- }),
454
- null
455
- ];
456
- }
457
- return [null, account];
458
- } catch (cause) {
459
- return [fromConvexError(cause), null];
460
782
  }
461
- },
462
- lookup: async () => stub("accounts.lookup"),
463
- update: async (input) => {
464
- if (!config.data) {
465
- return stub("accounts.update");
466
- }
467
- if (input.countryCode !== void 0) {
468
- return [
469
- new CapxulError({
783
+ const [err, raw] = await tryCatch(
784
+ config._data.mutation(api.subAccounts.mutations.create, {
785
+ parent: { kind: "account", id: input.accountId },
786
+ name: input.name,
787
+ purpose: input.purpose
788
+ })
789
+ );
790
+ if (err) {
791
+ return [
792
+ fromConvexError(err),
793
+ null
794
+ ];
795
+ }
796
+ if (!raw) {
797
+ return [
798
+ new CapxulError({
799
+ code: "NOT_FOUND",
800
+ message: "sub_account creation returned no resource"
801
+ }),
802
+ null
803
+ ];
804
+ }
805
+ const [brandErr, branded] = tryBrandSubAccount(raw);
806
+ if (brandErr) {
807
+ return [
808
+ brandErr,
809
+ null
810
+ ];
811
+ }
812
+ return [null, branded];
813
+ },
814
+ list: async (input) => {
815
+ if (!config._data) {
816
+ return stub(
817
+ "accounts.subAccounts.list"
818
+ );
819
+ }
820
+ const [err, rows] = await tryCatch(
821
+ config._data.query(api.subAccounts.queries.listByAccount, {
822
+ accountId: input.accountId
823
+ })
824
+ );
825
+ if (err) {
826
+ return [
827
+ fromConvexError(err),
828
+ null
829
+ ];
830
+ }
831
+ const branded = [];
832
+ for (const row of rows) {
833
+ const [brandErr, value] = tryBrandSubAccount(row);
834
+ if (brandErr) {
835
+ return [
836
+ brandErr,
837
+ null
838
+ ];
839
+ }
840
+ branded.push(value);
841
+ }
842
+ return [
843
+ null,
844
+ {
845
+ object: "list",
846
+ data: branded,
847
+ page: { hasMore: false }
848
+ }
849
+ ];
850
+ },
851
+ retrieve: async (subAccountId) => {
852
+ if (!config._data) {
853
+ return stub(
854
+ "accounts.subAccounts.retrieve"
855
+ );
856
+ }
857
+ const [err, raw] = await tryCatch(
858
+ config._data.query(api.subAccounts.queries.retrieve, {
859
+ subAccountId
860
+ })
861
+ );
862
+ if (err) {
863
+ return [
864
+ fromConvexError(err),
865
+ null
866
+ ];
867
+ }
868
+ if (!raw) {
869
+ return [
870
+ new CapxulError({
871
+ code: "NOT_FOUND",
872
+ message: `sub_account ${subAccountId} not found`
873
+ }),
874
+ null
875
+ ];
876
+ }
877
+ const [brandErr, branded] = tryBrandSubAccount(raw);
878
+ if (brandErr) {
879
+ return [
880
+ brandErr,
881
+ null
882
+ ];
883
+ }
884
+ return [null, branded];
885
+ },
886
+ remove: async (subAccountId) => {
887
+ if (!config._data) {
888
+ return stub(
889
+ "accounts.subAccounts.remove"
890
+ );
891
+ }
892
+ const [err, raw] = await tryCatch(
893
+ config._data.mutation(api.subAccounts.mutations.archive, {
894
+ subAccountId
895
+ })
896
+ );
897
+ if (err) {
898
+ return [
899
+ fromConvexError(err),
900
+ null
901
+ ];
902
+ }
903
+ if (!raw) {
904
+ return [
905
+ new CapxulError({
906
+ code: "NOT_FOUND",
907
+ message: `sub_account ${subAccountId} not found`
908
+ }),
909
+ null
910
+ ];
911
+ }
912
+ const [brandErr, branded] = tryBrandSubAccount(raw);
913
+ if (brandErr) {
914
+ return [
915
+ brandErr,
916
+ null
917
+ ];
918
+ }
919
+ return [null, branded];
920
+ }
921
+ };
922
+ }
923
+ function createAccountsClient(config = {}) {
924
+ return {
925
+ retrieve: async (accountId) => {
926
+ if (!config._data) {
927
+ return stub("accounts.retrieve");
928
+ }
929
+ try {
930
+ const account = await config._data.query(
931
+ api.openfort.queries.getMyAccount,
932
+ {}
933
+ );
934
+ if (account.id !== accountId) {
935
+ return [
936
+ new CapxulError({
937
+ code: "PERMISSION_DENIED",
938
+ message: "accounts.retrieve currently supports the authenticated caller's own account only.",
939
+ details: {
940
+ requestedAccountId: accountId,
941
+ authenticatedAccountId: account.id
942
+ }
943
+ }),
944
+ null
945
+ ];
946
+ }
947
+ return [null, account];
948
+ } catch (cause) {
949
+ return [fromConvexError(cause), null];
950
+ }
951
+ },
952
+ lookup: async () => stub("accounts.lookup"),
953
+ update: async (input) => {
954
+ if (!config._data) {
955
+ return stub("accounts.update");
956
+ }
957
+ if (input.countryCode !== void 0) {
958
+ return [
959
+ new CapxulError({
470
960
  code: "INVALID_INPUT",
471
961
  message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
472
962
  details: { field: "countryCode" }
@@ -475,7 +965,7 @@ function createAccountsClient(config = {}) {
475
965
  ];
476
966
  }
477
967
  try {
478
- const current = await config.data.query(
968
+ const current = await config._data.query(
479
969
  api.openfort.queries.getMyAccount,
480
970
  {}
481
971
  );
@@ -492,11 +982,11 @@ function createAccountsClient(config = {}) {
492
982
  null
493
983
  ];
494
984
  }
495
- await config.data.mutation(api.openfort.mutations.updateProfile, {
985
+ await config._data.mutation(api.openfort.mutations.updateProfile, {
496
986
  displayName: input.name,
497
987
  username: input.username
498
988
  });
499
- const updated = await config.data.query(
989
+ const updated = await config._data.query(
500
990
  api.openfort.queries.getMyAccount,
501
991
  {}
502
992
  );
@@ -506,7 +996,7 @@ function createAccountsClient(config = {}) {
506
996
  }
507
997
  },
508
998
  provisionPersonal: async (input) => {
509
- if (!config.data) {
999
+ if (!config._data) {
510
1000
  return stub(
511
1001
  "accounts.provisionPersonal"
512
1002
  );
@@ -521,17 +1011,17 @@ function createAccountsClient(config = {}) {
521
1011
  ];
522
1012
  }
523
1013
  try {
524
- await config.data.mutation(
1014
+ await config._data.mutation(
525
1015
  api.safe.mutations.provisionLocalPersonalAccount,
526
1016
  {
527
1017
  displayName: input.displayName,
528
1018
  username: input.username,
529
1019
  countryCode: input.countryCode,
530
1020
  eoaAddress: input.signerProvider.signerAddress,
531
- safeAddress: input.signerProvider.safeAddress
1021
+ safeAddress: deriveSafeAddress(input.signerProvider.signerAddress)
532
1022
  }
533
1023
  );
534
- const account = await config.data.query(
1024
+ const account = await config._data.query(
535
1025
  api.openfort.queries.getMyAccount,
536
1026
  {}
537
1027
  );
@@ -554,11 +1044,11 @@ function createAccountsClient(config = {}) {
554
1044
  },
555
1045
  safes: {
556
1046
  retrieve: async (safeId) => {
557
- if (!config.data) {
1047
+ if (!config._data) {
558
1048
  return stub("accounts.safes.retrieve");
559
1049
  }
560
1050
  try {
561
- const safe = await config.data.query(
1051
+ const safe = await config._data.query(
562
1052
  api.safe.queries.retrieveAccountSafe,
563
1053
  { safeId }
564
1054
  );
@@ -585,19 +1075,50 @@ function createAccountsClient(config = {}) {
585
1075
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
586
1076
  },
587
1077
  externalAccounts: createAccountExternalAccountsClient(config),
588
- subAccounts: {
589
- create: async () => stub("accounts.subAccounts.create"),
590
- list: async () => stub("accounts.subAccounts.list"),
591
- retrieve: async () => stub("accounts.subAccounts.retrieve"),
592
- remove: async () => stub("accounts.subAccounts.remove")
593
- },
1078
+ subAccounts: createAccountSubAccountsClient(config),
594
1079
  balanceLedger: {
595
- list: async () => stub(
596
- "accounts.balanceLedger.list"
597
- ),
598
- retrieve: async () => stub(
599
- "accounts.balanceLedger.retrieve"
600
- )
1080
+ list: async (input) => {
1081
+ if (!config._data) {
1082
+ return stub(
1083
+ "accounts.balanceLedger.list"
1084
+ );
1085
+ }
1086
+ try {
1087
+ const accountId = input.accountId.replace(/^acct_/, "");
1088
+ const page = await config._data.query(
1089
+ api.balanceLedger.queries.listForAccount,
1090
+ { accountId, limit: input.limit, cursor: input.cursor }
1091
+ );
1092
+ return [null, page];
1093
+ } catch (cause) {
1094
+ return [fromConvexError(cause), null];
1095
+ }
1096
+ },
1097
+ retrieve: async (entryId) => {
1098
+ if (!config._data) {
1099
+ return stub(
1100
+ "accounts.balanceLedger.retrieve"
1101
+ );
1102
+ }
1103
+ try {
1104
+ const entry = await config._data.query(
1105
+ api.balanceLedger.queries.retrieve,
1106
+ { entryId }
1107
+ );
1108
+ if (!entry) {
1109
+ return [
1110
+ new CapxulError({
1111
+ code: "NOT_FOUND",
1112
+ message: `balance_ledger_entry ${entryId} not found`
1113
+ }),
1114
+ null
1115
+ ];
1116
+ }
1117
+ return [null, entry];
1118
+ } catch (cause) {
1119
+ return [fromConvexError(cause), null];
1120
+ }
1121
+ }
601
1122
  }
602
1123
  };
603
1124
  }
@@ -611,130 +1132,21 @@ function createApiKeysClient() {
611
1132
  revoke: async () => stub("apiKeys.revoke")
612
1133
  };
613
1134
  }
1135
+ function createDefaultDataClient(convexUrl, jwt) {
1136
+ const client = new browser.ConvexHttpClient(convexUrl);
1137
+ client.setAuth(jwt);
1138
+ return client;
1139
+ }
614
1140
 
615
- // ../config/src/chain.ts
616
- var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
617
- var TEST_USDC_ADDRESS = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
618
- var CAPXUL_API_BASE_URL = "https://elated-oyster-269.convex.site";
619
-
620
- // ../config/src/timing.ts
621
- var FLOW_INVOKE_TIMEOUT_MS = 3e4;
622
- var WEBHOOK_FRESHNESS_WINDOW_MS = 5 * 60 * 1e3;
623
-
624
- // ../config/src/errors.ts
625
- var CapxulError2 = class extends Error {
626
- code;
627
- details;
628
- correlationId;
629
- layer;
630
- constructor(code, message, options) {
631
- super(message, options?.cause ? { cause: options.cause } : void 0);
632
- this.code = code;
633
- this.details = options?.details;
634
- this.correlationId = options?.correlationId;
635
- this.layer = options?.layer;
636
- }
637
- };
638
- var Errors = {
639
- notAuthenticated: () => new CapxulError2("NOT_AUTHENTICATED", "Not authenticated"),
640
- profileNotFound: (authUserId) => new CapxulError2("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`),
641
- smartAccountMissing: (authUserId) => new CapxulError2("SMART_ACCOUNT_MISSING", `Smart account not provisioned for user ${authUserId}`),
642
- envMissing: (name) => new CapxulError2("ENV_MISSING", `Environment variable ${name} not configured`),
643
- openfortApi: (operation, cause) => new CapxulError2(
644
- "PROVIDER_ERROR",
645
- `Openfort ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
646
- { cause, details: { provider: "openfort", operation } }
647
- ),
648
- shieldApi: (status, detail) => new CapxulError2(
649
- "PROVIDER_ERROR",
650
- `Shield API error (${status}): ${detail}`,
651
- { details: { provider: "shield", status } }
652
- ),
653
- providerError: (provider, operation, cause) => new CapxulError2(
654
- "PROVIDER_ERROR",
655
- `${provider} ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
656
- { cause, details: { provider, operation } }
657
- ),
658
- invalidInput: (field, reason) => new CapxulError2(
659
- "INVALID_INPUT",
660
- `Invalid ${field}: ${reason}`,
661
- { details: { field, reason } }
662
- ),
663
- playerNotFound: (playerId) => new CapxulError2(
664
- "PLAYER_NOT_FOUND",
665
- playerId ? `Openfort player ${playerId} not found` : "Openfort player not found"
666
- ),
667
- accountNotFound: (accountId) => new CapxulError2(
668
- "ACCOUNT_NOT_FOUND",
669
- accountId ? `Openfort account ${accountId} not found` : "Openfort smart account not found"
670
- ),
671
- invalidRecipient: (reason) => new CapxulError2("INVALID_RECIPIENT", reason),
672
- permissionDenied: (reason = "Permission denied") => new CapxulError2("PERMISSION_DENIED", reason),
673
- notFound: (resource, id) => new CapxulError2(
674
- "NOT_FOUND",
675
- id ? `${resource} ${id} not found` : `${resource} not found`
676
- ),
677
- idempotencyConflict: (details) => new CapxulError2(
678
- "IDEMPOTENCY_CONFLICT",
679
- "Idempotency key was already used for a different request",
680
- { details }
681
- ),
682
- emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
683
- details
684
- }),
685
- rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
686
- details: { ...details }
687
- }),
688
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
689
- /**
690
- * Verification gate. Surfaced when a request hits a verification
691
- * boundary the actor cannot cross under their current state. Two
692
- * variants share this code:
693
- *
694
- * - Rail gate (Withdrawals v1 W2, #465): the resolved
695
- * `external_account.kind` routes to a withdrawal rail (e.g.
696
- * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
697
- * `details.rail` + `details.currentKind`.
698
- * - KYC tier gate (legacy / future): the actor's KYC tier is below
699
- * the required tier. Carries `details.requiredTier`.
700
- *
701
- * Code is shared because both expose the same UX shape ("you cannot
702
- * proceed until verification advances"); the `details.*` keys
703
- * differentiate the route.
704
- */
705
- verificationRequired: (details) => {
706
- const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
707
- return new CapxulError2("VERIFICATION_REQUIRED", message, {
708
- details: { ...details }
709
- });
710
- }
711
- };
712
-
713
- // ../config/src/safe.ts
714
- var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
715
- var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
716
- var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
717
- var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
718
-
719
- // ../config/src/org-roles.ts
720
- function roleKeyFromLabel(label) {
721
- const bytes = new TextEncoder().encode(label);
722
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
723
- return "0x" + hex.padEnd(64, "0");
724
- }
725
- roleKeyFromLabel("OWNER");
726
- roleKeyFromLabel("FINANCE_MANAGER");
727
- roleKeyFromLabel("TEAM_LEAD");
728
-
729
- // src/transport.ts
730
- function makeHttpTransport(config) {
731
- switch (config.mode) {
732
- case "build-time-urls":
733
- return makeBuildTimeUrlsTransport(config);
734
- case "publishable-key":
735
- return makePublishableKeyTransport(config);
736
- default:
737
- return assertNever(config);
1141
+ // src/transport.ts
1142
+ function makeHttpTransport(config) {
1143
+ switch (config.mode) {
1144
+ case "build-time-urls":
1145
+ return makeBuildTimeUrlsTransport(config);
1146
+ case "publishable-key":
1147
+ return makePublishableKeyTransport(config);
1148
+ default:
1149
+ return assertNever(config);
738
1150
  }
739
1151
  }
740
1152
  function createLifecycle(initial) {
@@ -1055,7 +1467,7 @@ function readNonEmptyString(value) {
1055
1467
 
1056
1468
  // src/core/auth.ts
1057
1469
  function createAuthClient(config = {}) {
1058
- let dataClient = config.data ?? null;
1470
+ let dataClient = config._data ?? null;
1059
1471
  const sessionStore = config.auth?.sessionStore ?? createMemorySessionStore();
1060
1472
  const getTransport = createTransportProvider(config);
1061
1473
  return {
@@ -1111,11 +1523,20 @@ function createAuthClient(config = {}) {
1111
1523
  ).toISOString()
1112
1524
  };
1113
1525
  sessionStore.set(session);
1114
- if (config.auth?.createDataClient) {
1526
+ if (!dataClient) {
1115
1527
  try {
1116
- dataClient = await config.auth.createDataClient(session);
1117
- mutableConfig(config).data = dataClient;
1118
- transport.markAuthenticated({ dataClient });
1528
+ const convexUrl = transport.convexUrl;
1529
+ if (!convexUrl || !session.convexJwt) {
1530
+ return [
1531
+ new CapxulError({
1532
+ code: "NETWORK_ERROR",
1533
+ message: "Cannot create data client: missing convex URL or JWT."
1534
+ }),
1535
+ null
1536
+ ];
1537
+ }
1538
+ dataClient = createDefaultDataClient(convexUrl, session.convexJwt);
1539
+ mutableConfig(config)._data = dataClient;
1119
1540
  } catch (cause) {
1120
1541
  return [
1121
1542
  new CapxulError({
@@ -1126,14 +1547,89 @@ function createAuthClient(config = {}) {
1126
1547
  null
1127
1548
  ];
1128
1549
  }
1550
+ } else {
1551
+ const injected = dataClient;
1552
+ if (typeof injected.refreshAuth === "function") {
1553
+ injected.refreshAuth();
1554
+ } else if (typeof injected.setAuth === "function" && session.convexJwt) {
1555
+ injected.setAuth(session.convexJwt);
1556
+ }
1557
+ }
1558
+ transport.markAuthenticated({ dataClient });
1559
+ if (!dataClient) {
1560
+ return [
1561
+ new CapxulError({
1562
+ code: "NOT_AUTHENTICATED",
1563
+ message: "Auth bootstrap requires an authenticated Convex data client."
1564
+ }),
1565
+ null
1566
+ ];
1567
+ }
1568
+ try {
1569
+ const resolution = await dataClient.mutation(
1570
+ api.authBootstrap.resolveAfterOtp,
1571
+ {
1572
+ email: session.email,
1573
+ sessionToken: session.token
1574
+ }
1575
+ );
1576
+ if (resolution.kind === "existing_member") {
1577
+ return [null, { ...resolution, session }];
1578
+ }
1579
+ return [null, { ...resolution, session }];
1580
+ } catch (cause) {
1581
+ return [fromConvexError(cause), null];
1582
+ }
1583
+ },
1584
+ completeBootstrap: async (input) => {
1585
+ const session = sessionStore.get();
1586
+ const data = dataClient ?? config._data;
1587
+ if (!session || !data) {
1588
+ return [
1589
+ new CapxulError({
1590
+ code: "INVALID_INPUT",
1591
+ message: "completeBootstrap requires the OTP-verified session that issued the bootstrap token."
1592
+ }),
1593
+ null
1594
+ ];
1595
+ }
1596
+ const signerAddress = config.signer?.address;
1597
+ if (!signerAddress) {
1598
+ return [
1599
+ new CapxulError({
1600
+ code: "INVALID_INPUT",
1601
+ message: "completeBootstrap requires a signer to be configured on the client."
1602
+ }),
1603
+ null
1604
+ ];
1605
+ }
1606
+ try {
1607
+ const safeAddress = deriveSafeAddress(signerAddress);
1608
+ const result = await data.mutation(api.authBootstrap.completeBootstrap, {
1609
+ bootstrapToken: input.bootstrapToken,
1610
+ sessionToken: session.token,
1611
+ username: input.username,
1612
+ displayName: input.displayName,
1613
+ countryCode: input.countryCode,
1614
+ signerProvider: {
1615
+ kind: "local-private-key",
1616
+ signerAddress,
1617
+ safeAddress
1618
+ }
1619
+ });
1620
+ return [null, { kind: "authenticated", session, ...result }];
1621
+ } catch (cause) {
1622
+ return [
1623
+ fromConvexError(cause),
1624
+ null
1625
+ ];
1129
1626
  }
1130
- return [null, session];
1131
1627
  },
1132
1628
  getSession: async () => [null, sessionStore.get()],
1133
1629
  signOut: async () => {
1134
1630
  sessionStore.clear();
1135
1631
  dataClient = null;
1136
- mutableConfig(config).data = void 0;
1632
+ mutableConfig(config)._data = void 0;
1137
1633
  const transport = getTransport();
1138
1634
  transport?.clearAuth();
1139
1635
  return [null, void 0];
@@ -1204,6 +1700,9 @@ async function postBetterAuth(transport, path, body, code, signal) {
1204
1700
  }
1205
1701
  return [null, text ? JSON.parse(text) : void 0];
1206
1702
  } catch (cause) {
1703
+ if (cause instanceof CapxulError) {
1704
+ return [cause, null];
1705
+ }
1207
1706
  return [
1208
1707
  new CapxulError({
1209
1708
  code: "NETWORK_ERROR",
@@ -1242,7 +1741,7 @@ function parseBetterAuthError(text) {
1242
1741
  }
1243
1742
  }
1244
1743
  function isCapxulErrorCode2(code) {
1245
- 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";
1744
+ 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";
1246
1745
  }
1247
1746
  async function exchangeConvexToken(transport, config, token, signal) {
1248
1747
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
@@ -1272,6 +1771,9 @@ async function exchangeConvexToken(transport, config, token, signal) {
1272
1771
  }
1273
1772
  return [null, body.token];
1274
1773
  } catch (cause) {
1774
+ if (cause instanceof CapxulError) {
1775
+ return [cause, null];
1776
+ }
1275
1777
  return [
1276
1778
  new CapxulError({
1277
1779
  code: "NETWORK_ERROR",
@@ -1308,11 +1810,11 @@ function createOrgDocumentsClient() {
1308
1810
  function createMeClient(config = {}) {
1309
1811
  return {
1310
1812
  get: async () => {
1311
- if (!config.data) {
1813
+ if (!config._data) {
1312
1814
  return stub("me.get");
1313
1815
  }
1314
1816
  try {
1315
- const account = await config.data.query(
1817
+ const account = await config._data.query(
1316
1818
  api.openfort.queries.getMyAccount,
1317
1819
  {}
1318
1820
  );
@@ -1322,7 +1824,7 @@ function createMeClient(config = {}) {
1322
1824
  }
1323
1825
  },
1324
1826
  update: async (input) => {
1325
- if (!config.data) {
1827
+ if (!config._data) {
1326
1828
  return stub("me.update");
1327
1829
  }
1328
1830
  if (input.countryCode !== void 0) {
@@ -1336,11 +1838,11 @@ function createMeClient(config = {}) {
1336
1838
  ];
1337
1839
  }
1338
1840
  try {
1339
- await config.data.mutation(api.openfort.mutations.updateProfile, {
1841
+ await config._data.mutation(api.openfort.mutations.updateProfile, {
1340
1842
  displayName: input.name,
1341
1843
  username: input.username
1342
1844
  });
1343
- const account = await config.data.query(
1845
+ const account = await config._data.query(
1344
1846
  api.openfort.queries.getMyAccount,
1345
1847
  {}
1346
1848
  );
@@ -1355,11 +1857,11 @@ function createMeClient(config = {}) {
1355
1857
  // src/core/operations.ts
1356
1858
  function createOperationsClient(config = {}) {
1357
1859
  const retrieve = async (operationId) => {
1358
- if (!config.data) {
1860
+ if (!config._data) {
1359
1861
  return stub("operations.retrieve");
1360
1862
  }
1361
1863
  try {
1362
- const operation = await config.data.query(api.operations.queries.retrieve, {
1864
+ const operation = await config._data.query(api.operations.queries.retrieve, {
1363
1865
  operationId
1364
1866
  });
1365
1867
  if (!operation) {
@@ -1376,7 +1878,7 @@ function createOperationsClient(config = {}) {
1376
1878
  return {
1377
1879
  retrieve,
1378
1880
  wait: async (operationId, input = {}) => {
1379
- if (!config.data) {
1881
+ if (!config._data) {
1380
1882
  return stub("operations.wait");
1381
1883
  }
1382
1884
  const until = new Set(
@@ -1411,49 +1913,22 @@ function toTokenUnits(value, decimals = 6) {
1411
1913
  return viem.parseUnits(value, decimals);
1412
1914
  }
1413
1915
 
1414
- // src/internal/payment-token.ts
1415
- function resolvePaymentTokenAddress(currency) {
1916
+ // src/core/token-registry.ts
1917
+ function resolvePaymentToken(currency) {
1416
1918
  const normalized = currency.trim().toUpperCase();
1417
1919
  if (normalized === "USD" || normalized === "USDC") {
1418
- return TEST_USDC_ADDRESS.toLowerCase();
1920
+ return {
1921
+ address: TEST_USDC_ADDRESS.toLowerCase(),
1922
+ decimals: 6,
1923
+ symbol: "USDC"
1924
+ };
1419
1925
  }
1420
1926
  throw new CapxulError({
1421
- code: "NETWORK_ERROR",
1422
- message: `Currency ${currency} is not configured for on-chain payment submission.`,
1927
+ code: "NOT_IMPLEMENTED",
1928
+ message: `Currency ${currency} is not yet supported by the token registry.`,
1423
1929
  details: { currency: normalized }
1424
1930
  });
1425
1931
  }
1426
- async function buildSafeAccount(signer, chain) {
1427
- try {
1428
- const publicClient = viem.createPublicClient({
1429
- chain: chains.baseSepolia,
1430
- transport: viem.http(chain.rpcUrl)
1431
- });
1432
- return await accounts$1.toSafeSmartAccount({
1433
- client: publicClient,
1434
- entryPoint: { address: accountAbstraction.entryPoint07Address, version: "0.7" },
1435
- version: "1.4.1",
1436
- owners: [signer],
1437
- saltNonce: computeSaltNonce(signer.address),
1438
- safeSingletonAddress: SAFE_L2_SINGLETON,
1439
- safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
1440
- safeModuleSetupAddress: SAFE_MODULE_SETUP,
1441
- safe4337ModuleAddress: SAFE_4337_MODULE,
1442
- safeModules: [],
1443
- setupTransactions: []
1444
- });
1445
- } catch (cause) {
1446
- throw new CapxulError({
1447
- code: "NETWORK_ERROR",
1448
- message: cause instanceof Error ? cause.message : String(cause),
1449
- cause,
1450
- details: { chainId: chain.chainId }
1451
- });
1452
- }
1453
- }
1454
- function computeSaltNonce(ownerAddress) {
1455
- return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
1456
- }
1457
1932
  function createCapxulBundler(config) {
1458
1933
  const paymaster = accountAbstraction.createPaymasterClient({
1459
1934
  transport: viem.http(config.rpcUrl)
@@ -1560,13 +2035,13 @@ async function transferAsOwner(config, params) {
1560
2035
  function createPaymentsClient(config = {}) {
1561
2036
  return {
1562
2037
  create: async (input) => {
1563
- if (!config.data || !config.signer || !config.signing) {
2038
+ if (!config._data || !config.signer || !config.signing) {
1564
2039
  return stub("payments.create");
1565
2040
  }
1566
2041
  let created = null;
1567
2042
  let submitted = null;
1568
2043
  try {
1569
- created = await config.data.mutation(api.payments.mutations.create, {
2044
+ created = await config._data.mutation(api.payments.mutations.create, {
1570
2045
  to: input.to,
1571
2046
  amount: input.amount,
1572
2047
  reference: input.reference,
@@ -1574,15 +2049,21 @@ function createPaymentsClient(config = {}) {
1574
2049
  source: input.source
1575
2050
  });
1576
2051
  if (!created) {
1577
- return [new CapxulError({
1578
- code: "NETWORK_ERROR",
1579
- message: "payments.create returned no payment resource"
1580
- }), null];
2052
+ return [
2053
+ new CapxulError({
2054
+ code: "NETWORK_ERROR",
2055
+ message: "payments.create returned no payment resource"
2056
+ }),
2057
+ null
2058
+ ];
1581
2059
  }
1582
2060
  if (created.status !== "processing" || created.operation.status !== "processing") {
1583
2061
  return [null, created];
1584
2062
  }
1585
- const currentSigner = await config.data.query(api.safe.queries.getMySignerAddress, {});
2063
+ const currentSigner = await config._data.query(
2064
+ api.safe.queries.getMySignerAddress,
2065
+ {}
2066
+ );
1586
2067
  if (!currentSigner?.address) {
1587
2068
  throw new CapxulError({
1588
2069
  code: "PERMISSION_DENIED",
@@ -1601,9 +2082,12 @@ function createPaymentsClient(config = {}) {
1601
2082
  }
1602
2083
  });
1603
2084
  }
1604
- const submission = await config.data.query(api.payments.queries.prepareSubmission, {
1605
- paymentId: created.id
1606
- });
2085
+ const submission = await config._data.query(
2086
+ api.payments.queries.prepareSubmission,
2087
+ {
2088
+ paymentId: created.id
2089
+ }
2090
+ );
1607
2091
  if (!submission?.recipientAddress) {
1608
2092
  throw new CapxulError({
1609
2093
  code: "NETWORK_ERROR",
@@ -1611,15 +2095,16 @@ function createPaymentsClient(config = {}) {
1611
2095
  details: { paymentId: created.id }
1612
2096
  });
1613
2097
  }
2098
+ const token = resolvePaymentToken(submission.amount.currency);
1614
2099
  const transfer = await transferAsOwner(
1615
2100
  {
1616
2101
  signer: config.signer,
1617
2102
  signing: config.signing
1618
2103
  },
1619
2104
  {
1620
- tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
2105
+ tokenAddress: token.address,
1621
2106
  recipientAddress: submission.recipientAddress,
1622
- amount: toTokenUnits(submission.amount.value, 6)
2107
+ amount: toTokenUnits(submission.amount.value, token.decimals)
1623
2108
  }
1624
2109
  );
1625
2110
  if (!transfer.success) {
@@ -1637,7 +2122,7 @@ function createPaymentsClient(config = {}) {
1637
2122
  txHash: transfer.txHash,
1638
2123
  userOpHash: transfer.userOpHash
1639
2124
  };
1640
- await config.data.mutation(api.payments.mutations.recordSubmitted, {
2125
+ await config._data.mutation(api.payments.mutations.recordSubmitted, {
1641
2126
  paymentId: created.id,
1642
2127
  txHash: transfer.txHash,
1643
2128
  userOpHash: transfer.userOpHash,
@@ -1647,43 +2132,65 @@ function createPaymentsClient(config = {}) {
1647
2132
  } catch (cause) {
1648
2133
  const error = mapCreateError(fromConvexError(cause));
1649
2134
  if (created?.id && created.status === "processing" && !submitted) {
1650
- await bestEffortMarkFailed({ data: config.data }, created.id, error);
2135
+ await bestEffortMarkFailed({ _data: config._data }, created.id, error);
1651
2136
  }
1652
2137
  if (submitted && created?.id) {
1653
- return [new CapxulError({
1654
- code: "NETWORK_ERROR",
1655
- message: "Payment was submitted on-chain, but backend submission tracking failed.",
1656
- cause,
1657
- details: {
1658
- paymentId: created.id,
1659
- txHash: submitted.txHash,
1660
- userOpHash: submitted.userOpHash
1661
- }
1662
- }), null];
2138
+ return [
2139
+ new CapxulError({
2140
+ code: "NETWORK_ERROR",
2141
+ message: "Payment was submitted on-chain, but backend submission tracking failed.",
2142
+ cause,
2143
+ details: {
2144
+ paymentId: created.id,
2145
+ txHash: submitted.txHash,
2146
+ userOpHash: submitted.userOpHash
2147
+ }
2148
+ }),
2149
+ null
2150
+ ];
1663
2151
  }
1664
2152
  return [error, null];
1665
2153
  }
1666
2154
  },
1667
2155
  retrieve: async (paymentId) => {
1668
- if (!config.data) {
2156
+ if (!config._data) {
1669
2157
  return stub("payments.retrieve");
1670
2158
  }
1671
2159
  try {
1672
- const payment = await config.data.query(api.payments.queries.retrieve, {
1673
- paymentId
1674
- });
2160
+ const payment = await config._data.query(
2161
+ api.payments.queries.retrieve,
2162
+ {
2163
+ paymentId
2164
+ }
2165
+ );
1675
2166
  if (!payment) {
1676
- return [new CapxulError({
1677
- code: "NOT_FOUND",
1678
- message: `payment ${paymentId} not found`
1679
- }), null];
2167
+ return [
2168
+ new CapxulError({
2169
+ code: "NOT_FOUND",
2170
+ message: `payment ${paymentId} not found`
2171
+ }),
2172
+ null
2173
+ ];
1680
2174
  }
1681
2175
  return [null, payment];
1682
2176
  } catch (cause) {
1683
2177
  return [fromConvexError(cause), null];
1684
2178
  }
1685
2179
  },
1686
- list: async () => stub("payments.list")
2180
+ list: async (input) => {
2181
+ if (!config._data) {
2182
+ return stub("payments.list");
2183
+ }
2184
+ try {
2185
+ const page = await config._data.query(api.payments.queries.list, {
2186
+ limit: input?.limit,
2187
+ cursor: input?.cursor
2188
+ });
2189
+ return [null, page];
2190
+ } catch (cause) {
2191
+ return [fromConvexError(cause), null];
2192
+ }
2193
+ }
1687
2194
  };
1688
2195
  }
1689
2196
  function createOrgPaymentsClient() {
@@ -1697,7 +2204,7 @@ function createOrgPaymentsClient() {
1697
2204
  }
1698
2205
  async function bestEffortMarkFailed(config, paymentId, error) {
1699
2206
  try {
1700
- await config.data.mutation(api.payments.mutations.markFailed, {
2207
+ await config._data.mutation(api.payments.mutations.markFailed, {
1701
2208
  paymentId,
1702
2209
  errorCode: error.code,
1703
2210
  errorMessage: error.message,
@@ -1754,11 +2261,11 @@ function createOrgTransfersClient() {
1754
2261
  function createWithdrawalsClient(config = {}) {
1755
2262
  return {
1756
2263
  create: async (input) => {
1757
- if (!config.data) {
2264
+ if (!config._data) {
1758
2265
  return stub("withdrawals.create");
1759
2266
  }
1760
2267
  const [createErr, createdRaw] = await tryCatch(
1761
- config.data.mutation(api.withdrawals.mutations.create, {
2268
+ config._data.mutation(api.withdrawals.mutations.create, {
1762
2269
  amount: input.amount,
1763
2270
  destination: {
1764
2271
  externalAccountId: input.destination.externalAccountId
@@ -1788,18 +2295,18 @@ function createWithdrawalsClient(config = {}) {
1788
2295
  return [null, created];
1789
2296
  }
1790
2297
  const [signerErr, currentSigner] = await tryCatch(
1791
- config.data.query(api.safe.queries.getMySignerAddress, {})
2298
+ config._data.query(api.safe.queries.getMySignerAddress, {})
1792
2299
  );
1793
2300
  if (signerErr) {
1794
2301
  return await handleSubmissionFailure(
1795
- { data: config.data },
2302
+ { _data: config._data },
1796
2303
  created.id,
1797
2304
  mapCreateError2(fromConvexError(signerErr))
1798
2305
  );
1799
2306
  }
1800
2307
  if (!currentSigner?.address) {
1801
2308
  return await handleSubmissionFailure(
1802
- { data: config.data },
2309
+ { _data: config._data },
1803
2310
  created.id,
1804
2311
  new CapxulError({
1805
2312
  code: "PERMISSION_DENIED",
@@ -1810,7 +2317,7 @@ function createWithdrawalsClient(config = {}) {
1810
2317
  }
1811
2318
  if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
1812
2319
  return await handleSubmissionFailure(
1813
- { data: config.data },
2320
+ { _data: config._data },
1814
2321
  created.id,
1815
2322
  new CapxulError({
1816
2323
  code: "PERMISSION_DENIED",
@@ -1824,13 +2331,13 @@ function createWithdrawalsClient(config = {}) {
1824
2331
  );
1825
2332
  }
1826
2333
  const [prepErr, submission] = await tryCatch(
1827
- config.data.query(api.withdrawals.queries.prepareSubmission, {
2334
+ config._data.query(api.withdrawals.queries.prepareSubmission, {
1828
2335
  withdrawalId: created.id
1829
2336
  })
1830
2337
  );
1831
2338
  if (prepErr) {
1832
2339
  return await handleSubmissionFailure(
1833
- { data: config.data },
2340
+ { _data: config._data },
1834
2341
  created.id,
1835
2342
  mapCreateError2(fromConvexError(prepErr))
1836
2343
  );
@@ -1838,7 +2345,7 @@ function createWithdrawalsClient(config = {}) {
1838
2345
  const destinationAddress = submission?.destinationAddress;
1839
2346
  if (!submission || !destinationAddress) {
1840
2347
  return await handleSubmissionFailure(
1841
- { data: config.data },
2348
+ { _data: config._data },
1842
2349
  created.id,
1843
2350
  new CapxulError({
1844
2351
  code: "NETWORK_ERROR",
@@ -1847,6 +2354,7 @@ function createWithdrawalsClient(config = {}) {
1847
2354
  })
1848
2355
  );
1849
2356
  }
2357
+ const token = resolvePaymentToken(submission.amount.currency);
1850
2358
  const [transferErr, transferOk] = await tryCatch(
1851
2359
  transferAsOwner(
1852
2360
  {
@@ -1854,22 +2362,22 @@ function createWithdrawalsClient(config = {}) {
1854
2362
  signing: config.signing
1855
2363
  },
1856
2364
  {
1857
- tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
2365
+ tokenAddress: token.address,
1858
2366
  recipientAddress: destinationAddress,
1859
- amount: toTokenUnits(submission.amount.value, 6)
2367
+ amount: toTokenUnits(submission.amount.value, token.decimals)
1860
2368
  }
1861
2369
  )
1862
2370
  );
1863
2371
  if (transferErr) {
1864
2372
  return await handleSubmissionFailure(
1865
- { data: config.data },
2373
+ { _data: config._data },
1866
2374
  created.id,
1867
2375
  mapCreateError2(fromConvexError(transferErr))
1868
2376
  );
1869
2377
  }
1870
2378
  if (!transferOk.success) {
1871
2379
  return await handleSubmissionFailure(
1872
- { data: config.data },
2380
+ { _data: config._data },
1873
2381
  created.id,
1874
2382
  new CapxulError({
1875
2383
  code: "NETWORK_ERROR",
@@ -1883,7 +2391,7 @@ function createWithdrawalsClient(config = {}) {
1883
2391
  );
1884
2392
  }
1885
2393
  const [recordErr] = await tryCatch(
1886
- config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
2394
+ config._data.mutation(api.withdrawals.mutations.recordSubmitted, {
1887
2395
  withdrawalId: created.id,
1888
2396
  txHash: transferOk.txHash,
1889
2397
  userOpHash: transferOk.userOpHash
@@ -1907,11 +2415,11 @@ function createWithdrawalsClient(config = {}) {
1907
2415
  return [null, created];
1908
2416
  },
1909
2417
  retrieve: async (withdrawalId) => {
1910
- if (!config.data) {
2418
+ if (!config._data) {
1911
2419
  return stub("withdrawals.retrieve");
1912
2420
  }
1913
2421
  const [err, raw] = await tryCatch(
1914
- config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
2422
+ config._data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1915
2423
  );
1916
2424
  if (err) {
1917
2425
  return [fromConvexError(err), null];
@@ -1929,11 +2437,11 @@ function createWithdrawalsClient(config = {}) {
1929
2437
  return [null, withdrawal];
1930
2438
  },
1931
2439
  list: async (input) => {
1932
- if (!config.data) {
2440
+ if (!config._data) {
1933
2441
  return stub("withdrawals.list");
1934
2442
  }
1935
2443
  const [err, raw] = await tryCatch(
1936
- config.data.query(api.withdrawals.queries.list, {
2444
+ config._data.query(api.withdrawals.queries.list, {
1937
2445
  limit: input?.limit,
1938
2446
  cursor: input?.cursor
1939
2447
  })
@@ -1944,13 +2452,13 @@ function createWithdrawalsClient(config = {}) {
1944
2452
  return [null, raw];
1945
2453
  },
1946
2454
  recordCompleted: async (input) => {
1947
- if (!config.data) {
2455
+ if (!config._data) {
1948
2456
  return stub(
1949
2457
  "withdrawals.recordCompleted"
1950
2458
  );
1951
2459
  }
1952
2460
  const [err] = await tryCatch(
1953
- config.data.mutation(api.withdrawals.mutations.recordCompleted, {
2461
+ config._data.mutation(api.withdrawals.mutations.recordCompleted, {
1954
2462
  withdrawalId: input.withdrawalId,
1955
2463
  txHash: input.txHash
1956
2464
  })
@@ -1975,13 +2483,13 @@ function createOrgWithdrawalsClient(config = {}) {
1975
2483
  * orchestration ships in W3+.
1976
2484
  */
1977
2485
  create: async (input) => {
1978
- if (!config.data) {
2486
+ if (!config._data) {
1979
2487
  return stub(
1980
2488
  "organizations.withdrawals.create"
1981
2489
  );
1982
2490
  }
1983
2491
  const [err, raw] = await tryCatch(
1984
- config.data.mutation(api.withdrawals.mutations.createOrg, {
2492
+ config._data.mutation(api.withdrawals.mutations.createOrg, {
1985
2493
  organizationId: input.organizationId,
1986
2494
  amount: input.amount,
1987
2495
  destination: {
@@ -2008,13 +2516,13 @@ function createOrgWithdrawalsClient(config = {}) {
2008
2516
  return [null, created];
2009
2517
  },
2010
2518
  retrieve: async (input) => {
2011
- if (!config.data) {
2519
+ if (!config._data) {
2012
2520
  return stub(
2013
2521
  "organizations.withdrawals.retrieve"
2014
2522
  );
2015
2523
  }
2016
2524
  const [err, raw] = await tryCatch(
2017
- config.data.query(api.withdrawals.queries.retrieve, {
2525
+ config._data.query(api.withdrawals.queries.retrieve, {
2018
2526
  withdrawalId: input.withdrawalId
2019
2527
  })
2020
2528
  );
@@ -2044,13 +2552,13 @@ function createOrgWithdrawalsClient(config = {}) {
2044
2552
  return [null, withdrawal];
2045
2553
  },
2046
2554
  list: async (input) => {
2047
- if (!config.data) {
2555
+ if (!config._data) {
2048
2556
  return stub(
2049
2557
  "organizations.withdrawals.list"
2050
2558
  );
2051
2559
  }
2052
2560
  const [err, raw] = await tryCatch(
2053
- config.data.query(api.withdrawals.queries.listOrg, {
2561
+ config._data.query(api.withdrawals.queries.listOrg, {
2054
2562
  organizationId: input.organizationId,
2055
2563
  limit: input.limit,
2056
2564
  cursor: input.cursor
@@ -2069,7 +2577,7 @@ async function handleSubmissionFailure(config, withdrawalId, error) {
2069
2577
  }
2070
2578
  async function bestEffortMarkFailed2(config, withdrawalId, error) {
2071
2579
  await tryCatch(
2072
- config.data.mutation(api.withdrawals.mutations.markFailed, {
2580
+ config._data.mutation(api.withdrawals.mutations.markFailed, {
2073
2581
  withdrawalId,
2074
2582
  errorCode: error.code,
2075
2583
  errorMessage: error.message
@@ -2148,13 +2656,13 @@ function createWebhookEventsClient() {
2148
2656
  function createOrgExternalAccountsClient(config) {
2149
2657
  return {
2150
2658
  create: async (input) => {
2151
- if (!config.data) {
2659
+ if (!config._data) {
2152
2660
  return stub(
2153
2661
  "organizations.externalAccounts.create"
2154
2662
  );
2155
2663
  }
2156
2664
  const [err, raw] = await tryCatch(
2157
- config.data.mutation(api.externalAccounts.mutations.createOrg, {
2665
+ config._data.mutation(api.externalAccounts.mutations.createOrg, {
2158
2666
  organizationId: input.organizationId,
2159
2667
  kind: input.kind,
2160
2668
  label: input.label,
@@ -2168,10 +2676,7 @@ function createOrgExternalAccountsClient(config) {
2168
2676
  })
2169
2677
  );
2170
2678
  if (err) {
2171
- return [
2172
- fromConvexError(err),
2173
- null
2174
- ];
2679
+ return [fromConvexError(err), null];
2175
2680
  }
2176
2681
  if (!raw) {
2177
2682
  return [
@@ -2182,21 +2687,16 @@ function createOrgExternalAccountsClient(config) {
2182
2687
  null
2183
2688
  ];
2184
2689
  }
2185
- return [
2186
- null,
2187
- brandExternalAccount(
2188
- raw
2189
- )
2190
- ];
2690
+ return [null, brandExternalAccount(raw)];
2191
2691
  },
2192
2692
  list: async (input) => {
2193
- if (!config.data) {
2693
+ if (!config._data) {
2194
2694
  return stub(
2195
2695
  "organizations.externalAccounts.list"
2196
2696
  );
2197
2697
  }
2198
2698
  const [err, result] = await tryCatch(
2199
- config.data.query(api.externalAccounts.queries.listOrg, {
2699
+ config._data.query(api.externalAccounts.queries.listOrg, {
2200
2700
  organizationId: input.organizationId,
2201
2701
  limit: input.limit,
2202
2702
  cursor: input.cursor
@@ -2206,9 +2706,7 @@ function createOrgExternalAccountsClient(config) {
2206
2706
  return [fromConvexError(err), null];
2207
2707
  }
2208
2708
  const branded = result.data.map(
2209
- (row) => brandExternalAccount(
2210
- row
2211
- )
2709
+ (row) => brandExternalAccount(row)
2212
2710
  );
2213
2711
  return [
2214
2712
  null,
@@ -2220,17 +2718,66 @@ function createOrgExternalAccountsClient(config) {
2220
2718
  ];
2221
2719
  },
2222
2720
  retrieve: async (input) => {
2223
- if (!config.data) {
2721
+ if (!config._data) {
2224
2722
  return stub(
2225
2723
  "organizations.externalAccounts.retrieve"
2226
2724
  );
2227
2725
  }
2228
2726
  const [err, raw] = await tryCatch(
2229
- config.data.query(api.externalAccounts.queries.retrieveOrg, {
2727
+ config._data.query(api.externalAccounts.queries.retrieveOrg, {
2728
+ organizationId: input.organizationId,
2729
+ externalAccountId: input.externalAccountId
2730
+ })
2731
+ );
2732
+ if (err) {
2733
+ return [fromConvexError(err), null];
2734
+ }
2735
+ if (!raw) {
2736
+ return [
2737
+ new CapxulError({
2738
+ code: "NOT_FOUND",
2739
+ message: `external_account ${input.externalAccountId} not found`
2740
+ }),
2741
+ null
2742
+ ];
2743
+ }
2744
+ return [null, brandExternalAccount(raw)];
2745
+ },
2746
+ remove: async (input) => {
2747
+ if (!config._data) {
2748
+ return stub("organizations.externalAccounts.remove");
2749
+ }
2750
+ const [err] = await tryCatch(
2751
+ config._data.mutation(api.externalAccounts.mutations.removeOrg, {
2230
2752
  organizationId: input.organizationId,
2231
2753
  externalAccountId: input.externalAccountId
2232
2754
  })
2233
2755
  );
2756
+ if (err) {
2757
+ return [fromConvexError(err), null];
2758
+ }
2759
+ return [null, void 0];
2760
+ }
2761
+ };
2762
+ }
2763
+ function createOrgSubAccountsClient(config) {
2764
+ return {
2765
+ create: async (input) => {
2766
+ if (!config._data) {
2767
+ return stub(
2768
+ "organizations.subAccounts.create"
2769
+ );
2770
+ }
2771
+ const [err, raw] = await tryCatch(
2772
+ config._data.mutation(api.subAccounts.mutations.create, {
2773
+ parent: {
2774
+ kind: "organization",
2775
+ id: input.organizationId
2776
+ },
2777
+ name: input.name,
2778
+ purpose: input.purpose
2779
+ })
2780
+ );
2234
2781
  if (err) {
2235
2782
  return [
2236
2783
  fromConvexError(err),
@@ -2241,28 +2788,95 @@ function createOrgExternalAccountsClient(config) {
2241
2788
  return [
2242
2789
  new CapxulError({
2243
2790
  code: "NOT_FOUND",
2244
- message: `external_account ${input.externalAccountId} not found`
2791
+ message: "sub_account creation returned no resource"
2245
2792
  }),
2246
2793
  null
2247
2794
  ];
2248
2795
  }
2796
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2797
+ if (brandErr) {
2798
+ return [brandErr, null];
2799
+ }
2800
+ return [null, branded];
2801
+ },
2802
+ list: async (input) => {
2803
+ if (!config._data) {
2804
+ return stub(
2805
+ "organizations.subAccounts.list"
2806
+ );
2807
+ }
2808
+ const [err, rows] = await tryCatch(
2809
+ config._data.query(api.subAccounts.queries.listByOrganization, {
2810
+ organizationId: input.organizationId
2811
+ })
2812
+ );
2813
+ if (err) {
2814
+ return [
2815
+ fromConvexError(err),
2816
+ null
2817
+ ];
2818
+ }
2819
+ const branded = [];
2820
+ for (const row of rows) {
2821
+ const [brandErr, value] = tryBrandSubAccount(row);
2822
+ if (brandErr) {
2823
+ return [brandErr, null];
2824
+ }
2825
+ branded.push(value);
2826
+ }
2249
2827
  return [
2250
2828
  null,
2251
- brandExternalAccount(
2252
- raw
2253
- )
2829
+ {
2830
+ object: "list",
2831
+ data: branded,
2832
+ page: { hasMore: false }
2833
+ }
2254
2834
  ];
2255
2835
  },
2836
+ retrieve: async (input) => {
2837
+ if (!config._data) {
2838
+ return stub(
2839
+ "organizations.subAccounts.retrieve"
2840
+ );
2841
+ }
2842
+ const [err, raw] = await tryCatch(
2843
+ config._data.query(api.subAccounts.queries.retrieve, {
2844
+ subAccountId: input.subAccountId
2845
+ })
2846
+ );
2847
+ if (err) {
2848
+ return [
2849
+ fromConvexError(err),
2850
+ null
2851
+ ];
2852
+ }
2853
+ if (!raw) {
2854
+ return [
2855
+ new CapxulError({
2856
+ code: "NOT_FOUND",
2857
+ message: `sub_account ${input.subAccountId} not found`
2858
+ }),
2859
+ null
2860
+ ];
2861
+ }
2862
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2863
+ if (brandErr) {
2864
+ return [
2865
+ brandErr,
2866
+ null
2867
+ ];
2868
+ }
2869
+ return [null, branded];
2870
+ },
2256
2871
  remove: async (input) => {
2257
- if (!config.data) {
2872
+ if (!config._data) {
2258
2873
  return stub(
2259
- "organizations.externalAccounts.remove"
2874
+ "organizations.subAccounts.remove"
2260
2875
  );
2261
2876
  }
2262
- const [err] = await tryCatch(
2263
- config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2264
- organizationId: input.organizationId,
2265
- externalAccountId: input.externalAccountId
2877
+ const [err, raw] = await tryCatch(
2878
+ config._data.mutation(api.subAccounts.mutations.archive, {
2879
+ subAccountId: input.subAccountId
2266
2880
  })
2267
2881
  );
2268
2882
  if (err) {
@@ -2271,23 +2885,139 @@ function createOrgExternalAccountsClient(config) {
2271
2885
  null
2272
2886
  ];
2273
2887
  }
2274
- return [null, void 0];
2888
+ if (!raw) {
2889
+ return [
2890
+ new CapxulError({
2891
+ code: "NOT_FOUND",
2892
+ message: `sub_account ${input.subAccountId} not found`
2893
+ }),
2894
+ null
2895
+ ];
2896
+ }
2897
+ const [brandErr, branded] = tryBrandSubAccount(raw);
2898
+ if (brandErr) {
2899
+ return [
2900
+ brandErr,
2901
+ null
2902
+ ];
2903
+ }
2904
+ return [null, branded];
2275
2905
  }
2276
2906
  };
2277
2907
  }
2278
2908
  function createOrganizationsClient(config = {}) {
2279
2909
  return {
2280
- create: async () => stub("organizations.create"),
2281
- retrieve: async () => stub("organizations.retrieve"),
2282
- list: async () => stub("organizations.list"),
2283
- update: async () => stub("organizations.update"),
2910
+ create: async (input) => {
2911
+ if (!config._data) {
2912
+ return stub("organizations.create");
2913
+ }
2914
+ if (input.country !== void 0) {
2915
+ return [
2916
+ new CapxulError({
2917
+ code: "INVALID_INPUT",
2918
+ message: "organizations.create does not support country yet \u2014 backend mutation only accepts name.",
2919
+ details: { field: "country" }
2920
+ }),
2921
+ null
2922
+ ];
2923
+ }
2924
+ try {
2925
+ const orgId = await config._data.mutation(api.org.mutations.create, {
2926
+ name: input.name
2927
+ });
2928
+ const org = await config._data.query(api.org.queries.retrieve, {
2929
+ orgId
2930
+ });
2931
+ if (!org) {
2932
+ return [
2933
+ new CapxulError({
2934
+ code: "NETWORK_ERROR",
2935
+ message: "organization created but could not be retrieved"
2936
+ }),
2937
+ null
2938
+ ];
2939
+ }
2940
+ return [null, org];
2941
+ } catch (cause) {
2942
+ return [fromConvexError(cause), null];
2943
+ }
2944
+ },
2945
+ retrieve: async (organizationId) => {
2946
+ if (!config._data) {
2947
+ return stub("organizations.retrieve");
2948
+ }
2949
+ try {
2950
+ const orgId = organizationId.replace(/^org_/, "");
2951
+ const org = await config._data.query(api.org.queries.retrieve, {
2952
+ orgId
2953
+ });
2954
+ if (!org) {
2955
+ return [
2956
+ new CapxulError({
2957
+ code: "NOT_FOUND",
2958
+ message: `organization ${organizationId} not found`
2959
+ }),
2960
+ null
2961
+ ];
2962
+ }
2963
+ return [null, org];
2964
+ } catch (cause) {
2965
+ return [fromConvexError(cause), null];
2966
+ }
2967
+ },
2968
+ list: async (input) => {
2969
+ if (!config._data) {
2970
+ return stub("organizations.list");
2971
+ }
2972
+ try {
2973
+ const page = await config._data.query(api.org.queries.list, {
2974
+ limit: input?.limit,
2975
+ cursor: input?.cursor
2976
+ });
2977
+ const result = {
2978
+ object: "list",
2979
+ data: page.data,
2980
+ page: {
2981
+ hasMore: page.hasMore,
2982
+ cursor: page.nextCursor
2983
+ }
2984
+ };
2985
+ return [null, result];
2986
+ } catch (cause) {
2987
+ return [fromConvexError(cause), null];
2988
+ }
2989
+ },
2990
+ update: async (input) => {
2991
+ if (!config._data) {
2992
+ return stub("organizations.update");
2993
+ }
2994
+ try {
2995
+ const orgId = input.organizationId.replace(/^org_/, "");
2996
+ const org = await config._data.mutation(api.org.mutations.update, {
2997
+ orgId,
2998
+ name: input.name
2999
+ });
3000
+ if (!org) {
3001
+ return [
3002
+ new CapxulError({
3003
+ code: "NOT_FOUND",
3004
+ message: `organization ${input.organizationId} not found`
3005
+ }),
3006
+ null
3007
+ ];
3008
+ }
3009
+ return [null, org];
3010
+ } catch (cause) {
3011
+ return [fromConvexError(cause), null];
3012
+ }
3013
+ },
2284
3014
  safes: {
2285
3015
  retrieve: async (input) => {
2286
- if (!config.data) {
3016
+ if (!config._data) {
2287
3017
  return stub("organizations.safes.retrieve");
2288
3018
  }
2289
3019
  try {
2290
- const safe = await config.data.query(
3020
+ const safe = await config._data.query(
2291
3021
  api.safe.queries.retrieveOrganizationSafe,
2292
3022
  input
2293
3023
  );
@@ -2309,36 +3039,245 @@ function createOrganizationsClient(config = {}) {
2309
3039
  }
2310
3040
  }
2311
3041
  },
2312
- treasury: {
2313
- retrieve: async () => stub("organizations.treasury.retrieve")
2314
- },
2315
- members: {
2316
- list: async () => stub("organizations.members.list"),
2317
- retrieve: async () => stub("organizations.members.retrieve"),
2318
- invite: async () => stub("organizations.members.invite"),
2319
- updateRole: async () => stub("organizations.members.updateRole"),
2320
- remove: async () => stub("organizations.members.remove")
2321
- },
2322
- apiKeys: createApiKeysClient(),
2323
- kybProfile: {
2324
- start: async () => stub("organizations.kybProfile.start"),
2325
- retrieve: async () => stub("organizations.kybProfile.retrieve")
2326
- },
2327
- subAccounts: {
2328
- create: async () => stub("organizations.subAccounts.create"),
2329
- list: async () => stub("organizations.subAccounts.list"),
2330
- retrieve: async () => stub("organizations.subAccounts.retrieve"),
2331
- remove: async () => stub("organizations.subAccounts.remove")
2332
- },
2333
- externalAccounts: createOrgExternalAccountsClient(config),
2334
- balanceLedger: {
2335
- list: async () => stub(
2336
- "organizations.balanceLedger.list"
2337
- ),
2338
- retrieve: async () => stub(
2339
- "organizations.balanceLedger.retrieve"
2340
- )
2341
- },
3042
+ treasury: {
3043
+ retrieve: async (organizationId) => {
3044
+ if (!config._data) {
3045
+ return stub(
3046
+ "organizations.treasury.retrieve"
3047
+ );
3048
+ }
3049
+ try {
3050
+ const orgId = organizationId.replace(/^org_/, "");
3051
+ const raw = await config._data.query(
3052
+ api.safe.queries.getOrgTreasuryBalance,
3053
+ { orgId }
3054
+ );
3055
+ if (!raw) {
3056
+ return [
3057
+ new CapxulError({
3058
+ code: "NOT_FOUND",
3059
+ message: `treasury for organization ${organizationId} not found`
3060
+ }),
3061
+ null
3062
+ ];
3063
+ }
3064
+ const treasury = {
3065
+ object: "treasury",
3066
+ id: toTreasuryId(`try_${orgId}`),
3067
+ organizationId,
3068
+ status: "active",
3069
+ safeId: toSafeId(
3070
+ `safe_${raw.safeAddress.replace(/^0x/, "").toLowerCase()}`
3071
+ ),
3072
+ totalBalance: { value: "0", currency: "USD" },
3073
+ positions: raw.tokens.map((t) => ({
3074
+ symbol: t.symbol,
3075
+ contractAddress: t.tokenAddress,
3076
+ amount: t.balance
3077
+ })),
3078
+ asOf: raw.lastUpdatedAt ? new Date(raw.lastUpdatedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
3079
+ };
3080
+ return [null, treasury];
3081
+ } catch (cause) {
3082
+ return [
3083
+ fromConvexError(cause),
3084
+ null
3085
+ ];
3086
+ }
3087
+ }
3088
+ },
3089
+ members: {
3090
+ list: async (input) => {
3091
+ if (!config._data?.action) {
3092
+ return stub("organizations.members.list");
3093
+ }
3094
+ try {
3095
+ const orgId = input.organizationId.replace(/^org_/, "");
3096
+ const page = await config._data.action(api.org.actions.membersList, {
3097
+ organizationId: orgId,
3098
+ status: input.status,
3099
+ limit: input.limit,
3100
+ cursor: input.cursor
3101
+ });
3102
+ return [null, page];
3103
+ } catch (cause) {
3104
+ return [fromConvexError(cause), null];
3105
+ }
3106
+ },
3107
+ retrieve: async (input) => {
3108
+ if (!config._data?.action) {
3109
+ return stub("organizations.members.retrieve");
3110
+ }
3111
+ try {
3112
+ const orgId = input.organizationId.replace(/^org_/, "");
3113
+ const memberId = input.memberId.replace(/^mb_/, "");
3114
+ const member = await config._data.action(
3115
+ api.org.actions.retrieveMember,
3116
+ {
3117
+ organizationId: orgId,
3118
+ memberId
3119
+ }
3120
+ );
3121
+ return [null, member];
3122
+ } catch (cause) {
3123
+ return [fromConvexError(cause), null];
3124
+ }
3125
+ },
3126
+ invite: async (input) => {
3127
+ if (!config._data?.action) {
3128
+ return stub("organizations.members.invite");
3129
+ }
3130
+ try {
3131
+ const orgId = input.organizationId.replace(/^org_/, "");
3132
+ const result = await config._data.action(
3133
+ api.org.actions.inviteMember,
3134
+ {
3135
+ organizationId: orgId,
3136
+ email: input.email,
3137
+ role: input.role
3138
+ }
3139
+ );
3140
+ return [null, result];
3141
+ } catch (cause) {
3142
+ return [fromConvexError(cause), null];
3143
+ }
3144
+ },
3145
+ accept: async (input) => {
3146
+ if (!config._data?.action) {
3147
+ return stub("organizations.members.accept");
3148
+ }
3149
+ try {
3150
+ const member = await config._data.action(
3151
+ api.org.actions.acceptInvitation,
3152
+ { token: input.token }
3153
+ );
3154
+ return [null, member];
3155
+ } catch (cause) {
3156
+ return [fromConvexError(cause), null];
3157
+ }
3158
+ },
3159
+ updateRole: async (input) => {
3160
+ if (!config._data?.action) {
3161
+ return stub("organizations.members.updateRole");
3162
+ }
3163
+ try {
3164
+ const orgId = input.organizationId.replace(/^org_/, "");
3165
+ const memberId = input.memberId.replace(/^mb_/, "");
3166
+ const member = await config._data.action(
3167
+ api.org.actions.updateMemberRole,
3168
+ {
3169
+ organizationId: orgId,
3170
+ memberId,
3171
+ role: input.role
3172
+ }
3173
+ );
3174
+ return [null, member];
3175
+ } catch (cause) {
3176
+ return [fromConvexError(cause), null];
3177
+ }
3178
+ },
3179
+ revoke: async (input) => {
3180
+ if (!config._data?.action) {
3181
+ return stub("organizations.members.revoke");
3182
+ }
3183
+ try {
3184
+ const orgId = input.organizationId.replace(/^org_/, "");
3185
+ const memberId = input.memberId.replace(/^mb_/, "");
3186
+ const member = await config._data.action(
3187
+ api.org.actions.revokeMember,
3188
+ {
3189
+ organizationId: orgId,
3190
+ memberId
3191
+ }
3192
+ );
3193
+ return [null, member];
3194
+ } catch (cause) {
3195
+ return [fromConvexError(cause), null];
3196
+ }
3197
+ },
3198
+ remove: async (input) => {
3199
+ if (!config._data?.action) {
3200
+ return stub("organizations.members.remove");
3201
+ }
3202
+ try {
3203
+ const orgId = input.organizationId.replace(/^org_/, "");
3204
+ const memberId = input.memberId.replace(/^mb_/, "");
3205
+ await config._data.action(api.org.actions.removeMember, {
3206
+ organizationId: orgId,
3207
+ memberId
3208
+ });
3209
+ return [null, void 0];
3210
+ } catch (cause) {
3211
+ return [fromConvexError(cause), null];
3212
+ }
3213
+ },
3214
+ resend: async (input) => {
3215
+ if (!config._data?.action) {
3216
+ return stub("organizations.members.resend");
3217
+ }
3218
+ try {
3219
+ const orgId = input.organizationId.replace(/^org_/, "");
3220
+ const memberId = input.memberId.replace(/^mb_/, "");
3221
+ const result = await config._data.action(
3222
+ api.org.actions.resendInvitation,
3223
+ {
3224
+ organizationId: orgId,
3225
+ memberId
3226
+ }
3227
+ );
3228
+ return [null, result];
3229
+ } catch (cause) {
3230
+ return [fromConvexError(cause), null];
3231
+ }
3232
+ }
3233
+ },
3234
+ apiKeys: createApiKeysClient(),
3235
+ subAccounts: createOrgSubAccountsClient(config),
3236
+ externalAccounts: createOrgExternalAccountsClient(config),
3237
+ balanceLedger: {
3238
+ list: async (input) => {
3239
+ if (!config._data) {
3240
+ return stub(
3241
+ "organizations.balanceLedger.list"
3242
+ );
3243
+ }
3244
+ try {
3245
+ const orgId = input.organizationId.replace(/^org_/, "");
3246
+ const page = await config._data.query(
3247
+ api.balanceLedger.queries.listForOrg,
3248
+ { orgId, limit: input.limit, cursor: input.cursor }
3249
+ );
3250
+ return [null, page];
3251
+ } catch (cause) {
3252
+ return [fromConvexError(cause), null];
3253
+ }
3254
+ },
3255
+ retrieve: async (input) => {
3256
+ if (!config._data) {
3257
+ return stub(
3258
+ "organizations.balanceLedger.retrieve"
3259
+ );
3260
+ }
3261
+ try {
3262
+ const entry = await config._data.query(
3263
+ api.balanceLedger.queries.retrieve,
3264
+ { entryId: input.entryId }
3265
+ );
3266
+ if (!entry) {
3267
+ return [
3268
+ new CapxulError({
3269
+ code: "NOT_FOUND",
3270
+ message: `balance_ledger_entry ${input.entryId} not found`
3271
+ }),
3272
+ null
3273
+ ];
3274
+ }
3275
+ return [null, entry];
3276
+ } catch (cause) {
3277
+ return [fromConvexError(cause), null];
3278
+ }
3279
+ }
3280
+ },
2342
3281
  payments: createOrgPaymentsClient(),
2343
3282
  transfers: createOrgTransfersClient(),
2344
3283
  withdrawals: createOrgWithdrawalsClient(config),
@@ -2348,14 +3287,6 @@ function createOrganizationsClient(config = {}) {
2348
3287
  };
2349
3288
  }
2350
3289
 
2351
- // src/core/sub-accounts.ts
2352
- function createSubAccountsClient() {
2353
- return {
2354
- retrieve: async () => stub("subAccounts.retrieve"),
2355
- remove: async () => stub("subAccounts.remove")
2356
- };
2357
- }
2358
-
2359
3290
  // src/core/token-transfers.ts
2360
3291
  var toTokenTransferId = (raw) => {
2361
3292
  if (typeof raw !== "string" || raw.length === 0) {
@@ -2374,11 +3305,11 @@ function brandRow(row) {
2374
3305
  function createTokenTransfersClient(config = {}) {
2375
3306
  return {
2376
3307
  list: async (input) => {
2377
- if (!config.data) {
3308
+ if (!config._data) {
2378
3309
  return stub("tokenTransfers.list");
2379
3310
  }
2380
3311
  try {
2381
- const raw = await config.data.query(
3312
+ const raw = await config._data.query(
2382
3313
  api.tokenTransfers.queries.list,
2383
3314
  {
2384
3315
  limit: input?.limit,
@@ -2412,11 +3343,11 @@ function createTokenTransfersClient(config = {}) {
2412
3343
  }
2413
3344
  },
2414
3345
  retrieve: async (input) => {
2415
- if (!config.data) {
3346
+ if (!config._data) {
2416
3347
  return stub("tokenTransfers.retrieve");
2417
3348
  }
2418
3349
  try {
2419
- const raw = await config.data.query(
3350
+ const raw = await config._data.query(
2420
3351
  api.tokenTransfers.queries.getByTxLogIndex,
2421
3352
  {
2422
3353
  txHash: input.txHash,
@@ -2486,7 +3417,7 @@ function createAuthFlowMachine(client) {
2486
3417
  }),
2487
3418
  verifyOtp: xstate.fromPromise(
2488
3419
  async ({ input, signal }) => {
2489
- const [error, session] = await client.auth.verifyOtp(
3420
+ const [error, result] = await client.auth.verifyOtp(
2490
3421
  {
2491
3422
  email: input.email,
2492
3423
  otp: input.code
@@ -2494,7 +3425,14 @@ function createAuthFlowMachine(client) {
2494
3425
  { signal }
2495
3426
  );
2496
3427
  if (error) throw error;
2497
- return session;
3428
+ if (result.kind === "bootstrap_required") {
3429
+ throw new CapxulError({
3430
+ code: "ACTION_REQUIRED",
3431
+ message: "OTP verified but auth bootstrap is required. Use createAuthBootstrapFlowMachine for product sign-up.",
3432
+ details: { reason: result.reason }
3433
+ });
3434
+ }
3435
+ return result.session;
2498
3436
  }
2499
3437
  ),
2500
3438
  signOut: xstate.fromPromise(async () => {
@@ -2742,6 +3680,327 @@ function emailDomain(email) {
2742
3680
  const domain = email.split("@")[1]?.trim().toLowerCase();
2743
3681
  return domain || "unknown";
2744
3682
  }
3683
+ var initialContext = {
3684
+ email: null,
3685
+ code: null,
3686
+ username: null,
3687
+ bootstrapToken: null,
3688
+ bootstrapReason: null,
3689
+ session: null,
3690
+ account: null,
3691
+ safe: null,
3692
+ error: null
3693
+ };
3694
+ function createAuthBootstrapFlowMachine(client) {
3695
+ return xstate.setup({
3696
+ types: {},
3697
+ actors: {
3698
+ sendOtp: xstate.fromPromise(async ({ input, signal }) => {
3699
+ const [error] = await client.auth.sendOtp(
3700
+ { email: input.email },
3701
+ { signal }
3702
+ );
3703
+ if (error) throw error;
3704
+ }),
3705
+ verifyOtp: xstate.fromPromise(
3706
+ async ({ input, signal }) => {
3707
+ const [error, result] = await client.auth.verifyOtp(
3708
+ { email: input.email, otp: input.code },
3709
+ { signal }
3710
+ );
3711
+ if (error) throw error;
3712
+ return result;
3713
+ }
3714
+ ),
3715
+ completeBootstrap: xstate.fromPromise(async ({ input }) => {
3716
+ const [error, result] = await client.auth.completeBootstrap(input);
3717
+ if (error) throw error;
3718
+ return result;
3719
+ }),
3720
+ signOut: xstate.fromPromise(async () => {
3721
+ const [error] = await client.auth.signOut();
3722
+ if (error) throw error;
3723
+ })
3724
+ },
3725
+ actions: {
3726
+ trackOtpRequested: ({ context }) => {
3727
+ if (!context.email) return;
3728
+ track("auth_otp_requested", {
3729
+ email_domain: emailDomain2(context.email)
3730
+ });
3731
+ },
3732
+ trackFailed: ({ event }) => {
3733
+ track("auth_failed", {
3734
+ auth_type: "email_otp",
3735
+ reason: errorFromEvent2(event).code
3736
+ });
3737
+ },
3738
+ trackTimeoutFailed: () => {
3739
+ track("auth_failed", {
3740
+ auth_type: "email_otp",
3741
+ reason: "timeout"
3742
+ });
3743
+ },
3744
+ trackVerified: () => {
3745
+ track("auth_verified", { auth_type: "email_otp" });
3746
+ },
3747
+ trackBootstrapRequired: ({ context }) => {
3748
+ track("auth_verified", {
3749
+ auth_type: "email_otp",
3750
+ auth_mode: context.bootstrapReason ?? "bootstrap_required"
3751
+ });
3752
+ },
3753
+ identifyAndTrack: ({ context }) => {
3754
+ if (!context.session) return;
3755
+ identify(context.session.authUserId, {
3756
+ email_domain: emailDomain2(context.session.email)
3757
+ });
3758
+ track("auth_identified", {
3759
+ email_domain: emailDomain2(context.session.email)
3760
+ });
3761
+ },
3762
+ trackSignedOut: () => {
3763
+ track("auth_signed_out");
3764
+ }
3765
+ }
3766
+ }).createMachine({
3767
+ id: "authBootstrap",
3768
+ initial: "email",
3769
+ context: initialContext,
3770
+ states: {
3771
+ email: {
3772
+ on: {
3773
+ ENTER_EMAIL: {
3774
+ actions: xstate.assign({
3775
+ email: ({ event }) => event.email,
3776
+ error: () => null
3777
+ })
3778
+ },
3779
+ REQUEST_OTP: { target: "sending_otp" }
3780
+ }
3781
+ },
3782
+ sending_otp: {
3783
+ invoke: {
3784
+ src: "sendOtp",
3785
+ input: ({ context }) => ({ email: requireEmail2(context) }),
3786
+ onDone: { target: "otp_requested", actions: "trackOtpRequested" },
3787
+ onError: {
3788
+ target: "otp_requested",
3789
+ actions: [
3790
+ xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3791
+ "trackFailed"
3792
+ ]
3793
+ }
3794
+ },
3795
+ after: {
3796
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3797
+ target: "otp_requested",
3798
+ actions: [
3799
+ xstate.assign({ error: () => timeoutError2("sending_otp") }),
3800
+ "trackTimeoutFailed"
3801
+ ]
3802
+ }
3803
+ }
3804
+ },
3805
+ otp_requested: {
3806
+ on: {
3807
+ ENTER_OTP: {
3808
+ actions: xstate.assign({
3809
+ code: ({ event }) => event.code,
3810
+ error: () => null
3811
+ })
3812
+ },
3813
+ VERIFY_OTP: { target: "verifying_otp" },
3814
+ BACK: { target: "email" },
3815
+ RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3816
+ }
3817
+ },
3818
+ verifying_otp: {
3819
+ invoke: {
3820
+ src: "verifyOtp",
3821
+ input: ({ context }) => ({
3822
+ email: requireEmail2(context),
3823
+ code: requireCode(context)
3824
+ }),
3825
+ onDone: [
3826
+ {
3827
+ guard: ({ event }) => event.output.kind === "existing_member",
3828
+ target: "authenticated",
3829
+ actions: [
3830
+ xstate.assign({
3831
+ session: ({ event }) => event.output.session,
3832
+ account: ({ event }) => event.output.kind === "existing_member" ? event.output.account : null,
3833
+ username: ({ event }) => event.output.kind === "existing_member" ? event.output.username : null,
3834
+ safe: ({ event }) => event.output.kind === "existing_member" ? event.output.safe : null,
3835
+ email: () => null,
3836
+ error: () => null
3837
+ }),
3838
+ "trackVerified",
3839
+ "identifyAndTrack"
3840
+ ]
3841
+ },
3842
+ {
3843
+ target: "bootstrap_required",
3844
+ actions: [
3845
+ xstate.assign({
3846
+ session: ({ event }) => event.output.session,
3847
+ bootstrapToken: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.bootstrapToken : null,
3848
+ bootstrapReason: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.reason : null,
3849
+ username: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.username ?? null : null,
3850
+ email: ({ event }) => event.output.kind === "bootstrap_required" ? event.output.email : null,
3851
+ error: () => null
3852
+ }),
3853
+ "trackVerified",
3854
+ "trackBootstrapRequired"
3855
+ ]
3856
+ }
3857
+ ],
3858
+ onError: {
3859
+ target: "otp_requested",
3860
+ actions: [
3861
+ xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3862
+ "trackFailed"
3863
+ ]
3864
+ }
3865
+ },
3866
+ after: {
3867
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3868
+ target: "otp_requested",
3869
+ actions: [
3870
+ xstate.assign({ error: () => timeoutError2("verifying_otp") }),
3871
+ "trackTimeoutFailed"
3872
+ ]
3873
+ }
3874
+ }
3875
+ },
3876
+ bootstrap_required: {
3877
+ on: {
3878
+ ENTER_USERNAME: {
3879
+ actions: xstate.assign({
3880
+ username: ({ event }) => event.username,
3881
+ error: () => null
3882
+ })
3883
+ },
3884
+ COMPLETE_BOOTSTRAP: { target: "completing_bootstrap" },
3885
+ BACK: { target: "otp_requested" },
3886
+ RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3887
+ }
3888
+ },
3889
+ completing_bootstrap: {
3890
+ invoke: {
3891
+ src: "completeBootstrap",
3892
+ input: ({ context }) => ({
3893
+ bootstrapToken: requireBootstrapToken(context),
3894
+ username: requireUsername(context)
3895
+ }),
3896
+ onDone: {
3897
+ target: "authenticated",
3898
+ actions: [
3899
+ xstate.assign({
3900
+ session: ({ event }) => event.output.session,
3901
+ account: ({ event }) => event.output.account,
3902
+ username: ({ event }) => event.output.username,
3903
+ safe: ({ event }) => event.output.safe,
3904
+ bootstrapToken: () => null,
3905
+ bootstrapReason: () => null,
3906
+ email: () => null,
3907
+ error: () => null
3908
+ }),
3909
+ "identifyAndTrack"
3910
+ ]
3911
+ },
3912
+ onError: {
3913
+ target: "bootstrap_required",
3914
+ actions: [
3915
+ xstate.assign({ error: ({ event }) => errorFromEvent2(event) }),
3916
+ "trackFailed"
3917
+ ]
3918
+ }
3919
+ },
3920
+ after: {
3921
+ [FLOW_INVOKE_TIMEOUT_MS]: {
3922
+ target: "bootstrap_required",
3923
+ actions: [
3924
+ xstate.assign({ error: () => timeoutError2("completing_bootstrap") }),
3925
+ "trackTimeoutFailed"
3926
+ ]
3927
+ }
3928
+ }
3929
+ },
3930
+ authenticated: {
3931
+ on: {
3932
+ SIGN_OUT: { target: "signing_out" }
3933
+ }
3934
+ },
3935
+ signing_out: {
3936
+ invoke: {
3937
+ src: "signOut",
3938
+ onDone: {
3939
+ target: "email",
3940
+ actions: [
3941
+ xstate.assign(() => initialContext),
3942
+ "trackSignedOut"
3943
+ ]
3944
+ },
3945
+ onError: {
3946
+ target: "error",
3947
+ actions: xstate.assign({ error: ({ event }) => errorFromEvent2(event) })
3948
+ }
3949
+ }
3950
+ },
3951
+ error: {
3952
+ on: {
3953
+ RESET: { target: "email", actions: xstate.assign(() => initialContext) }
3954
+ }
3955
+ }
3956
+ }
3957
+ });
3958
+ }
3959
+ function requireEmail2(context) {
3960
+ if (!context.email) {
3961
+ throw Errors.invalidInput("email", "Auth bootstrap requires an email.");
3962
+ }
3963
+ return context.email;
3964
+ }
3965
+ function requireCode(context) {
3966
+ if (!context.code) {
3967
+ throw Errors.invalidInput("code", "Auth bootstrap requires an OTP code.");
3968
+ }
3969
+ return context.code;
3970
+ }
3971
+ function requireBootstrapToken(context) {
3972
+ if (!context.bootstrapToken) {
3973
+ throw Errors.invalidInput(
3974
+ "bootstrapToken",
3975
+ "Auth bootstrap requires a continuation token."
3976
+ );
3977
+ }
3978
+ return context.bootstrapToken;
3979
+ }
3980
+ function requireUsername(context) {
3981
+ if (!context.username) {
3982
+ throw Errors.invalidInput("username", "Auth bootstrap requires a username.");
3983
+ }
3984
+ return context.username;
3985
+ }
3986
+ function errorFromEvent2(event) {
3987
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3988
+ if (cause instanceof CapxulError || cause instanceof CapxulError2) {
3989
+ return cause;
3990
+ }
3991
+ return Errors.providerError("auth", "bootstrap", cause);
3992
+ }
3993
+ function timeoutError2(state) {
3994
+ return Errors.providerError(
3995
+ "auth",
3996
+ "bootstrap",
3997
+ new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
3998
+ );
3999
+ }
4000
+ function emailDomain2(email) {
4001
+ const domain = email.split("@")[1]?.trim().toLowerCase();
4002
+ return domain || "unknown";
4003
+ }
2745
4004
  function createProvisioningMachine(client) {
2746
4005
  return xstate.setup({
2747
4006
  types: {},
@@ -2769,7 +4028,7 @@ function createProvisioningMachine(client) {
2769
4028
  const provider = context.input?.signerProvider;
2770
4029
  if (!provider) return;
2771
4030
  track("provisioning_safe_created", {
2772
- safe_address: provider.safeAddress
4031
+ safe_address: deriveSafeAddress(provider.signerAddress)
2773
4032
  });
2774
4033
  }
2775
4034
  }
@@ -2814,13 +4073,13 @@ function createProvisioningMachine(client) {
2814
4073
  },
2815
4074
  onError: {
2816
4075
  target: "error",
2817
- actions: xstate.assign({ error: ({ event }) => errorFromEvent2(event) })
4076
+ actions: xstate.assign({ error: ({ event }) => errorFromEvent3(event) })
2818
4077
  }
2819
4078
  },
2820
4079
  after: {
2821
4080
  [FLOW_INVOKE_TIMEOUT_MS]: {
2822
4081
  target: "error",
2823
- actions: xstate.assign({ error: () => timeoutError2() })
4082
+ actions: xstate.assign({ error: () => timeoutError3() })
2824
4083
  }
2825
4084
  }
2826
4085
  },
@@ -2838,7 +4097,7 @@ function createProvisioningMachine(client) {
2838
4097
  * this payload on its `onDone` transition and branches via guards
2839
4098
  * on `event.output.error`.
2840
4099
  */
2841
- output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError2() }
4100
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError3() }
2842
4101
  });
2843
4102
  }
2844
4103
  function requireProvisionInput(context) {
@@ -2850,13 +4109,13 @@ function requireProvisionInput(context) {
2850
4109
  }
2851
4110
  return context.input;
2852
4111
  }
2853
- function errorFromEvent2(event) {
4112
+ function errorFromEvent3(event) {
2854
4113
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2855
4114
  if (cause instanceof CapxulError) return cause;
2856
4115
  if (cause instanceof CapxulError2) return cause;
2857
4116
  return Errors.providerError("provisioning", "flow", cause);
2858
4117
  }
2859
- function timeoutError2() {
4118
+ function timeoutError3() {
2860
4119
  return Errors.providerError(
2861
4120
  "provisioning",
2862
4121
  "flow",
@@ -2949,7 +4208,7 @@ function createOnboardingFlowMachine(client) {
2949
4208
  error: ({ event }) => extractChildErrorOrFallback(event)
2950
4209
  }),
2951
4210
  assignChildThrown: xstate.assign({
2952
- error: ({ event }) => errorFromEvent3(event)
4211
+ error: ({ event }) => errorFromEvent4(event)
2953
4212
  }),
2954
4213
  assignAccountFromChild: xstate.assign({
2955
4214
  account: ({ event }) => extractChildAccountOrNull(event)
@@ -3111,7 +4370,7 @@ function extractChildAccountOrNull(event) {
3111
4370
  if (output && "account" in output && output.account) return output.account;
3112
4371
  return null;
3113
4372
  }
3114
- function errorFromEvent3(event) {
4373
+ function errorFromEvent4(event) {
3115
4374
  const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
3116
4375
  if (cause instanceof CapxulError) return cause;
3117
4376
  if (cause instanceof CapxulError2) return cause;
@@ -3131,7 +4390,7 @@ function createCapxulClient(config = {}) {
3131
4390
  tokenTransfers: createTokenTransfersClient(config),
3132
4391
  withdrawals: createWithdrawalsClient(config),
3133
4392
  documents: createDocumentsClient(),
3134
- subAccounts: createSubAccountsClient(),
4393
+ subAccounts: createSubAccountsClient(config),
3135
4394
  virtualAccounts: createVirtualAccountsClient(),
3136
4395
  virtualCards: createVirtualCardsClient(),
3137
4396
  externalAccounts: createExternalAccountsClient(config),
@@ -3143,6 +4402,7 @@ function createCapxulClient(config = {}) {
3143
4402
  const client = clientWithoutFlows;
3144
4403
  client.flows = {
3145
4404
  auth: () => createAuthFlowMachine(client),
4405
+ authBootstrap: () => createAuthBootstrapFlowMachine(client),
3146
4406
  onboarding: () => createOnboardingFlowMachine(client),
3147
4407
  provisioning: () => createProvisioningMachine(client)
3148
4408
  };
@@ -3247,7 +4507,87 @@ function isWebhookEvent(value) {
3247
4507
  return typeof candidate.id === "string" && typeof candidate.type === "string" && typeof candidate.createdAt === "string" && (candidate.operationId === void 0 || typeof candidate.operationId === "string") && (candidate.correlationId === void 0 || typeof candidate.correlationId === "string") && !!candidate.data && typeof candidate.data === "object" && !Array.isArray(candidate.data);
3248
4508
  }
3249
4509
 
4510
+ // src/core/auth-service.ts
4511
+ var AuthService = class {
4512
+ authClient;
4513
+ sessionStore;
4514
+ config;
4515
+ constructor(config = {}) {
4516
+ this.config = config;
4517
+ this.sessionStore = config.auth?.sessionStore ?? createMemorySessionStore2();
4518
+ this.authClient = createAuthClient({
4519
+ ...config,
4520
+ auth: { ...config.auth, sessionStore: this.sessionStore }
4521
+ });
4522
+ }
4523
+ async sendOtp(email) {
4524
+ const [err] = await this.authClient.sendOtp({ email });
4525
+ if (err) throw err;
4526
+ }
4527
+ async verifyOtp(email, otp) {
4528
+ const [err, result] = await this.authClient.verifyOtp({ email, otp });
4529
+ if (err) throw err;
4530
+ return result;
4531
+ }
4532
+ async completeBootstrap(params, signer) {
4533
+ if (signer) {
4534
+ const tempClient = createAuthClient({
4535
+ ...this.config,
4536
+ signer
4537
+ });
4538
+ const [err2, result2] = await tempClient.completeBootstrap(params);
4539
+ if (err2) throw err2;
4540
+ return result2;
4541
+ }
4542
+ const [err, result] = await this.authClient.completeBootstrap(params);
4543
+ if (err) throw err;
4544
+ return result;
4545
+ }
4546
+ /**
4547
+ * Clears the persisted session and, when a transport was pre-injected,
4548
+ * drops the cached auth header.
4549
+ *
4550
+ * **Transport safety note:** `clearAuth()` is only invoked when
4551
+ * `config._transport` was supplied at construction (e.g. by the React
4552
+ * provider). If `AuthService` is instantiated directly in a Node/CLI
4553
+ * context without an injected transport, the transport-side auth cache
4554
+ * is the caller's responsibility.
4555
+ */
4556
+ async signOut() {
4557
+ this.sessionStore.clear();
4558
+ this.config._transport?.clearAuth();
4559
+ }
4560
+ async getSession() {
4561
+ const [err, session] = await this.authClient.getSession();
4562
+ if (err) throw err;
4563
+ return session;
4564
+ }
4565
+ };
4566
+ function createMemorySessionStore2() {
4567
+ let current = null;
4568
+ return {
4569
+ get: () => current,
4570
+ set: (session) => {
4571
+ current = session;
4572
+ },
4573
+ clear: () => {
4574
+ current = null;
4575
+ }
4576
+ };
4577
+ }
4578
+ var SignerProvisioner = class {
4579
+ provision() {
4580
+ const privateKey = accounts.generatePrivateKey();
4581
+ const signer = accounts.privateKeyToAccount(privateKey);
4582
+ const safeAddress = deriveSafeAddress(signer.address);
4583
+ return { signer, safeAddress };
4584
+ }
4585
+ };
4586
+
4587
+ exports.AuthService = AuthService;
3250
4588
  exports.CapxulError = CapxulError;
4589
+ exports.SignerProvisioner = SignerProvisioner;
4590
+ exports.createAuthBootstrapFlowMachine = createAuthBootstrapFlowMachine;
3251
4591
  exports.createAuthFlowMachine = createAuthFlowMachine;
3252
4592
  exports.createCapxulClient = createCapxulClient;
3253
4593
  exports.createLocalSigner = createLocalSigner;
@@ -3257,6 +4597,7 @@ exports.makeHttpTransport = makeHttpTransport;
3257
4597
  exports.matchAction = matchAction;
3258
4598
  exports.matchError = matchError;
3259
4599
  exports.matchStatus = matchStatus;
4600
+ exports.resolvePaymentToken = resolvePaymentToken;
3260
4601
  exports.toAccountId = toAccountId;
3261
4602
  exports.toApiKeyId = toApiKeyId;
3262
4603
  exports.toBalanceLedgerEntryId = toBalanceLedgerEntryId;