@capxul/sdk 0.1.0-alpha.4 → 0.1.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -70,7 +70,366 @@ function fromConvexError(error) {
70
70
  });
71
71
  }
72
72
 
73
+ // ../observability/src/try-catch.ts
74
+ async function tryCatch(promise) {
75
+ try {
76
+ return [null, await promise];
77
+ } catch (e) {
78
+ return [e instanceof Error ? e : new Error(String(e)), null];
79
+ }
80
+ }
81
+
82
+ // ../observability/src/debug-log.ts
83
+ function isDevelopmentBuild() {
84
+ if (typeof process === "undefined") {
85
+ return false;
86
+ }
87
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
88
+ }
89
+ function debugLog(line) {
90
+ if (!isDevelopmentBuild()) return;
91
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
92
+ console.info(line);
93
+ return;
94
+ }
95
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
96
+ process.stderr.write(`${line}
97
+ `);
98
+ }
99
+ }
100
+ function formatDebugValue(value) {
101
+ if (value === void 0 || value === "") return "";
102
+ if (typeof value === "string") return value;
103
+ try {
104
+ return JSON.stringify(value);
105
+ } catch {
106
+ return String(value);
107
+ }
108
+ }
109
+ function track(...args) {
110
+ const [name, props] = args;
111
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
112
+ }
113
+ function formatDebugValue2(value) {
114
+ if (value === void 0 || value === "") return "";
115
+ if (typeof value === "string") return value;
116
+ try {
117
+ return JSON.stringify(value);
118
+ } catch {
119
+ return String(value);
120
+ }
121
+ }
122
+ function identify(userId, traits) {
123
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
124
+ }
125
+
126
+ // ../platform-kernel/src/ids.ts
127
+ function makePrefixedIdConstructor(prefix, fieldName) {
128
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
129
+ return (raw) => {
130
+ if (typeof raw !== "string" || !re.test(raw)) {
131
+ throw new Error(
132
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
133
+ );
134
+ }
135
+ return raw;
136
+ };
137
+ }
138
+ var toAccountId = makePrefixedIdConstructor(
139
+ "acct",
140
+ "accountId"
141
+ );
142
+ var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
143
+ var toMemberId = makePrefixedIdConstructor(
144
+ "mem",
145
+ "memberId"
146
+ );
147
+ var toSafeId = makePrefixedIdConstructor(
148
+ "safe",
149
+ "safeId"
150
+ );
151
+ var toTreasuryId = makePrefixedIdConstructor(
152
+ "try",
153
+ "treasuryId"
154
+ );
155
+ var toApiKeyId = makePrefixedIdConstructor(
156
+ "ak",
157
+ "apiKeyId"
158
+ );
159
+ var toOperationId = makePrefixedIdConstructor(
160
+ "op",
161
+ "operationId"
162
+ );
163
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
164
+ var toKycProfileId = makePrefixedIdConstructor(
165
+ "kyc",
166
+ "kycProfileId"
167
+ );
168
+ var toKybProfileId = makePrefixedIdConstructor(
169
+ "kyb",
170
+ "kybProfileId"
171
+ );
172
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
173
+ var toPaymentId = makePrefixedIdConstructor(
174
+ "pay",
175
+ "paymentId"
176
+ );
177
+ var toWithdrawalId = makePrefixedIdConstructor(
178
+ "wd",
179
+ "withdrawalId"
180
+ );
181
+ var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
182
+ var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
183
+ var toSubAccountId = makePrefixedIdConstructor(
184
+ "sub",
185
+ "subAccountId"
186
+ );
187
+ var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
188
+ var toVirtualCardId = makePrefixedIdConstructor(
189
+ "vc",
190
+ "virtualCardId"
191
+ );
192
+ var toTransferId = makePrefixedIdConstructor(
193
+ "txfr",
194
+ "transferId"
195
+ );
196
+ var toDocumentId = makePrefixedIdConstructor(
197
+ "doc",
198
+ "documentId"
199
+ );
200
+ var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
201
+
202
+ // ../platform-kernel/src/value-objects.ts
203
+ var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
204
+ function toPhoneNumber(raw) {
205
+ if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
206
+ throw new Error(
207
+ `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
208
+ );
209
+ }
210
+ return raw;
211
+ }
212
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
213
+ function toEmail(raw) {
214
+ if (typeof raw !== "string") {
215
+ throw new Error(
216
+ `Invalid email: expected string, got ${String(raw)}`
217
+ );
218
+ }
219
+ const lowered = raw.trim().toLowerCase();
220
+ if (!EMAIL_RE.test(lowered)) {
221
+ throw new Error(
222
+ `Invalid email: expected local@domain.tld, got ${String(raw)}`
223
+ );
224
+ }
225
+ return lowered;
226
+ }
227
+ var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
228
+ function toUsername(raw) {
229
+ if (typeof raw !== "string") {
230
+ throw new Error(
231
+ `Invalid username: expected string, got ${String(raw)}`
232
+ );
233
+ }
234
+ const lowered = raw.toLowerCase();
235
+ if (!USERNAME_RE.test(lowered)) {
236
+ throw new Error(
237
+ `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
238
+ );
239
+ }
240
+ return lowered;
241
+ }
242
+
243
+ // src/core/external-accounts.ts
244
+ function brandExternalAccount(raw) {
245
+ return {
246
+ ...raw,
247
+ id: toExternalAccountId(raw.id),
248
+ operation: {
249
+ id: toOperationId(raw.operation.id),
250
+ status: raw.operation.status,
251
+ correlationId: toCorrelationId(raw.operation.correlationId)
252
+ }
253
+ };
254
+ }
255
+ function createExternalAccountsClient(config = {}) {
256
+ return {
257
+ retrieve: async (externalAccountId) => {
258
+ if (!config.data) {
259
+ return stub(
260
+ "externalAccounts.retrieve"
261
+ );
262
+ }
263
+ const [err, raw] = await tryCatch(
264
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
265
+ externalAccountId
266
+ })
267
+ );
268
+ if (err) {
269
+ return [
270
+ fromConvexError(err),
271
+ null
272
+ ];
273
+ }
274
+ if (!raw) {
275
+ return [
276
+ new CapxulError({
277
+ code: "NOT_FOUND",
278
+ message: `external_account ${externalAccountId} not found`
279
+ }),
280
+ null
281
+ ];
282
+ }
283
+ return [null, brandExternalAccount(raw)];
284
+ },
285
+ remove: async (externalAccountId) => {
286
+ if (!config.data) {
287
+ return stub("externalAccounts.remove");
288
+ }
289
+ const [err] = await tryCatch(
290
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
291
+ externalAccountId
292
+ })
293
+ );
294
+ if (err) {
295
+ return [
296
+ fromConvexError(err),
297
+ null
298
+ ];
299
+ }
300
+ return [null, void 0];
301
+ }
302
+ };
303
+ }
304
+
73
305
  // src/core/accounts.ts
