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

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
@@ -70,9 +70,79 @@ function fromConvexError(error) {
70
70
  // src/core/accounts.ts
71
71
  function createAccountsClient(config = {}) {
72
72
  return {
73
- retrieve: async () => stub("accounts.retrieve"),
73
+ retrieve: async (accountId) => {
74
+ if (!config.data) {
75
+ return stub("accounts.retrieve");
76
+ }
77
+ try {
78
+ const account = await config.data.query(
79
+ api.openfort.queries.getMyAccount,
80
+ {}
81
+ );
82
+ if (account.id !== accountId) {
83
+ return [
84
+ new CapxulError({
85
+ code: "PERMISSION_DENIED",
86
+ message: "accounts.retrieve currently supports the authenticated caller's own account only.",
87
+ details: {
88
+ requestedAccountId: accountId,
89
+ authenticatedAccountId: account.id
90
+ }
91
+ }),
92
+ null
93
+ ];
94
+ }
95
+ return [null, account];
96
+ } catch (cause) {
97
+ return [fromConvexError(cause), null];
98
+ }
99
+ },
74
100
  lookup: async () => stub("accounts.lookup"),
75
- update: async () => stub("accounts.update"),
101
+ update: async (input) => {
102
+ if (!config.data) {
103
+ return stub("accounts.update");
104
+ }
105
+ if (input.countryCode !== void 0) {
106
+ return [
107
+ new CapxulError({
108
+ code: "INVALID_INPUT",
109
+ message: "accounts.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
110
+ details: { field: "countryCode" }
111
+ }),
112
+ null
113
+ ];
114
+ }
115
+ try {
116
+ const current = await config.data.query(
117
+ api.openfort.queries.getMyAccount,
118
+ {}
119
+ );
120
+ if (current.id !== input.accountId) {
121
+ return [
122
+ new CapxulError({
123
+ code: "PERMISSION_DENIED",
124
+ message: "accounts.update currently supports the authenticated caller's own account only.",
125
+ details: {
126
+ requestedAccountId: input.accountId,
127
+ authenticatedAccountId: current.id
128
+ }
129
+ }),
130
+ null
131
+ ];
132
+ }
133
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
134
+ displayName: input.name,
135
+ username: input.username
136
+ });
137
+ const updated = await config.data.query(
138
+ api.openfort.queries.getMyAccount,
139
+ {}
140
+ );
141
+ return [null, updated];
142
+ } catch (cause) {
143
+ return [fromConvexError(cause), null];
144
+ }
145
+ },
76
146
  provisionPersonal: async (input) => {
77
147
  if (!config.data) {
78
148
  return stub(
@@ -695,7 +765,34 @@ function createMeClient(config = {}) {
695
765
  return [fromConvexError(cause), null];
696
766
  }
697
767
  },
698
- update: async () => stub("me.update")
768
+ update: async (input) => {
769
+ if (!config.data) {
770
+ return stub("me.update");
771
+ }
772
+ if (input.countryCode !== void 0) {
773
+ return [
774
+ new CapxulError({
775
+ code: "INVALID_INPUT",
776
+ message: "me.update does not support countryCode yet \u2014 backend mutation only patches displayName and username.",
777
+ details: { field: "countryCode" }
778
+ }),
779
+ null
780
+ ];
781
+ }
782
+ try {
783
+ await config.data.mutation(api.openfort.mutations.updateProfile, {
784
+ displayName: input.name,
785
+ username: input.username
786
+ });
787
+ const account = await config.data.query(
788
+ api.openfort.queries.getMyAccount,
789
+ {}
790
+ );
791
+ return [null, account];
792
+ } catch (cause) {
793
+ return [fromConvexError(cause), null];
794
+ }
795
+ }
699
796
  };
700
797
  }
701
798
 
@@ -1098,24 +1195,298 @@ function createOrgTransfersClient() {
1098
1195
  cancel: async () => stub("organizations.transfers.cancel")
1099
1196
  };
1100
1197
  }
