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