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