306
+ function createAccountExternalAccountsClient(config) {
307
+ return {
308
+ create: async (input) => {
309
+ if (!config.data) {
310
+ return stub(
311
+ "accounts.externalAccounts.create"
312
+ );
313
+ }
314
+ const [err, raw] = await tryCatch(
315
+ config.data.mutation(
316
+ api.externalAccounts.mutations.createPersonal,
317
+ {
318
+ kind: input.kind,
319
+ label: input.label,
320
+ address: input.address,
321
+ iban: input.iban,
322
+ bic: input.bic,
323
+ accountHolder: input.accountHolder,
324
+ network: input.network,
325
+ panToken: input.panToken,
326
+ last4: input.last4
327
+ }
328
+ )
329
+ );
330
+ if (err) {
331
+ return [
332
+ fromConvexError(err),
333
+ null
334
+ ];
335
+ }
336
+ if (!raw) {
337
+ return [
338
+ new CapxulError({
339
+ code: "NOT_FOUND",
340
+ message: "external_account creation returned no resource"
341
+ }),
342
+ null
343
+ ];
344
+ }
345
+ return [
346
+ null,
347
+ brandExternalAccount(
348
+ raw
349
+ )
350
+ ];
351
+ },
352
+ list: async (input) => {
353
+ if (!config.data) {
354
+ return stub(
355
+ "accounts.externalAccounts.list"
356
+ );
357
+ }
358
+ const [err, result] = await tryCatch(
359
+ config.data.query(api.externalAccounts.queries.listPersonal, {
360
+ limit: input.limit,
361
+ cursor: input.cursor
362
+ })
363
+ );
364
+ if (err) {
365
+ return [fromConvexError(err), null];
366
+ }
367
+ const branded = result.data.map(
368
+ (row) => brandExternalAccount(
369
+ row
370
+ )
371
+ );
372
+ return [
373
+ null,
374
+ {
375
+ object: "list",
376
+ data: branded,
377
+ page: result.page
378
+ }
379
+ ];
380
+ },
381
+ retrieve: async (externalAccountId) => {
382
+ if (!config.data) {
383
+ return stub(
384
+ "accounts.externalAccounts.retrieve"
385
+ );
386
+ }
387
+ const [err, raw] = await tryCatch(
388
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
389
+ externalAccountId
390
+ })
391
+ );
392
+ if (err) {
393
+ return [
394
+ fromConvexError(err),
395
+ null
396
+ ];
397
+ }
398
+ if (!raw) {
399
+ return [
400
+ new CapxulError({
401
+ code: "NOT_FOUND",
402
+ message: `external_account ${externalAccountId} not found`
403
+ }),
404
+ null
405
+ ];
406
+ }
407
+ return [
408
+ null,
409
+ brandExternalAccount(
410
+ raw
411
+ )
412
+ ];
413
+ },
414
+ remove: async (externalAccountId) => {
415
+ if (!config.data) {
416
+ return stub("accounts.externalAccounts.remove");
417
+ }
418
+ const [err] = await tryCatch(
419
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
420
+ externalAccountId
421
+ })
422
+ );
423
+ if (err) {
424
+ return [
425
+ fromConvexError(err),
426
+ null
427
+ ];
428
+ }
429
+ return [null, void 0];
430
+ }
431
+ };
432
+ }
74
433
  function createAccountsClient(config = {}) {
75
434
  return {
76
435
  retrieve: async (accountId) => {
@@ -225,18 +584,7 @@ function createAccountsClient(config = {}) {
225
584
  create: async () => stub("accounts.kycProfiles.create"),
226
585
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
227
586
  },
228
- externalAccounts: {
229
- create: async () => stub(
230
- "accounts.externalAccounts.create"
231
- ),
232
- list: async () => stub(
233
- "accounts.externalAccounts.list"
234
- ),
235
- retrieve: async () => stub(
236
- "accounts.externalAccounts.retrieve"
237
- ),
238
- remove: async () => stub("accounts.externalAccounts.remove")
239
- },
587
+ externalAccounts: createAccountExternalAccountsClient(config),
240
588
  subAccounts: {
241
589
  create: async () => stub("accounts.subAccounts.create"),
242
590
  list: async () => stub("accounts.subAccounts.list"),
@@ -331,8 +679,35 @@ var Errors = {
331
679
  "Idempotency key was already used for a different request",
332
680
  { details }
333
681
  ),
334
- emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
335
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
682
+ emailDeliveryFailed: (detail, details) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`, {
683
+ details
684
+ }),
685
+ rateLimited: (details) => new CapxulError2("RATE_LIMITED", "Request was rate limited", {
686
+ details: { ...details }
687
+ }),
688
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
689
+ /**
690
+ * Verification gate. Surfaced when a request hits a verification
691
+ * boundary the actor cannot cross under their current state. Two
692
+ * variants share this code:
693
+ *
694
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
695
+ * `external_account.kind` routes to a withdrawal rail (e.g.
696
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
697
+ * `details.rail` + `details.currentKind`.
698
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
699
+ * the required tier. Carries `details.requiredTier`.
700
+ *
701
+ * Code is shared because both expose the same UX shape ("you cannot
702
+ * proceed until verification advances"); the `details.*` keys
703
+ * differentiate the route.
704
+ */
705
+ verificationRequired: (details) => {
706
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
707
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
708
+ details: { ...details }
709
+ });
710
+ }
336
711
  };
337
712
 
338
713
  // ../config/src/safe.ts
@@ -382,13 +757,13 @@ function createLifecycle(initial) {
382
757
  }
383
758
  function makeBuildTimeUrlsTransport(config) {
384
759
  if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
385
- throw Errors.invalidInput(
760
+ throw invalidConfigError(
386
761
  "authBaseUrl",
387
762
  "build-time-urls transport requires a non-empty authBaseUrl."
388
763
  );
389
764
  }
390
765
  if (!config.convexUrl || config.convexUrl.trim().length === 0) {
391
- throw Errors.invalidInput(
766
+ throw invalidConfigError(
392
767
  "convexUrl",
393
768
  "build-time-urls transport requires a non-empty convexUrl."
394
769
  );
@@ -402,6 +777,7 @@ function makeBuildTimeUrlsTransport(config) {
402
777
  return {
403
778
  authBaseUrl,
404
779
  convexUrl,
780
+ ensureRuntime: async () => runtime,
405
781
  fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
406
782
  getState: lifecycle.getState,
407
783
  subscribe: lifecycle.subscribe,
@@ -417,14 +793,15 @@ function makeBuildTimeUrlsTransport(config) {
417
793
  };
418
794
  }
419
795
  function makePublishableKeyTransport(config) {
420
- if (!config.publishableKey || config.publishableKey.trim().length === 0) {
421
- throw Errors.invalidInput(
796
+ const publishableKey = config.publishableKey?.trim();
797
+ if (!publishableKey) {
798
+ throw invalidConfigError(
422
799
  "publishableKey",
423
800
  "publishable-key transport requires a non-empty publishableKey."
424
801
  );
425
802
  }
426
803
  const fetchImpl = config.fetchImpl ?? globalThis.fetch;
427
- const bootstrapUrl = stripTrailingSlash(
804
+ const bootstrapUrl = normalizeBootstrapUrl(
428
805
  config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
429
806
  );
430
807
  let authBaseUrl = "";
@@ -439,23 +816,20 @@ function makePublishableKeyTransport(config) {
439
816
  const response = await fetchImpl(bootstrapUrl, {
440
817
  method: "POST",
441
818
  headers: { "content-type": "application/json" },
442
- body: JSON.stringify({ publishableKey: config.publishableKey })
819
+ body: JSON.stringify({ publishableKey })
443
820
  });
444
821
  if (!response.ok) {
445
- throw Errors.invalidInput(
446
- "publishableKey",
447
- `${bootstrapUrl} failed with HTTP ${response.status}.`
448
- );
822
+ throw await bootstrapResponseError(response, bootstrapUrl);
449
823
  }
450
- const body = await response.json();
824
+ const body = await readBootstrapSuccessBody(response);
451
825
  if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
452
- throw Errors.invalidInput(
826
+ throw bootstrapContractError(
453
827
  "authBaseUrl",
454
828
  "/v1/client/bootstrap returned no authBaseUrl."
455
829
  );
456
830
  }
457
831
  if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
458
- throw Errors.invalidInput(
832
+ throw bootstrapContractError(
459
833
  "convexUrl",
460
834
  "/v1/client/bootstrap returned no convexUrl."
461
835
  );
@@ -467,16 +841,13 @@ function makePublishableKeyTransport(config) {
467
841
  return runtime;
468
842
  })();
469
843
  bootstrapPromise = attempt.catch((err) => {
844
+ const error = normalizeBootstrapThrownError(err);
470
845
  bootstrapPromise = null;
471
846
  lifecycle.setState({
472
847
  status: "error",
473
- error: err instanceof CapxulError ? err : new CapxulError({
474
- code: "UNKNOWN",
475
- message: "Bootstrap failed without a typed CapxulError.",
476
- cause: err
477
- })
848
+ error
478
849
  });
479
- throw err;
850
+ throw error;
480
851
  });
481
852
  return await bootstrapPromise;
482
853
  }
@@ -487,6 +858,7 @@ function makePublishableKeyTransport(config) {
487
858
  get convexUrl() {
488
859
  return convexUrl;
489
860
  },
861
+ ensureRuntime: ensureBootstrap,
490
862
  fetch: async (path, init) => {
491
863
  const resolved = await ensureBootstrap();
492
864
  return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
@@ -497,7 +869,7 @@ function makePublishableKeyTransport(config) {
497
869
  markAuthenticated: ({ dataClient: nextDataClient }) => {
498
870
  const current = lifecycle.getState();
499
871
  if (current.status !== "ready" && current.status !== "authenticated") {
500
- throw Errors.internalError(
872
+ throw internalTransportError(
501
873
  `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
502
874
  );
503
875
  }
@@ -519,16 +891,166 @@ function makePublishableKeyTransport(config) {
519
891
  function stripTrailingSlash(url) {
520
892
  return url.replace(/\/+$/, "");
521
893
  }
894
+ function normalizeBootstrapUrl(url) {
895
+ const normalized = stripTrailingSlash(url.trim());
896
+ if (!isAbsoluteHttpUrl(normalized)) {
897
+ throw invalidConfigError(
898
+ "bootstrapUrl",
899
+ "publishable-key transport requires an absolute http(s) bootstrapUrl."
900
+ );
901
+ }
902
+ return normalized;
903
+ }
904
+ function isAbsoluteHttpUrl(url) {
905
+ try {
906
+ const parsed = new URL(url);
907
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
908
+ } catch {
909
+ return false;
910
+ }
911
+ }
522
912
  function resolveUrl(authBaseUrl, path) {
523
913
  if (path.startsWith("http://") || path.startsWith("https://")) {
524
914
  return path;
525
915
  }
526
916
  return `${authBaseUrl}${path}`;
527
917
  }
528
- function assertNever(value) {
529
- throw Errors.internalError(
530
- `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
531
- );
918
+ function assertNever(value) {
919
+ throw internalTransportError(
920
+ `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
921
+ );
922
+ }
923
+ function invalidConfigError(field, reason) {
924
+ return new CapxulError({
925
+ code: "INVALID_INPUT",
926
+ message: `Invalid ${field}: ${reason}`,
927
+ details: { source: "sdk-config", field, reason }
928
+ });
929
+ }
930
+ function bootstrapContractError(field, message) {
931
+ return new CapxulError({
932
+ code: "INVALID_INPUT",
933
+ message,
934
+ details: {
935
+ source: "backend-bootstrap",
936
+ phase: "publishable-key-bootstrap",
937
+ field,
938
+ reason: message
939
+ }
940
+ });
941
+ }
942
+ function internalTransportError(reason) {
943
+ return new CapxulError({
944
+ code: "INTERNAL_ERROR",
945
+ message: `Internal error: ${reason}`,
946
+ details: { source: "sdk-transport", reason }
947
+ });
948
+ }
949
+ async function bootstrapResponseError(response, bootstrapUrl) {
950
+ const envelope = await readBootstrapErrorEnvelope(response);
951
+ const wireCode = readNonEmptyString(envelope?.error?.code);
952
+ const normalized = normalizeBootstrapErrorCode(wireCode);
953
+ const message = readNonEmptyString(envelope?.error?.message) ?? `${bootstrapUrl} failed with HTTP ${response.status}.`;
954
+ const backendDetails = readRecord(envelope?.error?.details);
955
+ return new CapxulError({
956
+ code: normalized.code,
957
+ message,
958
+ details: {
959
+ ...backendDetails,
960
+ source: "backend-bootstrap",
961
+ phase: "publishable-key-bootstrap",
962
+ httpStatus: response.status,
963
+ ...normalized.wireCode ? { wireCode: normalized.wireCode } : {}
964
+ },
965
+ operationId: readNonEmptyString(envelope?.error?.operationId),
966
+ correlationId: readNonEmptyString(envelope?.error?.correlationId),
967
+ retryable: typeof envelope?.error?.retryable === "boolean" ? envelope.error.retryable : void 0
968
+ });
969
+ }
970
+ async function readBootstrapErrorEnvelope(response) {
971
+ try {
972
+ const parsed = await response.json();
973
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
974
+ } catch {
975
+ return null;
976
+ }
977
+ }
978
+ async function readBootstrapSuccessBody(response) {
979
+ try {
980
+ const parsed = await response.json();
981
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
982
+ } catch {
983
+ throw bootstrapContractError(
984
+ "body",
985
+ "/v1/client/bootstrap returned invalid JSON."
986
+ );
987
+ }
988
+ }
989
+ function normalizeBootstrapThrownError(error) {
990
+ if (error instanceof CapxulError) return error;
991
+ return new CapxulError({
992
+ code: "NETWORK_ERROR",
993
+ message: "Publishable-key bootstrap network failure.",
994
+ cause: error,
995
+ details: {
996
+ source: "bootstrap-network",
997
+ phase: "publishable-key-bootstrap"
998
+ }
999
+ });
1000
+ }
1001
+ function normalizeBootstrapErrorCode(wireCode) {
1002
+ if (wireCode === "INTERNAL_SERVER_ERROR") {
1003
+ return { code: "INTERNAL_ERROR", wireCode };
1004
+ }
1005
+ if (wireCode && isCapxulErrorCode(wireCode)) {
1006
+ return { code: wireCode };
1007
+ }
1008
+ return wireCode ? { code: "UNKNOWN", wireCode } : { code: "UNKNOWN" };
1009
+ }
1010
+ var CAPXUL_ERROR_CODES = /* @__PURE__ */ new Set([
1011
+ "NOT_AUTHENTICATED",
1012
+ "EMAIL_DELIVERY_FAILED",
1013
+ "PROFILE_NOT_FOUND",
1014
+ "SMART_ACCOUNT_MISSING",
1015
+ "PLAYER_NOT_FOUND",
1016
+ "ACCOUNT_NOT_FOUND",
1017
+ "PROVIDER_ERROR",
1018
+ "INVALID_INPUT",
1019
+ "ENV_MISSING",
1020
+ "NOT_IMPLEMENTED",
1021
+ "VERIFICATION_REQUIRED",
1022
+ "INSUFFICIENT_BALANCE",
1023
+ "INVALID_RECIPIENT",
1024
+ "TRANSACTION_FAILED",
1025
+ "RATE_LIMITED",
1026
+ "NETWORK_ERROR",
1027
+ "UNKNOWN",
1028
+ "PERMISSION_DENIED",
1029
+ "API_KEY_INVALID",
1030
+ "API_KEY_EXPIRED",
1031
+ "IDEMPOTENCY_CONFLICT",
1032
+ "NOT_FOUND",
1033
+ "OPERATION_CANCELED",
1034
+ "OPERATION_TIMEOUT",
1035
+ "ACTION_REQUIRED",
1036
+ "KYC_REQUIRED",
1037
+ "POLICY_DENIED",
1038
+ "SAFE_NOT_READY",
1039
+ "PROVIDER_UNAVAILABLE",
1040
+ "PROVIDER_REJECTED",
1041
+ "RECONCILIATION_FAILED",
1042
+ "INTERNAL_ERROR",
1043
+ "QUOTE_EXPIRED",
1044
+ "QUOTE_NOT_FOUND"
1045
+ ]);
1046
+ function isCapxulErrorCode(value) {
1047
+ return CAPXUL_ERROR_CODES.has(value);
1048
+ }
1049
+ function readRecord(value) {
1050
+ return typeof value === "object" && value !== null ? value : null;
1051
+ }
1052
+ function readNonEmptyString(value) {
1053
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
532
1054
  }
533
1055
 
534
1056
  // src/core/auth.ts
@@ -584,13 +1106,16 @@ function createAuthClient(config = {}) {
584
1106
  email: signIn.user.email,
585
1107
  token: signIn.token,
586
1108
  convexJwt,
587
- expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
1109
+ expiresAt: new Date(
1110
+ Date.now() + 30 * 24 * 60 * 60 * 1e3
1111
+ ).toISOString()
588
1112
  };
589
1113
  sessionStore.set(session);
590
1114
  if (config.auth?.createDataClient) {
591
1115
  try {
592
1116
  dataClient = await config.auth.createDataClient(session);
593
1117
  mutableConfig(config).data = dataClient;
1118
+ transport.markAuthenticated({ dataClient });
594
1119
  } catch (cause) {
595
1120
  return [
596
1121
  new CapxulError({
@@ -609,6 +1134,8 @@ function createAuthClient(config = {}) {
609
1134
  sessionStore.clear();
610
1135
  dataClient = null;
611
1136
  mutableConfig(config).data = void 0;
1137
+ const transport = getTransport();
1138
+ transport?.clearAuth();
612
1139
  return [null, void 0];
613
1140
  },
614
1141
  serviceTokenMint: async () => stub("auth.serviceTokenMint"),
@@ -662,16 +1189,19 @@ async function postBetterAuth(transport, path, body, code, signal) {
662
1189
  body: JSON.stringify(body),
663
1190
  signal
664
1191
  });
1192
+ const text = await response.text();
665
1193
  if (!response.ok) {
1194
+ const parsedError = parseBetterAuthError(text);
666
1195
  return [
667
1196
  new CapxulError({
668
- code,
669
- message: `BetterAuth ${path} failed with HTTP ${response.status}.`
1197
+ code: parsedError.code ?? code,
1198
+ message: parsedError.message ?? `BetterAuth ${path} failed with HTTP ${response.status}.`,
1199
+ details: parsedError.details,
1200
+ retryable: parsedError.retryable
670
1201
  }),
671
1202
  null
672
1203
  ];
673
1204
  }
674
- const text = await response.text();
675
1205
  return [null, text ? JSON.parse(text) : void 0];
676
1206
  } catch (cause) {
677
1207
  return [
@@ -684,6 +1214,36 @@ async function postBetterAuth(transport, path, body, code, signal) {
684
1214
  ];
685
1215
  }
686
1216
  }
1217
+ function parseBetterAuthError(text) {
1218
+ if (!text.trim()) {
1219
+ return {};
1220
+ }
1221
+ try {
1222
+ const body = JSON.parse(text);
1223
+ if (!body || typeof body !== "object") {
1224
+ return {};
1225
+ }
1226
+ const record = body;
1227
+ const nested = record.error && typeof record.error === "object" ? record.error : record;
1228
+ const code = typeof nested.code === "string" ? nested.code : void 0;
1229
+ const message = typeof nested.message === "string" ? nested.message : void 0;
1230
+ const details = nested.details && typeof nested.details === "object" ? nested.details : void 0;
1231
+ const correlationId = typeof nested.correlationId === "string" ? nested.correlationId : void 0;
1232
+ const retryable = typeof nested.retryable === "boolean" ? nested.retryable : void 0;
1233
+ return {
1234
+ code: isCapxulErrorCode2(code) ? code : void 0,
1235
+ message,
1236
+ details,
1237
+ correlationId,
1238
+ retryable
1239
+ };
1240
+ } catch {
1241
+ return {};
1242
+ }
1243
+ }
1244
+ function isCapxulErrorCode2(code) {
1245
+ return code === "NOT_AUTHENTICATED" || code === "EMAIL_DELIVERY_FAILED" || code === "INVALID_INPUT" || code === "RATE_LIMITED" || code === "NETWORK_ERROR" || code === "API_KEY_INVALID" || code === "API_KEY_EXPIRED";
1246
+ }
687
1247
  async function exchangeConvexToken(transport, config, token, signal) {
688
1248
  const path = config.auth?.convexTokenUrl ?? "/convex/token";
689
1249
  try {
@@ -744,14 +1304,6 @@ function createOrgDocumentsClient() {
744
1304
  };
745
1305
  }
746
1306
 
747
- // src/core/external-accounts.ts
748
- function createExternalAccountsClient() {
749
- return {
750
- retrieve: async () => stub("externalAccounts.retrieve"),
751
- remove: async () => stub("externalAccounts.remove")
752
- };
753
- }
754
-
755
1307
  // src/core/me.ts
756
1308
  function createMeClient(config = {}) {
757
1309
  return {
@@ -1199,67 +1751,68 @@ function createOrgTransfersClient() {
1199
1751
  cancel: async () => stub("organizations.transfers.cancel")
1200
1752
  };
1201
1753
  }
1202
- var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1203
1754
  function createWithdrawalsClient(config = {}) {
1204
1755
  return {
1205
1756
  create: async (input) => {
1206
1757
  if (!config.data) {
1207
1758
  return stub("withdrawals.create");
1208
1759
  }
1209
- let created = null;
1210
- let submitted = null;
1211
- try {
1212
- created = await config.data.mutation(
1213
- api.withdrawals.mutations.create,
1214
- {
1215
- amount: input.amount,
1216
- destination: {
1217
- externalAccountId: input.destination.externalAccountId,
1218
- kind: input.destination.kind
1219
- },
1220
- source: input.source,
1221
- reference: input.reference,
1222
- idempotencyKey: input.idempotencyKey
1223
- }
1224
- );
1225
- if (!created) {
1226
- return [
1227
- new CapxulError({
1228
- code: "NETWORK_ERROR",
1229
- message: "withdrawals.create returned no withdrawal resource"
1230
- }),
1231
- null
1232
- ];
1233
- }
1234
- if (created.status !== "processing" || created.operation.status !== "processing") {
1235
- return [null, created];
1236
- }
1237
- if (input.destination.kind !== "evm") {
1238
- return [null, created];
1239
- }
1240
- if (!config.signer || !config.signing) {
1241
- return [null, created];
1242
- }
1243
- if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1244
- throw new CapxulError({
1245
- code: "INVALID_INPUT",
1246
- message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1247
- details: { field: "destination.externalAccountId" }
1248
- });
1249
- }
1250
- const currentSigner = await config.data.query(
1251
- api.safe.queries.getMySignerAddress,
1252
- {}
1760
+ const [createErr, createdRaw] = await tryCatch(
1761
+ config.data.mutation(api.withdrawals.mutations.create, {
1762
+ amount: input.amount,
1763
+ destination: {
1764
+ externalAccountId: input.destination.externalAccountId
1765
+ },
1766
+ source: input.source,
1767
+ reference: input.reference,
1768
+ idempotencyKey: input.idempotencyKey
1769
+ })
1770
+ );
1771
+ if (createErr) {
1772
+ return [mapCreateError2(fromConvexError(createErr)), null];
1773
+ }
1774
+ const created = createdRaw;
1775
+ if (!created) {
1776
+ return [
1777
+ new CapxulError({
1778
+ code: "NETWORK_ERROR",
1779
+ message: "withdrawals.create returned no withdrawal resource"
1780
+ }),
1781
+ null
1782
+ ];
1783
+ }
1784
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1785
+ return [null, created];
1786
+ }
1787
+ if (!config.signer || !config.signing) {
1788
+ return [null, created];
1789
+ }
1790
+ const [signerErr, currentSigner] = await tryCatch(
1791
+ config.data.query(api.safe.queries.getMySignerAddress, {})
1792
+ );
1793
+ if (signerErr) {
1794
+ return await handleSubmissionFailure(
1795
+ { data: config.data },
1796
+ created.id,
1797
+ mapCreateError2(fromConvexError(signerErr))
1253
1798
  );
1254
- if (!currentSigner?.address) {
1255
- throw new CapxulError({
1799
+ }
1800
+ if (!currentSigner?.address) {
1801
+ return await handleSubmissionFailure(
1802
+ { data: config.data },
1803
+ created.id,
1804
+ new CapxulError({
1256
1805
  code: "PERMISSION_DENIED",
1257
1806
  message: "No signer is registered for the authenticated account.",
1258
1807
  details: { withdrawalId: created.id }
1259
- });
1260
- }
1261
- if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
1262
- throw new CapxulError({
1808
+ })
1809
+ );
1810
+ }
1811
+ if (viem.getAddress(currentSigner.address) !== viem.getAddress(config.signer.address)) {
1812
+ return await handleSubmissionFailure(
1813
+ { data: config.data },
1814
+ created.id,
1815
+ new CapxulError({
1263
1816
  code: "PERMISSION_DENIED",
1264
1817
  message: "Configured signer does not match the authenticated account signer.",
1265
1818
  details: {
@@ -1267,170 +1820,228 @@ function createWithdrawalsClient(config = {}) {
1267
1820
  expectedSignerAddress: currentSigner.address,
1268
1821
  actualSignerAddress: config.signer.address
1269
1822
  }
1270
- });
1271
- }
1272
- const submission = await config.data.query(
1273
- api.withdrawals.queries.prepareSubmission,
1274
- { withdrawalId: created.id }
1823
+ })
1275
1824
  );
1276
- if (!submission?.externalAccountId) {
1277
- throw new CapxulError({
1825
+ }
1826
+ const [prepErr, submission] = await tryCatch(
1827
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
1828
+ withdrawalId: created.id
1829
+ })
1830
+ );
1831
+ if (prepErr) {
1832
+ return await handleSubmissionFailure(
1833
+ { data: config.data },
1834
+ created.id,
1835
+ mapCreateError2(fromConvexError(prepErr))
1836
+ );
1837
+ }
1838
+ const destinationAddress = submission?.destinationAddress;
1839
+ if (!submission || !destinationAddress) {
1840
+ return await handleSubmissionFailure(
1841
+ { data: config.data },
1842
+ created.id,
1843
+ new CapxulError({
1278
1844
  code: "NETWORK_ERROR",
1279
1845
  message: "withdrawals.prepareSubmission returned no destination.",
1280
1846
  details: { withdrawalId: created.id }
1281
- });
1282
- }
1283
- const transfer = await transferAsOwner(
1847
+ })
1848
+ );
1849
+ }
1850
+ const [transferErr, transferOk] = await tryCatch(
1851
+ transferAsOwner(
1284
1852
  {
1285
1853
  signer: config.signer,
1286
1854
  signing: config.signing
1287
1855
  },
1288
1856
  {
1289
1857
  tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1290
- recipientAddress: submission.externalAccountId,
1858
+ recipientAddress: destinationAddress,
1291
1859
  amount: toTokenUnits(submission.amount.value, 6)
1292
1860
  }
1861
+ )
1862
+ );
1863
+ if (transferErr) {
1864
+ return await handleSubmissionFailure(
1865
+ { data: config.data },
1866
+ created.id,
1867
+ mapCreateError2(fromConvexError(transferErr))
1293
1868
  );
1294
- if (!transfer.success) {
1295
- throw new CapxulError({
1869
+ }
1870
+ if (!transferOk.success) {
1871
+ return await handleSubmissionFailure(
1872
+ { data: config.data },
1873
+ created.id,
1874
+ new CapxulError({
1296
1875
  code: "NETWORK_ERROR",
1297
1876
  message: "Bundler submission did not succeed.",
1298
1877
  details: {
1299
1878
  withdrawalId: created.id,
1300
- txHash: transfer.txHash,
1301
- userOpHash: transfer.userOpHash
1879
+ txHash: transferOk.txHash,
1880
+ userOpHash: transferOk.userOpHash
1302
1881
  }
1303
- });
1304
- }
1305
- submitted = {
1306
- txHash: transfer.txHash,
1307
- userOpHash: transfer.userOpHash
1308
- };
1309
- await config.data.mutation(
1310
- api.withdrawals.mutations.recordSubmitted,
1311
- {
1312
- withdrawalId: created.id,
1313
- txHash: transfer.txHash,
1314
- userOpHash: transfer.userOpHash
1315
- }
1882
+ })
1316
1883
  );
1317
- return [null, created];
1318
- } catch (cause) {
1319
- const error = mapCreateError2(fromConvexError(cause));
1320
- if (created?.id && created.status === "processing" && !submitted) {
1321
- await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1322
- }
1323
- if (submitted && created?.id) {
1324
- return [
1325
- new CapxulError({
1326
- code: "NETWORK_ERROR",
1327
- message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1328
- cause,
1329
- details: {
1330
- withdrawalId: created.id,
1331
- txHash: submitted.txHash,
1332
- userOpHash: submitted.userOpHash
1333
- }
1334
- }),
1335
- null
1336
- ];
1337
- }
1338
- return [error, null];
1339
1884
  }
1885
+ const [recordErr] = await tryCatch(
1886
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
1887
+ withdrawalId: created.id,
1888
+ txHash: transferOk.txHash,
1889
+ userOpHash: transferOk.userOpHash
1890
+ })
1891
+ );
1892
+ if (recordErr) {
1893
+ return [
1894
+ new CapxulError({
1895
+ code: "NETWORK_ERROR",
1896
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1897
+ cause: recordErr,
1898
+ details: {
1899
+ withdrawalId: created.id,
1900
+ txHash: transferOk.txHash,
1901
+ userOpHash: transferOk.userOpHash
1902
+ }
1903
+ }),
1904
+ null
1905
+ ];
1906
+ }
1907
+ return [null, created];
1340
1908
  },
1341
1909
  retrieve: async (withdrawalId) => {
1342
1910
  if (!config.data) {
1343
1911
  return stub("withdrawals.retrieve");
1344
1912
  }
1345
- try {
1346
- const withdrawal = await config.data.query(
1347
- api.withdrawals.queries.retrieve,
1348
- { withdrawalId }
1349
- );
1350
- if (!withdrawal) {
1351
- return [
1352
- new CapxulError({
1353
- code: "NOT_FOUND",
1354
- message: `withdrawal ${withdrawalId} not found`
1355
- }),
1356
- null
1357
- ];
1358
- }
1359
- return [null, withdrawal];
1360
- } catch (cause) {
1913
+ const [err, raw] = await tryCatch(
1914
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1915
+ );
1916
+ if (err) {
1917
+ return [fromConvexError(err), null];
1918
+ }
1919
+ const withdrawal = raw;
1920
+ if (!withdrawal) {
1361
1921
  return [
1362
- fromConvexError(cause),
1922
+ new CapxulError({
1923
+ code: "NOT_FOUND",
1924
+ message: `withdrawal ${withdrawalId} not found`
1925
+ }),
1363
1926
  null
1364
1927
  ];
1365
1928
  }
1929
+ return [null, withdrawal];
1366
1930
  },
1367
1931
  list: async (input) => {
1368
1932
  if (!config.data) {
1369
1933
  return stub("withdrawals.list");
1370
1934
  }
1371
- try {
1372
- const result = await config.data.query(
1373
- api.withdrawals.queries.list,
1374
- {
1375
- limit: input?.limit,
1376
- cursor: input?.cursor
1377
- }
1935
+ const [err, raw] = await tryCatch(
1936
+ config.data.query(api.withdrawals.queries.list, {
1937
+ limit: input?.limit,
1938
+ cursor: input?.cursor
1939
+ })
1940
+ );
1941
+ if (err) {
1942
+ return [fromConvexError(err), null];
1943
+ }
1944
+ return [null, raw];
1945
+ },
1946
+ recordCompleted: async (input) => {
1947
+ if (!config.data) {
1948
+ return stub(
1949
+ "withdrawals.recordCompleted"
1378
1950
  );
1379
- return [null, result];
1380
- } catch (cause) {
1951
+ }
1952
+ const [err] = await tryCatch(
1953
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
1954
+ withdrawalId: input.withdrawalId,
1955
+ txHash: input.txHash
1956
+ })
1957
+ );
1958
+ if (err) {
1381
1959
  return [
1382
- fromConvexError(cause),
1960
+ mapRecordCompletedError(fromConvexError(err)),
1383
1961
  null
1384
1962
  ];
1385
1963
  }
1964
+ return [null, null];
1386
1965
  }
1387
1966
  };
1388
1967
  }
1389
1968
  function createOrgWithdrawalsClient(config = {}) {
1390
1969
  return {
1391
- // Slice 1 ships personal-scope only end-to-end; org-scope create
1392
- // remains stubbed pending org-scoped backend mutation. List + retrieve
1393
- // are wired through the org-aware query.
1394
- create: async () => stub(
1395
- "organizations.withdrawals.create"
1396
- ),
1970
+ /**
1971
+ * Org-scope create (Withdrawals v1 W2, #465).
1972
+ *
1973
+ * D6 returns the `processing` row only. No `transferAsOwner`
1974
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
1975
+ * orchestration ships in W3+.
1976
+ */
1977
+ create: async (input) => {
1978
+ if (!config.data) {
1979
+ return stub(
1980
+ "organizations.withdrawals.create"
1981
+ );
1982
+ }
1983
+ const [err, raw] = await tryCatch(
1984
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
1985
+ organizationId: input.organizationId,
1986
+ amount: input.amount,
1987
+ destination: {
1988
+ externalAccountId: input.destination.externalAccountId
1989
+ },
1990
+ source: input.source,
1991
+ reference: input.reference,
1992
+ idempotencyKey: input.idempotencyKey
1993
+ })
1994
+ );
1995
+ if (err) {
1996
+ return [mapCreateError2(fromConvexError(err)), null];
1997
+ }
1998
+ const created = raw;
1999
+ if (!created) {
2000
+ return [
2001
+ new CapxulError({
2002
+ code: "NETWORK_ERROR",
2003
+ message: "organizations.withdrawals.create returned no withdrawal resource"
2004
+ }),
2005
+ null
2006
+ ];
2007
+ }
2008
+ return [null, created];
2009
+ },
1397
2010
  retrieve: async (input) => {
1398
2011
  if (!config.data) {
1399
2012
  return stub(
1400
2013
  "organizations.withdrawals.retrieve"
1401
2014
  );
1402
2015
  }
1403
- try {
1404
- const withdrawal = await config.data.query(
1405
- api.withdrawals.queries.retrieve,
1406
- { withdrawalId: input.withdrawalId }
1407
- );
1408
- if (!withdrawal) {
1409
- return [
1410
- new CapxulError({
1411
- code: "NOT_FOUND",
1412
- message: `withdrawal ${input.withdrawalId} not found`
1413
- }),
1414
- null
1415
- ];
1416
- }
1417
- const ownerCheck = withdrawal.owner;
1418
- if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1419
- return [
1420
- new CapxulError({
1421
- code: "NOT_FOUND",
1422
- message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1423
- }),
1424
- null
1425
- ];
1426
- }
1427
- return [null, withdrawal];
1428
- } catch (cause) {
2016
+ const [err, raw] = await tryCatch(
2017
+ config.data.query(api.withdrawals.queries.retrieve, {
2018
+ withdrawalId: input.withdrawalId
2019
+ })
2020
+ );
2021
+ if (err) {
2022
+ return [fromConvexError(err), null];
2023
+ }
2024
+ const withdrawal = raw;
2025
+ if (!withdrawal) {
1429
2026
  return [
1430
- fromConvexError(cause),
2027
+ new CapxulError({
2028
+ code: "NOT_FOUND",
2029
+ message: `withdrawal ${input.withdrawalId} not found`
2030
+ }),
2031
+ null
2032
+ ];
2033
+ }
2034
+ const ownerCheck = withdrawal.owner;
2035
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
2036
+ return [
2037
+ new CapxulError({
2038
+ code: "NOT_FOUND",
2039
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
2040
+ }),
1431
2041
  null
1432
2042
  ];
1433
2043
  }
2044
+ return [null, withdrawal];
1434
2045
  },
1435
2046
  list: async (input) => {
1436
2047
  if (!config.data) {
@@ -1438,46 +2049,67 @@ function createOrgWithdrawalsClient(config = {}) {
1438
2049
  "organizations.withdrawals.list"
1439
2050
  );
1440
2051
  }
1441
- try {
1442
- const result = await config.data.query(
1443
- api.withdrawals.queries.listOrg,
1444
- {
1445
- organizationId: input.organizationId,
1446
- limit: input.limit,
1447
- cursor: input.cursor
1448
- }
1449
- );
1450
- return [null, result];
1451
- } catch (cause) {
1452
- return [
1453
- fromConvexError(cause),
1454
- null
1455
- ];
2052
+ const [err, raw] = await tryCatch(
2053
+ config.data.query(api.withdrawals.queries.listOrg, {
2054
+ organizationId: input.organizationId,
2055
+ limit: input.limit,
2056
+ cursor: input.cursor
2057
+ })
2058
+ );
2059
+ if (err) {
2060
+ return [fromConvexError(err), null];
1456
2061
  }
2062
+ return [null, raw];
1457
2063
  }
1458
2064
  };
1459
2065
  }
2066
+ async function handleSubmissionFailure(config, withdrawalId, error) {
2067
+ await bestEffortMarkFailed2(config, withdrawalId, error);
2068
+ return [error, null];
2069
+ }
1460
2070
  async function bestEffortMarkFailed2(config, withdrawalId, error) {
1461
- try {
1462
- await config.data.mutation(api.withdrawals.mutations.markFailed, {
2071
+ await tryCatch(
2072
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
1463
2073
  withdrawalId,
1464
2074
  errorCode: error.code,
1465
2075
  errorMessage: error.message
1466
- });
1467
- } catch {
2076
+ })
2077
+ );
2078
+ }
2079
+ function mapCreateError2(error) {
2080
+ switch (error.code) {
2081
+ case "NOT_AUTHENTICATED":
2082
+ case "PERMISSION_DENIED":
2083
+ case "INVALID_INPUT":
2084
+ case "INSUFFICIENT_BALANCE":
2085
+ case "IDEMPOTENCY_CONFLICT":
2086
+ case "KYC_REQUIRED":
2087
+ case "POLICY_DENIED":
2088
+ case "RATE_LIMITED":
2089
+ case "NETWORK_ERROR":
2090
+ case "NOT_FOUND":
2091
+ case "VERIFICATION_REQUIRED":
2092
+ return error;
2093
+ default:
2094
+ return new CapxulError({
2095
+ code: "NETWORK_ERROR",
2096
+ message: error.message,
2097
+ cause: error,
2098
+ details: error.details,
2099
+ operationId: error.operationId,
2100
+ correlationId: error.correlationId,
2101
+ retryable: error.retryable
2102
+ });
1468
2103
  }
1469
2104
  }
1470
- function mapCreateError2(error) {
2105
+ function mapRecordCompletedError(error) {
1471
2106
  switch (error.code) {
1472
2107
  case "NOT_AUTHENTICATED":
1473
2108
  case "PERMISSION_DENIED":
1474
2109
  case "INVALID_INPUT":
1475
- case "INSUFFICIENT_BALANCE":
1476
- case "IDEMPOTENCY_CONFLICT":
1477
- case "KYC_REQUIRED":
1478
- case "POLICY_DENIED":
1479
- case "RATE_LIMITED":
2110
+ case "NOT_FOUND":
1480
2111
  case "NETWORK_ERROR":
2112
+ case "INTERNAL_ERROR":
1481
2113
  return error;
1482
2114
  default:
1483
2115
  return new CapxulError({
@@ -1513,6 +2145,136 @@ function createWebhookEventsClient() {
1513
2145
  }
1514
2146
 
1515
2147
  // src/core/organizations.ts
2148
+ function createOrgExternalAccountsClient(config) {
2149
+ return {
2150
+ create: async (input) => {
2151
+ if (!config.data) {
2152
+ return stub(
2153
+ "organizations.externalAccounts.create"
2154
+ );
2155
+ }
2156
+ const [err, raw] = await tryCatch(
2157
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
2158
+ organizationId: input.organizationId,
2159
+ kind: input.kind,
2160
+ label: input.label,
2161
+ address: input.address,
2162
+ iban: input.iban,
2163
+ bic: input.bic,
2164
+ accountHolder: input.accountHolder,
2165
+ network: input.network,
2166
+ panToken: input.panToken,
2167
+ last4: input.last4
2168
+ })
2169
+ );
2170
+ if (err) {
2171
+ return [
2172
+ fromConvexError(err),
2173
+ null
2174
+ ];
2175
+ }
2176
+ if (!raw) {
2177
+ return [
2178
+ new CapxulError({
2179
+ code: "NOT_FOUND",
2180
+ message: "external_account creation returned no resource"
2181
+ }),
2182
+ null
2183
+ ];
2184
+ }
2185
+ return [
2186
+ null,
2187
+ brandExternalAccount(
2188
+ raw
2189
+ )
2190
+ ];
2191
+ },
2192
+ list: async (input) => {
2193
+ if (!config.data) {
2194
+ return stub(
2195
+ "organizations.externalAccounts.list"
2196
+ );
2197
+ }
2198
+ const [err, result] = await tryCatch(
2199
+ config.data.query(api.externalAccounts.queries.listOrg, {
2200
+ organizationId: input.organizationId,
2201
+ limit: input.limit,
2202
+ cursor: input.cursor
2203
+ })
2204
+ );
2205
+ if (err) {
2206
+ return [fromConvexError(err), null];
2207
+ }
2208
+ const branded = result.data.map(
2209
+ (row) => brandExternalAccount(
2210
+ row
2211
+ )
2212
+ );
2213
+ return [
2214
+ null,
2215
+ {
2216
+ object: "list",
2217
+ data: branded,
2218
+ page: result.page
2219
+ }
2220
+ ];
2221
+ },
2222
+ retrieve: async (input) => {
2223
+ if (!config.data) {
2224
+ return stub(
2225
+ "organizations.externalAccounts.retrieve"
2226
+ );
2227
+ }
2228
+ const [err, raw] = await tryCatch(
2229
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
2230
+ organizationId: input.organizationId,
2231
+ externalAccountId: input.externalAccountId
2232
+ })
2233
+ );
2234
+ if (err) {
2235
+ return [
2236
+ fromConvexError(err),
2237
+ null
2238
+ ];
2239
+ }
2240
+ if (!raw) {
2241
+ return [
2242
+ new CapxulError({
2243
+ code: "NOT_FOUND",
2244
+ message: `external_account ${input.externalAccountId} not found`
2245
+ }),
2246
+ null
2247
+ ];
2248
+ }
2249
+ return [
2250
+ null,
2251
+ brandExternalAccount(
2252
+ raw
2253
+ )
2254
+ ];
2255
+ },
2256
+ remove: async (input) => {
2257
+ if (!config.data) {
2258
+ return stub(
2259
+ "organizations.externalAccounts.remove"
2260
+ );
2261
+ }
2262
+ const [err] = await tryCatch(
2263
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2264
+ organizationId: input.organizationId,
2265
+ externalAccountId: input.externalAccountId
2266
+ })
2267
+ );
2268
+ if (err) {
2269
+ return [
2270
+ fromConvexError(err),
2271
+ null
2272
+ ];
2273
+ }
2274
+ return [null, void 0];
2275
+ }
2276
+ };
2277
+ }
1516
2278
  function createOrganizationsClient(config = {}) {
1517
2279
  return {
1518
2280
  create: async () => stub("organizations.create"),
@@ -1568,18 +2330,7 @@ function createOrganizationsClient(config = {}) {
1568
2330
  retrieve: async () => stub("organizations.subAccounts.retrieve"),
1569
2331
  remove: async () => stub("organizations.subAccounts.remove")
1570
2332
  },
1571
- externalAccounts: {
1572
- create: async () => stub(
1573
- "organizations.externalAccounts.create"
1574
- ),
1575
- list: async () => stub(
1576
- "organizations.externalAccounts.list"
1577
- ),
1578
- retrieve: async () => stub(
1579
- "organizations.externalAccounts.retrieve"
1580
- ),
1581
- remove: async () => stub("organizations.externalAccounts.remove")
1582
- },
2333
+ externalAccounts: createOrgExternalAccountsClient(config),
1583
2334
  balanceLedger: {
1584
2335
  list: async () => stub(
1585
2336
  "organizations.balanceLedger.list"
@@ -1716,59 +2467,6 @@ function createVirtualCardsClient() {
1716
2467
  cancel: async () => stub("virtualCards.cancel")
1717
2468
  };
1718
2469
  }
1719
-
1720
- // ../observability/src/try-catch.ts
1721
- async function tryCatch(promise) {
1722
- try {
1723
- return [null, await promise];
1724
- } catch (e) {
1725
- return [e instanceof Error ? e : new Error(String(e)), null];
1726
- }
1727
- }
1728
-
1729
- // ../observability/src/debug-log.ts
1730
- function isDevelopmentBuild() {
1731
- if (typeof process === "undefined") {
1732
- return false;
1733
- }
1734
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1735
- }
1736
- function debugLog(line) {
1737
- if (!isDevelopmentBuild()) return;
1738
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1739
- console.info(line);
1740
- return;
1741
- }
1742
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1743
- process.stderr.write(`${line}
1744
- `);
1745
- }
1746
- }
1747
- function formatDebugValue(value) {
1748
- if (value === void 0 || value === "") return "";
1749
- if (typeof value === "string") return value;
1750
- try {
1751
- return JSON.stringify(value);
1752
- } catch {
1753
- return String(value);
1754
- }
1755
- }
1756
- function track(...args) {
1757
- const [name, props] = args;
1758
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1759
- }
1760
- function formatDebugValue2(value) {
1761
- if (value === void 0 || value === "") return "";
1762
- if (typeof value === "string") return value;
1763
- try {
1764
- return JSON.stringify(value);
1765
- } catch {
1766
- return String(value);
1767
- }
1768
- }
1769
- function identify(userId, traits) {
1770
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1771
- }
1772
2470
  function createAuthFlowMachine(client) {
1773
2471
  return xstate.setup({
1774
2472
  types: {},
@@ -2044,122 +2742,6 @@ function emailDomain(email) {
2044
2742
  const domain = email.split("@")[1]?.trim().toLowerCase();
2045
2743
  return domain || "unknown";
2046
2744
  }
2047
-
2048
- // ../platform-kernel/src/ids.ts
2049
- function makePrefixedIdConstructor(prefix, fieldName) {
2050
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
2051
- return (raw) => {
2052
- if (typeof raw !== "string" || !re.test(raw)) {
2053
- throw new Error(
2054
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
2055
- );
2056
- }
2057
- return raw;
2058
- };
2059
- }
2060
- var toAccountId = makePrefixedIdConstructor(
2061
- "acct",
2062
- "accountId"
2063
- );
2064
- var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
2065
- var toMemberId = makePrefixedIdConstructor(
2066
- "mem",
2067
- "memberId"
2068
- );
2069
- var toSafeId = makePrefixedIdConstructor(
2070
- "safe",
2071
- "safeId"
2072
- );
2073
- var toTreasuryId = makePrefixedIdConstructor(
2074
- "try",
2075
- "treasuryId"
2076
- );
2077
- var toApiKeyId = makePrefixedIdConstructor(
2078
- "ak",
2079
- "apiKeyId"
2080
- );
2081
- var toOperationId = makePrefixedIdConstructor(
2082
- "op",
2083
- "operationId"
2084
- );
2085
- var toKycProfileId = makePrefixedIdConstructor(
2086
- "kyc",
2087
- "kycProfileId"
2088
- );
2089
- var toKybProfileId = makePrefixedIdConstructor(
2090
- "kyb",
2091
- "kybProfileId"
2092
- );
2093
- var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
2094
- var toPaymentId = makePrefixedIdConstructor(
2095
- "pay",
2096
- "paymentId"
2097
- );
2098
- var toWithdrawalId = makePrefixedIdConstructor(
2099
- "wd",
2100
- "withdrawalId"
2101
- );
2102
- var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
2103
- var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
2104
- var toSubAccountId = makePrefixedIdConstructor(
2105
- "sub",
2106
- "subAccountId"
2107
- );
2108
- var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
2109
- var toVirtualCardId = makePrefixedIdConstructor(
2110
- "vc",
2111
- "virtualCardId"
2112
- );
2113
- var toTransferId = makePrefixedIdConstructor(
2114
- "txfr",
2115
- "transferId"
2116
- );
2117
- var toDocumentId = makePrefixedIdConstructor(
2118
- "doc",
2119
- "documentId"
2120
- );
2121
- var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
2122
-
2123
- // ../platform-kernel/src/value-objects.ts
2124
- var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
2125
- function toPhoneNumber(raw) {
2126
- if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
2127
- throw new Error(
2128
- `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
2129
- );
2130
- }
2131
- return raw;
2132
- }
2133
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2134
- function toEmail(raw) {
2135
- if (typeof raw !== "string") {
2136
- throw new Error(
2137
- `Invalid email: expected string, got ${String(raw)}`
2138
- );
2139
- }
2140
- const lowered = raw.trim().toLowerCase();
2141
- if (!EMAIL_RE.test(lowered)) {
2142
- throw new Error(
2143
- `Invalid email: expected local@domain.tld, got ${String(raw)}`
2144
- );
2145
- }
2146
- return lowered;
2147
- }
2148
- var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
2149
- function toUsername(raw) {
2150
- if (typeof raw !== "string") {
2151
- throw new Error(
2152
- `Invalid username: expected string, got ${String(raw)}`
2153
- );
2154
- }
2155
- const lowered = raw.toLowerCase();
2156
- if (!USERNAME_RE.test(lowered)) {
2157
- throw new Error(
2158
- `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
2159
- );
2160
- }
2161
- return lowered;
2162
- }
2163
2745
  function createProvisioningMachine(client) {
2164
2746
  return xstate.setup({
2165
2747
  types: {},
@@ -2552,7 +3134,7 @@ function createCapxulClient(config = {}) {
2552
3134
  subAccounts: createSubAccountsClient(),
2553
3135
  virtualAccounts: createVirtualAccountsClient(),
2554
3136
  virtualCards: createVirtualCardsClient(),
2555
- externalAccounts: createExternalAccountsClient(),
3137
+ externalAccounts: createExternalAccountsClient(config),
2556
3138
  operations: createOperationsClient(config),
2557
3139
  webhookEndpoints: createWebhookEndpointsClient(),
2558
3140
  webhookEvents: createWebhookEventsClient(),