1101
-
1102
- // src/core/withdrawals.ts
1103
- function createWithdrawalsClient() {
1198
+ var EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
1199
+ function createWithdrawalsClient(config = {}) {
1104
1200
  return {
1105
- create: async () => stub("withdrawals.create"),
1106
- retrieve: async () => stub("withdrawals.retrieve"),
1107
- list: async () => stub("withdrawals.list")
1201
+ create: async (input) => {
1202
+ if (!config.data) {
1203
+ return stub("withdrawals.create");
1204
+ }
1205
+ let created = null;
1206
+ let submitted = null;
1207
+ try {
1208
+ created = await config.data.mutation(
1209
+ api.withdrawals.mutations.create,
1210
+ {
1211
+ amount: input.amount,
1212
+ destination: {
1213
+ externalAccountId: input.destination.externalAccountId,
1214
+ kind: input.destination.kind
1215
+ },
1216
+ source: input.source,
1217
+ reference: input.reference,
1218
+ idempotencyKey: input.idempotencyKey
1219
+ }
1220
+ );
1221
+ if (!created) {
1222
+ return [
1223
+ new CapxulError({
1224
+ code: "NETWORK_ERROR",
1225
+ message: "withdrawals.create returned no withdrawal resource"
1226
+ }),
1227
+ null
1228
+ ];
1229
+ }
1230
+ if (created.status !== "processing" || created.operation.status !== "processing") {
1231
+ return [null, created];
1232
+ }
1233
+ if (input.destination.kind !== "evm") {
1234
+ return [null, created];
1235
+ }
1236
+ if (!config.signer || !config.signing) {
1237
+ return [null, created];
1238
+ }
1239
+ if (!EVM_ADDRESS_RE.test(input.destination.externalAccountId)) {
1240
+ throw new CapxulError({
1241
+ code: "INVALID_INPUT",
1242
+ message: "destination.externalAccountId must be a 0x-prefixed EVM address while external_accounts resolution is pending (slice 1).",
1243
+ details: { field: "destination.externalAccountId" }
1244
+ });
1245
+ }
1246
+ const currentSigner = await config.data.query(
1247
+ api.safe.queries.getMySignerAddress,
1248
+ {}
1249
+ );
1250
+ if (!currentSigner?.address) {
1251
+ throw new CapxulError({
1252
+ code: "PERMISSION_DENIED",
1253
+ message: "No signer is registered for the authenticated account.",
1254
+ details: { withdrawalId: created.id }
1255
+ });
1256
+ }
1257
+ if (getAddress(currentSigner.address) !== getAddress(config.signer.address)) {
1258
+ throw new CapxulError({
1259
+ code: "PERMISSION_DENIED",
1260
+ message: "Configured signer does not match the authenticated account signer.",
1261
+ details: {
1262
+ withdrawalId: created.id,
1263
+ expectedSignerAddress: currentSigner.address,
1264
+ actualSignerAddress: config.signer.address
1265
+ }
1266
+ });
1267
+ }
1268
+ const submission = await config.data.query(
1269
+ api.withdrawals.queries.prepareSubmission,
1270
+ { withdrawalId: created.id }
1271
+ );
1272
+ if (!submission?.externalAccountId) {
1273
+ throw new CapxulError({
1274
+ code: "NETWORK_ERROR",
1275
+ message: "withdrawals.prepareSubmission returned no destination.",
1276
+ details: { withdrawalId: created.id }
1277
+ });
1278
+ }
1279
+ const transfer = await transferAsOwner(
1280
+ {
1281
+ signer: config.signer,
1282
+ signing: config.signing
1283
+ },
1284
+ {
1285
+ tokenAddress: resolvePaymentTokenAddress(submission.amount.currency),
1286
+ recipientAddress: submission.externalAccountId,
1287
+ amount: toTokenUnits(submission.amount.value, 6)
1288
+ }
1289
+ );
1290
+ if (!transfer.success) {
1291
+ throw new CapxulError({
1292
+ code: "NETWORK_ERROR",
1293
+ message: "Bundler submission did not succeed.",
1294
+ details: {
1295
+ withdrawalId: created.id,
1296
+ txHash: transfer.txHash,
1297
+ userOpHash: transfer.userOpHash
1298
+ }
1299
+ });
1300
+ }
1301
+ submitted = {
1302
+ txHash: transfer.txHash,
1303
+ userOpHash: transfer.userOpHash
1304
+ };
1305
+ await config.data.mutation(
1306
+ api.withdrawals.mutations.recordSubmitted,
1307
+ {
1308
+ withdrawalId: created.id,
1309
+ txHash: transfer.txHash,
1310
+ userOpHash: transfer.userOpHash
1311
+ }
1312
+ );
1313
+ return [null, created];
1314
+ } catch (cause) {
1315
+ const error = mapCreateError2(fromConvexError(cause));
1316
+ if (created?.id && created.status === "processing" && !submitted) {
1317
+ await bestEffortMarkFailed2({ data: config.data }, created.id, error);
1318
+ }
1319
+ if (submitted && created?.id) {
1320
+ return [
1321
+ new CapxulError({
1322
+ code: "NETWORK_ERROR",
1323
+ message: "Withdrawal was submitted on-chain, but backend submission tracking failed.",
1324
+ cause,
1325
+ details: {
1326
+ withdrawalId: created.id,
1327
+ txHash: submitted.txHash,
1328
+ userOpHash: submitted.userOpHash
1329
+ }
1330
+ }),
1331
+ null
1332
+ ];
1333
+ }
1334
+ return [error, null];
1335
+ }
1336
+ },
1337
+ retrieve: async (withdrawalId) => {
1338
+ if (!config.data) {
1339
+ return stub("withdrawals.retrieve");
1340
+ }
1341
+ try {
1342
+ const withdrawal = await config.data.query(
1343
+ api.withdrawals.queries.retrieve,
1344
+ { withdrawalId }
1345
+ );
1346
+ if (!withdrawal) {
1347
+ return [
1348
+ new CapxulError({
1349
+ code: "NOT_FOUND",
1350
+ message: `withdrawal ${withdrawalId} not found`
1351
+ }),
1352
+ null
1353
+ ];
1354
+ }
1355
+ return [null, withdrawal];
1356
+ } catch (cause) {
1357
+ return [
1358
+ fromConvexError(cause),
1359
+ null
1360
+ ];
1361
+ }
1362
+ },
1363
+ list: async (input) => {
1364
+ if (!config.data) {
1365
+ return stub("withdrawals.list");
1366
+ }
1367
+ try {
1368
+ const result = await config.data.query(
1369
+ api.withdrawals.queries.list,
1370
+ {
1371
+ limit: input?.limit,
1372
+ cursor: input?.cursor
1373
+ }
1374
+ );
1375
+ return [null, result];
1376
+ } catch (cause) {
1377
+ return [
1378
+ fromConvexError(cause),
1379
+ null
1380
+ ];
1381
+ }
1382
+ }
1108
1383
  };
1109
1384
  }
1110
- function createOrgWithdrawalsClient() {
1385
+ function createOrgWithdrawalsClient(config = {}) {
1111
1386
  return {
1387
+ // Slice 1 ships personal-scope only end-to-end; org-scope create
1388
+ // remains stubbed pending org-scoped backend mutation. List + retrieve
1389
+ // are wired through the org-aware query.
1112
1390
  create: async () => stub(
1113
1391
  "organizations.withdrawals.create"
1114
1392
  ),
1115
- retrieve: async () => stub("organizations.withdrawals.retrieve"),
1116
- list: async () => stub("organizations.withdrawals.list")
1393
+ retrieve: async (input) => {
1394
+ if (!config.data) {
1395
+ return stub(
1396
+ "organizations.withdrawals.retrieve"
1397
+ );
1398
+ }
1399
+ try {
1400
+ const withdrawal = await config.data.query(
1401
+ api.withdrawals.queries.retrieve,
1402
+ { withdrawalId: input.withdrawalId }
1403
+ );
1404
+ if (!withdrawal) {
1405
+ return [
1406
+ new CapxulError({
1407
+ code: "NOT_FOUND",
1408
+ message: `withdrawal ${input.withdrawalId} not found`
1409
+ }),
1410
+ null
1411
+ ];
1412
+ }
1413
+ const ownerCheck = withdrawal.owner;
1414
+ if (ownerCheck?.type !== "organization" || ownerCheck.id !== input.organizationId) {
1415
+ return [
1416
+ new CapxulError({
1417
+ code: "NOT_FOUND",
1418
+ message: `withdrawal ${input.withdrawalId} does not belong to organization ${input.organizationId}`
1419
+ }),
1420
+ null
1421
+ ];
1422
+ }
1423
+ return [null, withdrawal];
1424
+ } catch (cause) {
1425
+ return [
1426
+ fromConvexError(cause),
1427
+ null
1428
+ ];
1429
+ }
1430
+ },
1431
+ list: async (input) => {
1432
+ if (!config.data) {
1433
+ return stub(
1434
+ "organizations.withdrawals.list"
1435
+ );
1436
+ }
1437
+ try {
1438
+ const result = await config.data.query(
1439
+ api.withdrawals.queries.listOrg,
1440
+ {
1441
+ organizationId: input.organizationId,
1442
+ limit: input.limit,
1443
+ cursor: input.cursor
1444
+ }
1445
+ );
1446
+ return [null, result];
1447
+ } catch (cause) {
1448
+ return [
1449
+ fromConvexError(cause),
1450
+ null
1451
+ ];
1452
+ }
1453
+ }
1117
1454
  };
1118
1455
  }
1456
+ async function bestEffortMarkFailed2(config, withdrawalId, error) {
1457
+ try {
1458
+ await config.data.mutation(api.withdrawals.mutations.markFailed, {
1459
+ withdrawalId,
1460
+ errorCode: error.code,
1461
+ errorMessage: error.message
1462
+ });
1463
+ } catch {
1464
+ }
1465
+ }
1466
+ function mapCreateError2(error) {
1467
+ switch (error.code) {
1468
+ case "NOT_AUTHENTICATED":
1469
+ case "PERMISSION_DENIED":
1470
+ case "INVALID_INPUT":
1471
+ case "INSUFFICIENT_BALANCE":
1472
+ case "IDEMPOTENCY_CONFLICT":
1473
+ case "KYC_REQUIRED":
1474
+ case "POLICY_DENIED":
1475
+ case "RATE_LIMITED":
1476
+ case "NETWORK_ERROR":
1477
+ return error;
1478
+ default:
1479
+ return new CapxulError({
1480
+ code: "NETWORK_ERROR",
1481
+ message: error.message,
1482
+ cause: error,
1483
+ details: error.details,
1484
+ operationId: error.operationId,
1485
+ correlationId: error.correlationId,
1486
+ retryable: error.retryable
1487
+ });
1488
+ }
1489
+ }
1119
1490
 
1120
1491
  // src/core/webhook-endpoints.ts
1121
1492
  function createWebhookEndpointsClient() {
@@ -1215,7 +1586,7 @@ function createOrganizationsClient(config = {}) {
1215
1586
  },
1216
1587
  payments: createOrgPaymentsClient(),
1217
1588
  transfers: createOrgTransfersClient(),
1218
- withdrawals: createOrgWithdrawalsClient(),
1589
+ withdrawals: createOrgWithdrawalsClient(config),
1219
1590
  documents: createOrgDocumentsClient(),
1220
1591
  webhookEndpoints: createWebhookEndpointsClient(),
1221
1592
  webhookEvents: createWebhookEventsClient()
@@ -1230,6 +1601,96 @@ function createSubAccountsClient() {
1230
1601
  };
1231
1602
  }
1232
1603
 
1604
+ // src/core/token-transfers.ts
1605
+ var toTokenTransferId = (raw) => {
1606
+ if (typeof raw !== "string" || raw.length === 0) {
1607
+ throw new Error(
1608
+ `Invalid tokenTransferId: expected non-empty string, got ${String(raw)}`
1609
+ );
1610
+ }
1611
+ return raw;
1612
+ };
1613
+ function brandRow(row) {
1614
+ return {
1615
+ ...row,
1616
+ id: toTokenTransferId(row.id)
1617
+ };
1618
+ }
1619
+ function createTokenTransfersClient(config = {}) {
1620
+ return {
1621
+ list: async (input) => {
1622
+ if (!config.data) {
1623
+ return stub("tokenTransfers.list");
1624
+ }
1625
+ try {
1626
+ const raw = await config.data.query(
1627
+ api.tokenTransfers.queries.list,
1628
+ {
1629
+ limit: input?.limit,
1630
+ cursor: input?.cursor,
1631
+ direction: input?.direction
1632
+ }
1633
+ );
1634
+ if (!raw) {
1635
+ return [
1636
+ new CapxulError({
1637
+ code: "NOT_AUTHENTICATED",
1638
+ message: "tokenTransfers.list requires an authenticated session."
1639
+ }),
1640
+ null
1641
+ ];
1642
+ }
1643
+ return [
1644
+ null,
1645
+ {
1646
+ object: "list",
1647
+ data: raw.items.map(brandRow),
1648
+ page: {
1649
+ hasMore: raw.hasMore,
1650
+ nextCursor: raw.nextCursor
1651
+ },
1652
+ displayCurrency: raw.displayCurrency
1653
+ }
1654
+ ];
1655
+ } catch (cause) {
1656
+ return [fromConvexError(cause), null];
1657
+ }
1658
+ },
1659
+ retrieve: async (input) => {
1660
+ if (!config.data) {
1661
+ return stub("tokenTransfers.retrieve");
1662
+ }
1663
+ try {
1664
+ const raw = await config.data.query(
1665
+ api.tokenTransfers.queries.getByTxLogIndex,
1666
+ {
1667
+ txHash: input.txHash,
1668
+ logIndex: input.logIndex,
1669
+ chainId: input.chainId
1670
+ }
1671
+ );
1672
+ if (!raw) {
1673
+ return [
1674
+ new CapxulError({
1675
+ code: "NOT_FOUND",
1676
+ message: `tokenTransfer (txHash=${input.txHash}, logIndex=${input.logIndex}) not found or not visible to caller.`,
1677
+ details: {
1678
+ txHash: input.txHash,
1679
+ logIndex: input.logIndex,
1680
+ chainId: input.chainId
1681
+ }
1682
+ }),
1683
+ null
1684
+ ];
1685
+ }
1686
+ return [null, brandRow(raw)];
1687
+ } catch (cause) {
1688
+ return [fromConvexError(cause), null];
1689
+ }
1690
+ }
1691
+ };
1692
+ }
1693
+
1233
1694
  // src/core/virtual-accounts.ts
1234
1695
  function createVirtualAccountsClient() {
1235
1696
  return {
@@ -1973,7 +2434,8 @@ function createCapxulClient(config = {}) {
1973
2434
  organizations: createOrganizationsClient(config),
1974
2435
  payments: createPaymentsClient(config),
1975
2436
  transfers: createTransfersClient(),
1976
- withdrawals: createWithdrawalsClient(),
2437
+ tokenTransfers: createTokenTransfersClient(config),
2438
+ withdrawals: createWithdrawalsClient(config),
1977
2439
  documents: createDocumentsClient(),
1978
2440
  subAccounts: createSubAccountsClient(),
1979
2441
  virtualAccounts: createVirtualAccountsClient(),