@capxul/sdk 0.1.0-alpha.3 → 0.1.0-alpha.6

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 CHANGED
@@ -67,12 +67,343 @@ function fromConvexError(error) {
67
67
  });
68
68
  }
69
69
 
70
+ // ../observability/src/try-catch.ts
71
+ async function tryCatch(promise) {
72
+ try {
73
+ return [null, await promise];
74
+ } catch (e) {
75
+ return [e instanceof Error ? e : new Error(String(e)), null];
76
+ }
77
+ }
78
+
79
+ // ../observability/src/debug-log.ts
80
+ function isDevelopmentBuild() {
81
+ if (typeof process === "undefined") {
82
+ return false;
83
+ }
84
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
85
+ }
86
+ function debugLog(line) {
87
+ if (!isDevelopmentBuild()) return;
88
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
89
+ console.info(line);
90
+ return;
91
+ }
92
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
93
+ process.stderr.write(`${line}
94
+ `);
95
+ }
96
+ }
97
+ function formatDebugValue(value) {
98
+ if (value === void 0 || value === "") return "";
99
+ if (typeof value === "string") return value;
100
+ try {
101
+ return JSON.stringify(value);
102
+ } catch {
103
+ return String(value);
104
+ }
105
+ }
106
+ function track(...args) {
107
+ const [name, props] = args;
108
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
109
+ }
110
+ function formatDebugValue2(value) {
111
+ if (value === void 0 || value === "") return "";
112
+ if (typeof value === "string") return value;
113
+ try {
114
+ return JSON.stringify(value);
115
+ } catch {
116
+ return String(value);
117
+ }
118
+ }
119
+ function identify(userId, traits) {
120
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
121
+ }
122
+
123
+ // ../platform-kernel/src/ids.ts
124
+ function makePrefixedIdConstructor(prefix, fieldName) {
125
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
126
+ return (raw) => {
127
+ if (typeof raw !== "string" || !re.test(raw)) {
128
+ throw new Error(
129
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
130
+ );
131
+ }
132
+ return raw;
133
+ };
134
+ }
135
+ var toOperationId = makePrefixedIdConstructor(
136
+ "op",
137
+ "operationId"
138
+ );
139
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
140
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
141
+
142
+ // src/core/external-accounts.ts
143
+ function brandExternalAccount(raw) {
144
+ return {
145
+ ...raw,
146
+ id: toExternalAccountId(raw.id),
147
+ operation: {
148
+ id: toOperationId(raw.operation.id),
149
+ status: raw.operation.status,
150
+ correlationId: toCorrelationId(raw.operation.correlationId)
151
+ }
152
+ };
153
+ }
154
+ function createExternalAccountsClient(config = {}) {
155
+ return {
156
+ retrieve: async (externalAccountId) => {
157
+ if (!config.data) {
158
+ return stub(
159
+ "externalAccounts.retrieve"
160
+ );
161
+ }
162
+ const [err, raw] = await tryCatch(
163
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
164
+ externalAccountId
165
+ })
166
+ );
167
+ if (err) {
168
+ return [
169
+ fromConvexError(err),
170
+ null
171
+ ];
172
+ }
173
+ if (!raw) {
174
+ return [
175
+ new CapxulError({
176
+ code: "NOT_FOUND",
177
+ message: `external_account ${externalAccountId} not found`
178
+ }),
179
+ null
180
+ ];
181
+ }
182
+ return [null, brandExternalAccount(raw)];
183
+ },
184
+ remove: async (externalAccountId) => {
185
+ if (!config.data) {
186
+ return stub("externalAccounts.remove");
187
+ }
188
+ const [err] = await tryCatch(
189
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
190
+ externalAccountId
191
+ })
192
+ );
193
+ if (err) {
194
+ return [
195
+ fromConvexError(err),
196
+ null
197
+ ];
198
+ }
199
+ return [null, void 0];
200
+ }
201
+ };
202
+ }
203
+
70
204
  // src/core/accounts.ts
