@capxul/sdk 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,2270 @@
1
+ import { componentsGeneric, anyApi } from 'convex/server';
2
+ import { getAddress, parseUnits, createPublicClient, http, encodeFunctionData } from 'viem';
3
+ import { toSafeSmartAccount } from 'permissionless/accounts';
4
+ import { entryPoint07Address, createPaymasterClient, createBundlerClient } from 'viem/account-abstraction';
5
+ import { baseSepolia } from 'viem/chains';
6
+ import { setup, fromPromise, assign } from 'xstate';
7
+
8
+ // src/_generated/api.js
9
+ var api = anyApi;
10
+ componentsGeneric();
11
+
12
+ // src/errors.ts
13
+ var CapxulError = class extends Error {
14
+ code;
15
+ details;
16
+ operationId;
17
+ correlationId;
18
+ retryable;
19
+ constructor(init) {
20
+ super(
21
+ init.message,
22
+ init.cause !== void 0 ? { cause: init.cause } : void 0
23
+ );
24
+ this.name = "CapxulError";
25
+ this.code = init.code;
26
+ this.details = init.details;
27
+ this.operationId = init.operationId;
28
+ this.correlationId = init.correlationId;
29
+ this.retryable = init.retryable;
30
+ }
31
+ };
32
+ function notImplemented(method) {
33
+ return new CapxulError({
34
+ code: "NOT_IMPLEMENTED",
35
+ message: `${method} is not yet implemented in @capxul/sdk (Slice C scaffold).`
36
+ });
37
+ }
38
+ function stub(method) {
39
+ return [notImplemented(method), null];
40
+ }
41
+
42
+ // src/internal/convex-error.ts
43
+ function isConvexClientError(error) {
44
+ if (typeof error !== "object" || error === null || !("data" in error)) {
45
+ return false;
46
+ }
47
+ const data = error.data;
48
+ return typeof data === "object" && data !== null;
49
+ }
50
+ function fromConvexError(error) {
51
+ if (error instanceof CapxulError) {
52
+ return error;
53
+ }
54
+ if (isConvexClientError(error)) {
55
+ return new CapxulError({
56
+ code: error.data.code ?? "UNKNOWN",
57
+ message: error.data.message ?? error.message,
58
+ details: error.data.details,
59
+ correlationId: error.data.correlationId,
60
+ cause: error
61
+ });
62
+ }
63
+ return new CapxulError({
64
+ code: "UNKNOWN",
65
+ message: error instanceof Error ? error.message : String(error),
66
+ cause: error
67
+ });
68
+ }
69
+
70
+ // src/core/accounts.ts
71
+ function createAccountsClient(config = {}) {
72
+ return {
73
+ retrieve: async () => stub("accounts.retrieve"),
74
+ lookup: async () => stub("accounts.lookup"),
75
+ update: async () => stub("accounts.update"),
76
+ provisionPersonal: async (input) => {
77
+ if (!config.data) {
78
+ return stub(
79
+ "accounts.provisionPersonal"
80
+ );
81
+ }
82
+ if (input.signerProvider.kind !== "local-private-key") {
83
+ return [
84
+ new CapxulError({
85
+ code: "INVALID_INPUT",
86
+ message: "accounts.provisionPersonal currently supports local-private-key signer providers only."
87
+ }),
88
+ null
89
+ ];
90
+ }
91
+ try {
92
+ await config.data.mutation(
93
+ api.safe.mutations.provisionLocalPersonalAccount,
94
+ {
95
+ displayName: input.displayName,
96
+ username: input.username,
97
+ countryCode: input.countryCode,
98
+ eoaAddress: input.signerProvider.signerAddress,
99
+ safeAddress: input.signerProvider.safeAddress
100
+ }
101
+ );
102
+ const account = await config.data.query(
103
+ api.openfort.queries.getMyAccount,
104
+ {}
105
+ );
106
+ if (!account) {
107
+ return [
108
+ new CapxulError({
109
+ code: "NOT_FOUND",
110
+ message: "accounts.provisionPersonal completed but no account resource was readable."
111
+ }),
112
+ null
113
+ ];
114
+ }
115
+ return [null, account];
116
+ } catch (cause) {
117
+ return [
118
+ fromConvexError(cause),
119
+ null
120
+ ];
121
+ }
122
+ },
123
+ safes: {
124
+ retrieve: async (safeId) => {
125
+ if (!config.data) {
126
+ return stub("accounts.safes.retrieve");
127
+ }
128
+ try {
129
+ const safe = await config.data.query(
130
+ api.safe.queries.retrieveAccountSafe,
131
+ { safeId }
132
+ );
133
+ if (!safe) {
134
+ return [
135
+ new CapxulError({
136
+ code: "NOT_FOUND",
137
+ message: `safe ${safeId} not found`
138
+ }),
139
+ null
140
+ ];
141
+ }
142
+ return [null, safe];
143
+ } catch (cause) {
144
+ return [
145
+ fromConvexError(cause),
146
+ null
147
+ ];
148
+ }
149
+ }
150
+ },
151
+ kycProfiles: {
152
+ create: async () => stub("accounts.kycProfiles.create"),
153
+ retrieve: async () => stub("accounts.kycProfiles.retrieve")
154
+ },
155
+ externalAccounts: {
156
+ create: async () => stub(
157
+ "accounts.externalAccounts.create"
158
+ ),
159
+ list: async () => stub(
160
+ "accounts.externalAccounts.list"
161
+ ),
162
+ retrieve: async () => stub(
163
+ "accounts.externalAccounts.retrieve"
164
+ ),
165
+ remove: async () => stub("accounts.externalAccounts.remove")
166
+ },
167
+ subAccounts: {
168
+ create: async () => stub("accounts.subAccounts.create"),
169
+ list: async () => stub("accounts.subAccounts.list"),
170
+ retrieve: async () => stub("accounts.subAccounts.retrieve"),
171
+ remove: async () => stub("accounts.subAccounts.remove")
172
+ },
173
+ balanceLedger: {
174
+ list: async () => stub(
175
+ "accounts.balanceLedger.list"
176
+ ),
177
+ retrieve: async () => stub(
178
+ "accounts.balanceLedger.retrieve"
179
+ )
180
+ }
181
+ };
182
+ }
183
+
184
+ // src/core/api-keys.ts
185
+ function createApiKeysClient() {
186
+ return {
187
+ create: async () => stub("apiKeys.create"),
188
+ retrieve: async () => stub("apiKeys.retrieve"),
189
+ list: async () => stub("apiKeys.list"),
190
+ revoke: async () => stub("apiKeys.revoke")
191
+ };
192
+ }
193
+
194
+ // ../config/src/chain.ts
195
+ var CAPXUL_PAYMENTS_ADDRESS = "0xe740ea65521edc9bef0ea75d30b5334711db99f4";
196
+ var TEST_USDC_ADDRESS = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
197
+ var CAPXUL_API_BASE_URL = "https://elated-oyster-269.convex.site";
198
+
199
+ // ../config/src/timing.ts
200
+ var FLOW_INVOKE_TIMEOUT_MS = 3e4;
201
+
202
+ // ../config/src/errors.ts
203
+ var CapxulError2 = class extends Error {
204
+ code;
205
+ details;
206
+ correlationId;
207
+ layer;
208
+ constructor(code, message, options) {
209
+ super(message, options?.cause ? { cause: options.cause } : void 0);
210
+ this.code = code;
211
+ this.details = options?.details;
212
+ this.correlationId = options?.correlationId;
213
+ this.layer = options?.layer;
214
+ }
215
+ };
216
+ var Errors = {
217
+ notAuthenticated: () => new CapxulError2("NOT_AUTHENTICATED", "Not authenticated"),
218
+ profileNotFound: (authUserId) => new CapxulError2("PROFILE_NOT_FOUND", `Profile not found for user ${authUserId}`),
219
+ smartAccountMissing: (authUserId) => new CapxulError2("SMART_ACCOUNT_MISSING", `Smart account not provisioned for user ${authUserId}`),
220
+ envMissing: (name) => new CapxulError2("ENV_MISSING", `Environment variable ${name} not configured`),
221
+ openfortApi: (operation, cause) => new CapxulError2(
222
+ "PROVIDER_ERROR",
223
+ `Openfort ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
224
+ { cause, details: { provider: "openfort", operation } }
225
+ ),
226
+ shieldApi: (status, detail) => new CapxulError2(
227
+ "PROVIDER_ERROR",
228
+ `Shield API error (${status}): ${detail}`,
229
+ { details: { provider: "shield", status } }
230
+ ),
231
+ providerError: (provider, operation, cause) => new CapxulError2(
232
+ "PROVIDER_ERROR",
233
+ `${provider} ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
234
+ { cause, details: { provider, operation } }
235
+ ),
236
+ invalidInput: (field, reason) => new CapxulError2(
237
+ "INVALID_INPUT",
238
+ `Invalid ${field}: ${reason}`,
239
+ { details: { field, reason } }
240
+ ),
241
+ playerNotFound: (playerId) => new CapxulError2(
242
+ "PLAYER_NOT_FOUND",
243
+ playerId ? `Openfort player ${playerId} not found` : "Openfort player not found"
244
+ ),
245
+ accountNotFound: (accountId) => new CapxulError2(
246
+ "ACCOUNT_NOT_FOUND",
247
+ accountId ? `Openfort account ${accountId} not found` : "Openfort smart account not found"
248
+ ),
249
+ invalidRecipient: (reason) => new CapxulError2("INVALID_RECIPIENT", reason),
250
+ permissionDenied: (reason = "Permission denied") => new CapxulError2("PERMISSION_DENIED", reason),
251
+ notFound: (resource, id) => new CapxulError2(
252
+ "NOT_FOUND",
253
+ id ? `${resource} ${id} not found` : `${resource} not found`
254
+ ),
255
+ idempotencyConflict: (details) => new CapxulError2(
256
+ "IDEMPOTENCY_CONFLICT",
257
+ "Idempotency key was already used for a different request",
258
+ { details }
259
+ ),
260
+ emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
261
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
262
+ };
263
+
264
+ // ../config/src/safe.ts
265
+ var SAFE_PROXY_FACTORY = "0x4e1dcf7ad4e460cfd30791ccc4f9c8a4f820ec67";
266
+ var SAFE_L2_SINGLETON = "0x29fcb43b46531bca003ddc8fcb67ffe91900c762";
267
+ var SAFE_MODULE_SETUP = "0x2dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47";
268
+ var SAFE_4337_MODULE = "0x75cf11467937ce3f2f357ce24ffc3dbf8fd5c226";
269
+
270
+ // ../config/src/org-roles.ts
271
+ function roleKeyFromLabel(label) {
272
+ const bytes = new TextEncoder().encode(label);
273
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
274
+ return "0x" + hex.padEnd(64, "0");
275
+ }
276
+ roleKeyFromLabel("OWNER");
277
+ roleKeyFromLabel("FINANCE_MANAGER");
278
+ roleKeyFromLabel("TEAM_LEAD");
279
+
280
+ // src/transport.ts
281
+ function makeHttpTransport(config) {
282
+ switch (config.mode) {
283
+ case "build-time-urls":
284
+ return makeBuildTimeUrlsTransport(config);
285
+ case "publishable-key":
286
+ return makePublishableKeyTransport(config);
287
+ default:
288
+ return assertNever(config);
289
+ }
290
+ }
291
+ function createLifecycle(initial) {
292
+ let state = initial;
293
+ const listeners = /* @__PURE__ */ new Set();
294
+ return {
295
+ getState: () => state,
296
+ setState: (next) => {
297
+ if (Object.is(state, next)) return;
298
+ state = next;
299
+ for (const listener of listeners) listener();
300
+ },
301
+ subscribe: (listener) => {
302
+ listeners.add(listener);
303
+ return () => {
304
+ listeners.delete(listener);
305
+ };
306
+ }
307
+ };
308
+ }
309
+ function makeBuildTimeUrlsTransport(config) {
310
+ if (!config.authBaseUrl || config.authBaseUrl.trim().length === 0) {
311
+ throw Errors.invalidInput(
312
+ "authBaseUrl",
313
+ "build-time-urls transport requires a non-empty authBaseUrl."
314
+ );
315
+ }
316
+ if (!config.convexUrl || config.convexUrl.trim().length === 0) {
317
+ throw Errors.invalidInput(
318
+ "convexUrl",
319
+ "build-time-urls transport requires a non-empty convexUrl."
320
+ );
321
+ }
322
+ const authBaseUrl = stripTrailingSlash(config.authBaseUrl);
323
+ const convexUrl = config.convexUrl;
324
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
325
+ const runtime = { authBaseUrl, convexUrl };
326
+ const lifecycle = createLifecycle({ status: "ready", runtime });
327
+ let dataClient = null;
328
+ return {
329
+ authBaseUrl,
330
+ convexUrl,
331
+ fetch: (path, init) => fetchImpl(resolveUrl(authBaseUrl, path), init),
332
+ getState: lifecycle.getState,
333
+ subscribe: lifecycle.subscribe,
334
+ getDataClient: () => dataClient,
335
+ markAuthenticated: ({ dataClient: nextDataClient }) => {
336
+ if (nextDataClient !== void 0) dataClient = nextDataClient;
337
+ lifecycle.setState({ status: "authenticated", runtime });
338
+ },
339
+ clearAuth: () => {
340
+ dataClient = null;
341
+ lifecycle.setState({ status: "ready", runtime });
342
+ }
343
+ };
344
+ }
345
+ function makePublishableKeyTransport(config) {
346
+ if (!config.publishableKey || config.publishableKey.trim().length === 0) {
347
+ throw Errors.invalidInput(
348
+ "publishableKey",
349
+ "publishable-key transport requires a non-empty publishableKey."
350
+ );
351
+ }
352
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
353
+ const bootstrapUrl = stripTrailingSlash(
354
+ config.bootstrapUrl ?? `${CAPXUL_API_BASE_URL}/v1/client/bootstrap`
355
+ );
356
+ let authBaseUrl = "";
357
+ let convexUrl = "";
358
+ let bootstrapPromise = null;
359
+ let dataClient = null;
360
+ const lifecycle = createLifecycle({ status: "idle" });
361
+ async function ensureBootstrap() {
362
+ if (bootstrapPromise) return await bootstrapPromise;
363
+ lifecycle.setState({ status: "bootstrapping" });
364
+ const attempt = (async () => {
365
+ const response = await fetchImpl(bootstrapUrl, {
366
+ method: "POST",
367
+ headers: { "content-type": "application/json" },
368
+ body: JSON.stringify({ publishableKey: config.publishableKey })
369
+ });
370
+ if (!response.ok) {
371
+ throw Errors.invalidInput(
372
+ "publishableKey",
373
+ `${bootstrapUrl} failed with HTTP ${response.status}.`
374
+ );
375
+ }
376
+ const body = await response.json();
377
+ if (typeof body.authBaseUrl !== "string" || body.authBaseUrl.trim().length === 0) {
378
+ throw Errors.invalidInput(
379
+ "authBaseUrl",
380
+ "/v1/client/bootstrap returned no authBaseUrl."
381
+ );
382
+ }
383
+ if (typeof body.convexUrl !== "string" || body.convexUrl.trim().length === 0) {
384
+ throw Errors.invalidInput(
385
+ "convexUrl",
386
+ "/v1/client/bootstrap returned no convexUrl."
387
+ );
388
+ }
389
+ authBaseUrl = stripTrailingSlash(body.authBaseUrl);
390
+ convexUrl = body.convexUrl;
391
+ const runtime = { authBaseUrl, convexUrl };
392
+ lifecycle.setState({ status: "ready", runtime });
393
+ return runtime;
394
+ })();
395
+ bootstrapPromise = attempt.catch((err) => {
396
+ bootstrapPromise = null;
397
+ lifecycle.setState({
398
+ status: "error",
399
+ error: err instanceof CapxulError ? err : new CapxulError({
400
+ code: "UNKNOWN",
401
+ message: "Bootstrap failed without a typed CapxulError.",
402
+ cause: err
403
+ })
404
+ });
405
+ throw err;
406
+ });
407
+ return await bootstrapPromise;
408
+ }
409
+ return {
410
+ get authBaseUrl() {
411
+ return authBaseUrl;
412
+ },
413
+ get convexUrl() {
414
+ return convexUrl;
415
+ },
416
+ fetch: async (path, init) => {
417
+ const resolved = await ensureBootstrap();
418
+ return await fetchImpl(resolveUrl(resolved.authBaseUrl, path), init);
419
+ },
420
+ getState: lifecycle.getState,
421
+ subscribe: lifecycle.subscribe,
422
+ getDataClient: () => dataClient,
423
+ markAuthenticated: ({ dataClient: nextDataClient }) => {
424
+ const current = lifecycle.getState();
425
+ if (current.status !== "ready" && current.status !== "authenticated") {
426
+ throw Errors.internalError(
427
+ `markAuthenticated() called from status="${current.status}". Expected "ready" or "authenticated".`
428
+ );
429
+ }
430
+ if (nextDataClient !== void 0) dataClient = nextDataClient;
431
+ lifecycle.setState({
432
+ status: "authenticated",
433
+ runtime: current.runtime
434
+ });
435
+ },
436
+ clearAuth: () => {
437
+ const current = lifecycle.getState();
438
+ dataClient = null;
439
+ if (current.status === "authenticated") {
440
+ lifecycle.setState({ status: "ready", runtime: current.runtime });
441
+ }
442
+ }
443
+ };
444
+ }
445
+ function stripTrailingSlash(url) {
446
+ return url.replace(/\/+$/, "");
447
+ }
448
+ function resolveUrl(authBaseUrl, path) {
449
+ if (path.startsWith("http://") || path.startsWith("https://")) {
450
+ return path;
451
+ }
452
+ return `${authBaseUrl}${path}`;
453
+ }
454
+ function assertNever(value) {
455
+ throw Errors.internalError(
456
+ `Unhandled BrowserCapxulConfig.mode: ${String(value.mode)}.`
457
+ );
458
+ }
459
+
460
+ // src/core/auth.ts
461
+ function createAuthClient(config = {}) {
462
+ let dataClient = config.data ?? null;
463
+ const sessionStore = config.auth?.sessionStore ?? createMemorySessionStore();
464
+ const getTransport = createTransportProvider(config);
465
+ return {
466
+ sendOtp: async (input, options) => {
467
+ const transport = getTransport();
468
+ if (!transport) {
469
+ return stub("auth.sendOtp");
470
+ }
471
+ return await postBetterAuth(
472
+ transport,
473
+ "/email-otp/send-verification-otp",
474
+ { email: input.email, type: "sign-in" },
475
+ "EMAIL_DELIVERY_FAILED",
476
+ options?.signal
477
+ );
478
+ },
479
+ verifyOtp: async (input, options) => {
480
+ const transport = getTransport();
481
+ if (!transport) {
482
+ return stub("auth.verifyOtp");
483
+ }
484
+ const [signInError, signIn] = await postBetterAuth(
485
+ transport,
486
+ "/sign-in/email-otp",
487
+ { email: input.email, otp: input.otp },
488
+ "NOT_AUTHENTICATED",
489
+ options?.signal
490
+ );
491
+ if (signInError) return [signInError, null];
492
+ if (!signIn?.token || !signIn.user?.id) {
493
+ return [
494
+ new CapxulError({
495
+ code: "NOT_AUTHENTICATED",
496
+ message: "BetterAuth did not return a usable session."
497
+ }),
498
+ null
499
+ ];
500
+ }
501
+ const [convexError, convexJwt] = await exchangeConvexToken(
502
+ transport,
503
+ config,
504
+ signIn.token,
505
+ options?.signal
506
+ );
507
+ if (convexError) return [convexError, null];
508
+ const session = {
509
+ authUserId: signIn.user.id,
510
+ email: signIn.user.email,
511
+ token: signIn.token,
512
+ convexJwt,
513
+ expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString()
514
+ };
515
+ sessionStore.set(session);
516
+ if (config.auth?.createDataClient) {
517
+ try {
518
+ dataClient = await config.auth.createDataClient(session);
519
+ mutableConfig(config).data = dataClient;
520
+ } catch (cause) {
521
+ return [
522
+ new CapxulError({
523
+ code: "NETWORK_ERROR",
524
+ message: "Authenticated data client creation failed.",
525
+ cause
526
+ }),
527
+ null
528
+ ];
529
+ }
530
+ }
531
+ return [null, session];
532
+ },
533
+ getSession: async () => [null, sessionStore.get()],
534
+ signOut: async () => {
535
+ sessionStore.clear();
536
+ dataClient = null;
537
+ mutableConfig(config).data = void 0;
538
+ return [null, void 0];
539
+ },
540
+ serviceTokenMint: async () => stub("auth.serviceTokenMint"),
541
+ getDataClient: () => dataClient
542
+ };
543
+ }
544
+ function createMemorySessionStore() {
545
+ let current = null;
546
+ return {
547
+ get: () => current,
548
+ set: (session) => {
549
+ current = session;
550
+ },
551
+ clear: () => {
552
+ current = null;
553
+ }
554
+ };
555
+ }
556
+ function createTransportProvider(config) {
557
+ let cached = config._transport ?? null;
558
+ return () => {
559
+ if (cached) return cached;
560
+ const baseUrl = config.auth?.baseUrl;
561
+ if (baseUrl) {
562
+ cached = makeHttpTransport({
563
+ mode: "build-time-urls",
564
+ authBaseUrl: betterAuthRoot(baseUrl),
565
+ convexUrl: baseUrl,
566
+ fetchImpl: config.fetch
567
+ });
568
+ return cached;
569
+ }
570
+ if (!config.publishableKey) return null;
571
+ cached = makeHttpTransport({
572
+ mode: "publishable-key",
573
+ publishableKey: config.publishableKey,
574
+ fetchImpl: config.fetch
575
+ });
576
+ return cached;
577
+ };
578
+ }
579
+ function betterAuthRoot(rawBaseUrl) {
580
+ const trimmed = rawBaseUrl.replace(/\/+$/, "");
581
+ return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
582
+ }
583
+ async function postBetterAuth(transport, path, body, code, signal) {
584
+ try {
585
+ const response = await transport.fetch(path, {
586
+ method: "POST",
587
+ headers: { "content-type": "application/json" },
588
+ body: JSON.stringify(body),
589
+ signal
590
+ });
591
+ if (!response.ok) {
592
+ return [
593
+ new CapxulError({
594
+ code,
595
+ message: `BetterAuth ${path} failed with HTTP ${response.status}.`
596
+ }),
597
+ null
598
+ ];
599
+ }
600
+ const text = await response.text();
601
+ return [null, text ? JSON.parse(text) : void 0];
602
+ } catch (cause) {
603
+ return [
604
+ new CapxulError({
605
+ code: "NETWORK_ERROR",
606
+ message: `BetterAuth ${path} network failure.`,
607
+ cause
608
+ }),
609
+ null
610
+ ];
611
+ }
612
+ }
613
+ async function exchangeConvexToken(transport, config, token, signal) {
614
+ const path = config.auth?.convexTokenUrl ?? "/convex/token";
615
+ try {
616
+ const response = await transport.fetch(path, {
617
+ headers: { authorization: `Bearer ${token}` },
618
+ signal
619
+ });
620
+ if (!response.ok) {
621
+ return [
622
+ new CapxulError({
623
+ code: "NOT_AUTHENTICATED",
624
+ message: `Convex token exchange failed with HTTP ${response.status}.`
625
+ }),
626
+ null
627
+ ];
628
+ }
629
+ const body = await response.json();
630
+ if (typeof body.token !== "string") {
631
+ return [
632
+ new CapxulError({
633
+ code: "NOT_AUTHENTICATED",
634
+ message: "Convex token exchange returned no token."
635
+ }),
636
+ null
637
+ ];
638
+ }
639
+ return [null, body.token];
640
+ } catch (cause) {
641
+ return [
642
+ new CapxulError({
643
+ code: "NETWORK_ERROR",
644
+ message: "Convex token exchange network failure.",
645
+ cause
646
+ }),
647
+ null
648
+ ];
649
+ }
650
+ }
651
+ function mutableConfig(config) {
652
+ return config;
653
+ }
654
+
655
+ // src/core/documents.ts
656
+ function createDocumentsClient() {
657
+ return {
658
+ create: async () => stub("documents.create"),
659
+ retrieve: async () => stub("documents.retrieve"),
660
+ list: async () => stub("documents.list"),
661
+ cancel: async () => stub("documents.cancel")
662
+ };
663
+ }
664
+ function createOrgDocumentsClient() {
665
+ return {
666
+ create: async () => stub("organizations.documents.create"),
667
+ retrieve: async () => stub("organizations.documents.retrieve"),
668
+ list: async () => stub("organizations.documents.list"),
669
+ cancel: async () => stub("organizations.documents.cancel")
670
+ };
671
+ }
672
+
673
+ // src/core/external-accounts.ts
674
+ function createExternalAccountsClient() {
675
+ return {
676
+ retrieve: async () => stub("externalAccounts.retrieve"),
677
+ remove: async () => stub("externalAccounts.remove")
678
+ };
679
+ }
680
+
681
+ // src/core/me.ts
682
+ function createMeClient(config = {}) {
683
+ return {
684
+ get: async () => {
685
+ if (!config.data) {
686
+ return stub("me.get");
687
+ }
688
+ try {
689
+ const account = await config.data.query(
690
+ api.openfort.queries.getMyAccount,
691
+ {}
692
+ );
693
+ return [null, account];
694
+ } catch (cause) {
695
+ return [fromConvexError(cause), null];
696
+ }
697
+ },
698
+ update: async () => stub("me.update")
699
+ };
700
+ }
701
+
702
+ // src/core/operations.ts
703
+ function createOperationsClient(config = {}) {
704
+ const retrieve = async (operationId) => {
705
+ if (!config.data) {
706
+ return stub("operations.retrieve");
707
+ }
708
+ try {
709
+ const operation = await config.data.query(api.operations.queries.retrieve, {
710
+ operationId
711
+ });
712
+ if (!operation) {
713
+ return [new CapxulError({
714
+ code: "NOT_FOUND",
715
+ message: `operation ${operationId} not found`
716
+ }), null];
717
+ }
718
+ return [null, operation];
719
+ } catch (cause) {
720
+ return [fromConvexError(cause), null];
721
+ }
722
+ };
723
+ return {
724
+ retrieve,
725
+ wait: async (operationId, input = {}) => {
726
+ if (!config.data) {
727
+ return stub("operations.wait");
728
+ }
729
+ const until = new Set(
730
+ input.until ?? ["succeeded", "failed", "canceled", "indexed"]
731
+ );
732
+ const timeoutMs = (input.timeoutSeconds ?? 60) * 1e3;
733
+ const pollIntervalMs = input.pollIntervalMs ?? 1e3;
734
+ const deadline = Date.now() + timeoutMs;
735
+ while (Date.now() <= deadline) {
736
+ const [error, operation] = await retrieve(operationId);
737
+ if (error) {
738
+ return [error, null];
739
+ }
740
+ if (until.has(operation.status)) {
741
+ return [null, operation];
742
+ }
743
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
744
+ }
745
+ return [new CapxulError({
746
+ code: "OPERATION_TIMEOUT",
747
+ message: `operation ${operationId} did not reach a terminal state before the timeout`,
748
+ details: {
749
+ operationId,
750
+ timeoutSeconds: input.timeoutSeconds ?? 60,
751
+ pollIntervalMs
752
+ }
753
+ }), null];
754
+ }
755
+ };
756
+ }
757
+ function toTokenUnits(value, decimals = 6) {
758
+ return parseUnits(value, decimals);
759
+ }
760
+
761
+ // src/internal/payment-token.ts
762
+ function resolvePaymentTokenAddress(currency) {
763
+ const normalized = currency.trim().toUpperCase();
764
+ if (normalized === "USD" || normalized === "USDC") {
765
+ return TEST_USDC_ADDRESS.toLowerCase();
766
+ }
767
+ throw new CapxulError({
768
+ code: "NETWORK_ERROR",
769
+ message: `Currency ${currency} is not configured for on-chain payment submission.`,
770
+ details: { currency: normalized }
771
+ });
772
+ }
773
+ async function buildSafeAccount(signer, chain) {
774
+ try {
775
+ const publicClient = createPublicClient({
776
+ chain: baseSepolia,
777
+ transport: http(chain.rpcUrl)
778
+ });
779
+ return await toSafeSmartAccount({
780
+ client: publicClient,
781
+ entryPoint: { address: entryPoint07Address, version: "0.7" },
782
+ version: "1.4.1",
783
+ owners: [signer],
784
+ saltNonce: computeSaltNonce(signer.address),
785
+ safeSingletonAddress: SAFE_L2_SINGLETON,
786
+ safeProxyFactoryAddress: SAFE_PROXY_FACTORY,
787
+ safeModuleSetupAddress: SAFE_MODULE_SETUP,
788
+ safe4337ModuleAddress: SAFE_4337_MODULE,
789
+ safeModules: [],
790
+ setupTransactions: []
791
+ });
792
+ } catch (cause) {
793
+ throw new CapxulError({
794
+ code: "NETWORK_ERROR",
795
+ message: cause instanceof Error ? cause.message : String(cause),
796
+ cause,
797
+ details: { chainId: chain.chainId }
798
+ });
799
+ }
800
+ }
801
+ function computeSaltNonce(ownerAddress) {
802
+ return BigInt(`0x${ownerAddress.toLowerCase().slice(2).padEnd(64, "0")}`);
803
+ }
804
+ function createCapxulBundler(config) {
805
+ const paymaster = createPaymasterClient({
806
+ transport: http(config.rpcUrl)
807
+ });
808
+ return createBundlerClient({
809
+ chain: baseSepolia,
810
+ transport: http(config.rpcUrl),
811
+ paymaster,
812
+ paymasterContext: { policyId: config.gasPolicyId }
813
+ });
814
+ }
815
+ var CAPXUL_PAYMENTS_SEND_ABI = [
816
+ {
817
+ name: "send",
818
+ type: "function",
819
+ stateMutability: "nonpayable",
820
+ inputs: [
821
+ { name: "token", type: "address" },
822
+ { name: "recipient", type: "address" },
823
+ { name: "amount", type: "uint256" },
824
+ { name: "documentHash", type: "bytes32" },
825
+ { name: "paymentType", type: "uint8" }
826
+ ],
827
+ outputs: []
828
+ }
829
+ ];
830
+ var ERC20_APPROVE_ABI = [
831
+ {
832
+ name: "approve",
833
+ type: "function",
834
+ stateMutability: "nonpayable",
835
+ inputs: [
836
+ { name: "spender", type: "address" },
837
+ { name: "amount", type: "uint256" }
838
+ ],
839
+ outputs: [{ name: "", type: "bool" }]
840
+ }
841
+ ];
842
+ function encodeOwnerTransferCalls(params) {
843
+ const documentHash = params.documentHash ?? "0x" + "0".repeat(64);
844
+ const paymentType = params.paymentType ?? 0;
845
+ const approve = encodeFunctionData({
846
+ abi: ERC20_APPROVE_ABI,
847
+ functionName: "approve",
848
+ args: [CAPXUL_PAYMENTS_ADDRESS, params.amount]
849
+ });
850
+ const send = encodeFunctionData({
851
+ abi: CAPXUL_PAYMENTS_SEND_ABI,
852
+ functionName: "send",
853
+ args: [
854
+ params.tokenAddress,
855
+ params.recipientAddress,
856
+ params.amount,
857
+ documentHash,
858
+ paymentType
859
+ ]
860
+ });
861
+ return [
862
+ {
863
+ to: params.tokenAddress.toLowerCase(),
864
+ data: approve,
865
+ value: 0n
866
+ },
867
+ {
868
+ to: CAPXUL_PAYMENTS_ADDRESS,
869
+ data: send,
870
+ value: 0n
871
+ }
872
+ ];
873
+ }
874
+
875
+ // src/internal/safe/operations.ts
876
+ var USER_OP_RECEIPT_TIMEOUT_MS = 12e4;
877
+ async function transferAsOwner(config, params) {
878
+ try {
879
+ const safeAccount = await buildSafeAccount(config.signer, config.signing);
880
+ const bundler = createCapxulBundler(config.signing);
881
+ const calls = encodeOwnerTransferCalls(params);
882
+ const userOpHash = await bundler.sendUserOperation({
883
+ account: safeAccount,
884
+ calls
885
+ });
886
+ const receipt = await bundler.waitForUserOperationReceipt({
887
+ hash: userOpHash,
888
+ timeout: USER_OP_RECEIPT_TIMEOUT_MS
889
+ });
890
+ return {
891
+ txHash: receipt.receipt.transactionHash,
892
+ userOpHash,
893
+ blockNumber: Number(receipt.receipt.blockNumber),
894
+ success: receipt.success,
895
+ logs: receipt.receipt.logs
896
+ };
897
+ } catch (cause) {
898
+ throw new CapxulError({
899
+ code: "NETWORK_ERROR",
900
+ message: cause instanceof Error ? cause.message : String(cause),
901
+ cause
902
+ });
903
+ }
904
+ }
905
+
906
+ // src/core/payments.ts
907
+ function createPaymentsClient(config = {}) {
908
+ return {
909
+ create: async (input) => {
910
+ if (!config.data || !config.signer || !config.signing) {
911
+ return stub("payments.create");
912
+ }
913
+ let created = null;
914
+ let submitted = null;
915
+ try {
916
+ created = await config.data.mutation(api.payments.mutations.create, {
917
+ to: input.to,
918
+ amount: input.amount,
919
+ reference: input.reference,
920
+ idempotencyKey: input.idempotencyKey,
921
+ source: input.source
922
+ });
923
+ if (!created) {
924
+ return [new CapxulError({
925
+ code: "NETWORK_ERROR",
926
+ message: "payments.create returned no payment resource"
927
+ }), null];
928
+ }
929
+ if (created.status !== "processing" || created.operation.status !== "processing") {
930
+ return [null, created];
931
+ }
932
+ const currentSigner = await config.data.query(api.safe.queries.getMySignerAddress, {});
933
+ if (!currentSigner?.address) {
934
+ throw new CapxulError({
935
+ code: "PERMISSION_DENIED",
936
+ message: "No signer is registered for the authenticated account.",
937
+ details: { paymentId: created.id }
938
+ });
939
+ }
940
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
941
+ throw new CapxulError({
942
+ code: "PERMISSION_DENIED",
943
+ message: "Configured signer does not match the authenticated account signer.",
944
+ details: {
945
+ paymentId: created.id,
946
+ expectedSignerAddress: currentSigner.address,
947
+ actualSignerAddress: config.signer.address
948
+ }
949
+ });
950
+ }
951
+ const submission = await config.data.query(api.payments.queries.prepareSubmission, {
952
+ paymentId: created.id
953
+ });
954
+ if (!submission?.recipientAddress) {
955
+ throw new CapxulError({
956
+ code: "NETWORK_ERROR",
957
+ message: "payments.prepareSubmission returned no recipient address.",
958
+ details: { paymentId: created.id }
959
+ });
960
+ }
961
+ const transfer = await transferAsOwner(
962
+ {
963
+ signer: config.signer,
964
+ signing: config.signing
965
+ },
966
+ {
967
+ tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
968
+ recipientAddress: submission.recipientAddress,
969
+ amount: toTokenUnits(submission.amount.value, 6)
970
+ }
971
+ );
972
+ if (!transfer.success) {
973
+ throw new CapxulError({
974
+ code: "NETWORK_ERROR",
975
+ message: "Bundler submission did not succeed.",
976
+ details: {
977
+ paymentId: created.id,
978
+ txHash: transfer.txHash,
979
+ userOpHash: transfer.userOpHash
980
+ }
981
+ });
982
+ }
983
+ submitted = {
984
+ txHash: transfer.txHash,
985
+ userOpHash: transfer.userOpHash
986
+ };
987
+ await config.data.mutation(api.payments.mutations.recordSubmitted, {
988
+ paymentId: created.id,
989
+ txHash: transfer.txHash,
990
+ userOpHash: transfer.userOpHash,
991
+ source: "sdk"
992
+ });
993
+ return [null, created];
994
+ } catch (cause) {
995
+ const error = mapCreateError(fromConvexError(cause));
996
+ if (created?.id && created.status === "processing" && !submitted) {
997
+ await bestEffortMarkFailed({ data: config.data }, created.id, error);
998
+ }
999
+ if (submitted && created?.id) {
1000
+ return [new CapxulError({
1001
+ code: "NETWORK_ERROR",
1002
+ message: "Payment was submitted on-chain, but backend submission tracking failed.",
1003
+ cause,
1004
+ details: {
1005
+ paymentId: created.id,
1006
+ txHash: submitted.txHash,
1007
+ userOpHash: submitted.userOpHash
1008
+ }
1009
+ }), null];
1010
+ }
1011
+ return [error, null];
1012
+ }
1013
+ },
1014
+ retrieve: async (paymentId) => {
1015
+ if (!config.data) {
1016
+ return stub("payments.retrieve");
1017
+ }
1018
+ try {
1019
+ const payment = await config.data.query(api.payments.queries.retrieve, {
1020
+ paymentId
1021
+ });
1022
+ if (!payment) {
1023
+ return [new CapxulError({
1024
+ code: "NOT_FOUND",
1025
+ message: `payment ${paymentId} not found`
1026
+ }), null];
1027
+ }
1028
+ return [null, payment];
1029
+ } catch (cause) {
1030
+ return [fromConvexError(cause), null];
1031
+ }
1032
+ },
1033
+ list: async () => stub("payments.list")
1034
+ };
1035
+ }
1036
+ function createOrgPaymentsClient() {
1037
+ return {
1038
+ create: async () => stub(
1039
+ "organizations.payments.create"
1040
+ ),
1041
+ retrieve: async () => stub("organizations.payments.retrieve"),
1042
+ list: async () => stub("organizations.payments.list")
1043
+ };
1044
+ }
1045
+ async function bestEffortMarkFailed(config, paymentId, error) {
1046
+ try {
1047
+ await config.data.mutation(api.payments.mutations.markFailed, {
1048
+ paymentId,
1049
+ errorCode: error.code,
1050
+ errorMessage: error.message,
1051
+ source: "sdk"
1052
+ });
1053
+ } catch {
1054
+ }
1055
+ }
1056
+ function mapCreateError(error) {
1057
+ switch (error.code) {
1058
+ case "NOT_AUTHENTICATED":
1059
+ case "PERMISSION_DENIED":
1060
+ case "INVALID_INPUT":
1061
+ case "INVALID_RECIPIENT":
1062
+ case "INSUFFICIENT_BALANCE":
1063
+ case "IDEMPOTENCY_CONFLICT":
1064
+ case "RATE_LIMITED":
1065
+ case "NETWORK_ERROR":
1066
+ return error;
1067
+ default:
1068
+ return new CapxulError({
1069
+ code: "NETWORK_ERROR",
1070
+ message: error.message,
1071
+ cause: error,
1072
+ details: error.details,
1073
+ operationId: error.operationId,
1074
+ correlationId: error.correlationId,
1075
+ retryable: error.retryable
1076
+ });
1077
+ }
1078
+ }
1079
+
1080
+ // src/core/transfers.ts
1081
+ function createTransfersClient() {
1082
+ return {
1083
+ create: async () => stub("transfers.create"),
1084
+ retrieve: async () => stub("transfers.retrieve"),
1085
+ list: async () => stub("transfers.list"),
1086
+ confirm: async () => stub("transfers.confirm"),
1087
+ cancel: async () => stub("transfers.cancel")
1088
+ };
1089
+ }
1090
+ function createOrgTransfersClient() {
1091
+ return {
1092
+ create: async () => stub(
1093
+ "organizations.transfers.create"
1094
+ ),
1095
+ retrieve: async () => stub("organizations.transfers.retrieve"),
1096
+ list: async () => stub("organizations.transfers.list"),
1097
+ confirm: async () => stub("organizations.transfers.confirm"),
1098
+ cancel: async () => stub("organizations.transfers.cancel")
1099
+ };
1100
+ }
1101
+ var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1102
+ function createWithdrawalsClient(config = {}) {
1103
+ return {
1104
+ create: async (input) => {
1105
+ if (!config.data) {
1106
+ return stub("withdrawals.create");
1107
+ }
1108
+ let created = null;
1109
+ let submitted = null;
1110
+ try {
1111
+ created = await config.data.mutation(
1112
+ api.withdrawals.mutations.create,
1113
+ {
1114
+ amount: input.amount,
1115
+ destination: {
1116
+ externalAccountId: input.destination.externalAccountId,
1117
+ kind: input.destination.kind
1118
+ },
1119
+ source: input.source,
1120
+ reference: input.reference,
1121
+ idempotencyKey: input.idempotencyKey
1122
+ }
1123
+ );
1124
+ if (!created) {
1125
+ return [
1126
+ new CapxulError({
1127
+ code: "NETWORK_ERROR",
1128
+ message: "withdrawals.create returned no withdrawal resource"
1129
+ }),
1130
+ null
1131
+ ];
1132
+ }
1133
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1134
+ return [null, created];
1135
+ }
1136
+ if (input.destination.kind !== "evm") {
1137
+ return [null, created];
1138
+ }
1139
+ if (!config.signer || !config.signing) {
1140
+ return [null, created];
1141
+ }
1142
+ if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1143
+ throw new CapxulError({
1144
+ code: "INVALID_INPUT",
1145
+ message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1146
+ details: { field: "destination.externalAccountId" }
1147
+ });
1148
+ }
1149
+ const currentSigner = await config.data.query(
1150
+ api.safe.queries.getMySignerAddress,
1151
+ {}
1152
+ );
1153
+ if (!currentSigner?.address) {
1154
+ throw new CapxulError({
1155
+ code: "PERMISSION_DENIED",
1156
+ message: "No signer is registered for the authenticated account.",
1157
+ details: { withdrawalId: created.id }
1158
+ });
1159
+ }
1160
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1161
+ throw new CapxulError({
1162
+ code: "PERMISSION_DENIED",
1163
+ message: "Configured signer does not match the authenticated account signer.",
1164
+ details: {
1165
+ withdrawalId: created.id,
1166
+ expectedSignerAddress: currentSigner.address,
1167
+ actualSignerAddress: config.signer.address
1168
+ }
1169
+ });
1170
+ }
1171
+ const submission = await config.data.query(
1172
+ api.withdrawals.queries.prepareSubmission,
1173
+ { withdrawalId: created.id }
1174
+ );
1175
+ if (!submission?.externalAccountId) {
1176
+ throw new CapxulError({
1177
+ code: "NETWORK_ERROR",
1178
+ message: "withdrawals.prepareSubmission returned no destination.",
1179
+ details: { withdrawalId: created.id }
1180
+ });
1181
+ }
1182
+ const transfer = await transferAsOwner(
1183
+ {
1184
+ signer: config.signer,
1185
+ signing: config.signing
1186
+ },
1187
+ {
1188
+ tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1189
+ recipientAddress: submission.externalAccountId,
1190
+ amount: toTokenUnits(submission.amount.value, 6)
1191
+ }
1192
+ );
1193
+ if (!transfer.success) {
1194
+ throw new CapxulError({
1195
+ code: "NETWORK_ERROR",
1196
+ message: "Bundler submission did not succeed.",
1197
+ details: {
1198
+ withdrawalId: created.id,
1199
+ txHash: transfer.txHash,
1200
+ userOpHash: transfer.userOpHash
1201
+ }
1202
+ });
1203
+ }
1204
+ submitted = {
1205
+ txHash: transfer.txHash,
1206
+ userOpHash: transfer.userOpHash
1207
+ };
1208
+ await config.data.mutation(
1209
+ api.withdrawals.mutations.recordSubmitted,
1210
+ {
1211
+ withdrawalId: created.id,
1212
+ txHash: transfer.txHash,
1213
+ userOpHash: transfer.userOpHash
1214
+ }
1215
+ );
1216
+ return [null, created];
1217
+ } catch (cause) {
1218
+ const error = mapCreateError2(fromConvexError(cause));
1219
+ if (created?.id && created.status === "processing" && !submitted) {
1220
+ await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1221
+ }
1222
+ if (submitted && created?.id) {
1223
+ return [
1224
+ new CapxulError({
1225
+ code: "NETWORK_ERROR",
1226
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1227
+ cause,
1228
+ details: {
1229
+ withdrawalId: created.id,
1230
+ txHash: submitted.txHash,
1231
+ userOpHash: submitted.userOpHash
1232
+ }
1233
+ }),
1234
+ null
1235
+ ];
1236
+ }
1237
+ return [error, null];
1238
+ }
1239
+ },
1240
+ retrieve: async (withdrawalId) => {
1241
+ if (!config.data) {
1242
+ return stub("withdrawals.retrieve");
1243
+ }
1244
+ try {
1245
+ const withdrawal = await config.data.query(
1246
+ api.withdrawals.queries.retrieve,
1247
+ { withdrawalId }
1248
+ );
1249
+ if (!withdrawal) {
1250
+ return [
1251
+ new CapxulError({
1252
+ code: "NOT_FOUND",
1253
+ message: `withdrawal ${withdrawalId} not found`
1254
+ }),
1255
+ null
1256
+ ];
1257
+ }
1258
+ return [null, withdrawal];
1259
+ } catch (cause) {
1260
+ return [
1261
+ fromConvexError(cause),
1262
+ null
1263
+ ];
1264
+ }
1265
+ },
1266
+ list: async (input) => {
1267
+ if (!config.data) {
1268
+ return stub("withdrawals.list");
1269
+ }
1270
+ try {
1271
+ const result = await config.data.query(
1272
+ api.withdrawals.queries.list,
1273
+ {
1274
+ limit: input?.limit,
1275
+ cursor: input?.cursor
1276
+ }
1277
+ );
1278
+ return [null, result];
1279
+ } catch (cause) {
1280
+ return [
1281
+ fromConvexError(cause),
1282
+ null
1283
+ ];
1284
+ }
1285
+ }
1286
+ };
1287
+ }
1288
+ function createOrgWithdrawalsClient(config = {}) {
1289
+ return {
1290
+ // Slice 1 ships personal-scope only end-to-end; org-scope create
1291
+ // remains stubbed pending org-scoped backend mutation. List + retrieve
1292
+ // are wired through the org-aware query.
1293
+ create: async () => stub(
1294
+ "organizations.withdrawals.create"
1295
+ ),
1296
+ retrieve: async (input) => {
1297
+ if (!config.data) {
1298
+ return stub(
1299
+ "organizations.withdrawals.retrieve"
1300
+ );
1301
+ }
1302
+ try {
1303
+ const withdrawal = await config.data.query(
1304
+ api.withdrawals.queries.retrieve,
1305
+ { withdrawalId: input.withdrawalId }
1306
+ );
1307
+ if (!withdrawal) {
1308
+ return [
1309
+ new CapxulError({
1310
+ code: "NOT_FOUND",
1311
+ message: `withdrawal ${input.withdrawalId} not found`
1312
+ }),
1313
+ null
1314
+ ];
1315
+ }
1316
+ const ownerCheck = withdrawal.owner;
1317
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1318
+ return [
1319
+ new CapxulError({
1320
+ code: "NOT_FOUND",
1321
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1322
+ }),
1323
+ null
1324
+ ];
1325
+ }
1326
+ return [null, withdrawal];
1327
+ } catch (cause) {
1328
+ return [
1329
+ fromConvexError(cause),
1330
+ null
1331
+ ];
1332
+ }
1333
+ },
1334
+ list: async (input) => {
1335
+ if (!config.data) {
1336
+ return stub(
1337
+ "organizations.withdrawals.list"
1338
+ );
1339
+ }
1340
+ try {
1341
+ const result = await config.data.query(
1342
+ api.withdrawals.queries.listOrg,
1343
+ {
1344
+ organizationId: input.organizationId,
1345
+ limit: input.limit,
1346
+ cursor: input.cursor
1347
+ }
1348
+ );
1349
+ return [null, result];
1350
+ } catch (cause) {
1351
+ return [
1352
+ fromConvexError(cause),
1353
+ null
1354
+ ];
1355
+ }
1356
+ }
1357
+ };
1358
+ }
1359
+ async function bestEffortMarkFailed2(config, withdrawalId, error) {
1360
+ try {
1361
+ await config.data.mutation(api.withdrawals.mutations.markFailed, {
1362
+ withdrawalId,
1363
+ errorCode: error.code,
1364
+ errorMessage: error.message
1365
+ });
1366
+ } catch {
1367
+ }
1368
+ }
1369
+ function mapCreateError2(error) {
1370
+ switch (error.code) {
1371
+ case "NOT_AUTHENTICATED":
1372
+ case "PERMISSION_DENIED":
1373
+ case "INVALID_INPUT":
1374
+ case "INSUFFICIENT_BALANCE":
1375
+ case "IDEMPOTENCY_CONFLICT":
1376
+ case "KYC_REQUIRED":
1377
+ case "POLICY_DENIED":
1378
+ case "RATE_LIMITED":
1379
+ case "NETWORK_ERROR":
1380
+ return error;
1381
+ default:
1382
+ return new CapxulError({
1383
+ code: "NETWORK_ERROR",
1384
+ message: error.message,
1385
+ cause: error,
1386
+ details: error.details,
1387
+ operationId: error.operationId,
1388
+ correlationId: error.correlationId,
1389
+ retryable: error.retryable
1390
+ });
1391
+ }
1392
+ }
1393
+
1394
+ // src/core/webhook-endpoints.ts
1395
+ function createWebhookEndpointsClient() {
1396
+ return {
1397
+ create: async () => stub(
1398
+ "webhookEndpoints.create"
1399
+ ),
1400
+ retrieve: async () => stub("webhookEndpoints.retrieve"),
1401
+ list: async () => stub("webhookEndpoints.list"),
1402
+ remove: async () => stub("webhookEndpoints.remove")
1403
+ };
1404
+ }
1405
+
1406
+ // src/core/webhook-events.ts
1407
+ function createWebhookEventsClient() {
1408
+ return {
1409
+ retrieve: async () => stub("webhookEvents.retrieve"),
1410
+ list: async () => stub("webhookEvents.list")
1411
+ };
1412
+ }
1413
+
1414
+ // src/core/organizations.ts
1415
+ function createOrganizationsClient(config = {}) {
1416
+ return {
1417
+ create: async () => stub("organizations.create"),
1418
+ retrieve: async () => stub("organizations.retrieve"),
1419
+ list: async () => stub("organizations.list"),
1420
+ update: async () => stub("organizations.update"),
1421
+ safes: {
1422
+ retrieve: async (input) => {
1423
+ if (!config.data) {
1424
+ return stub("organizations.safes.retrieve");
1425
+ }
1426
+ try {
1427
+ const safe = await config.data.query(
1428
+ api.safe.queries.retrieveOrganizationSafe,
1429
+ input
1430
+ );
1431
+ if (!safe) {
1432
+ return [
1433
+ new CapxulError({
1434
+ code: "NOT_FOUND",
1435
+ message: `safe ${input.safeId} not found`
1436
+ }),
1437
+ null
1438
+ ];
1439
+ }
1440
+ return [null, safe];
1441
+ } catch (cause) {
1442
+ return [
1443
+ fromConvexError(cause),
1444
+ null
1445
+ ];
1446
+ }
1447
+ }
1448
+ },
1449
+ treasury: {
1450
+ retrieve: async () => stub("organizations.treasury.retrieve")
1451
+ },
1452
+ members: {
1453
+ list: async () => stub("organizations.members.list"),
1454
+ retrieve: async () => stub("organizations.members.retrieve"),
1455
+ invite: async () => stub("organizations.members.invite"),
1456
+ updateRole: async () => stub("organizations.members.updateRole"),
1457
+ remove: async () => stub("organizations.members.remove")
1458
+ },
1459
+ apiKeys: createApiKeysClient(),
1460
+ kybProfile: {
1461
+ start: async () => stub("organizations.kybProfile.start"),
1462
+ retrieve: async () => stub("organizations.kybProfile.retrieve")
1463
+ },
1464
+ subAccounts: {
1465
+ create: async () => stub("organizations.subAccounts.create"),
1466
+ list: async () => stub("organizations.subAccounts.list"),
1467
+ retrieve: async () => stub("organizations.subAccounts.retrieve"),
1468
+ remove: async () => stub("organizations.subAccounts.remove")
1469
+ },
1470
+ externalAccounts: {
1471
+ create: async () => stub(
1472
+ "organizations.externalAccounts.create"
1473
+ ),
1474
+ list: async () => stub(
1475
+ "organizations.externalAccounts.list"
1476
+ ),
1477
+ retrieve: async () => stub(
1478
+ "organizations.externalAccounts.retrieve"
1479
+ ),
1480
+ remove: async () => stub("organizations.externalAccounts.remove")
1481
+ },
1482
+ balanceLedger: {
1483
+ list: async () => stub(
1484
+ "organizations.balanceLedger.list"
1485
+ ),
1486
+ retrieve: async () => stub(
1487
+ "organizations.balanceLedger.retrieve"
1488
+ )
1489
+ },
1490
+ payments: createOrgPaymentsClient(),
1491
+ transfers: createOrgTransfersClient(),
1492
+ withdrawals: createOrgWithdrawalsClient(config),
1493
+ documents: createOrgDocumentsClient(),
1494
+ webhookEndpoints: createWebhookEndpointsClient(),
1495
+ webhookEvents: createWebhookEventsClient()
1496
+ };
1497
+ }
1498
+
1499
+ // src/core/sub-accounts.ts
1500
+ function createSubAccountsClient() {
1501
+ return {
1502
+ retrieve: async () => stub("subAccounts.retrieve"),
1503
+ remove: async () => stub("subAccounts.remove")
1504
+ };
1505
+ }
1506
+
1507
+ // src/core/virtual-accounts.ts
1508
+ function createVirtualAccountsClient() {
1509
+ return {
1510
+ create: async () => stub("virtualAccounts.create"),
1511
+ retrieve: async () => stub("virtualAccounts.retrieve"),
1512
+ list: async () => stub("virtualAccounts.list"),
1513
+ remove: async () => stub("virtualAccounts.remove")
1514
+ };
1515
+ }
1516
+
1517
+ // src/core/virtual-cards.ts
1518
+ function createVirtualCardsClient() {
1519
+ return {
1520
+ create: async () => stub("virtualCards.create"),
1521
+ retrieve: async () => stub("virtualCards.retrieve"),
1522
+ list: async () => stub("virtualCards.list"),
1523
+ freeze: async () => stub("virtualCards.freeze"),
1524
+ unfreeze: async () => stub("virtualCards.unfreeze"),
1525
+ cancel: async () => stub("virtualCards.cancel")
1526
+ };
1527
+ }
1528
+
1529
+ // ../observability/src/debug-log.ts
1530
+ function isDevelopmentBuild() {
1531
+ if (typeof process === "undefined") {
1532
+ return false;
1533
+ }
1534
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1535
+ }
1536
+ function debugLog(line) {
1537
+ if (!isDevelopmentBuild()) return;
1538
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1539
+ console.info(line);
1540
+ return;
1541
+ }
1542
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1543
+ process.stderr.write(`${line}
1544
+ `);
1545
+ }
1546
+ }
1547
+ function formatDebugValue(value) {
1548
+ if (value === void 0 || value === "") return "";
1549
+ if (typeof value === "string") return value;
1550
+ try {
1551
+ return JSON.stringify(value);
1552
+ } catch {
1553
+ return String(value);
1554
+ }
1555
+ }
1556
+ function track(...args) {
1557
+ const [name, props] = args;
1558
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1559
+ }
1560
+ function formatDebugValue2(value) {
1561
+ if (value === void 0 || value === "") return "";
1562
+ if (typeof value === "string") return value;
1563
+ try {
1564
+ return JSON.stringify(value);
1565
+ } catch {
1566
+ return String(value);
1567
+ }
1568
+ }
1569
+ function identify(userId, traits) {
1570
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1571
+ }
1572
+ function createAuthFlowMachine(client) {
1573
+ return setup({
1574
+ types: {},
1575
+ actors: {
1576
+ // XState v5's `fromPromise` injects an `AbortSignal` that aborts
1577
+ // when the actor is stopped (parent transition fires, machine is
1578
+ // disposed, etc.). Plumbing it through `client.auth.sendOtp` /
1579
+ // `client.auth.verifyOtp` makes the in-flight HTTP request
1580
+ // cancellable: stale responses can't race a state machine
1581
+ // that's already moved on. See PR #406 S5.
1582
+ sendOtp: fromPromise(async ({ input, signal }) => {
1583
+ const [error] = await client.auth.sendOtp(
1584
+ { email: input.email },
1585
+ { signal }
1586
+ );
1587
+ if (error) throw error;
1588
+ }),
1589
+ verifyOtp: fromPromise(
1590
+ async ({ input, signal }) => {
1591
+ const [error, session] = await client.auth.verifyOtp(
1592
+ {
1593
+ email: input.email,
1594
+ otp: input.code
1595
+ },
1596
+ { signal }
1597
+ );
1598
+ if (error) throw error;
1599
+ return session;
1600
+ }
1601
+ ),
1602
+ signOut: fromPromise(async () => {
1603
+ const [error] = await client.auth.signOut();
1604
+ if (error) throw error;
1605
+ })
1606
+ },
1607
+ actions: {
1608
+ trackOtpRequested: ({ context }) => {
1609
+ if (!context.email) return;
1610
+ track("auth_otp_requested", {
1611
+ email_domain: emailDomain(context.email)
1612
+ });
1613
+ },
1614
+ trackOtpFailed: ({ event }) => {
1615
+ const error = errorFromEvent(event);
1616
+ track("auth_failed", {
1617
+ auth_type: "email_otp",
1618
+ reason: error.code
1619
+ });
1620
+ },
1621
+ trackTimeoutFailed: () => {
1622
+ track("auth_failed", {
1623
+ auth_type: "email_otp",
1624
+ reason: "timeout"
1625
+ });
1626
+ },
1627
+ trackVerified: () => {
1628
+ track("auth_verified", { auth_type: "email_otp" });
1629
+ },
1630
+ identifyAndTrack: ({ context }) => {
1631
+ if (!context.session) return;
1632
+ identify(context.session.authUserId, {
1633
+ email_domain: emailDomain(context.session.email)
1634
+ });
1635
+ track("auth_identified", {
1636
+ email_domain: emailDomain(context.session.email)
1637
+ });
1638
+ },
1639
+ trackSignedOut: () => {
1640
+ track("auth_signed_out");
1641
+ }
1642
+ }
1643
+ }).createMachine({
1644
+ id: "auth",
1645
+ initial: "idle",
1646
+ context: { email: null, session: null, error: null },
1647
+ states: {
1648
+ idle: {
1649
+ on: {
1650
+ REQUEST_OTP: {
1651
+ target: "sending_otp",
1652
+ actions: assign({
1653
+ email: ({ event }) => event.email,
1654
+ error: () => null
1655
+ })
1656
+ }
1657
+ }
1658
+ },
1659
+ sending_otp: {
1660
+ invoke: {
1661
+ src: "sendOtp",
1662
+ input: ({ context }) => ({ email: requireEmail(context) }),
1663
+ onDone: {
1664
+ target: "otp_requested",
1665
+ actions: ["trackOtpRequested"]
1666
+ },
1667
+ onError: {
1668
+ target: "error",
1669
+ actions: [
1670
+ assign({ error: ({ event }) => errorFromEvent(event) }),
1671
+ "trackOtpFailed"
1672
+ ]
1673
+ }
1674
+ },
1675
+ after: {
1676
+ [FLOW_INVOKE_TIMEOUT_MS]: {
1677
+ target: "error",
1678
+ actions: [
1679
+ assign({
1680
+ error: () => timeoutError("sending_otp")
1681
+ }),
1682
+ "trackTimeoutFailed"
1683
+ ]
1684
+ }
1685
+ }
1686
+ },
1687
+ otp_requested: {
1688
+ on: {
1689
+ VERIFY: { target: "verifying" },
1690
+ RESET: {
1691
+ target: "idle",
1692
+ actions: assign({ email: () => null, error: () => null })
1693
+ }
1694
+ }
1695
+ },
1696
+ verifying: {
1697
+ invoke: {
1698
+ src: "verifyOtp",
1699
+ input: ({ context, event }) => ({
1700
+ email: requireEmail(context),
1701
+ code: requireCodeFromEvent(event)
1702
+ }),
1703
+ onDone: {
1704
+ target: "authenticated",
1705
+ actions: [
1706
+ // Scrub the duplicate `context.email` (input value
1707
+ // captured during sendOtp) since the verified
1708
+ // `session.email` is now the canonical source
1709
+ // post-authentication. The session's email is
1710
+ // intentionally retained — it's the auth result, not
1711
+ // lingering input. See PR #406 S2.
1712
+ assign({
1713
+ session: ({ event }) => event.output,
1714
+ email: () => null
1715
+ }),
1716
+ "trackVerified",
1717
+ "identifyAndTrack"
1718
+ ]
1719
+ },
1720
+ onError: {
1721
+ target: "error",
1722
+ actions: [
1723
+ assign({ error: ({ event }) => errorFromEvent(event) }),
1724
+ "trackOtpFailed"
1725
+ ]
1726
+ }
1727
+ },
1728
+ after: {
1729
+ [FLOW_INVOKE_TIMEOUT_MS]: {
1730
+ target: "error",
1731
+ actions: [
1732
+ assign({
1733
+ error: () => timeoutError("verifying")
1734
+ }),
1735
+ "trackTimeoutFailed"
1736
+ ]
1737
+ }
1738
+ }
1739
+ },
1740
+ authenticated: {
1741
+ on: {
1742
+ SIGN_OUT: { target: "signing_out" }
1743
+ }
1744
+ },
1745
+ signing_out: {
1746
+ invoke: {
1747
+ src: "signOut",
1748
+ onDone: {
1749
+ target: "idle",
1750
+ actions: [
1751
+ assign({
1752
+ session: () => null,
1753
+ email: () => null,
1754
+ error: () => null
1755
+ }),
1756
+ "trackSignedOut"
1757
+ ]
1758
+ },
1759
+ onError: {
1760
+ target: "error",
1761
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
1762
+ }
1763
+ }
1764
+ },
1765
+ error: {
1766
+ on: {
1767
+ RESET: {
1768
+ target: "idle",
1769
+ actions: assign({ error: () => null })
1770
+ }
1771
+ }
1772
+ }
1773
+ }
1774
+ });
1775
+ }
1776
+ function requireEmail(context) {
1777
+ if (!context.email) {
1778
+ throw Errors.invalidInput(
1779
+ "email",
1780
+ "Auth flow advanced without an email captured in context."
1781
+ );
1782
+ }
1783
+ return context.email;
1784
+ }
1785
+ function requireCodeFromEvent(event) {
1786
+ if (event.type !== "VERIFY") {
1787
+ throw Errors.invalidInput(
1788
+ "code",
1789
+ `Auth flow's verifying state requires a VERIFY event, got ${event.type}.`
1790
+ );
1791
+ }
1792
+ return event.code;
1793
+ }
1794
+ function errorFromEvent(event) {
1795
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
1796
+ if (cause instanceof CapxulError) {
1797
+ if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
1798
+ return new CapxulError({
1799
+ code: cause.code,
1800
+ message: redactEmail(cause.message),
1801
+ cause,
1802
+ details: cause.details,
1803
+ operationId: cause.operationId,
1804
+ correlationId: cause.correlationId,
1805
+ retryable: cause.retryable
1806
+ });
1807
+ }
1808
+ if (cause instanceof CapxulError2) {
1809
+ if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
1810
+ return new CapxulError2(cause.code, redactEmail(cause.message), {
1811
+ cause,
1812
+ details: cause.details,
1813
+ correlationId: cause.correlationId,
1814
+ layer: cause.layer
1815
+ });
1816
+ }
1817
+ return Errors.providerError("auth", "flow", redactCauseEmail(cause));
1818
+ }
1819
+ var EMAIL_MATCH_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
1820
+ var EMAIL_REDACT_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
1821
+ function redactEmail(message) {
1822
+ return message.replace(EMAIL_REDACT_PATTERN, "<redacted>");
1823
+ }
1824
+ function redactCauseEmail(cause) {
1825
+ if (cause instanceof Error) {
1826
+ if (!EMAIL_MATCH_PATTERN.test(cause.message)) return cause;
1827
+ const redacted = new Error(redactEmail(cause.message));
1828
+ redacted.cause = cause;
1829
+ return redacted;
1830
+ }
1831
+ if (typeof cause === "string") {
1832
+ return redactEmail(cause);
1833
+ }
1834
+ return cause;
1835
+ }
1836
+ function timeoutError(state) {
1837
+ return Errors.providerError(
1838
+ "auth",
1839
+ "flow",
1840
+ new Error(`timeout: ${state} exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
1841
+ );
1842
+ }
1843
+ function emailDomain(email) {
1844
+ const domain = email.split("@")[1]?.trim().toLowerCase();
1845
+ return domain || "unknown";
1846
+ }
1847
+
1848
+ // ../platform-kernel/src/ids.ts
1849
+ function makePrefixedIdConstructor(prefix, fieldName) {
1850
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
1851
+ return (raw) => {
1852
+ if (typeof raw !== "string" || !re.test(raw)) {
1853
+ throw new Error(
1854
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
1855
+ );
1856
+ }
1857
+ return raw;
1858
+ };
1859
+ }
1860
+ var toOperationId = makePrefixedIdConstructor(
1861
+ "op",
1862
+ "operationId"
1863
+ );
1864
+ function createProvisioningMachine(client) {
1865
+ return setup({
1866
+ types: {},
1867
+ actors: {
1868
+ provisionPersonal: fromPromise(async ({ input }) => {
1869
+ const [error, account] = await client.accounts.provisionPersonal(
1870
+ input.input
1871
+ );
1872
+ if (error) throw error;
1873
+ return account;
1874
+ })
1875
+ },
1876
+ guards: {
1877
+ hasInput: ({ context }) => context.input !== null
1878
+ },
1879
+ actions: {
1880
+ trackWalletCreated: ({ context }) => {
1881
+ const provider = context.input?.signerProvider;
1882
+ if (!provider) return;
1883
+ track("provisioning_wallet_created", {
1884
+ eoa_address: provider.signerAddress
1885
+ });
1886
+ },
1887
+ trackSafeCreated: ({ context }) => {
1888
+ const provider = context.input?.signerProvider;
1889
+ if (!provider) return;
1890
+ track("provisioning_safe_created", {
1891
+ safe_address: provider.safeAddress
1892
+ });
1893
+ }
1894
+ }
1895
+ }).createMachine({
1896
+ id: "provisioning",
1897
+ initial: "starting",
1898
+ context: ({ input }) => ({
1899
+ input: input?.input ?? null,
1900
+ account: null,
1901
+ error: null
1902
+ }),
1903
+ states: {
1904
+ // Transient routing state: skip `idle` when input was provided
1905
+ // at creation time (the invoked-by-parent path).
1906
+ starting: {
1907
+ always: [
1908
+ { guard: "hasInput", target: "running" },
1909
+ { target: "idle" }
1910
+ ]
1911
+ },
1912
+ idle: {
1913
+ on: {
1914
+ START: {
1915
+ target: "running",
1916
+ actions: assign({
1917
+ input: ({ event }) => event.input,
1918
+ account: () => null,
1919
+ error: () => null
1920
+ })
1921
+ }
1922
+ }
1923
+ },
1924
+ running: {
1925
+ invoke: {
1926
+ src: "provisionPersonal",
1927
+ input: ({ context }) => ({
1928
+ input: requireProvisionInput(context)
1929
+ }),
1930
+ onDone: {
1931
+ target: "done",
1932
+ actions: assign({ account: ({ event }) => event.output })
1933
+ },
1934
+ onError: {
1935
+ target: "error",
1936
+ actions: assign({ error: ({ event }) => errorFromEvent2(event) })
1937
+ }
1938
+ },
1939
+ after: {
1940
+ [FLOW_INVOKE_TIMEOUT_MS]: {
1941
+ target: "error",
1942
+ actions: assign({ error: () => timeoutError2() })
1943
+ }
1944
+ }
1945
+ },
1946
+ done: {
1947
+ type: "final",
1948
+ entry: ["trackWalletCreated", "trackSafeCreated"]
1949
+ },
1950
+ error: {
1951
+ type: "final"
1952
+ }
1953
+ },
1954
+ /**
1955
+ * Root-level output mapper — fires when the machine reaches any
1956
+ * top-level `final` state (`done` or `error`). The parent receives
1957
+ * this payload on its `onDone` transition and branches via guards
1958
+ * on `event.output.error`.
1959
+ */
1960
+ output: ({ context }) => context.error ? { error: context.error } : context.account ? { account: context.account } : { error: timeoutError2() }
1961
+ });
1962
+ }
1963
+ function requireProvisionInput(context) {
1964
+ if (!context.input) {
1965
+ throw Errors.invalidInput(
1966
+ "input",
1967
+ "Provisioning flow advanced to running without input captured in context."
1968
+ );
1969
+ }
1970
+ return context.input;
1971
+ }
1972
+ function errorFromEvent2(event) {
1973
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
1974
+ if (cause instanceof CapxulError) return cause;
1975
+ if (cause instanceof CapxulError2) return cause;
1976
+ return Errors.providerError("provisioning", "flow", cause);
1977
+ }
1978
+ function timeoutError2() {
1979
+ return Errors.providerError(
1980
+ "provisioning",
1981
+ "flow",
1982
+ new Error(`timeout: running exceeded ${FLOW_INVOKE_TIMEOUT_MS}ms`)
1983
+ );
1984
+ }
1985
+
1986
+ // src/flows/onboarding.ts
1987
+ function createOnboardingFlowMachine(client) {
1988
+ const provisioningMachine = createProvisioningMachine(client);
1989
+ return setup({
1990
+ types: {},
1991
+ actors: {
1992
+ provisioningMachine
1993
+ },
1994
+ guards: {
1995
+ isOrg: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind !== "account",
1996
+ missingSigner: ({ event }) => event.type === "START_PROVISIONING" && event.input.kind === "account" && !event.input.signerProvider,
1997
+ childReportedError: ({ event }) => {
1998
+ if (typeof event !== "object" || event === null || !("output" in event)) {
1999
+ return false;
2000
+ }
2001
+ const output = event.output;
2002
+ return typeof output === "object" && output !== null && "error" in output && output.error != null;
2003
+ }
2004
+ },
2005
+ actions: {
2006
+ // Always-on for any START_PROVISIONING transition.
2007
+ assignOperationIdAndInput: assign({
2008
+ operationId: () => generateOperationId(),
2009
+ input: ({ event }) => event.type === "START_PROVISIONING" ? event.input : null,
2010
+ error: () => null,
2011
+ account: () => null
2012
+ }),
2013
+ // Pre-actor rejection branches stamp the synchronous error.
2014
+ assignOrgNotImplemented: assign({
2015
+ error: () => new CapxulError({
2016
+ code: "NOT_IMPLEMENTED",
2017
+ message: "useOnboardingFlow organization onboarding is not implemented in this local-private-key slice."
2018
+ })
2019
+ }),
2020
+ assignMissingSigner: assign({
2021
+ error: () => new CapxulError({
2022
+ code: "INVALID_INPUT",
2023
+ message: "useOnboardingFlow requires a signerProvider for account onboarding."
2024
+ })
2025
+ }),
2026
+ // Telemetry actions — order matters; the source fires the
2027
+ // "submitted" track BEFORE the rejection track on validation
2028
+ // errors, and the happy path follows the documented sequence.
2029
+ trackOrgSubmitted: ({ event }) => {
2030
+ if (event.type !== "START_PROVISIONING") return;
2031
+ track("onboarding_org_submitted", {
2032
+ country: event.input.country ?? "unknown"
2033
+ });
2034
+ },
2035
+ trackPersonalSubmitted: ({ event }) => {
2036
+ if (event.type !== "START_PROVISIONING") return;
2037
+ track("onboarding_personal_submitted", {
2038
+ country: event.input.country ?? "unknown",
2039
+ wallet_count: event.input.signerProvider ? 1 : 0
2040
+ });
2041
+ },
2042
+ trackOrgNotImplementedError: () => {
2043
+ track("onboarding_wallet_error", {
2044
+ step: "profile",
2045
+ reason: "organization_not_implemented"
2046
+ });
2047
+ },
2048
+ trackMissingSignerError: () => {
2049
+ track("onboarding_wallet_error", {
2050
+ step: "profile",
2051
+ reason: "missing_signer_provider"
2052
+ });
2053
+ },
2054
+ trackWalletCreating: () => {
2055
+ track("onboarding_wallet_creating");
2056
+ },
2057
+ trackOnboardingCompleted: () => {
2058
+ track("onboarding_completed", { account_type: "personal" });
2059
+ },
2060
+ trackProvisioningFailed: ({ context }) => {
2061
+ const code = context.error?.code ?? "UNKNOWN";
2062
+ track("onboarding_wallet_error", {
2063
+ step: "provision_personal",
2064
+ reason: code
2065
+ });
2066
+ },
2067
+ assignChildReportedError: assign({
2068
+ error: ({ event }) => extractChildErrorOrFallback(event)
2069
+ }),
2070
+ assignChildThrown: assign({
2071
+ error: ({ event }) => errorFromEvent3(event)
2072
+ }),
2073
+ assignAccountFromChild: assign({
2074
+ account: ({ event }) => extractChildAccountOrNull(event)
2075
+ }),
2076
+ resetContext: assign({
2077
+ input: () => null,
2078
+ operationId: () => null,
2079
+ account: () => null,
2080
+ error: () => null
2081
+ })
2082
+ }
2083
+ }).createMachine({
2084
+ id: "onboarding",
2085
+ initial: "profile",
2086
+ context: {
2087
+ input: null,
2088
+ operationId: null,
2089
+ account: null,
2090
+ error: null
2091
+ },
2092
+ states: {
2093
+ profile: {
2094
+ on: {
2095
+ START_PROVISIONING: [
2096
+ {
2097
+ guard: "isOrg",
2098
+ target: "error",
2099
+ actions: [
2100
+ "assignOperationIdAndInput",
2101
+ "trackOrgSubmitted",
2102
+ "assignOrgNotImplemented",
2103
+ "trackOrgNotImplementedError"
2104
+ ]
2105
+ },
2106
+ {
2107
+ guard: "missingSigner",
2108
+ target: "error",
2109
+ actions: [
2110
+ "assignOperationIdAndInput",
2111
+ "trackPersonalSubmitted",
2112
+ "assignMissingSigner",
2113
+ "trackMissingSignerError"
2114
+ ]
2115
+ },
2116
+ {
2117
+ target: "provisioning",
2118
+ actions: [
2119
+ "assignOperationIdAndInput",
2120
+ "trackPersonalSubmitted"
2121
+ ]
2122
+ }
2123
+ ]
2124
+ }
2125
+ },
2126
+ provisioning: {
2127
+ entry: ["trackWalletCreating"],
2128
+ invoke: {
2129
+ src: "provisioningMachine",
2130
+ input: ({ context }) => ({
2131
+ input: requireProvisionInput2(context)
2132
+ }),
2133
+ // The child machine reaches a top-level `final` state for
2134
+ // both success and failure, so the parent's `onDone` fires
2135
+ // in both cases. We branch via a guard on
2136
+ // `event.output.error`. The child machine has its own
2137
+ // `FLOW_INVOKE_TIMEOUT_MS` timer that ends in a final
2138
+ // `error` state on timeout — that signal flows back through
2139
+ // `onDone` + the `childReportedError` guard. The previous
2140
+ // duplicate parent `after: FLOW_INVOKE_TIMEOUT_MS` was
2141
+ // removed in PR #406 (X1+G3) so the child's `output.error`
2142
+ // is the single source of provisioning failure.
2143
+ onDone: [
2144
+ {
2145
+ guard: "childReportedError",
2146
+ target: "error",
2147
+ actions: [
2148
+ "assignChildReportedError",
2149
+ "trackProvisioningFailed"
2150
+ ]
2151
+ },
2152
+ {
2153
+ target: "complete",
2154
+ actions: [
2155
+ "assignAccountFromChild",
2156
+ "trackOnboardingCompleted"
2157
+ ]
2158
+ }
2159
+ ],
2160
+ // `onError` is the safety net for an unexpected throw from
2161
+ // inside the child machine itself (not the spawned actor's
2162
+ // `error` final state, which goes through `onDone`). In
2163
+ // normal flow this never fires.
2164
+ onError: {
2165
+ target: "error",
2166
+ actions: ["assignChildThrown", "trackProvisioningFailed"]
2167
+ }
2168
+ }
2169
+ },
2170
+ // TODO(stack-1): wire `action_required` once the KYC gate /
2171
+ // async-resume `NextAction` path is lifted from
2172
+ // `useOperation(operationId)`. PROCEED → provisioning re-enters
2173
+ // the actor with the resumed input. Currently unreachable from
2174
+ // any transition; declared for parity with the public type.
2175
+ action_required: {
2176
+ on: {
2177
+ PROCEED: { target: "provisioning" },
2178
+ RESET: { target: "profile", actions: "resetContext" }
2179
+ }
2180
+ },
2181
+ complete: {
2182
+ on: {
2183
+ RESET: { target: "profile", actions: "resetContext" }
2184
+ }
2185
+ },
2186
+ error: {
2187
+ on: {
2188
+ RESET: { target: "profile", actions: "resetContext" }
2189
+ }
2190
+ }
2191
+ }
2192
+ });
2193
+ }
2194
+ function generateOperationId() {
2195
+ return toOperationId(`op_onboarding_local_${Date.now().toString(36)}`);
2196
+ }
2197
+ function requireProvisionInput2(context) {
2198
+ if (!context.input || !context.input.signerProvider) {
2199
+ throw Errors.invalidInput(
2200
+ "signerProvider",
2201
+ "Onboarding flow advanced to provisioning without a signerProvider in context."
2202
+ );
2203
+ }
2204
+ return {
2205
+ displayName: context.input.displayName,
2206
+ username: context.input.username,
2207
+ countryCode: context.input.country,
2208
+ signerProvider: context.input.signerProvider
2209
+ };
2210
+ }
2211
+ function extractChildOutput(event) {
2212
+ if (typeof event !== "object" || event === null || !("output" in event)) {
2213
+ return null;
2214
+ }
2215
+ const output = event.output;
2216
+ if (typeof output !== "object" || output === null) return null;
2217
+ return output;
2218
+ }
2219
+ function extractChildErrorOrFallback(event) {
2220
+ const output = extractChildOutput(event);
2221
+ if (output && "error" in output && output.error) return output.error;
2222
+ return Errors.providerError(
2223
+ "onboarding",
2224
+ "flow",
2225
+ new Error("provisioning child reported error without payload")
2226
+ );
2227
+ }
2228
+ function extractChildAccountOrNull(event) {
2229
+ const output = extractChildOutput(event);
2230
+ if (output && "account" in output && output.account) return output.account;
2231
+ return null;
2232
+ }
2233
+ function errorFromEvent3(event) {
2234
+ const cause = typeof event === "object" && event !== null && "error" in event ? event.error : event;
2235
+ if (cause instanceof CapxulError) return cause;
2236
+ if (cause instanceof CapxulError2) return cause;
2237
+ return Errors.providerError("onboarding", "flow", cause);
2238
+ }
2239
+
2240
+ // src/client.ts
2241
+ function createCapxulClient(config = {}) {
2242
+ const clientWithoutFlows = {
2243
+ id: crypto.randomUUID(),
2244
+ auth: createAuthClient(config),
2245
+ me: createMeClient(config),
2246
+ accounts: createAccountsClient(config),
2247
+ organizations: createOrganizationsClient(config),
2248
+ payments: createPaymentsClient(config),
2249
+ transfers: createTransfersClient(),
2250
+ withdrawals: createWithdrawalsClient(config),
2251
+ documents: createDocumentsClient(),
2252
+ subAccounts: createSubAccountsClient(),
2253
+ virtualAccounts: createVirtualAccountsClient(),
2254
+ virtualCards: createVirtualCardsClient(),
2255
+ externalAccounts: createExternalAccountsClient(),
2256
+ operations: createOperationsClient(config),
2257
+ webhookEndpoints: createWebhookEndpointsClient(),
2258
+ webhookEvents: createWebhookEventsClient(),
2259
+ apiKeys: createApiKeysClient()
2260
+ };
2261
+ const client = clientWithoutFlows;
2262
+ client.flows = {
2263
+ auth: () => createAuthFlowMachine(client),
2264
+ onboarding: () => createOnboardingFlowMachine(client),
2265
+ provisioning: () => createProvisioningMachine(client)
2266
+ };
2267
+ return client;
2268
+ }
2269
+
2270
+ export { createCapxulClient };