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