205
+ function createAccountExternalAccountsClient(config) {
206
+ return {
207
+ create: async (input) => {
208
+ if (!config.data) {
209
+ return stub(
210
+ "accounts.externalAccounts.create"
211
+ );
212
+ }
213
+ const [err, raw] = await tryCatch(
214
+ config.data.mutation(
215
+ api.externalAccounts.mutations.createPersonal,
216
+ {
217
+ kind: input.kind,
218
+ label: input.label,
219
+ address: input.address,
220
+ iban: input.iban,
221
+ bic: input.bic,
222
+ accountHolder: input.accountHolder,
223
+ network: input.network,
224
+ panToken: input.panToken,
225
+ last4: input.last4
226
+ }
227
+ )
228
+ );
229
+ if (err) {
230
+ return [
231
+ fromConvexError(err),
232
+ null
233
+ ];
234
+ }
235
+ if (!raw) {
236
+ return [
237
+ new CapxulError({
238
+ code: "NOT_FOUND",
239
+ message: "external_account creation returned no resource"
240
+ }),
241
+ null
242
+ ];
243
+ }
244
+ return [
245
+ null,
246
+ brandExternalAccount(
247
+ raw
248
+ )
249
+ ];
250
+ },
251
+ list: async (input) => {
252
+ if (!config.data) {
253
+ return stub(
254
+ "accounts.externalAccounts.list"
255
+ );
256
+ }
257
+ const [err, result] = await tryCatch(
258
+ config.data.query(api.externalAccounts.queries.listPersonal, {
259
+ limit: input.limit,
260
+ cursor: input.cursor
261
+ })
262
+ );
263
+ if (err) {
264
+ return [fromConvexError(err), null];
265
+ }
266
+ const branded = result.data.map(
267
+ (row) => brandExternalAccount(
268
+ row
269
+ )
270
+ );
271
+ return [
272
+ null,
273
+ {
274
+ object: "list",
275
+ data: branded,
276
+ page: result.page
277
+ }
278
+ ];
279
+ },
280
+ retrieve: async (externalAccountId) => {
281
+ if (!config.data) {
282
+ return stub(
283
+ "accounts.externalAccounts.retrieve"
284
+ );
285
+ }
286
+ const [err, raw] = await tryCatch(
287
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
288
+ externalAccountId
289
+ })
290
+ );
291
+ if (err) {
292
+ return [
293
+ fromConvexError(err),
294
+ null
295
+ ];
296
+ }
297
+ if (!raw) {
298
+ return [
299
+ new CapxulError({
300
+ code: "NOT_FOUND",
301
+ message: `external_account ${externalAccountId} not found`
302
+ }),
303
+ null
304
+ ];
305
+ }
306
+ return [
307
+ null,
308
+ brandExternalAccount(
309
+ raw
310
+ )
311
+ ];
312
+ },
313
+ remove: async (externalAccountId) => {
314
+ if (!config.data) {
315
+ return stub("accounts.externalAccounts.remove");
316
+ }
317
+ const [err] = await tryCatch(
318
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
319
+ externalAccountId
320
+ })
321
+ );
322
+ if (err) {
323
+ return [
324
+ fromConvexError(err),
325
+ null
326
+ ];
327
+ }
328
+ return [null, void 0];
329
+ }
330
+ };
331
+ }
71
332
  function createAccountsClient(config = {}) {
72
333
  return {
73
- retrieve: async () => stub("accounts.retrieve"),
334
+ retrieve: async (accountId) => {
335
+ if (!config.data) {
336
+ return stub("accounts.retrieve");
337
+ }
338
+ try {
339
+ const account = await config.data.query(
340
+ api.openfort.queries.getMyAccount,
341
+ {}
342
+ );
343
+ if (account.id !== accountId) {
344
+ return [
345
+ new CapxulError({
346
+ code: "PERMISSION_DENIED",
347
+ message: "accounts.retrieve currently supports the authenticated caller's own account only.",
348
+ details: {
349
+ requestedAccountId: accountId,
350
+ authenticatedAccountId: account.id
351
+ }
352
+ }),
353
+ null
354
+ ];
355
+ }
356
+ return [null, account];
357
+ } catch (cause) {
358
+ return [fromConvexError(cause), null];
359
+ }
360
+ },
74
361
  lookup: async () => stub("accounts.lookup"),
75
- update: async () => stub("accounts.update"),
362
+ update: async (input) => {
363
+ if (!config.data) {
364
+ return stub("accounts.update");
365
+ }
366
+ if (input.countryCode !== void 0) {
367
+ return [
368
+ new CapxulError({
369
+ code: "INVALID_INPUT",
370
+ message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
371
+ details: { field: "countryCode" }
372
+ }),
373
+ null
374
+ ];
375
+ }
376
+ try {
377
+ const current = await config.data.query(
378
+ api.openfort.queries.getMyAccount,
379
+ {}
380
+ );
381
+ if (current.id !== input.accountId) {
382
+ return [
383
+ new CapxulError({
384
+ code: "PERMISSION_DENIED",
385
+ message: "accounts.update currently supports the authenticated caller's own account only.",
386
+ details: {
387
+ requestedAccountId: input.accountId,
388
+ authenticatedAccountId: current.id
389
+ }
390
+ }),
391
+ null
392
+ ];
393
+ }
394
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
395
+ displayName: input.name,
396
+ username: input.username
397
+ });
398
+ const updated = await config.data.query(
399
+ api.openfort.queries.getMyAccount,
400
+ {}
401
+ );
402
+ return [null, updated];
403
+ } catch (cause) {
404
+ return [fromConvexError(cause), null];
405
+ }
406
+ },
76
407
  provisionPersonal: async (input) => {
77
408
  if (!config.data) {
78
409
  return stub(
@@ -152,18 +483,7 @@ function createAccountsClient(config = {}) {
152
483
  create: async () => stub("accounts.kycProfiles.create"),
153
484
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
154
485
  },
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
- },
486
+ externalAccounts: createAccountExternalAccountsClient(config),
167
487
  subAccounts: {
168
488
  create: async () => stub("accounts.subAccounts.create"),
169
489
  list: async () => stub("accounts.subAccounts.list"),
@@ -258,7 +578,29 @@ var Errors = {
258
578
  { details }
259
579
  ),
260
580
  emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
261
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
581
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
582
+ /**
583
+ * Verification gate. Surfaced when a request hits a verification
584
+ * boundary the actor cannot cross under their current state. Two
585
+ * variants share this code:
586
+ *
587
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
588
+ * `external_account.kind` routes to a withdrawal rail (e.g.
589
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
590
+ * `details.rail` + `details.currentKind`.
591
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
592
+ * the required tier. Carries `details.requiredTier`.
593
+ *
594
+ * Code is shared because both expose the same UX shape ("you cannot
595
+ * proceed until verification advances"); the `details.*` keys
596
+ * differentiate the route.
597
+ */
598
+ verificationRequired: (details) => {
599
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
600
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
601
+ details: { ...details }
602
+ });
603
+ }
262
604
  };
