@capxul/sdk 0.1.0-alpha.4 → 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/index.js CHANGED
@@ -68,7 +68,366 @@ function fromConvexError(error) {
68
68
  });
69
69
  }
70
70
 
71
+ // ../observability/src/try-catch.ts
72
+ async function tryCatch(promise) {
73
+ try {
74
+ return [null, await promise];
75
+ } catch (e) {
76
+ return [e instanceof Error ? e : new Error(String(e)), null];
77
+ }
78
+ }
79
+
80
+ // ../observability/src/debug-log.ts
81
+ function isDevelopmentBuild() {
82
+ if (typeof process === "undefined") {
83
+ return false;
84
+ }
85
+ return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
86
+ }
87
+ function debugLog(line) {
88
+ if (!isDevelopmentBuild()) return;
89
+ if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
90
+ console.info(line);
91
+ return;
92
+ }
93
+ if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
94
+ process.stderr.write(`${line}
95
+ `);
96
+ }
97
+ }
98
+ function formatDebugValue(value) {
99
+ if (value === void 0 || value === "") return "";
100
+ if (typeof value === "string") return value;
101
+ try {
102
+ return JSON.stringify(value);
103
+ } catch {
104
+ return String(value);
105
+ }
106
+ }
107
+ function track(...args) {
108
+ const [name, props] = args;
109
+ debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
110
+ }
111
+ function formatDebugValue2(value) {
112
+ if (value === void 0 || value === "") return "";
113
+ if (typeof value === "string") return value;
114
+ try {
115
+ return JSON.stringify(value);
116
+ } catch {
117
+ return String(value);
118
+ }
119
+ }
120
+ function identify(userId, traits) {
121
+ debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
122
+ }
123
+
124
+ // ../platform-kernel/src/ids.ts
125
+ function makePrefixedIdConstructor(prefix, fieldName) {
126
+ const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
127
+ return (raw) => {
128
+ if (typeof raw !== "string" || !re.test(raw)) {
129
+ throw new Error(
130
+ `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
131
+ );
132
+ }
133
+ return raw;
134
+ };
135
+ }
136
+ var toAccountId = makePrefixedIdConstructor(
137
+ "acct",
138
+ "accountId"
139
+ );
140
+ var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
141
+ var toMemberId = makePrefixedIdConstructor(
142
+ "mem",
143
+ "memberId"
144
+ );
145
+ var toSafeId = makePrefixedIdConstructor(
146
+ "safe",
147
+ "safeId"
148
+ );
149
+ var toTreasuryId = makePrefixedIdConstructor(
150
+ "try",
151
+ "treasuryId"
152
+ );
153
+ var toApiKeyId = makePrefixedIdConstructor(
154
+ "ak",
155
+ "apiKeyId"
156
+ );
157
+ var toOperationId = makePrefixedIdConstructor(
158
+ "op",
159
+ "operationId"
160
+ );
161
+ var toCorrelationId = makePrefixedIdConstructor("ctx", "correlationId");
162
+ var toKycProfileId = makePrefixedIdConstructor(
163
+ "kyc",
164
+ "kycProfileId"
165
+ );
166
+ var toKybProfileId = makePrefixedIdConstructor(
167
+ "kyb",
168
+ "kybProfileId"
169
+ );
170
+ var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
171
+ var toPaymentId = makePrefixedIdConstructor(
172
+ "pay",
173
+ "paymentId"
174
+ );
175
+ var toWithdrawalId = makePrefixedIdConstructor(
176
+ "wd",
177
+ "withdrawalId"
178
+ );
179
+ var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
180
+ var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
181
+ var toSubAccountId = makePrefixedIdConstructor(
182
+ "sub",
183
+ "subAccountId"
184
+ );
185
+ var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
186
+ var toVirtualCardId = makePrefixedIdConstructor(
187
+ "vc",
188
+ "virtualCardId"
189
+ );
190
+ var toTransferId = makePrefixedIdConstructor(
191
+ "txfr",
192
+ "transferId"
193
+ );
194
+ var toDocumentId = makePrefixedIdConstructor(
195
+ "doc",
196
+ "documentId"
197
+ );
198
+ var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
199
+
200
+ // ../platform-kernel/src/value-objects.ts
201
+ var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
202
+ function toPhoneNumber(raw) {
203
+ if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
204
+ throw new Error(
205
+ `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
206
+ );
207
+ }
208
+ return raw;
209
+ }
210
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
211
+ function toEmail(raw) {
212
+ if (typeof raw !== "string") {
213
+ throw new Error(
214
+ `Invalid email: expected string, got ${String(raw)}`
215
+ );
216
+ }
217
+ const lowered = raw.trim().toLowerCase();
218
+ if (!EMAIL_RE.test(lowered)) {
219
+ throw new Error(
220
+ `Invalid email: expected local@domain.tld, got ${String(raw)}`
221
+ );
222
+ }
223
+ return lowered;
224
+ }
225
+ var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
226
+ function toUsername(raw) {
227
+ if (typeof raw !== "string") {
228
+ throw new Error(
229
+ `Invalid username: expected string, got ${String(raw)}`
230
+ );
231
+ }
232
+ const lowered = raw.toLowerCase();
233
+ if (!USERNAME_RE.test(lowered)) {
234
+ throw new Error(
235
+ `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
236
+ );
237
+ }
238
+ return lowered;
239
+ }
240
+
241
+ // src/core/external-accounts.ts
242
+ function brandExternalAccount(raw) {
243
+ return {
244
+ ...raw,
245
+ id: toExternalAccountId(raw.id),
246
+ operation: {
247
+ id: toOperationId(raw.operation.id),
248
+ status: raw.operation.status,
249
+ correlationId: toCorrelationId(raw.operation.correlationId)
250
+ }
251
+ };
252
+ }
253
+ function createExternalAccountsClient(config = {}) {
254
+ return {
255
+ retrieve: async (externalAccountId) => {
256
+ if (!config.data) {
257
+ return stub(
258
+ "externalAccounts.retrieve"
259
+ );
260
+ }
261
+ const [err, raw] = await tryCatch(
262
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
263
+ externalAccountId
264
+ })
265
+ );
266
+ if (err) {
267
+ return [
268
+ fromConvexError(err),
269
+ null
270
+ ];
271
+ }
272
+ if (!raw) {
273
+ return [
274
+ new CapxulError({
275
+ code: "NOT_FOUND",
276
+ message: `external_account ${externalAccountId} not found`
277
+ }),
278
+ null
279
+ ];
280
+ }
281
+ return [null, brandExternalAccount(raw)];
282
+ },
283
+ remove: async (externalAccountId) => {
284
+ if (!config.data) {
285
+ return stub("externalAccounts.remove");
286
+ }
287
+ const [err] = await tryCatch(
288
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
289
+ externalAccountId
290
+ })
291
+ );
292
+ if (err) {
293
+ return [
294
+ fromConvexError(err),
295
+ null
296
+ ];
297
+ }
298
+ return [null, void 0];
299
+ }
300
+ };
301
+ }
302
+
71
303
  // src/core/accounts.ts
304
+ function createAccountExternalAccountsClient(config) {
305
+ return {
306
+ create: async (input) => {
307
+ if (!config.data) {
308
+ return stub(
309
+ "accounts.externalAccounts.create"
310
+ );
311
+ }
312
+ const [err, raw] = await tryCatch(
313
+ config.data.mutation(
314
+ api.externalAccounts.mutations.createPersonal,
315
+ {
316
+ kind: input.kind,
317
+ label: input.label,
318
+ address: input.address,
319
+ iban: input.iban,
320
+ bic: input.bic,
321
+ accountHolder: input.accountHolder,
322
+ network: input.network,
323
+ panToken: input.panToken,
324
+ last4: input.last4
325
+ }
326
+ )
327
+ );
328
+ if (err) {
329
+ return [
330
+ fromConvexError(err),
331
+ null
332
+ ];
333
+ }
334
+ if (!raw) {
335
+ return [
336
+ new CapxulError({
337
+ code: "NOT_FOUND",
338
+ message: "external_account creation returned no resource"
339
+ }),
340
+ null
341
+ ];
342
+ }
343
+ return [
344
+ null,
345
+ brandExternalAccount(
346
+ raw
347
+ )
348
+ ];
349
+ },
350
+ list: async (input) => {
351
+ if (!config.data) {
352
+ return stub(
353
+ "accounts.externalAccounts.list"
354
+ );
355
+ }
356
+ const [err, result] = await tryCatch(
357
+ config.data.query(api.externalAccounts.queries.listPersonal, {
358
+ limit: input.limit,
359
+ cursor: input.cursor
360
+ })
361
+ );
362
+ if (err) {
363
+ return [fromConvexError(err), null];
364
+ }
365
+ const branded = result.data.map(
366
+ (row) => brandExternalAccount(
367
+ row
368
+ )
369
+ );
370
+ return [
371
+ null,
372
+ {
373
+ object: "list",
374
+ data: branded,
375
+ page: result.page
376
+ }
377
+ ];
378
+ },
379
+ retrieve: async (externalAccountId) => {
380
+ if (!config.data) {
381
+ return stub(
382
+ "accounts.externalAccounts.retrieve"
383
+ );
384
+ }
385
+ const [err, raw] = await tryCatch(
386
+ config.data.query(api.externalAccounts.queries.retrievePersonal, {
387
+ externalAccountId
388
+ })
389
+ );
390
+ if (err) {
391
+ return [
392
+ fromConvexError(err),
393
+ null
394
+ ];
395
+ }
396
+ if (!raw) {
397
+ return [
398
+ new CapxulError({
399
+ code: "NOT_FOUND",
400
+ message: `external_account ${externalAccountId} not found`
401
+ }),
402
+ null
403
+ ];
404
+ }
405
+ return [
406
+ null,
407
+ brandExternalAccount(
408
+ raw
409
+ )
410
+ ];
411
+ },
412
+ remove: async (externalAccountId) => {
413
+ if (!config.data) {
414
+ return stub("accounts.externalAccounts.remove");
415
+ }
416
+ const [err] = await tryCatch(
417
+ config.data.mutation(api.externalAccounts.mutations.removePersonal, {
418
+ externalAccountId
419
+ })
420
+ );
421
+ if (err) {
422
+ return [
423
+ fromConvexError(err),
424
+ null
425
+ ];
426
+ }
427
+ return [null, void 0];
428
+ }
429
+ };
430
+ }
72
431
  function createAccountsClient(config = {}) {
73
432
  return {
74
433
  retrieve: async (accountId) => {
@@ -223,18 +582,7 @@ function createAccountsClient(config = {}) {
223
582
  create: async () => stub("accounts.kycProfiles.create"),
224
583
  retrieve: async () => stub("accounts.kycProfiles.retrieve")
225
584
  },
226
- externalAccounts: {
227
- create: async () => stub(
228
- "accounts.externalAccounts.create"
229
- ),
230
- list: async () => stub(
231
- "accounts.externalAccounts.list"
232
- ),
233
- retrieve: async () => stub(
234
- "accounts.externalAccounts.retrieve"
235
- ),
236
- remove: async () => stub("accounts.externalAccounts.remove")
237
- },
585
+ externalAccounts: createAccountExternalAccountsClient(config),
238
586
  subAccounts: {
239
587
  create: async () => stub("accounts.subAccounts.create"),
240
588
  list: async () => stub("accounts.subAccounts.list"),
@@ -330,7 +678,29 @@ var Errors = {
330
678
  { details }
331
679
  ),
332
680
  emailDeliveryFailed: (detail) => new CapxulError2("EMAIL_DELIVERY_FAILED", `Failed to send email: ${detail}`),
333
- internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`)
681
+ internalError: (reason) => new CapxulError2("INTERNAL_ERROR", `Internal error: ${reason}`),
682
+ /**
683
+ * Verification gate. Surfaced when a request hits a verification
684
+ * boundary the actor cannot cross under their current state. Two
685
+ * variants share this code:
686
+ *
687
+ * - Rail gate (Withdrawals v1 W2, #465): the resolved
688
+ * `external_account.kind` routes to a withdrawal rail (e.g.
689
+ * `fiat_offramp`, `card_payout`) that is not yet supported. Carries
690
+ * `details.rail` + `details.currentKind`.
691
+ * - KYC tier gate (legacy / future): the actor's KYC tier is below
692
+ * the required tier. Carries `details.requiredTier`.
693
+ *
694
+ * Code is shared because both expose the same UX shape ("you cannot
695
+ * proceed until verification advances"); the `details.*` keys
696
+ * differentiate the route.
697
+ */
698
+ verificationRequired: (details) => {
699
+ const message = "rail" in details ? `Withdrawal rail "${details.rail}" (kind=${details.currentKind}) is not yet supported.` : `Verification tier ${details.requiredTier} is required.`;
700
+ return new CapxulError2("VERIFICATION_REQUIRED", message, {
701
+ details: { ...details }
702
+ });
703
+ }
334
704
  };
335
705
 
336
706
  // ../config/src/safe.ts
@@ -742,14 +1112,6 @@ function createOrgDocumentsClient() {
742
1112
  };
743
1113
  }
744
1114
 
745
- // src/core/external-accounts.ts
746
- function createExternalAccountsClient() {
747
- return {
748
- retrieve: async () => stub("externalAccounts.retrieve"),
749
- remove: async () => stub("externalAccounts.remove")
750
- };
751
- }
752
-
753
1115
  // src/core/me.ts
754
1116
  function createMeClient(config = {}) {
755
1117
  return {
@@ -1197,67 +1559,68 @@ function createOrgTransfersClient() {
1197
1559
  cancel: async () => stub("organizations.transfers.cancel")
1198
1560
  };
1199
1561
  }
1200
- var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1201
1562
  function createWithdrawalsClient(config = {}) {
1202
1563
  return {
1203
1564
  create: async (input) => {
1204
1565
  if (!config.data) {
1205
1566
  return stub("withdrawals.create");
1206
1567
  }
1207
- let created = null;
1208
- let submitted = null;
1209
- try {
1210
- created = await config.data.mutation(
1211
- api.withdrawals.mutations.create,
1212
- {
1213
- amount: input.amount,
1214
- destination: {
1215
- externalAccountId: input.destination.externalAccountId,
1216
- kind: input.destination.kind
1217
- },
1218
- source: input.source,
1219
- reference: input.reference,
1220
- idempotencyKey: input.idempotencyKey
1221
- }
1222
- );
1223
- if (!created) {
1224
- return [
1225
- new CapxulError({
1226
- code: "NETWORK_ERROR",
1227
- message: "withdrawals.create returned no withdrawal resource"
1228
- }),
1229
- null
1230
- ];
1231
- }
1232
- if (created.status !== "processing" || created.operation.status !== "processing") {
1233
- return [null, created];
1234
- }
1235
- if (input.destination.kind !== "evm") {
1236
- return [null, created];
1237
- }
1238
- if (!config.signer || !config.signing) {
1239
- return [null, created];
1240
- }
1241
- if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1242
- throw new CapxulError({
1243
- code: "INVALID_INPUT",
1244
- message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1245
- details: { field: "destination.externalAccountId" }
1246
- });
1247
- }
1248
- const currentSigner = await config.data.query(
1249
- api.safe.queries.getMySignerAddress,
1250
- {}
1568
+ const [createErr, createdRaw] = await tryCatch(
1569
+ config.data.mutation(api.withdrawals.mutations.create, {
1570
+ amount: input.amount,
1571
+ destination: {
1572
+ externalAccountId: input.destination.externalAccountId
1573
+ },
1574
+ source: input.source,
1575
+ reference: input.reference,
1576
+ idempotencyKey: input.idempotencyKey
1577
+ })
1578
+ );
1579
+ if (createErr) {
1580
+ return [mapCreateError2(fromConvexError(createErr)), null];
1581
+ }
1582
+ const created = createdRaw;
1583
+ if (!created) {
1584
+ return [
1585
+ new CapxulError({
1586
+ code: "NETWORK_ERROR",
1587
+ message: "withdrawals.create returned no withdrawal resource"
1588
+ }),
1589
+ null
1590
+ ];
1591
+ }
1592
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1593
+ return [null, created];
1594
+ }
1595
+ if (!config.signer || !config.signing) {
1596
+ return [null, created];
1597
+ }
1598
+ const [signerErr, currentSigner] = await tryCatch(
1599
+ config.data.query(api.safe.queries.getMySignerAddress, {})
1600
+ );
1601
+ if (signerErr) {
1602
+ return await handleSubmissionFailure(
1603
+ { data: config.data },
1604
+ created.id,
1605
+ mapCreateError2(fromConvexError(signerErr))
1251
1606
  );
1252
- if (!currentSigner?.address) {
1253
- throw new CapxulError({
1607
+ }
1608
+ if (!currentSigner?.address) {
1609
+ return await handleSubmissionFailure(
1610
+ { data: config.data },
1611
+ created.id,
1612
+ new CapxulError({
1254
1613
  code: "PERMISSION_DENIED",
1255
1614
  message: "No signer is registered for the authenticated account.",
1256
1615
  details: { withdrawalId: created.id }
1257
- });
1258
- }
1259
- if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1260
- throw new CapxulError({
1616
+ })
1617
+ );
1618
+ }
1619
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1620
+ return await handleSubmissionFailure(
1621
+ { data: config.data },
1622
+ created.id,
1623
+ new CapxulError({
1261
1624
  code: "PERMISSION_DENIED",
1262
1625
  message: "Configured signer does not match the authenticated account signer.",
1263
1626
  details: {
@@ -1265,170 +1628,228 @@ function createWithdrawalsClient(config = {}) {
1265
1628
  expectedSignerAddress: currentSigner.address,
1266
1629
  actualSignerAddress: config.signer.address
1267
1630
  }
1268
- });
1269
- }
1270
- const submission = await config.data.query(
1271
- api.withdrawals.queries.prepareSubmission,
1272
- { withdrawalId: created.id }
1631
+ })
1273
1632
  );
1274
- if (!submission?.externalAccountId) {
1275
- throw new CapxulError({
1633
+ }
1634
+ const [prepErr, submission] = await tryCatch(
1635
+ config.data.query(api.withdrawals.queries.prepareSubmission, {
1636
+ withdrawalId: created.id
1637
+ })
1638
+ );
1639
+ if (prepErr) {
1640
+ return await handleSubmissionFailure(
1641
+ { data: config.data },
1642
+ created.id,
1643
+ mapCreateError2(fromConvexError(prepErr))
1644
+ );
1645
+ }
1646
+ const destinationAddress = submission?.destinationAddress;
1647
+ if (!submission || !destinationAddress) {
1648
+ return await handleSubmissionFailure(
1649
+ { data: config.data },
1650
+ created.id,
1651
+ new CapxulError({
1276
1652
  code: "NETWORK_ERROR",
1277
1653
  message: "withdrawals.prepareSubmission returned no destination.",
1278
1654
  details: { withdrawalId: created.id }
1279
- });
1280
- }
1281
- const transfer = await transferAsOwner(
1655
+ })
1656
+ );
1657
+ }
1658
+ const [transferErr, transferOk] = await tryCatch(
1659
+ transferAsOwner(
1282
1660
  {
1283
1661
  signer: config.signer,
1284
1662
  signing: config.signing
1285
1663
  },
1286
1664
  {
1287
1665
  tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1288
- recipientAddress: submission.externalAccountId,
1666
+ recipientAddress: destinationAddress,
1289
1667
  amount: toTokenUnits(submission.amount.value, 6)
1290
1668
  }
1669
+ )
1670
+ );
1671
+ if (transferErr) {
1672
+ return await handleSubmissionFailure(
1673
+ { data: config.data },
1674
+ created.id,
1675
+ mapCreateError2(fromConvexError(transferErr))
1291
1676
  );
1292
- if (!transfer.success) {
1293
- throw new CapxulError({
1677
+ }
1678
+ if (!transferOk.success) {
1679
+ return await handleSubmissionFailure(
1680
+ { data: config.data },
1681
+ created.id,
1682
+ new CapxulError({
1294
1683
  code: "NETWORK_ERROR",
1295
1684
  message: "Bundler submission did not succeed.",
1296
1685
  details: {
1297
1686
  withdrawalId: created.id,
1298
- txHash: transfer.txHash,
1299
- userOpHash: transfer.userOpHash
1687
+ txHash: transferOk.txHash,
1688
+ userOpHash: transferOk.userOpHash
1300
1689
  }
1301
- });
1302
- }
1303
- submitted = {
1304
- txHash: transfer.txHash,
1305
- userOpHash: transfer.userOpHash
1306
- };
1307
- await config.data.mutation(
1308
- api.withdrawals.mutations.recordSubmitted,
1309
- {
1310
- withdrawalId: created.id,
1311
- txHash: transfer.txHash,
1312
- userOpHash: transfer.userOpHash
1313
- }
1690
+ })
1314
1691
  );
1315
- return [null, created];
1316
- } catch (cause) {
1317
- const error = mapCreateError2(fromConvexError(cause));
1318
- if (created?.id && created.status === "processing" && !submitted) {
1319
- await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1320
- }
1321
- if (submitted && created?.id) {
1322
- return [
1323
- new CapxulError({
1324
- code: "NETWORK_ERROR",
1325
- message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1326
- cause,
1327
- details: {
1328
- withdrawalId: created.id,
1329
- txHash: submitted.txHash,
1330
- userOpHash: submitted.userOpHash
1331
- }
1332
- }),
1333
- null
1334
- ];
1335
- }
1336
- return [error, null];
1337
1692
  }
1693
+ const [recordErr] = await tryCatch(
1694
+ config.data.mutation(api.withdrawals.mutations.recordSubmitted, {
1695
+ withdrawalId: created.id,
1696
+ txHash: transferOk.txHash,
1697
+ userOpHash: transferOk.userOpHash
1698
+ })
1699
+ );
1700
+ if (recordErr) {
1701
+ return [
1702
+ new CapxulError({
1703
+ code: "NETWORK_ERROR",
1704
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1705
+ cause: recordErr,
1706
+ details: {
1707
+ withdrawalId: created.id,
1708
+ txHash: transferOk.txHash,
1709
+ userOpHash: transferOk.userOpHash
1710
+ }
1711
+ }),
1712
+ null
1713
+ ];
1714
+ }
1715
+ return [null, created];
1338
1716
  },
1339
1717
  retrieve: async (withdrawalId) => {
1340
1718
  if (!config.data) {
1341
1719
  return stub("withdrawals.retrieve");
1342
1720
  }
1343
- try {
1344
- const withdrawal = await config.data.query(
1345
- api.withdrawals.queries.retrieve,
1346
- { withdrawalId }
1347
- );
1348
- if (!withdrawal) {
1349
- return [
1350
- new CapxulError({
1351
- code: "NOT_FOUND",
1352
- message: `withdrawal ${withdrawalId} not found`
1353
- }),
1354
- null
1355
- ];
1356
- }
1357
- return [null, withdrawal];
1358
- } catch (cause) {
1721
+ const [err, raw] = await tryCatch(
1722
+ config.data.query(api.withdrawals.queries.retrieve, { withdrawalId })
1723
+ );
1724
+ if (err) {
1725
+ return [fromConvexError(err), null];
1726
+ }
1727
+ const withdrawal = raw;
1728
+ if (!withdrawal) {
1359
1729
  return [
1360
- fromConvexError(cause),
1730
+ new CapxulError({
1731
+ code: "NOT_FOUND",
1732
+ message: `withdrawal ${withdrawalId} not found`
1733
+ }),
1361
1734
  null
1362
1735
  ];
1363
1736
  }
1737
+ return [null, withdrawal];
1364
1738
  },
1365
1739
  list: async (input) => {
1366
1740
  if (!config.data) {
1367
1741
  return stub("withdrawals.list");
1368
1742
  }
1369
- try {
1370
- const result = await config.data.query(
1371
- api.withdrawals.queries.list,
1372
- {
1373
- limit: input?.limit,
1374
- cursor: input?.cursor
1375
- }
1743
+ const [err, raw] = await tryCatch(
1744
+ config.data.query(api.withdrawals.queries.list, {
1745
+ limit: input?.limit,
1746
+ cursor: input?.cursor
1747
+ })
1748
+ );
1749
+ if (err) {
1750
+ return [fromConvexError(err), null];
1751
+ }
1752
+ return [null, raw];
1753
+ },
1754
+ recordCompleted: async (input) => {
1755
+ if (!config.data) {
1756
+ return stub(
1757
+ "withdrawals.recordCompleted"
1376
1758
  );
1377
- return [null, result];
1378
- } catch (cause) {
1759
+ }
1760
+ const [err] = await tryCatch(
1761
+ config.data.mutation(api.withdrawals.mutations.recordCompleted, {
1762
+ withdrawalId: input.withdrawalId,
1763
+ txHash: input.txHash
1764
+ })
1765
+ );
1766
+ if (err) {
1379
1767
  return [
1380
- fromConvexError(cause),
1768
+ mapRecordCompletedError(fromConvexError(err)),
1381
1769
  null
1382
1770
  ];
1383
1771
  }
1772
+ return [null, null];
1384
1773
  }
1385
1774
  };
1386
1775
  }
1387
1776
  function createOrgWithdrawalsClient(config = {}) {
1388
1777
  return {
1389
- // Slice 1 ships personal-scope only end-to-end; org-scope create
1390
- // remains stubbed pending org-scoped backend mutation. List + retrieve
1391
- // are wired through the org-aware query.
1392
- create: async () => stub(
1393
- "organizations.withdrawals.create"
1394
- ),
1778
+ /**
1779
+ * Org-scope create (Withdrawals v1 W2, #465).
1780
+ *
1781
+ * D6 returns the `processing` row only. No `transferAsOwner`
1782
+ * tail, no `recordSubmitted` call. Org Safe + Zodiac submission
1783
+ * orchestration ships in W3+.
1784
+ */
1785
+ create: async (input) => {
1786
+ if (!config.data) {
1787
+ return stub(
1788
+ "organizations.withdrawals.create"
1789
+ );
1790
+ }
1791
+ const [err, raw] = await tryCatch(
1792
+ config.data.mutation(api.withdrawals.mutations.createOrg, {
1793
+ organizationId: input.organizationId,
1794
+ amount: input.amount,
1795
+ destination: {
1796
+ externalAccountId: input.destination.externalAccountId
1797
+ },
1798
+ source: input.source,
1799
+ reference: input.reference,
1800
+ idempotencyKey: input.idempotencyKey
1801
+ })
1802
+ );
1803
+ if (err) {
1804
+ return [mapCreateError2(fromConvexError(err)), null];
1805
+ }
1806
+ const created = raw;
1807
+ if (!created) {
1808
+ return [
1809
+ new CapxulError({
1810
+ code: "NETWORK_ERROR",
1811
+ message: "organizations.withdrawals.create returned no withdrawal resource"
1812
+ }),
1813
+ null
1814
+ ];
1815
+ }
1816
+ return [null, created];
1817
+ },
1395
1818
  retrieve: async (input) => {
1396
1819
  if (!config.data) {
1397
1820
  return stub(
1398
1821
  "organizations.withdrawals.retrieve"
1399
1822
  );
1400
1823
  }
1401
- try {
1402
- const withdrawal = await config.data.query(
1403
- api.withdrawals.queries.retrieve,
1404
- { withdrawalId: input.withdrawalId }
1405
- );
1406
- if (!withdrawal) {
1407
- return [
1408
- new CapxulError({
1409
- code: "NOT_FOUND",
1410
- message: `withdrawal ${input.withdrawalId} not found`
1411
- }),
1412
- null
1413
- ];
1414
- }
1415
- const ownerCheck = withdrawal.owner;
1416
- if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1417
- return [
1418
- new CapxulError({
1419
- code: "NOT_FOUND",
1420
- message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1421
- }),
1422
- null
1423
- ];
1424
- }
1425
- return [null, withdrawal];
1426
- } catch (cause) {
1824
+ const [err, raw] = await tryCatch(
1825
+ config.data.query(api.withdrawals.queries.retrieve, {
1826
+ withdrawalId: input.withdrawalId
1827
+ })
1828
+ );
1829
+ if (err) {
1830
+ return [fromConvexError(err), null];
1831
+ }
1832
+ const withdrawal = raw;
1833
+ if (!withdrawal) {
1427
1834
  return [
1428
- fromConvexError(cause),
1835
+ new CapxulError({
1836
+ code: "NOT_FOUND",
1837
+ message: `withdrawal ${input.withdrawalId} not found`
1838
+ }),
1839
+ null
1840
+ ];
1841
+ }
1842
+ const ownerCheck = withdrawal.owner;
1843
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1844
+ return [
1845
+ new CapxulError({
1846
+ code: "NOT_FOUND",
1847
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1848
+ }),
1429
1849
  null
1430
1850
  ];
1431
1851
  }
1852
+ return [null, withdrawal];
1432
1853
  },
1433
1854
  list: async (input) => {
1434
1855
  if (!config.data) {
@@ -1436,46 +1857,67 @@ function createOrgWithdrawalsClient(config = {}) {
1436
1857
  "organizations.withdrawals.list"
1437
1858
  );
1438
1859
  }
1439
- try {
1440
- const result = await config.data.query(
1441
- api.withdrawals.queries.listOrg,
1442
- {
1443
- organizationId: input.organizationId,
1444
- limit: input.limit,
1445
- cursor: input.cursor
1446
- }
1447
- );
1448
- return [null, result];
1449
- } catch (cause) {
1450
- return [
1451
- fromConvexError(cause),
1452
- null
1453
- ];
1860
+ const [err, raw] = await tryCatch(
1861
+ config.data.query(api.withdrawals.queries.listOrg, {
1862
+ organizationId: input.organizationId,
1863
+ limit: input.limit,
1864
+ cursor: input.cursor
1865
+ })
1866
+ );
1867
+ if (err) {
1868
+ return [fromConvexError(err), null];
1454
1869
  }
1870
+ return [null, raw];
1455
1871
  }
1456
1872
  };
1457
1873
  }
1874
+ async function handleSubmissionFailure(config, withdrawalId, error) {
1875
+ await bestEffortMarkFailed2(config, withdrawalId, error);
1876
+ return [error, null];
1877
+ }
1458
1878
  async function bestEffortMarkFailed2(config, withdrawalId, error) {
1459
- try {
1460
- await config.data.mutation(api.withdrawals.mutations.markFailed, {
1879
+ await tryCatch(
1880
+ config.data.mutation(api.withdrawals.mutations.markFailed, {
1461
1881
  withdrawalId,
1462
1882
  errorCode: error.code,
1463
1883
  errorMessage: error.message
1464
- });
1465
- } catch {
1884
+ })
1885
+ );
1886
+ }
1887
+ function mapCreateError2(error) {
1888
+ switch (error.code) {
1889
+ case "NOT_AUTHENTICATED":
1890
+ case "PERMISSION_DENIED":
1891
+ case "INVALID_INPUT":
1892
+ case "INSUFFICIENT_BALANCE":
1893
+ case "IDEMPOTENCY_CONFLICT":
1894
+ case "KYC_REQUIRED":
1895
+ case "POLICY_DENIED":
1896
+ case "RATE_LIMITED":
1897
+ case "NETWORK_ERROR":
1898
+ case "NOT_FOUND":
1899
+ case "VERIFICATION_REQUIRED":
1900
+ return error;
1901
+ default:
1902
+ return new CapxulError({
1903
+ code: "NETWORK_ERROR",
1904
+ message: error.message,
1905
+ cause: error,
1906
+ details: error.details,
1907
+ operationId: error.operationId,
1908
+ correlationId: error.correlationId,
1909
+ retryable: error.retryable
1910
+ });
1466
1911
  }
1467
1912
  }
1468
- function mapCreateError2(error) {
1913
+ function mapRecordCompletedError(error) {
1469
1914
  switch (error.code) {
1470
1915
  case "NOT_AUTHENTICATED":
1471
1916
  case "PERMISSION_DENIED":
1472
1917
  case "INVALID_INPUT":
1473
- case "INSUFFICIENT_BALANCE":
1474
- case "IDEMPOTENCY_CONFLICT":
1475
- case "KYC_REQUIRED":
1476
- case "POLICY_DENIED":
1477
- case "RATE_LIMITED":
1918
+ case "NOT_FOUND":
1478
1919
  case "NETWORK_ERROR":
1920
+ case "INTERNAL_ERROR":
1479
1921
  return error;
1480
1922
  default:
1481
1923
  return new CapxulError({
@@ -1511,6 +1953,136 @@ function createWebhookEventsClient() {
1511
1953
  }
1512
1954
 
1513
1955
  // src/core/organizations.ts
1956
+ function createOrgExternalAccountsClient(config) {
1957
+ return {
1958
+ create: async (input) => {
1959
+ if (!config.data) {
1960
+ return stub(
1961
+ "organizations.externalAccounts.create"
1962
+ );
1963
+ }
1964
+ const [err, raw] = await tryCatch(
1965
+ config.data.mutation(api.externalAccounts.mutations.createOrg, {
1966
+ organizationId: input.organizationId,
1967
+ kind: input.kind,
1968
+ label: input.label,
1969
+ address: input.address,
1970
+ iban: input.iban,
1971
+ bic: input.bic,
1972
+ accountHolder: input.accountHolder,
1973
+ network: input.network,
1974
+ panToken: input.panToken,
1975
+ last4: input.last4
1976
+ })
1977
+ );
1978
+ if (err) {
1979
+ return [
1980
+ fromConvexError(err),
1981
+ null
1982
+ ];
1983
+ }
1984
+ if (!raw) {
1985
+ return [
1986
+ new CapxulError({
1987
+ code: "NOT_FOUND",
1988
+ message: "external_account creation returned no resource"
1989
+ }),
1990
+ null
1991
+ ];
1992
+ }
1993
+ return [
1994
+ null,
1995
+ brandExternalAccount(
1996
+ raw
1997
+ )
1998
+ ];
1999
+ },
2000
+ list: async (input) => {
2001
+ if (!config.data) {
2002
+ return stub(
2003
+ "organizations.externalAccounts.list"
2004
+ );
2005
+ }
2006
+ const [err, result] = await tryCatch(
2007
+ config.data.query(api.externalAccounts.queries.listOrg, {
2008
+ organizationId: input.organizationId,
2009
+ limit: input.limit,
2010
+ cursor: input.cursor
2011
+ })
2012
+ );
2013
+ if (err) {
2014
+ return [fromConvexError(err), null];
2015
+ }
2016
+ const branded = result.data.map(
2017
+ (row) => brandExternalAccount(
2018
+ row
2019
+ )
2020
+ );
2021
+ return [
2022
+ null,
2023
+ {
2024
+ object: "list",
2025
+ data: branded,
2026
+ page: result.page
2027
+ }
2028
+ ];
2029
+ },
2030
+ retrieve: async (input) => {
2031
+ if (!config.data) {
2032
+ return stub(
2033
+ "organizations.externalAccounts.retrieve"
2034
+ );
2035
+ }
2036
+ const [err, raw] = await tryCatch(
2037
+ config.data.query(api.externalAccounts.queries.retrieveOrg, {
2038
+ organizationId: input.organizationId,
2039
+ externalAccountId: input.externalAccountId
2040
+ })
2041
+ );
2042
+ if (err) {
2043
+ return [
2044
+ fromConvexError(err),
2045
+ null
2046
+ ];
2047
+ }
2048
+ if (!raw) {
2049
+ return [
2050
+ new CapxulError({
2051
+ code: "NOT_FOUND",
2052
+ message: `external_account ${input.externalAccountId} not found`
2053
+ }),
2054
+ null
2055
+ ];
2056
+ }
2057
+ return [
2058
+ null,
2059
+ brandExternalAccount(
2060
+ raw
2061
+ )
2062
+ ];
2063
+ },
2064
+ remove: async (input) => {
2065
+ if (!config.data) {
2066
+ return stub(
2067
+ "organizations.externalAccounts.remove"
2068
+ );
2069
+ }
2070
+ const [err] = await tryCatch(
2071
+ config.data.mutation(api.externalAccounts.mutations.removeOrg, {
2072
+ organizationId: input.organizationId,
2073
+ externalAccountId: input.externalAccountId
2074
+ })
2075
+ );
2076
+ if (err) {
2077
+ return [
2078
+ fromConvexError(err),
2079
+ null
2080
+ ];
2081
+ }
2082
+ return [null, void 0];
2083
+ }
2084
+ };
2085
+ }
1514
2086
  function createOrganizationsClient(config = {}) {
1515
2087
  return {
1516
2088
  create: async () => stub("organizations.create"),
@@ -1566,18 +2138,7 @@ function createOrganizationsClient(config = {}) {
1566
2138
  retrieve: async () => stub("organizations.subAccounts.retrieve"),
1567
2139
  remove: async () => stub("organizations.subAccounts.remove")
1568
2140
  },
1569
- externalAccounts: {
1570
- create: async () => stub(
1571
- "organizations.externalAccounts.create"
1572
- ),
1573
- list: async () => stub(
1574
- "organizations.externalAccounts.list"
1575
- ),
1576
- retrieve: async () => stub(
1577
- "organizations.externalAccounts.retrieve"
1578
- ),
1579
- remove: async () => stub("organizations.externalAccounts.remove")
1580
- },
2141
+ externalAccounts: createOrgExternalAccountsClient(config),
1581
2142
  balanceLedger: {
1582
2143
  list: async () => stub(
1583
2144
  "organizations.balanceLedger.list"
@@ -1714,59 +2275,6 @@ function createVirtualCardsClient() {
1714
2275
  cancel: async () => stub("virtualCards.cancel")
1715
2276
  };
1716
2277
  }
1717
-
1718
- // ../observability/src/try-catch.ts
1719
- async function tryCatch(promise) {
1720
- try {
1721
- return [null, await promise];
1722
- } catch (e) {
1723
- return [e instanceof Error ? e : new Error(String(e)), null];
1724
- }
1725
- }
1726
-
1727
- // ../observability/src/debug-log.ts
1728
- function isDevelopmentBuild() {
1729
- if (typeof process === "undefined") {
1730
- return false;
1731
- }
1732
- return process.env?.NODE_ENV === "development" || process.env?.NODE_ENV === "test";
1733
- }
1734
- function debugLog(line) {
1735
- if (!isDevelopmentBuild()) return;
1736
- if (typeof globalThis !== "undefined" && "window" in globalThis && typeof console?.info === "function") {
1737
- console.info(line);
1738
- return;
1739
- }
1740
- if (typeof process !== "undefined" && typeof process.stderr?.write === "function") {
1741
- process.stderr.write(`${line}
1742
- `);
1743
- }
1744
- }
1745
- function formatDebugValue(value) {
1746
- if (value === void 0 || value === "") return "";
1747
- if (typeof value === "string") return value;
1748
- try {
1749
- return JSON.stringify(value);
1750
- } catch {
1751
- return String(value);
1752
- }
1753
- }
1754
- function track(...args) {
1755
- const [name, props] = args;
1756
- debugLog(`[TRACK] ${name} ${formatDebugValue(props ?? "")}`);
1757
- }
1758
- function formatDebugValue2(value) {
1759
- if (value === void 0 || value === "") return "";
1760
- if (typeof value === "string") return value;
1761
- try {
1762
- return JSON.stringify(value);
1763
- } catch {
1764
- return String(value);
1765
- }
1766
- }
1767
- function identify(userId, traits) {
1768
- debugLog(`[IDENTIFY] ${userId} ${formatDebugValue2(traits ?? "")}`);
1769
- }
1770
2278
  function createAuthFlowMachine(client) {
1771
2279
  return setup({
1772
2280
  types: {},
@@ -2042,122 +2550,6 @@ function emailDomain(email) {
2042
2550
  const domain = email.split("@")[1]?.trim().toLowerCase();
2043
2551
  return domain || "unknown";
2044
2552
  }
2045
-
2046
- // ../platform-kernel/src/ids.ts
2047
- function makePrefixedIdConstructor(prefix, fieldName) {
2048
- const re = new RegExp(`^${prefix}_[A-Za-z0-9_\\-]+$`);
2049
- return (raw) => {
2050
- if (typeof raw !== "string" || !re.test(raw)) {
2051
- throw new Error(
2052
- `Invalid ${fieldName}: expected string matching ^${prefix}_[A-Za-z0-9_\\-]+$, got ${String(raw)}`
2053
- );
2054
- }
2055
- return raw;
2056
- };
2057
- }
2058
- var toAccountId = makePrefixedIdConstructor(
2059
- "acct",
2060
- "accountId"
2061
- );
2062
- var toOrganizationId = makePrefixedIdConstructor("org", "organizationId");
2063
- var toMemberId = makePrefixedIdConstructor(
2064
- "mem",
2065
- "memberId"
2066
- );
2067
- var toSafeId = makePrefixedIdConstructor(
2068
- "safe",
2069
- "safeId"
2070
- );
2071
- var toTreasuryId = makePrefixedIdConstructor(
2072
- "try",
2073
- "treasuryId"
2074
- );
2075
- var toApiKeyId = makePrefixedIdConstructor(
2076
- "ak",
2077
- "apiKeyId"
2078
- );
2079
- var toOperationId = makePrefixedIdConstructor(
2080
- "op",
2081
- "operationId"
2082
- );
2083
- var toKycProfileId = makePrefixedIdConstructor(
2084
- "kyc",
2085
- "kycProfileId"
2086
- );
2087
- var toKybProfileId = makePrefixedIdConstructor(
2088
- "kyb",
2089
- "kybProfileId"
2090
- );
2091
- var toExternalAccountId = makePrefixedIdConstructor("ext", "externalAccountId");
2092
- var toPaymentId = makePrefixedIdConstructor(
2093
- "pay",
2094
- "paymentId"
2095
- );
2096
- var toWithdrawalId = makePrefixedIdConstructor(
2097
- "wd",
2098
- "withdrawalId"
2099
- );
2100
- var toWebhookEndpointId = makePrefixedIdConstructor("we", "webhookEndpointId");
2101
- var toWebhookEventId = makePrefixedIdConstructor("evt", "webhookEventId");
2102
- var toSubAccountId = makePrefixedIdConstructor(
2103
- "sub",
2104
- "subAccountId"
2105
- );
2106
- var toVirtualAccountId = makePrefixedIdConstructor("va", "virtualAccountId");
2107
- var toVirtualCardId = makePrefixedIdConstructor(
2108
- "vc",
2109
- "virtualCardId"
2110
- );
2111
- var toTransferId = makePrefixedIdConstructor(
2112
- "txfr",
2113
- "transferId"
2114
- );
2115
- var toDocumentId = makePrefixedIdConstructor(
2116
- "doc",
2117
- "documentId"
2118
- );
2119
- var toBalanceLedgerEntryId = makePrefixedIdConstructor("bal", "balanceLedgerEntryId");
2120
-
2121
- // ../platform-kernel/src/value-objects.ts
2122
- var PHONE_NUMBER_RE = /^\+[1-9]\d{1,14}$/;
2123
- function toPhoneNumber(raw) {
2124
- if (typeof raw !== "string" || !PHONE_NUMBER_RE.test(raw)) {
2125
- throw new Error(
2126
- `Invalid phoneNumber: expected E.164 string matching ^\\+[1-9]\\d{1,14}$, got ${String(raw)}`
2127
- );
2128
- }
2129
- return raw;
2130
- }
2131
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2132
- function toEmail(raw) {
2133
- if (typeof raw !== "string") {
2134
- throw new Error(
2135
- `Invalid email: expected string, got ${String(raw)}`
2136
- );
2137
- }
2138
- const lowered = raw.trim().toLowerCase();
2139
- if (!EMAIL_RE.test(lowered)) {
2140
- throw new Error(
2141
- `Invalid email: expected local@domain.tld, got ${String(raw)}`
2142
- );
2143
- }
2144
- return lowered;
2145
- }
2146
- var USERNAME_RE = /^[a-z][a-z0-9_-]{2,29}$/;
2147
- function toUsername(raw) {
2148
- if (typeof raw !== "string") {
2149
- throw new Error(
2150
- `Invalid username: expected string, got ${String(raw)}`
2151
- );
2152
- }
2153
- const lowered = raw.toLowerCase();
2154
- if (!USERNAME_RE.test(lowered)) {
2155
- throw new Error(
2156
- `Invalid username: expected 3-30 chars, letter-first, [a-z0-9_-], got ${String(raw)}`
2157
- );
2158
- }
2159
- return lowered;
2160
- }
2161
2553
  function createProvisioningMachine(client) {
2162
2554
  return setup({
2163
2555
  types: {},
@@ -2550,7 +2942,7 @@ function createCapxulClient(config = {}) {
2550
2942
  subAccounts: createSubAccountsClient(),
2551
2943
  virtualAccounts: createVirtualAccountsClient(),
2552
2944
  virtualCards: createVirtualCardsClient(),
2553
- externalAccounts: createExternalAccountsClient(),
2945
+ externalAccounts: createExternalAccountsClient(config),
2554
2946
  operations: createOperationsClient(config),
2555
2947
  webhookEndpoints: createWebhookEndpointsClient(),
2556
2948
  webhookEvents: createWebhookEventsClient(),