263
605
 
264
606
  // ../config/src/safe.ts
@@ -670,14 +1012,6 @@ function createOrgDocumentsClient() {
670
1012
  };
671
1013
  }
672
1014
 
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
1015
  // src/core/me.ts
682
1016
  function createMeClient(config = {}) {
683
1017
  return {
@@ -695,7 +1029,34 @@ function createMeClient(config = {}) {
695
1029
  return [fromConvexError(cause), null];
696
1030
  }
697
1031
  },
698
- update: async () => stub("me.update")
1032
+ update: async (input) => {
1033
+ if (!config.data) {
1034
+ return stub("me.update");
1035
+ }
1036
+ if (input.countryCode !== void 0) {
1037
+ return [
1038
+ new CapxulError({
1039
+ code: "INVALID_INPUT",
1040
+ message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
1041
+ details: { field: "countryCode" }
1042
+ }),
1043
+ null
1044
+ ];
1045
+ }
1046
+ try {
1047
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
1048
+ displayName: input.name,
1049
+ username: input.username
1050
+ });
1051
+ const account = await config.data.query(
1052
+ api.openfort.queries.getMyAccount,
1053
+ {}
1054
+ );
1055
+ return [null, account];
1056
+ } catch (cause) {
1057
+ return [fromConvexError(cause), null];
1058
+ }
1059
+ }
699
1060
  };
700
1061
  }
701
1062
 
@@ -1098,25 +1459,379 @@ function createOrgTransfersClient() {
1098
1459
  cancel: async () => stub("organizations.transfers.cancel")
1099
1460
  };
1100
1461
  }
1101
-
1102
- // src/core/withdrawals.ts
1103
- function createWithdrawalsClient() {
1104
- return {
1105
- create: async () => stub("withdrawals.create"),
1106
- retrieve: async () => stub("withdrawals.retrieve"),
1107
- list: async () => stub("withdrawals.list")
1108
- };
1109
- }
1110
- function createOrgWithdrawalsClient() {
1462
+ function createWithdrawalsClient(config = {}) {
1111
1463
  return {
1112
- create: async () => stub(
1113
- "organizations.withdrawals.create"
1114
- ),
1115
- retrieve: async () => stub("organizations.withdrawals.retrieve"),
1116
- list: async () => stub("organizations.withdrawals.list")
1117
- };
1118
- }
1119
-
1464
+ create: async (input) => {
1465
+ if (!config.data) {
1466
+ return stub("withdrawals.create");
1467
+ }
1468
+ const [createErr, createdRaw] = await tryCatch(
1469
+ config.data.mutation(api.withdrawals.mutations.create, {
1470
+ amount: input.amount,
1471
+ destination: {
1472
+ externalAccountId: input.destination.externalAccountId
1473
+ },
1474
+ source: input.source,
1475
+ reference: input.reference,
1476
+ idempotencyKey: input.idempotencyKey
1477
+ })
1478
+ );
1479
+ if (createErr) {
1480
+ return [mapCreateError2(fromConvexError(createErr)), null];
1481
+ }
1482
+ const created = createdRaw;
1483
+ if (!created) {
1484
+ return [
1485
+ new CapxulError({
1486
+ code: "NETWORK_ERROR",
1487
+ message: "withdrawals.create returned no withdrawal resource"
1488
+ }),
1489
+ null
1490
+ ];
1491
+ }
1492
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1493
+ return [null, created];
1494
+ }
1495
+ if (!config.signer || !config.signing) {
1496
+ return [null, created];
1497
+ }
1498
+ const [signerErr, currentSigner] = await tryCatch(
1499
+ config.data.query(api.safe.queries.getMySignerAddress, {})
1500
+ );
1501
+ if (signerErr) {
1502
+ return await handleSubmissionFailure(
1503
+ { data: config.data },
1504
+ created.id,
1505
+ mapCreateError2(fromConvexError(signerErr))
1506
+ );
1507
+ }
1508
+ if (!currentSigner?.address) {
1509
+ return await handleSubmissionFailure(
1510
+ { data: config.data },
1511
+ created.id,
1512
+ new CapxulError({
1513
+ code: "PERMISSION_DENIED",
1514
+ message: "No signer is registered for the authenticated account.",
1515
+ details: { withdrawalId: created.id }
1516
+ })
1517
+ );
1518
+ }
1519
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1520
+ return await handleSubmissionFailure(
1521
+ { data: config.data },
1522
+ created.id,
1523
+ new CapxulError({
1524
+ code: "PERMISSION_DENIED",
1525
+ message: "Configured signer does not match the authenticated account signer.",
1526
+ details: {
1527
+ withdrawalId: created.id,
1528
+ expectedSignerAddress: currentSigner.address,
1529
+ actualSignerAddress: config.signer.address
1530
+ }
1531
+ })
1532
+ );
1533
+ }
1534
+ const [prepErr, submission] = await tryCatch(
1535
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
1536
+ withdrawalId: created.id
1537
+ })
1538
+ );
1539
+ if (prepErr) {
1540
+ return await handleSubmissionFailure(
1541
+ { data: config.data },
1542
+ created.id,
1543
+ mapCreateError2(fromConvexError(prepErr))
1544
+ );
1545
+ }
1546
+ const destinationAddress = submission?.destinationAddress;
1547
+ if (!submission || !destinationAddress) {
1548
+ return await handleSubmissionFailure(
1549
+ { data: config.data },
1550
+ created.id,
1551
+ new CapxulError({
1552
+ code: "NETWORK_ERROR",
1553
+ message: "withdrawals.prepareSubmission returned no destination.",
1554
+ details: { withdrawalId: created.id }
1555
+ })
1556
+ );
1557
+ }
1558
+ const [transferErr, transferOk] = await tryCatch(
1559
+ transferAsOwner(
1560
+ {
1561
+ signer: config.signer,
1562
+ signing: config.signing
1563
+ },
1564
+ {
1565
+ tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1566
+ recipientAddress: destinationAddress,
1567
+ amount: toTokenUnits(submission.amount.value, 6)
1568
+ }
1569
+ )
1570
+ );
1571
+ if (transferErr) {
1572
+ return await handleSubmissionFailure(
1573
+ { data: config.data },
1574
+ created.id,
1575
+ mapCreateError2(fromConvexError(transferErr))
1576
+ );
1577
+ }
1578
+ if (!transferOk.success) {
1579
+ return await handleSubmissionFailure(
1580
+ { data: config.data },
1581
+ created.id,
1582
+ new CapxulError({
1583
+ code: "NETWORK_ERROR",
1584
+ message: "Bundler submission did not succeed.",
1585
+ details: {
1586
+ withdrawalId: created.id,
1587
+ txHash: transferOk.txHash,
1588
+ userOpHash: transferOk.userOpHash
1589
+ }
1590
+ })
1591
+ );
1592
+ }
1593
+ const [recordErr] = await tryCatch(
1594
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
1595
+ withdrawalId: created.id,
1596
+ txHash: transferOk.txHash,
1597
+ userOpHash: transferOk.userOpHash
1598
+ })
1599
+ );
1600
+ if (recordErr) {
1601
+ return [
1602
+ new CapxulError({
1603
+ code: "NETWORK_ERROR",
1604
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1605
+ cause: recordErr,
1606
+ details: {
1607
+ withdrawalId: created.id,
1608
+ txHash: transferOk.txHash,
1609
+ userOpHash: transferOk.userOpHash
1610
+ }
1611
+ }),
1612
+ null
1613
+ ];
1614
+ }
1615
+ return [null, created];
1616
+ },
1617
+ retrieve: async (withdrawalId) => {
1618
+ if (!config.data) {
1619
+ return stub("withdrawals.retrieve");
1620
+ }
1621
+ const [err, raw] = await tryCatch(
1622
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1623
+ );
1624
+ if (err) {
1625
+ return [fromConvexError(err), null];
1626
+ }
1627
+ const withdrawal = raw;
1628
+ if (!withdrawal) {
1629
+ return [
1630
+ new CapxulError({
1631
+ code: "NOT_FOUND",
1632
+ message: `withdrawal ${withdrawalId} not found`
1633
+ }),
1634
+ null
1635
+ ];
1636
+ }
1637
+ return [null, withdrawal];
1638
+ },
1639
+ list: async (input) => {
1640
+ if (!config.data) {
1641
+ return stub("withdrawals.list");
1642
+ }
1643
+ const [err, raw] = await tryCatch(
1644
+ config.data.query(api.withdrawals.queries.list, {
1645
+ limit: input?.limit,
1646
+ cursor: input?.cursor
1647
+ })
1648
+ );
1649
+ if (err) {
1650
+ return [fromConvexError(err), null];
1651
+ }
1652
+ return [null, raw];
1653
+ },
1654
+ recordCompleted: async (input) => {
1655
+ if (!config.data) {
1656
+ return stub(
1657
+ "withdrawals.recordCompleted"
1658
+ );
1659
+ }
1660
+ const [err] = await tryCatch(
1661
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
1662
+ withdrawalId: input.withdrawalId,
1663
+ txHash: input.txHash
1664
+ })
1665
+ );
1666
+ if (err) {
1667
+ return [
1668
+ mapRecordCompletedError(fromConvexError(err)),
1669
+ null
1670
+ ];
1671
+ }
1672
+ return [null, null];
1673
+ }
1674
+ };
1675
+ }
1676
+ function createOrgWithdrawalsClient(config = {}) {
1677
+ return {
1678
+ /**
1679
+ * Org-scope create (Withdrawals v1 W2, #465).
1680
+ *
1681
+ * D6 — returns the `processing` row only. No `transferAsOwner`
1682
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
1683
+ * orchestration ships in W3+.
1684
+ */
1685
+ create: async (input) => {
1686
+ if (!config.data) {
1687
+ return stub(
1688
+ "organizations.withdrawals.create"
1689
+ );
1690
+ }
1691
+ const [err, raw] = await tryCatch(
1692
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
1693
+ organizationId: input.organizationId,
1694
+ amount: input.amount,
1695
+ destination: {
1696
+ externalAccountId: input.destination.externalAccountId
1697
+ },
1698
+ source: input.source,
1699
+ reference: input.reference,
1700
+ idempotencyKey: input.idempotencyKey
1701
+ })
1702
+ );
1703
+ if (err) {
1704
+ return [mapCreateError2(fromConvexError(err)), null];
1705
+ }
1706
+ const created = raw;
1707
+ if (!created) {
1708
+ return [
1709
+ new CapxulError({
1710
+ code: "NETWORK_ERROR",
1711
+ message: "organizations.withdrawals.create returned no withdrawal resource"
1712
+ }),
1713
+ null
1714
+ ];
1715
+ }
1716
+ return [null, created];
1717
+ },
1718
+ retrieve: async (input) => {
1719
+ if (!config.data) {
1720
+ return stub(
1721
+ "organizations.withdrawals.retrieve"
1722
+ );
1723
+ }
1724
+ const [err, raw] = await tryCatch(
1725
+ config.data.query(api.withdrawals.queries.retrieve, {
1726
+ withdrawalId: input.withdrawalId
1727
+ })
1728
+ );
1729
+ if (err) {
1730
+ return [fromConvexError(err), null];
1731
+ }
1732
+ const withdrawal = raw;
1733
+ if (!withdrawal) {
1734
+ return [
1735
+ new CapxulError({
1736
+ code: "NOT_FOUND",
1737
+ message: `withdrawal ${input.withdrawalId} not found`
1738
+ }),
1739
+ null
1740
+ ];
1741
+ }
1742
+ const ownerCheck = withdrawal.owner;
1743
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1744
+ return [
1745
+ new CapxulError({
1746
+ code: "NOT_FOUND",
1747
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1748
+ }),
1749
+ null
1750
+ ];
1751
+ }
1752
+ return [null, withdrawal];
1753
+ },
1754
+ list: async (input) => {
1755
+ if (!config.data) {
1756
+ return stub(
1757
+ "organizations.withdrawals.list"
1758
+ );
1759
+ }
1760
+ const [err, raw] = await tryCatch(
1761
+ config.data.query(api.withdrawals.queries.listOrg, {
1762
+ organizationId: input.organizationId,
1763
+ limit: input.limit,
1764
+ cursor: input.cursor
1765
+ })
1766
+ );
1767
+ if (err) {
1768
+ return [fromConvexError(err), null];
1769
+ }
1770
+ return [null, raw];
1771
+ }
1772
+ };
1773
+ }
1774
+ async function handleSubmissionFailure(config, withdrawalId, error) {
1775
+ await bestEffortMarkFailed2(config, withdrawalId, error);
1776
+ return [error, null];
1777
+ }
1778
+ async function bestEffortMarkFailed2(config, withdrawalId, error) {
1779
+ await tryCatch(
1780
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
1781
+ withdrawalId,
1782
+ errorCode: error.code,
1783
+ errorMessage: error.message
1784
+ })
1785
+ );
1786
+ }
1787
+ function mapCreateError2(error) {
1788
+ switch (error.code) {
1789
+ case "NOT_AUTHENTICATED":
1790
+ case "PERMISSION_DENIED":
1791
+ case "INVALID_INPUT":
1792
+ case "INSUFFICIENT_BALANCE":
1793
+ case "IDEMPOTENCY_CONFLICT":
1794
+ case "KYC_REQUIRED":
1795
+ case "POLICY_DENIED":
1796
+ case "RATE_LIMITED":
1797
+ case "NETWORK_ERROR":
1798
+ case "NOT_FOUND":
1799
+ case "VERIFICATION_REQUIRED":
1800
+ return error;
1801
+ default:
1802
+ return new CapxulError({
1803
+ code: "NETWORK_ERROR",
1804
+ message: error.message,
1805
+ cause: error,
1806
+ details: error.details,
1807
+ operationId: error.operationId,
1808
+ correlationId: error.correlationId,
1809
+ retryable: error.retryable
1810
+ });
1811
+ }
1812
+ }
1813
+ function mapRecordCompletedError(error) {
1814
+ switch (error.code) {
1815
+ case "NOT_AUTHENTICATED":
1816
+ case "PERMISSION_DENIED":
1817
+ case "INVALID_INPUT":
1818
+ case "NOT_FOUND":
1819
+ case "NETWORK_ERROR":
1820
+ case "INTERNAL_ERROR":
1821
+ return error;
1822
+ default:
1823
+ return new CapxulError({
1824
+ code: "NETWORK_ERROR",
1825
+ message: error.message,
1826
+ cause: error,
1827
+ details: error.details,
1828
+ operationId: error.operationId,
1829
+ correlationId: error.correlationId,
1830
+ retryable: error.retryable
1831
+ });
1832
+ }
1833
+ }
1834
+
1120
1835
  // src/core/webhook-endpoints.ts
1121
1836
  function createWebhookEndpointsClient() {
1122
1837
  return {
@@ -1138,6 +1853,136 @@ function createWebhookEventsClient() {
1138
1853
  }
1139
1854
 
1140
1855
  // src/core/organizations.ts
1856
+ function createOrgExternalAccountsClient(config) {
1857
+ return {
1858
+ create: async (input) => {
1859
+ if (!config.data) {
1860
+ return stub(
1861
+ "organizations.externalAccounts.create"
1862
+ );
1863
+ }
1864
+ const [err, raw] = await tryCatch(
1865
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
1866
+ organizationId: input.organizationId,
1867
+ kind: input.kind,
1868
+ label: input.label,
1869
+ address: input.address,
1870
+ iban: input.iban,
1871
+ bic: input.bic,
1872
+ accountHolder: input.accountHolder,
1873
+ network: input.network,
1874
+ panToken: input.panToken,
1875
+ last4: input.last4
1876
+ })
1877
+ );
1878
+ if (err) {
1879
+ return [
1880
+ fromConvexError(err),
1881
+ null
1882
+ ];
1883
+ }
1884
+ if (!raw) {
1885
+ return [
1886
+ new CapxulError({
1887
+ code: "NOT_FOUND",
1888
+ message: "external_account creation returned no resource"
1889
+ }),
1890
+ null
1891
+ ];
1892
+ }
1893
+ return [
1894
+ null,
1895
+ brandExternalAccount(
1896
+ raw
1897
+ )
1898
+ ];
1899
+ },
1900
+ list: async (input) => {
1901
+ if (!config.data) {
1902
+ return stub(
1903
+ "organizations.externalAccounts.list"
1904
+ );
1905
+ }
1906
+ const [err, result] = await tryCatch(
1907
+ config.data.query(api.externalAccounts.queries.listOrg, {
1908
+ organizationId: input.organizationId,
1909
+ limit: input.limit,
1910
+ cursor: input.cursor
1911
+ })
1912
+ );
1913
+ if (err) {
1914
+ return [fromConvexError(err), null];
1915
+ }
1916
+ const branded = result.data.map(
1917
+ (row) => brandExternalAccount(
1918
+ row
1919
+ )
1920
+ );
1921
+ return [
1922
+ null,
1923
+ {
1924
+ object: "list",
1925
+ data: branded,
1926
+ page: result.page
1927
+ }
1928
+ ];
1929
+ },
1930
+ retrieve: async (input) => {
1931
+ if (!config.data) {
1932
+ return stub(
1933
+ "organizations.externalAccounts.retrieve"
1934
+ );
1935
+ }
1936
+ const [err, raw] = await tryCatch(
1937
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
1938
+ organizationId: input.organizationId,
1939
+ externalAccountId: input.externalAccountId
1940
+ })
1941
+ );
1942
+ if (err) {
1943
+ return [
1944
+ fromConvexError(err),
1945
+ null
1946
+ ];
1947
+ }
1948
+ if (!raw) {
1949
+ return [
1950
+ new CapxulError({
1951
+ code: "NOT_FOUND",
1952
+ message: `external_account ${input.externalAccountId} not found`
1953
+ }),
1954
+ null
1955
+ ];
1956
+ }
1957
+ return [
1958
+ null,
1959
+ brandExternalAccount(
1960
+ raw
1961
+ )
1962
+ ];
1963
+ },
1964
+ remove: async (input) => {
1965
+ if (!config.data) {
1966
+ return stub(
1967
+ "organizations.externalAccounts.remove"
1968
+ );
1969
+ }
1970
+ const [err] = await tryCatch(
1971
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
1972
+ organizationId: input.organizationId,
1973
+ externalAccountId: input.externalAccountId
1974
+ })
1975
+ );
1976
+ if (err) {
1977
+ return [
1978
+ fromConvexError(err),
1979
+ null
1980
+ ];
1981
+ }
1982
+ return [null, void 0];
1983
+ }
1984
+ };
1985
+ }
1141
1986
  function createOrganizationsClient(config = {}) {
1142
1987
  return {
1143
1988
  create: async () => stub("organizations.create"),
@@ -1193,18 +2038,7 @@ function createOrganizationsClient(config = {}) {
1193
2038
  retrieve: async () => stub("organizations.subAccounts.retrieve"),
1194
2039
  remove: async () => stub("organizations.subAccounts.remove")
1195
2040
  },
1196
- externalAccounts: {
1197
- create: async () => stub(
1198
- "organizations.externalAccounts.create"
1199
- ),
1200
- list: async () => stub(
1201
- "organizations.externalAccounts.list"
1202
- ),
1203
- retrieve: async () => stub(
1204
- "organizations.externalAccounts.retrieve"
1205
- ),
1206
- remove: async () => stub("organizations.externalAccounts.remove")
1207
- },
2041
+ externalAccounts: createOrgExternalAccountsClient(config),
1208
2042
  balanceLedger: {
1209
2043
  list: async () => stub(
1210
2044
  "organizations.balanceLedger.list"
@@ -1215,7 +2049,7 @@ function createOrganizationsClient(config = {}) {
1215
2049
  },
1216
2050
  payments: createOrgPaymentsClient(),
1217
2051
  transfers: createOrgTransfersClient(),
1218
- withdrawals: createOrgWithdrawalsClient(),
2052
+ withdrawals: createOrgWithdrawalsClient(config),
1219
2053
  documents: createOrgDocumentsClient(),
1220
2054
  webhookEndpoints: createWebhookEndpointsClient(),
1221
2055
  webhookEvents: createWebhookEventsClient()
@@ -1230,6 +2064,96 @@ function createSubAccountsClient() {
1230
2064
  };
1231
2065
  }
1232
2066
 
2067
+ // src/core/token-transfers.ts
2068
+ var toTokenTransferId = (raw) => {
2069
+ if (typeof raw !== "string" || raw.length === 0) {
2070
+ throw new Error(
2071
+ `Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
2072
+ );
2073
+ }
2074
+ return raw;
2075
+ };
2076
+ function brandRow(row) {
2077
+ return {
2078
+ ...row,
2079
+ id: toTokenTransferId(row.id)
2080
+ };
2081
+ }
2082
+ function createTokenTransfersClient(config = {}) {
2083
+ return {
2084
+ list: async (input) => {
2085
+ if (!config.data) {
2086
+ return stub("tokenTransfers.list");
2087
+ }
2088
+ try {
2089
+ const raw = await config.data.query(
2090
+ api.tokenTransfers.queries.list,
2091
+ {
2092
+ limit: input?.limit,
2093
+ cursor: input?.cursor,
2094
+ direction: input?.direction
2095
+ }
2096
+ );
2097
+ if (!raw) {
2098
+ return [
2099
+ new CapxulError({
2100
+ code: "NOT_AUTHENTICATED",
2101
+ message: "tokenTransfers.list requires an authenticated session."
2102
+ }),
2103
+ null
2104
+ ];
2105
+ }
2106
+ return [
2107
+ null,
2108
+ {
2109
+ object: "list",
2110
+ data: raw.items.map(brandRow),
2111
+ page: {
2112
+ hasMore: raw.hasMore,
2113
+ nextCursor: raw.nextCursor
2114
+ },
2115
+ displayCurrency: raw.displayCurrency
2116
+ }
2117
+ ];
2118
+ } catch (cause) {
2119
+ return [fromConvexError(cause), null];
2120
+ }
2121
+ },
2122
+ retrieve: async (input) => {
2123
+ if (!config.data) {
2124
+ return stub("tokenTransfers.retrieve");
2125
+ }
2126
+ try {
2127
+ const raw = await config.data.query(
2128
+ api.tokenTransfers.queries.getByTxLogIndex,
2129
+ {
2130
+ txHash: input.txHash,
2131
+ logIndex: input.logIndex,
2132
+ chainId: input.chainId
2133
+ }
2134
+ );
2135
+ if (!raw) {
2136
+ return [
2137
+ new CapxulError({
2138
+ code: "NOT_FOUND",
2139
+ message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
2140
+ details: {
2141
+ txHash: input.txHash,
2142
+ logIndex: input.logIndex,
2143
+ chainId: input.chainId
2144
+ }
2145
+ }),
2146
+ null
2147
+ ];
2148
+ }
2149
+ return [null, brandRow(raw)];
2150
+ } catch (cause) {
2151
+ return [fromConvexError(cause), null];
2152
+ }
2153
+ }
2154
+ };
2155
+ }
2156
+
1233
2157
  // src/core/virtual-accounts.ts
1234
2158
  function createVirtualAccountsClient() {
1235
2159
  return {
@@ -1251,50 +2175,6 @@ function createVirtualCardsClient() {
1251
2175
  cancel: async () => stub("virtualCards.cancel")
1252
2176
  };
1253
2177
  }
1254
-
1255
- // ../observability/src/debug-log.ts
1256
- function isDevelopmentBuild() {
1257
- if (typeof process === "undefined") {
1258
- return false;
1259
- }
1260
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1261
- }
1262
- function debugLog(line) {
1263
- if (!isDevelopmentBuild()) return;
1264
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1265
- console.info(line);
1266
- return;
1267
- }
1268
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1269
- process.stderr.write(`${line}
1270
- `);
1271
- }
1272
- }
1273
- function formatDebugValue(value) {
1274
- if (value === void 0 || value === "") return "";
1275
- if (typeof value === "string") return value;
1276
- try {
1277
- return JSON.stringify(value);
1278
- } catch {
1279
- return String(value);
1280
- }
1281
- }
1282
- function track(...args) {
1283
- const [name, props] = args;
1284
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1285
- }
1286
- function formatDebugValue2(value) {
1287
- if (value === void 0 || value === "") return "";
1288
- if (typeof value === "string") return value;
1289
- try {
1290
- return JSON.stringify(value);
1291
- } catch {
1292
- return String(value);
1293
- }
1294
- }
1295
- function identify(userId, traits) {
1296
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1297
- }
1298
2178
  function createAuthFlowMachine(client) {
1299
2179
  return setup({
1300
2180
  types: {},
@@ -1570,23 +2450,6 @@ function emailDomain(email) {
1570
2450
  const domain = email.split("@")[1]?.trim().toLowerCase();
1571
2451
  return domain || "unknown";
1572
2452
  }
1573
-
1574
- // ../platform-kernel/src/ids.ts
1575
- function makePrefixedIdConstructor(prefix, fieldName) {
1576
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
1577
- return (raw) => {
1578
- if (typeof raw !== "string" || !re.test(raw)) {
1579
- throw new Error(
1580
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
1581
- );
1582
- }
1583
- return raw;
1584
- };
1585
- }
1586
- var toOperationId = makePrefixedIdConstructor(
1587
- "op",
1588
- "operationId"
1589
- );
1590
2453
  function createProvisioningMachine(client) {
1591
2454
  return setup({
1592
2455
  types: {},
@@ -1973,12 +2836,13 @@ function createCapxulClient(config = {}) {
1973
2836
  organizations: createOrganizationsClient(config),
1974
2837
  payments: createPaymentsClient(config),
1975
2838
  transfers: createTransfersClient(),
1976
- withdrawals: createWithdrawalsClient(),
2839
+ tokenTransfers: createTokenTransfersClient(config),
2840
+ withdrawals: createWithdrawalsClient(config),
1977
2841
  documents: createDocumentsClient(),
1978
2842
  subAccounts: createSubAccountsClient(),
1979
2843
  virtualAccounts: createVirtualAccountsClient(),
1980
2844
  virtualCards: createVirtualCardsClient(),
1981
- externalAccounts: createExternalAccountsClient(),
2845
+ externalAccounts: createExternalAccountsClient(config),
1982
2846
  operations: createOperationsClient(config),
1983
2847
  webhookEndpoints: createWebhookEndpointsClient(),
1984
2848
  webhookEvents: createWebhookEventsClient(),