@capxul/sdk-react 4.20.0-beta.1 → 4.20.0-beta.11

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.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { _ as useCapxul, a as useCapxulAuth, c as useCapxulIdentityOrNull, d as toInvocationControls, f as toInvocationOptions, g as useCapxulClientOrNull, h as capxulKeys, i as entered, l as useCapxulSend, m as actingAccount, n as CapxulOnboardingController, o as useCapxulDestination, p as accountKey, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-DXmEHrnw.mjs";
2
+ import { _ as useCapxul, a as useCapxulAuth, c as useCapxulIdentityOrNull, d as toInvocationControls, f as toInvocationOptions, g as useCapxulClientOrNull, h as capxulKeys, i as entered, l as useCapxulSend, m as actingAccount, n as CapxulOnboardingController, o as useCapxulDestination, p as accountKey, r as CapxulProvider, s as useCapxulIdentity, t as CapxulAuthenticationController, u as useCapxulTransitions } from "./controllers-BV3e8WD3.mjs";
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5
- import { CAPXUL_OPERATIONS, CapxulError, EVM_ADDRESS_RE, Errors, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, fingerprintPaymentIntent, formatMoney, isCapxulError, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseMoney, paymentPhase, resolveIdentityDestination, toEvmAddress, toPartyId } from "@capxul/sdk";
5
+ import { CAPXUL_OPERATIONS, CapxulError, EVM_ADDRESS_RE, Errors, HANDLE_RE, PAYMENT_DIRECTIONS, PAYMENT_STATUSES, assetSymbolFor, fingerprintPaymentIntent, formatAssetAmount, formatMoney, isCapxulError, isClaimed, isClaimed as isClaimed$1, isMoneyParseError, isRestoring, isRestoring as isRestoring$1, parseAssetAmount, parseMoney, paymentPhase, requireAsset, resolveIdentityDestination, toEvmAddress, toPartyId, toRaw, valueActivitySummary } from "@capxul/sdk";
6
6
  import { Fragment, jsx } from "react/jsx-runtime";
7
7
  //#region src/internal/require-bootstrapped-client.ts
8
8
  /**
@@ -538,7 +538,7 @@ function useCapxulCreateOrg() {
538
538
  const queryClient = useQueryClient();
539
539
  return useMutation({
540
540
  mutationFn: async (input) => {
541
- return unwrapCapxulResult(await requireBootstrappedClient(client, "createOrg").createOrg(input));
541
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.onboarding.completeOrganization).onboarding.completeOrganization(input));
542
542
  },
543
543
  onSuccess: async () => {
544
544
  await queryClient.invalidateQueries({ queryKey: capxulKeys.orgs });
@@ -546,22 +546,6 @@ function useCapxulCreateOrg() {
546
546
  });
547
547
  }
548
548
  //#endregion
549
- //#region src/hooks/use-capxul-invite-member.ts
550
- function useCapxulInviteMember(orgId) {
551
- const client = useCapxulClientOrNull();
552
- const queryClient = useQueryClient();
553
- return useMutation({
554
- mutationFn: async ({ options, ...input }) => {
555
- if (orgId === void 0) throw Errors.invalidInput("orgId", "required for useCapxulInviteMember");
556
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.invite).org(orgId).invite(input, toInvocationControls(options)));
557
- },
558
- onSuccess: async () => {
559
- if (orgId === void 0) return;
560
- await queryClient.invalidateQueries({ queryKey: capxulKeys.orgMembers(orgId) });
561
- }
562
- });
563
- }
564
- //#endregion
565
549
  //#region src/headless/journey/journey-state.ts
566
550
  const JOURNEY_KEY = "capxul.onboarding.journey.v1";
567
551
  const VALIDATED_OWNER_KEY = "capxul.onboarding.validated-owner.v1";
@@ -1079,7 +1063,7 @@ function useContactsContext() {
1079
1063
  if (engine === null) throw new Error("CapxulContacts parts must be used inside <CapxulContacts>");
1080
1064
  return engine;
1081
1065
  }
1082
- function Root$6({ actor, onAdded, onFailed, children }) {
1066
+ function Root$7({ actor, onAdded, onFailed, children }) {
1083
1067
  const engine = useContacts({
1084
1068
  actor,
1085
1069
  onAdded,
@@ -1103,7 +1087,7 @@ function Add({ children }) {
1103
1087
  return /* @__PURE__ */ jsx(Fragment, { children: children(useContactsContext().add) });
1104
1088
  }
1105
1089
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
1106
- const CapxulContacts = Object.assign(Root$6, {
1090
+ const CapxulContacts = Object.assign(Root$7, {
1107
1091
  Summary: Summary$3,
1108
1092
  List,
1109
1093
  Add
@@ -1141,15 +1125,8 @@ function useActivityEnabled() {
1141
1125
  }
1142
1126
  //#endregion
1143
1127
  //#region src/headless/activity/detail.ts
1144
- /**
1145
- * ONE finished document, never the array, so no consumer writes `documents[0]`:
1146
- * the payslip, else the receipt, else nothing. Those two kinds are the ones the
1147
- * Download receipt button is for. A Movement detail carries no documents.
1148
- */
1149
- function pickReceiptDocument(detail) {
1150
- if (detail === null || detail.kind !== "payment") return null;
1151
- const documents = detail.payment.documents;
1152
- return documents.find((document) => document.kind === "payslip") ?? documents.find((document) => document.kind === "receipt") ?? null;
1128
+ function detailDocuments(detail) {
1129
+ return detail === null ? [] : detail.kind === "payment" ? detail.payment.documents : detail.documents;
1153
1130
  }
1154
1131
  /**
1155
1132
  * The root actor and the whole reference are included. A payment reference can
@@ -1184,27 +1161,76 @@ function useActivityDetail(reference, actor) {
1184
1161
  const enabled = useActivityEnabled() && reference !== void 0;
1185
1162
  const detail = useQuery({
1186
1163
  queryKey: accountKey(capxulKeys.activityDetail(reference, actor), identity),
1187
- queryFn: async () => {
1164
+ queryFn: async ({ signal }) => {
1188
1165
  if (reference === void 0) throw Errors.invalidInput("reference", "required for activity.get");
1189
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference, actor === void 0 ? void 0 : { actor }));
1166
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.get).activity.get(reference, {
1167
+ ...actor === void 0 ? {} : { actor },
1168
+ signal
1169
+ }));
1190
1170
  },
1191
1171
  enabled
1192
1172
  });
1193
- const receiptDocument = pickReceiptDocument(detail.data ?? null);
1173
+ const documents = detailDocuments(detail.data ?? null);
1174
+ const [selected, setSelected] = useState(null);
1175
+ const selectedDocument = (selected === null ? void 0 : documents.find((doc) => doc.documentHash === selected.documentHash && doc.contentHash === selected.contentHash)) ?? documents.find((doc) => doc.role === "instruction") ?? documents.find((doc) => doc.role === "movement") ?? documents[0] ?? null;
1176
+ const pending = useRef(/* @__PURE__ */ new Set());
1177
+ const mounted = useRef(true);
1178
+ useEffect(() => {
1179
+ mounted.current = true;
1180
+ const reads = pending.current;
1181
+ return () => {
1182
+ mounted.current = false;
1183
+ for (const read of reads) read.abort();
1184
+ reads.clear();
1185
+ };
1186
+ }, []);
1187
+ const selectDocument = useCallback((ref) => {
1188
+ if (!documents.some((doc) => doc.documentHash === ref.documentHash && doc.contentHash === ref.contentHash)) throw Errors.invalidInput("documentHash", "document not found");
1189
+ for (const read of pending.current) read.abort();
1190
+ pending.current.clear();
1191
+ setIsOpeningDocument(false);
1192
+ setSelected({
1193
+ documentHash: ref.documentHash,
1194
+ contentHash: ref.contentHash
1195
+ });
1196
+ }, [documents]);
1194
1197
  const [isOpeningDocument, setIsOpeningDocument] = useState(false);
1195
- const openReceiptDocument = useCallback(async () => {
1196
- if (reference === void 0 || receiptDocument === null) throw Errors.invalidInput("receiptDocument", "no renderable document on this row");
1198
+ const readDocument = useCallback(async (download) => {
1199
+ const operation = download ? CAPXUL_OPERATIONS.paymentDocuments.get : CAPXUL_OPERATIONS.paymentDocuments.render;
1200
+ if (!mounted.current) throw Errors.cancelled({ operation });
1201
+ if (reference === void 0 || selectedDocument === null) throw Errors.invalidInput("documentHash", "no document on this row");
1202
+ const controller = new AbortController();
1203
+ pending.current.add(controller);
1197
1204
  setIsOpeningDocument(true);
1198
1205
  try {
1199
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.paymentDocuments.render).paymentDocuments.render(receiptDocument.documentHash));
1206
+ const bootstrapped = requireBootstrappedClient(client, download ? CAPXUL_OPERATIONS.paymentDocuments.get : CAPXUL_OPERATIONS.paymentDocuments.render);
1207
+ const pair = {
1208
+ documentHash: selectedDocument.documentHash,
1209
+ contentHash: selectedDocument.contentHash
1210
+ };
1211
+ const options = { signal: controller.signal };
1212
+ const result = download ? await bootstrapped.paymentDocuments.get(pair, options) : await bootstrapped.paymentDocuments.render(pair, options);
1213
+ if (controller.signal.aborted) throw Errors.cancelled({ operation });
1214
+ return unwrapCapxulResult(result);
1200
1215
  } finally {
1201
- setIsOpeningDocument(false);
1216
+ pending.current.delete(controller);
1217
+ if (!controller.signal.aborted) setIsOpeningDocument(pending.current.size > 0);
1202
1218
  }
1203
1219
  }, [
1204
1220
  client,
1205
- receiptDocument,
1221
+ selectedDocument,
1206
1222
  reference
1207
1223
  ]);
1224
+ const renderDocument = useCallback(async () => {
1225
+ const result = await readDocument(false);
1226
+ if (!("html" in result)) throw Errors.invalidInput("documentHash", "invalid document render");
1227
+ return result;
1228
+ }, [readDocument]);
1229
+ const downloadDocument = useCallback(async () => {
1230
+ const result = await readDocument(true);
1231
+ if (!("bytesBase64" in result)) throw Errors.invalidInput("documentHash", "invalid document download");
1232
+ return result;
1233
+ }, [readDocument]);
1208
1234
  const [typed, setTyped] = useState(null);
1209
1235
  const annotate = useCapxulAnnotateMovement();
1210
1236
  const saveMemo = useCallback(async () => {
@@ -1229,8 +1255,15 @@ function useActivityDetail(reference, actor) {
1229
1255
  typed
1230
1256
  ]);
1231
1257
  return {
1232
- receiptDocument,
1233
- openReceiptDocument,
1258
+ ...detail.data?.kind === "payment" ? { payment: {
1259
+ status: detail.data.payment.status,
1260
+ ...detail.data.payment.execution === void 0 ? {} : { execution: detail.data.payment.execution }
1261
+ } } : {},
1262
+ documents,
1263
+ selectedDocument,
1264
+ selectDocument,
1265
+ renderDocument,
1266
+ downloadDocument,
1234
1267
  isOpeningDocument,
1235
1268
  memo: typed ?? detail.data?.annotation?.memo ?? "",
1236
1269
  setMemo: setTyped,
@@ -1243,6 +1276,7 @@ function useActivityDetail(reference, actor) {
1243
1276
  //#endregion
1244
1277
  //#region src/headless/activity/summary.ts
1245
1278
  const DAY_MS = 864e5;
1279
+ const display = (money) => money === null ? null : formatMoney(money);
1246
1280
  /**
1247
1281
  * `"last30days"` is the last 30 whole UTC days including today. Quantizing to
1248
1282
  * the UTC day keeps the cache key stable for a whole day instead of changing on
@@ -1253,57 +1287,6 @@ function resolveWindow(window, now) {
1253
1287
  if (window === "last30days") return { from: Math.floor(now / DAY_MS) * DAY_MS - 29 * DAY_MS };
1254
1288
  return window;
1255
1289
  }
1256
- /** An integer raw-unit string lifted into a major-unit decimal string. */
1257
- function majorUnits(raw, decimals) {
1258
- const negative = raw.startsWith("-");
1259
- const digits = negative ? raw.slice(1) : raw;
1260
- const sign = negative ? "-" : "";
1261
- if (decimals === 0) return `${sign}${digits}`;
1262
- const padded = digits.padStart(decimals + 1, "0");
1263
- const cut = padded.length - decimals;
1264
- return `${sign}${padded.slice(0, cut)}.${padded.slice(cut)}`;
1265
- }
1266
- function absoluteNet(total) {
1267
- const net = BigInt(total.net);
1268
- return net < 0n ? -net : net;
1269
- }
1270
- /**
1271
- * Buckets carry different `decimals`, so their raw integers are not comparable.
1272
- * Both are scaled to the wider precision before the magnitudes are ordered.
1273
- */
1274
- function compareNet(left, right) {
1275
- const width = Math.max(left.decimals, right.decimals);
1276
- const scaledLeft = absoluteNet(left) * 10n ** BigInt(width - left.decimals);
1277
- const scaledRight = absoluteNet(right) * 10n ** BigInt(width - right.decimals);
1278
- if (scaledLeft === scaledRight) return 0;
1279
- return scaledLeft < scaledRight ? -1 : 1;
1280
- }
1281
- /**
1282
- * Which currency `.Summary` shows: the bucket matching the primary holding's
1283
- * currency, else the bucket with the largest absolute `net`, breaking a tie on
1284
- * currency code ascending so the choice is stable across reloads.
1285
- *
1286
- * The primary holding is matched on `available.currency` — the consumer
1287
- * `CurrencyCode` — and NOT on `Holding.symbol`, which is the raw token ticker
1288
- * ("USDX") and can never equal a bucket's currency ("USD"). See the contract
1289
- * correction on #1449.
1290
- */
1291
- function pickBucket(totals, primaryCurrency) {
1292
- if (primaryCurrency !== null) {
1293
- const matched = totals.find((total) => total.currency === primaryCurrency);
1294
- if (matched !== void 0) return matched;
1295
- }
1296
- let best = null;
1297
- for (const total of totals) {
1298
- if (best === null) {
1299
- best = total;
1300
- continue;
1301
- }
1302
- const order = compareNet(total, best);
1303
- if (order > 0 || order === 0 && total.currency < best.currency) best = total;
1304
- }
1305
- return best;
1306
- }
1307
1290
  /**
1308
1291
  * The current UTC day number, which advances ON ITS OWN at UTC midnight.
1309
1292
  *
@@ -1347,47 +1330,36 @@ function useActivitySummary(actor, window) {
1347
1330
  const params = {};
1348
1331
  if (actor !== void 0) params.actor = actor;
1349
1332
  if (range !== void 0) params.window = range;
1350
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.summary).activity.summary(params));
1351
- },
1352
- enabled
1353
- });
1354
- const primary = useQuery({
1355
- queryKey: accountKey([...capxulKeys.holdings(actor), "primary"], identity),
1356
- queryFn: async () => {
1357
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.holdings.primary).holdings.primary(actor === void 0 ? void 0 : { actor }));
1333
+ const result = unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.activity.summary).activity.summary(params));
1334
+ return {
1335
+ ...result,
1336
+ valuation: valueActivitySummary(result.totals)
1337
+ };
1358
1338
  },
1359
1339
  enabled
1360
1340
  });
1361
1341
  const totals = summary.data?.totals;
1362
- const primaryCurrency = primary.data?.available.currency ?? null;
1363
- const isLoading = summary.isLoading || primary.isLoading;
1364
- const bucket = useMemo(() => totals === void 0 || isLoading ? null : pickBucket(totals, primaryCurrency), [
1365
- isLoading,
1366
- primaryCurrency,
1367
- totals
1368
- ]);
1369
- if (bucket === null) return {
1342
+ const isLoading = summary.isLoading;
1343
+ if (summary.data === void 0 || totals === void 0 || totals.length === 0 || isLoading) return {
1370
1344
  currency: null,
1371
1345
  moneyIn: null,
1372
1346
  moneyOut: null,
1373
1347
  net: null,
1374
1348
  pending: null,
1349
+ unrated: [],
1375
1350
  count: summary.data?.count ?? 0,
1376
1351
  isLoading,
1377
1352
  error: asCapxulError$1(summary.error),
1378
1353
  ...summary.data?.readWarning === void 0 ? {} : { readWarning: summary.data.readWarning }
1379
1354
  };
1380
- const display = (raw) => formatMoney({
1381
- currency: bucket.currency,
1382
- value: majorUnits(raw, bucket.decimals),
1383
- decimals: bucket.decimals
1384
- });
1355
+ const valuation = summary.data.valuation;
1385
1356
  return {
1386
- currency: bucket.currency,
1387
- moneyIn: display(bucket.in),
1388
- moneyOut: display(bucket.out),
1389
- net: display(bucket.net),
1390
- pending: display(bucket.pendingIn),
1357
+ currency: valuation.moneyIn?.currency ?? null,
1358
+ moneyIn: display(valuation.moneyIn),
1359
+ moneyOut: display(valuation.moneyOut),
1360
+ net: display(valuation.net),
1361
+ pending: display(valuation.pending),
1362
+ unrated: valuation.unrated,
1391
1363
  count: summary.data?.count ?? 0,
1392
1364
  isLoading,
1393
1365
  error: asCapxulError$1(summary.error),
@@ -1414,7 +1386,7 @@ const SIGN = Object.freeze({
1414
1386
  self: ""
1415
1387
  });
1416
1388
  function toActivityRow(item, actor) {
1417
- const amountDisplay = formatMoney(item.amount);
1389
+ const amountDisplay = formatAssetAmount(item.amount);
1418
1390
  const signedAmountDisplay = `${SIGN[item.direction]}${amountDisplay}`;
1419
1391
  if (item.kind === "movement") return {
1420
1392
  kind: "movement",
@@ -1426,32 +1398,37 @@ function toActivityRow(item, actor) {
1426
1398
  counterpartyLabel: item.counterpartyLabel,
1427
1399
  amountDisplay,
1428
1400
  signedAmountDisplay,
1429
- currency: item.amount.currency,
1401
+ currency: assetSymbolFor(item.amount.asset),
1430
1402
  createdAt: item.createdAt,
1431
1403
  classification: item.classification,
1432
1404
  phase: MOVEMENT_PHASE,
1433
1405
  receipt: item.receipt
1434
1406
  };
1407
+ const reference = actor === void 0 ? {
1408
+ kind: "payment",
1409
+ id: item.id
1410
+ } : {
1411
+ kind: "payment",
1412
+ id: item.id,
1413
+ actor
1414
+ };
1435
1415
  return {
1436
1416
  kind: "payment",
1437
- reference: actor === void 0 ? {
1438
- kind: "payment",
1439
- id: item.id
1440
- } : {
1441
- kind: "payment",
1442
- id: item.id,
1443
- actor
1444
- },
1417
+ ...item.execution === void 0 ? {} : { execution: item.execution },
1418
+ reference,
1445
1419
  direction: item.direction,
1446
1420
  counterpartyLabel: item.counterpartyLabel,
1447
1421
  amountDisplay,
1448
1422
  signedAmountDisplay,
1449
- currency: item.amount.currency,
1423
+ currency: assetSymbolFor(item.amount.asset),
1450
1424
  createdAt: item.createdAt,
1451
1425
  ledgerKind: item.ledgerKind,
1452
1426
  paymentType: item.paymentType,
1453
1427
  status: item.status,
1454
- phase: paymentPhase(item.status),
1428
+ phase: item.execution?.notSent ? {
1429
+ ...paymentPhase(item.status),
1430
+ needsAction: item.direction !== "in"
1431
+ } : paymentPhase(item.status),
1455
1432
  receipt: item.receipt
1456
1433
  };
1457
1434
  }
@@ -1524,8 +1501,8 @@ function buildCsv(items, actor, search) {
1524
1501
  row.counterpartyLabel,
1525
1502
  row.direction,
1526
1503
  item.amount.value,
1527
- item.amount.currency,
1528
- row.kind === "payment" ? row.status : "",
1504
+ assetSymbolFor(item.amount.asset),
1505
+ row.kind === "payment" ? row.execution?.notSent ? "not_sent" : row.execution?.status === "unresolved" ? "verifying" : row.status : "",
1529
1506
  row.reference.id,
1530
1507
  row.receipt?.transactionHash ?? ""
1531
1508
  ]));
@@ -1642,6 +1619,16 @@ function useActivity(input) {
1642
1619
  searchText
1643
1620
  ]);
1644
1621
  const { fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = query;
1622
+ const emptyContinuation = pages?.at(-1)?.items.length === 0 && hasNextPage && query.error === null;
1623
+ useEffect(() => {
1624
+ if (enabled && emptyContinuation && !isFetchingNextPage) fetchNextPage();
1625
+ }, [
1626
+ enabled,
1627
+ emptyContinuation,
1628
+ fetchNextPage,
1629
+ isFetchingNextPage,
1630
+ pages
1631
+ ]);
1645
1632
  const pagingRef = useRef({
1646
1633
  fetchNextPage,
1647
1634
  hasNextPage,
@@ -1744,7 +1731,7 @@ function useActivity(input) {
1744
1731
  },
1745
1732
  rows: {
1746
1733
  rows,
1747
- isLoading: query.isLoading,
1734
+ isLoading: query.isLoading || rows.length === 0 && emptyContinuation,
1748
1735
  error: asCapxulError$1(query.error),
1749
1736
  retry,
1750
1737
  observedAt: pages === void 0 ? null : pages[pages.length - 1]?.observedAt ?? null,
@@ -1770,7 +1757,7 @@ function useActivityContext() {
1770
1757
  if (engine === null) throw new Error("CapxulActivity parts must be used inside <CapxulActivity>");
1771
1758
  return engine;
1772
1759
  }
1773
- function Root$5({ actor, pageSize, children }) {
1760
+ function Root$6({ actor, pageSize, children }) {
1774
1761
  const engine = useActivity({
1775
1762
  ...actor === void 0 ? {} : { actor },
1776
1763
  ...pageSize === void 0 ? {} : { pageSize }
@@ -1821,7 +1808,7 @@ function Export({ children }) {
1821
1808
  return /* @__PURE__ */ jsx(Fragment, { children: children(useActivityContext().csv) });
1822
1809
  }
1823
1810
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
1824
- const CapxulActivity = Object.assign(Root$5, {
1811
+ const CapxulActivity = Object.assign(Root$6, {
1825
1812
  Summary: Summary$2,
1826
1813
  Filters,
1827
1814
  Search,
@@ -1862,6 +1849,26 @@ function useOrgMeReading(orgId) {
1862
1849
  ]);
1863
1850
  }
1864
1851
  //#endregion
1852
+ //#region src/internal/write-names-asset.ts
1853
+ /**
1854
+ * Identity law: "A payment names an asset or inherits it from an authorized
1855
+ * fact. Missing asset refuses." The rule was recorded in the retired
1856
+ * treasury-cut handoff (removed from the current tree by the exact-asset
1857
+ * contraction; its record survives in Git history).
1858
+ *
1859
+ * A Capxul payment writer takes `PaymentMoney` — a peg currency, a value and
1860
+ * `decimals` — and settles one fixed legacy token. No `AssetId` crosses that
1861
+ * seam, so a payment composed from a named position cannot name the asset it
1862
+ * moves: a position selected as USDT authorizes a different token.
1863
+ *
1864
+ * `false` until the writer takes the command's asset. The Send and Payroll
1865
+ * composers read this to refuse a submission rather than authorize a token the
1866
+ * person did not pick.
1867
+ */
1868
+ function writeNamesAsset() {
1869
+ return false;
1870
+ }
1871
+ //#endregion
1865
1872
  //#region src/headless/send-money/signer-status.ts
1866
1873
  const NO_SUBSCRIPTION = () => () => {};
1867
1874
  function useSignerStatus(client) {
@@ -2011,6 +2018,37 @@ function useSelectedOption(options) {
2011
2018
  }, [options]);
2012
2019
  return [options.find((option) => option.id === selectedId) ?? null, select];
2013
2020
  }
2021
+ /**
2022
+ * One Send `.Asset` option per position the Account holds, taken from
2023
+ * `account.balances` — never from `account.available`, which is the Account's
2024
+ * fiat VALUATION of those positions (ADR-0028 R5/R6). Labelling the valuation
2025
+ * was the false identity this cut removes, and it collapsed USDC+USDT into one
2026
+ * "USD" row.
2027
+ *
2028
+ * The option is keyed on the ASSET id, never the symbol and never the account:
2029
+ * several positions share one account id, and a symbol is display only
2030
+ * (ADR-0028 R3). Every row is listed, so a real multi-position Account still
2031
+ * offers its holdings.
2032
+ */
2033
+ function assetSelectionsFromAccount$1(account) {
2034
+ if (account === null) return [];
2035
+ return account.balances.map((position) => {
2036
+ return {
2037
+ option: {
2038
+ id: position.asset,
2039
+ symbol: position.symbol,
2040
+ availableDisplay: formatAssetAmount({
2041
+ asset: position.asset,
2042
+ value: position.available
2043
+ })
2044
+ },
2045
+ money: {
2046
+ asset: position.asset,
2047
+ value: position.available
2048
+ }
2049
+ };
2050
+ });
2051
+ }
2014
2052
  function useSendMoney(input) {
2015
2053
  const { actor, onSent, onFailed } = input;
2016
2054
  const client = useCapxulClientOrNull();
@@ -2023,15 +2061,12 @@ function useSendMoney(input) {
2023
2061
  const treasury = useCapxulOrgTreasury(orgId, { enabled: actor.kind === "organization" });
2024
2062
  const accountQuery = actor.kind === "personal" ? balance : treasury;
2025
2063
  const account = accountQuery.data ?? null;
2026
- const assetOptions = useMemo(() => account === null ? [] : [{
2027
- id: account.id,
2028
- symbol: account.available.currency,
2029
- availableDisplay: formatMoney(account.available, { grammar: "code" })
2030
- }], [account]);
2064
+ const assetSelections = useMemo(() => assetSelectionsFromAccount$1(account), [account]);
2065
+ const assetOptions = useMemo(() => assetSelections.map((selection) => selection.option), [assetSelections]);
2031
2066
  const [selectedAssetOption, selectAsset] = useSelectedOption(assetOptions);
2032
- const selectedAssetMoney = selectedAssetOption !== null && account !== null ? account.available : null;
2067
+ const selectedAssetMoney = useMemo(() => assetSelections.find((selection) => selection.option.id === selectedAssetOption?.id)?.money ?? null, [assetSelections, selectedAssetOption]);
2033
2068
  const [amountText, setAmountText] = useState("");
2034
- const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null : parseMoney(amountText, selectedAssetMoney), [amountText, selectedAssetMoney]);
2069
+ const parsed = useMemo(() => selectedAssetMoney === null || amountText.trim() === "" ? null : parseAssetAmount(amountText, requireAsset(selectedAssetMoney.asset)), [amountText, selectedAssetMoney]);
2035
2070
  const parsedMoney = parsed !== null && !isMoneyParseError(parsed) ? parsed : null;
2036
2071
  const amountError = parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null;
2037
2072
  const recipient = useRecipientResolution(client, JSON.stringify([
@@ -2043,7 +2078,7 @@ function useSendMoney(input) {
2043
2078
  const personalPay = useCapxulPay();
2044
2079
  const organizationPay = useCapxulOrganizationPay(orgId);
2045
2080
  const pay = actor.kind === "personal" ? personalPay : organizationPay;
2046
- const blockedReason = signerStatus !== "ready" ? "signer-not-ready" : actor.kind === "organization" && selectedSource === null ? "no-source" : selectedAssetOption === null ? "no-asset" : parsedMoney === null ? "amount-invalid" : resolvedReference === null ? "recipient-unresolved" : null;
2081
+ const blockedReason = signerStatus !== "ready" ? "signer-not-ready" : actor.kind === "organization" && selectedSource === null ? "no-source" : selectedAssetOption === null ? "no-asset" : parsedMoney === null ? "amount-invalid" : resolvedReference === null ? "recipient-unresolved" : writeNamesAsset() ? null : "asset-unnamed";
2047
2082
  const submitGate = useRef(false);
2048
2083
  const submit = useCallback(() => {
2049
2084
  if (blockedReason !== null || submitGate.current) return;
@@ -2130,7 +2165,7 @@ function useSendMoneyContext() {
2130
2165
  if (engine === null) throw new Error("CapxulSendMoney parts must be used inside <CapxulSendMoney>");
2131
2166
  return engine;
2132
2167
  }
2133
- function Root$4({ actor, onSent, onFailed, children }) {
2168
+ function Root$5({ actor, onSent, onFailed, children }) {
2134
2169
  const engine = useSendMoney({
2135
2170
  actor,
2136
2171
  onSent,
@@ -2157,7 +2192,7 @@ function Actions$1({ children }) {
2157
2192
  return /* @__PURE__ */ jsx(Fragment, { children: children(useSendMoneyContext().actions) });
2158
2193
  }
2159
2194
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
2160
- const CapxulSendMoney = Object.assign(Root$4, {
2195
+ const CapxulSendMoney = Object.assign(Root$5, {
2161
2196
  Source,
2162
2197
  Asset: Asset$1,
2163
2198
  Amount,
@@ -2247,7 +2282,7 @@ function useOrgMemberContext() {
2247
2282
  if (state === null) throw new Error("CapxulOrgMember parts must be used inside <CapxulOrgMember>");
2248
2283
  return state;
2249
2284
  }
2250
- function Root$3({ orgId, children }) {
2285
+ function Root$4({ orgId, children }) {
2251
2286
  const state = useOrgMember(orgId);
2252
2287
  return /* @__PURE__ */ jsx(OrgMemberContext.Provider, {
2253
2288
  value: state,
@@ -2267,7 +2302,7 @@ function Standing({ children }) {
2267
2302
  return /* @__PURE__ */ jsx(Fragment, { children: children(useOrgMemberContext().standing) });
2268
2303
  }
2269
2304
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
2270
- const CapxulOrgMember = Object.assign(Root$3, {
2305
+ const CapxulOrgMember = Object.assign(Root$4, {
2271
2306
  Can,
2272
2307
  Standing
2273
2308
  });
@@ -2307,18 +2342,14 @@ function isOrgLaneSettled(identity) {
2307
2342
  if (identity.phase !== "authenticated" || identity.account.at !== "claimed") return true;
2308
2343
  const org = identity.account.org;
2309
2344
  if (org === null) return true;
2310
- switch (org.at) {
2311
- case "ready":
2312
- case "failed": return true;
2313
- default: return false;
2314
- }
2345
+ return org.observation === "connected" && org.setup !== null;
2315
2346
  }
2316
2347
  /** Exactly the Organization asked for, and ready to be used. */
2317
2348
  function isExactOrgReady(lifecycle, orgId) {
2318
2349
  return lifecycle?.status === "ready" && String(lifecycle.orgId) === orgId;
2319
2350
  }
2320
2351
  function isLifecycleSettled(lifecycle) {
2321
- return lifecycle?.status === "ready" || lifecycle?.status === "failed";
2352
+ return lifecycle !== void 0;
2322
2353
  }
2323
2354
  function destinationOrNull(identity) {
2324
2355
  return identity === null ? null : resolveIdentityDestination(identity);
@@ -2346,19 +2377,27 @@ function decidePhase(input) {
2346
2377
  * Which probe produced the surfaced error — the SAME precedence `error` itself
2347
2378
  * is chosen by, written once so the code and its source can never disagree.
2348
2379
  */
2349
- function probeOf(session, membership, lifecycle) {
2380
+ function probeOf(session, membership, lifecycle, authority) {
2350
2381
  if (session !== null) return "session";
2351
2382
  if (membership !== null) return "membership";
2352
2383
  if (lifecycle !== null) return "lifecycle";
2384
+ if (authority !== null) return "authority";
2353
2385
  return null;
2354
2386
  }
2355
2387
  /** The engine — all the logic. NOT exported from the package barrel. */
2356
2388
  function useDashboardAccess(scope) {
2357
2389
  const client = useCapxulClientOrNull();
2390
+ const queryClient = useQueryClient();
2358
2391
  const identity = useCapxulIdentityOrNull();
2359
2392
  const destination = useMemo(() => destinationOrNull(identity), [identity]);
2360
2393
  const orgId = scope.kind === "organization" ? scope.orgId : void 0;
2361
2394
  const claimed = isClaimedOrFalse(identity);
2395
+ const scopeKey = `${orgId ?? ""}:${identity?.phase === "authenticated" ? identity.session.authUserId : ""}:${actingAccount(identity) ?? ""}`;
2396
+ const [observation, setObservation] = useState({
2397
+ scopeKey,
2398
+ status: "loading"
2399
+ });
2400
+ const [subscriptionRetry, setSubscriptionRetry] = useState(0);
2362
2401
  const sessionError = useMemo(() => identity?.phase === "faulted" ? identityError(identity) : null, [identity]);
2363
2402
  const wantsMemberships = orgId !== void 0 && claimed;
2364
2403
  const memberships = useCapxulOrgs({ enabled: wantsMemberships });
@@ -2366,33 +2405,125 @@ function useDashboardAccess(scope) {
2366
2405
  const membershipsSettled = !wantsMemberships || memberships.data !== void 0 || membershipError !== null;
2367
2406
  const hasMembership = orgId !== void 0 && (memberships.data ?? []).some((organization) => String(organization.id) === orgId);
2368
2407
  const wantsLifecycle = wantsMemberships && membershipsSettled && hasMembership;
2408
+ const setupKey = accountKey(capxulKeys.orgLifecycle(orgId), identity);
2369
2409
  const lifecycle = useQuery({
2370
- queryKey: capxulKeys.orgLifecycle(orgId),
2410
+ queryKey: setupKey,
2371
2411
  queryFn: async () => {
2372
- return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.getLifecycle).org(orgId).getLifecycle());
2412
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.organization.setup).org(orgId).setup.get());
2373
2413
  },
2374
2414
  enabled: wantsLifecycle && client !== null
2375
2415
  });
2376
- const lifecycleError = wantsLifecycle ? lifecycle.error : null;
2377
- const lifecycleSettled = !wantsLifecycle || lifecycleError !== null || isLifecycleSettled(lifecycle.data);
2416
+ const meKey = capxulKeys.orgMe(orgId, actingAccount(identity));
2417
+ useEffect(() => {
2418
+ if (!wantsLifecycle || client === null || orgId === void 0) return;
2419
+ const controller = new AbortController();
2420
+ let closed = false;
2421
+ let stop = null;
2422
+ setObservation({
2423
+ scopeKey,
2424
+ status: "loading"
2425
+ });
2426
+ client.org(orgId).setup.subscribe((snapshot) => {
2427
+ if (closed) return;
2428
+ if (snapshot.status === "value") {
2429
+ setObservation({
2430
+ scopeKey,
2431
+ status: "value",
2432
+ value: snapshot.value
2433
+ });
2434
+ queryClient.setQueryData(setupKey, snapshot.value);
2435
+ } else if (snapshot.status === "error") setObservation({
2436
+ scopeKey,
2437
+ status: "error",
2438
+ error: snapshot.error
2439
+ });
2440
+ }, { signal: controller.signal }).then((result) => {
2441
+ if (!result.ok) {
2442
+ if (!closed) setObservation({
2443
+ scopeKey,
2444
+ status: "error",
2445
+ error: result.error
2446
+ });
2447
+ return;
2448
+ }
2449
+ stop = result.value;
2450
+ if (closed) stop();
2451
+ }).catch(() => {
2452
+ if (!closed) setObservation({
2453
+ scopeKey,
2454
+ status: "error",
2455
+ error: new CapxulError("UNKNOWN", "Organization setup subscription failed")
2456
+ });
2457
+ });
2458
+ return () => {
2459
+ closed = true;
2460
+ controller.abort();
2461
+ stop?.();
2462
+ };
2463
+ }, [
2464
+ client,
2465
+ orgId,
2466
+ wantsLifecycle,
2467
+ queryClient,
2468
+ scopeKey,
2469
+ subscriptionRetry
2470
+ ]);
2471
+ const currentSetup = observation.scopeKey === scopeKey && observation.status === "value" ? observation.value : void 0;
2472
+ const wantsAuthority = wantsLifecycle && currentSetup?.status === "ready";
2473
+ const authority = useQuery({
2474
+ queryKey: meKey,
2475
+ queryFn: async () => {
2476
+ return unwrapCapxulResult(await requireBootstrappedClient(client, CAPXUL_OPERATIONS.org.me).org(orgId).me());
2477
+ },
2478
+ enabled: wantsAuthority && client !== null
2479
+ });
2480
+ const [checkedAuthority, setCheckedAuthority] = useState(null);
2481
+ useEffect(() => {
2482
+ if (!wantsAuthority || currentSetup === void 0) return;
2483
+ let active = true;
2484
+ authority.refetch().then((result) => {
2485
+ if (active && result.isSuccess) setCheckedAuthority({
2486
+ scopeKey,
2487
+ setup: currentSetup
2488
+ });
2489
+ });
2490
+ return () => {
2491
+ active = false;
2492
+ };
2493
+ }, [
2494
+ scopeKey,
2495
+ currentSetup,
2496
+ wantsAuthority,
2497
+ authority.refetch
2498
+ ]);
2499
+ const authorityCurrent = checkedAuthority?.scopeKey === scopeKey && checkedAuthority.setup === currentSetup && authority.data !== void 0;
2500
+ const observationError = observation.scopeKey === scopeKey && observation.status === "error" ? observation.error : void 0;
2501
+ const lifecycleError = wantsLifecycle ? observationError ?? lifecycle.error : null;
2502
+ const authorityError = wantsAuthority ? authority.error : null;
2503
+ const lifecycleSettled = !wantsLifecycle || lifecycleError !== null || authorityError !== null || isLifecycleSettled(currentSetup) && (!wantsAuthority || authorityCurrent);
2378
2504
  const retry = useCallback(() => {
2379
2505
  if (sessionError !== null && client !== null) client.auth.getSession();
2380
2506
  if (membershipError !== null) memberships.refetch();
2507
+ if (observationError !== void 0) setSubscriptionRetry((attempt) => attempt + 1);
2381
2508
  if (lifecycleError !== null) lifecycle.refetch();
2509
+ if (authorityError !== null) authority.refetch();
2382
2510
  }, [
2383
2511
  client,
2384
2512
  sessionError,
2385
2513
  membershipError,
2514
+ observationError,
2386
2515
  lifecycleError,
2387
2516
  memberships.refetch,
2388
- lifecycle.refetch
2517
+ lifecycle.refetch,
2518
+ authorityError,
2519
+ authority.refetch
2389
2520
  ]);
2390
- const error = sessionError ?? membershipError ?? lifecycleError;
2391
- const probe = probeOf(sessionError, membershipError, lifecycleError);
2521
+ const error = sessionError ?? membershipError ?? lifecycleError ?? authorityError;
2522
+ const probe = probeOf(sessionError, membershipError, lifecycleError, authorityError);
2392
2523
  const phase = decidePhase({
2393
2524
  error,
2394
2525
  settled: client !== null && identity !== null && !isRestoring$1(identity) && membershipsSettled && lifecycleSettled,
2395
- granted: scope.kind === "personal" ? claimed : claimed && orgId !== void 0 && hasMembership && isExactOrgReady(lifecycle.data, orgId),
2526
+ granted: scope.kind === "personal" ? claimed : claimed && orgId !== void 0 && hasMembership && isExactOrgReady(currentSetup, orgId) && authorityCurrent,
2396
2527
  orgLaneSettled: identity !== null && isOrgLaneSettled(identity),
2397
2528
  destination
2398
2529
  });
@@ -2427,7 +2558,7 @@ function usePart(phase) {
2427
2558
  }, [placed, phase]);
2428
2559
  return state.phase === phase ? state : null;
2429
2560
  }
2430
- function Root$2({ scope, children }) {
2561
+ function Root$3({ scope, children }) {
2431
2562
  const state = useDashboardAccess(scope);
2432
2563
  const [placed] = useState(() => /* @__PURE__ */ new Set());
2433
2564
  const value = useMemo(() => ({
@@ -2445,7 +2576,7 @@ function Root$2({ scope, children }) {
2445
2576
  });
2446
2577
  }
2447
2578
  /** Hands nothing — today's app renders `null` here, exactly as it does now. */
2448
- function Checking({ children }) {
2579
+ function Checking$1({ children }) {
2449
2580
  return usePart("checking") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
2450
2581
  }
2451
2582
  function DashboardError({ children }) {
@@ -2472,19 +2603,14 @@ function Granted({ children }) {
2472
2603
  return usePart("granted") === null ? null : /* @__PURE__ */ jsx(Fragment, { children });
2473
2604
  }
2474
2605
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
2475
- const CapxulDashboardAccess = Object.assign(Root$2, {
2476
- Checking,
2606
+ const CapxulDashboardAccess = Object.assign(Root$3, {
2607
+ Checking: Checking$1,
2477
2608
  Error: DashboardError,
2478
2609
  Redirect,
2479
2610
  Granted
2480
2611
  });
2481
2612
  //#endregion
2482
2613
  //#region src/headless/payroll/money.ts
2483
- /** `{ value: "12.34", decimals: 6 }` → `12340000n`. */
2484
- function toMinorUnits(money) {
2485
- const [whole = "0", fraction = ""] = money.value.split(".");
2486
- return BigInt(`${whole}${fraction.padEnd(money.decimals, "0")}`);
2487
- }
2488
2614
  const TYPED_AMOUNT = /^(-?)(\d+)(?:\.(\d+))?$/;
2489
2615
  /**
2490
2616
  * Σ of the employer's TYPED amounts on a saved group — a DISPLAY total.
@@ -2517,6 +2643,17 @@ function fromMinorUnits(raw, asset) {
2517
2643
  decimals: asset.decimals
2518
2644
  };
2519
2645
  }
2646
+ /** Convert exact raw units to one asset quantity without a fiat denomination. */
2647
+ function assetFromMinorUnits(raw, asset) {
2648
+ const money = fromMinorUnits(raw, {
2649
+ currency: "USD",
2650
+ decimals: asset.decimals
2651
+ });
2652
+ return {
2653
+ asset: asset.asset,
2654
+ value: money.value
2655
+ };
2656
+ }
2520
2657
  //#endregion
2521
2658
  //#region src/headless/payroll/reads.ts
2522
2659
  function toRecipientOption(entry) {
@@ -2633,7 +2770,7 @@ function scopedClient(client, method, orgId) {
2633
2770
  //#endregion
2634
2771
  //#region src/headless/payroll/use-payroll.ts
2635
2772
  /** Money reads the same way everywhere on this screen: amount then code. */
2636
- const GRAMMAR$1 = { grammar: "code" };
2773
+ const GRAMMAR = { grammar: "code" };
2637
2774
  /**
2638
2775
  * One captured snapshot, or nothing — never a fabricated empty options object,
2639
2776
  * and never a DOM event a screen wired straight to `onClick`.
@@ -2649,7 +2786,7 @@ function toPayrollGroupRow(group) {
2649
2786
  name: group.name,
2650
2787
  tone: group.tone,
2651
2788
  memberCount: group.members.length,
2652
- totalDisplay: currency === void 0 ? "" : formatMoney(sumTypedAmounts(group.members.map((member) => member.amount), currency), GRAMMAR$1),
2789
+ totalDisplay: currency === void 0 ? "" : formatMoney(sumTypedAmounts(group.members.map((member) => member.amount), currency), GRAMMAR),
2653
2790
  members: group.members.map((member) => ({
2654
2791
  recipientId: member.partyId,
2655
2792
  amount: member.amount,
@@ -2739,8 +2876,8 @@ function usePayroll(orgId) {
2739
2876
  const started = orgId !== void 0;
2740
2877
  return {
2741
2878
  summary: {
2742
- walletBalanceDisplay: treasury.data === void 0 ? null : formatMoney(treasury.data.available, GRAMMAR$1),
2743
- paidThisMonthDisplay: runs.data === void 0 ? null : formatMoney(runs.data.settledThisMonth, GRAMMAR$1),
2879
+ walletBalanceDisplay: treasury.data?.available === void 0 || treasury.data.available === null ? null : formatMoney(treasury.data.available, GRAMMAR),
2880
+ paidThisMonthDisplay: runs.data === void 0 ? null : runs.data.settledThisMonth.map(formatAssetAmount).join(", "),
2744
2881
  isLoading: started && (treasury.data === void 0 && treasury.error === null || runs.data === void 0 && runs.error === null),
2745
2882
  error: treasury.error ?? runs.error
2746
2883
  },
@@ -2773,7 +2910,7 @@ function usePayrollContext() {
2773
2910
  if (engine === null) throw new Error("CapxulPayroll parts must be used inside <CapxulPayroll>");
2774
2911
  return engine;
2775
2912
  }
2776
- function Root$1({ orgId, children }) {
2913
+ function Root$2({ orgId, children }) {
2777
2914
  const engine = usePayroll(orgId);
2778
2915
  return /* @__PURE__ */ jsx(PayrollContext.Provider, {
2779
2916
  value: engine,
@@ -2791,7 +2928,7 @@ function Groups({ children }) {
2791
2928
  * There is NO `.Runs` part (ruling Q6): no shipped region has the right shape
2792
2929
  * for a run list, and a part without a production caller may not ship.
2793
2930
  */
2794
- const CapxulPayroll = Object.assign(Root$1, {
2931
+ const CapxulPayroll = Object.assign(Root$2, {
2795
2932
  Summary: Summary$1,
2796
2933
  Groups
2797
2934
  });
@@ -2801,8 +2938,14 @@ function record(value) {
2801
2938
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw Errors.invalidInput("payrollDraft", "Invalid run draft");
2802
2939
  return value;
2803
2940
  }
2941
+ /**
2942
+ * A draft-scoped opaque key. `/` is in the class so `asset.id` can be the
2943
+ * canonical CAIP-19 `AssetId` (`eip155:<chainId>/erc20:<address>`): the draft
2944
+ * records WHICH asset was authorized, and a symbol is not an identity
2945
+ * (ADR-0028 R3). The field never reaches the backend — only `input` is sent.
2946
+ */
2804
2947
  function identifier(value) {
2805
- if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) throw Errors.invalidInput("payrollDraft", "Invalid draft identifier");
2948
+ if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u.test(value)) throw Errors.invalidInput("payrollDraft", "Invalid draft identifier");
2806
2949
  return value;
2807
2950
  }
2808
2951
  function minorAmount(value) {
@@ -2818,8 +2961,9 @@ function decodeDraft(value, scope) {
2818
2961
  const items = input.items.map((raw) => {
2819
2962
  const item = record(raw);
2820
2963
  const to = record(item.to);
2964
+ const amount = record(item.amount);
2821
2965
  const partyId = toPartyId(identifier(item.partyId));
2822
- if (to.kind !== "party" || to.partyId !== partyId || item.gross !== item.net || !Array.isArray(item.adjustments) || item.adjustments.length !== 0) throw Errors.invalidInput("payrollDraft", "Invalid draft recipient");
2966
+ if (to.kind !== "party" || to.partyId !== partyId || item.gross !== item.net || typeof amount.asset !== "string" || typeof amount.value !== "string" || typeof item.currency !== "string" || typeof item.decimals !== "number" || !Number.isInteger(item.decimals) || !Array.isArray(item.adjustments) || item.adjustments.length !== 0) throw Errors.invalidInput("payrollDraft", "Invalid draft recipient");
2823
2967
  const net = minorAmount(item.net);
2824
2968
  return {
2825
2969
  to: {
@@ -2827,12 +2971,20 @@ function decodeDraft(value, scope) {
2827
2971
  partyId
2828
2972
  },
2829
2973
  partyId,
2974
+ amount: {
2975
+ asset: requireAsset(amount.asset).assetId,
2976
+ value: amount.value
2977
+ },
2830
2978
  gross: net,
2831
2979
  net,
2980
+ currency: item.currency,
2981
+ decimals: item.decimals,
2832
2982
  adjustments: []
2833
2983
  };
2834
2984
  });
2835
2985
  if (new Set(items.map((item) => item.partyId)).size !== items.length) throw Errors.invalidInput("payrollDraft", "Duplicate draft recipient");
2986
+ const exactAsset = requireAsset(identifier(asset.id));
2987
+ if (items.some((item) => item.amount.asset !== exactAsset.assetId)) throw Errors.invalidInput("payrollDraft", "Draft item asset does not match the run asset");
2836
2988
  return {
2837
2989
  version: 1,
2838
2990
  scope,
@@ -2843,6 +2995,7 @@ function decodeDraft(value, scope) {
2843
2995
  },
2844
2996
  input: {
2845
2997
  permissionId: identifier(input.permissionId),
2998
+ asset: exactAsset.assetId,
2846
2999
  period: {
2847
3000
  start: period.start,
2848
3001
  end: period.start
@@ -3046,7 +3199,6 @@ function termsSeeds(terms, asset, period) {
3046
3199
  }
3047
3200
  //#endregion
3048
3201
  //#region src/headless/payroll/use-payroll-run.ts
3049
- const GRAMMAR = { grammar: "code" };
3050
3202
  /**
3051
3203
  * The period this composer composes: an INSTANT run (ruling Q3). `authorizeRun`
3052
3204
  * stamps `start === end === runAt` at submit, and the payslip's period renders
@@ -3070,6 +3222,32 @@ const INSTANT_RUN_PERIOD = {
3070
3222
  function amountText(amounts, seeds, recipientId) {
3071
3223
  return amounts[recipientId]?.value ?? seeds.get(recipientId) ?? "";
3072
3224
  }
3225
+ function freshRunItems(input) {
3226
+ const items = [];
3227
+ for (const [index, recipientId] of input.selected.entries()) {
3228
+ const to = input.payableRefs[index];
3229
+ const amount = input.amounts[index];
3230
+ const terms = input.compensationTerms.get(recipientId);
3231
+ if (to === void 0 || amount === void 0 || terms === void 0) throw Errors.unknown();
3232
+ items.push({
3233
+ to: {
3234
+ kind: "party",
3235
+ partyId: toPartyId(recipientId)
3236
+ },
3237
+ partyId: recipientId,
3238
+ amount,
3239
+ gross: terms.rate,
3240
+ net: terms.rate,
3241
+ currency: terms.currency,
3242
+ decimals: terms.decimals,
3243
+ adjustments: []
3244
+ });
3245
+ }
3246
+ return items;
3247
+ }
3248
+ function hasRequiredCompensation(frozen, writerNamesSelectedAsset, selected, terms) {
3249
+ return frozen || !writerNamesSelectedAsset || selected.every((recipientId) => terms.has(recipientId));
3250
+ }
3073
3251
  /**
3074
3252
  * WHICH run the composer is composing, by MEANING rather than by object shape.
3075
3253
  *
@@ -3101,6 +3279,33 @@ function initialAmounts(prefill) {
3101
3279
  };
3102
3280
  return seeded;
3103
3281
  }
3282
+ /**
3283
+ * One `.Asset` option per position the treasury Account holds, taken from
3284
+ * `account.balances` — never from `account.available`, which is the Account's
3285
+ * fiat VALUATION of those positions (ADR-0028 R5/R6). The option is keyed on
3286
+ * the ASSET id: several positions share one account id, and a symbol is display
3287
+ * only (ADR-0028 R3). Every row is listed, so a real multi-position treasury
3288
+ * still offers its holdings.
3289
+ */
3290
+ function assetSelectionsFromAccount(account) {
3291
+ if (account === null) return [];
3292
+ return account.balances.map((position) => {
3293
+ return {
3294
+ option: {
3295
+ id: position.asset,
3296
+ symbol: position.symbol,
3297
+ availableDisplay: formatAssetAmount({
3298
+ asset: position.asset,
3299
+ value: position.available
3300
+ })
3301
+ },
3302
+ money: {
3303
+ asset: position.asset,
3304
+ value: position.available
3305
+ }
3306
+ };
3307
+ });
3308
+ }
3104
3309
  function usePayrollRun(input) {
3105
3310
  const { orgId, prefill, onRun, onFailed } = input;
3106
3311
  const client = useCapxulClientOrNull();
@@ -3114,17 +3319,13 @@ function usePayrollRun(input) {
3114
3319
  const terms = useOrgTerms(orgId);
3115
3320
  const [selected, setSelected] = useState(() => prefill?.recipientIds ?? []);
3116
3321
  const [amounts, setAmounts] = useState(() => initialAmounts(prefill));
3117
- const restoreCurrency = treasury.data?.available.currency;
3118
3322
  const { draftScope, currentScope, draft, draftError, restorationPending, activateDraft, restoreDraft, setDraft, recovery, draftBlocked } = useRunDraft(client, orgId, useCallback((restored) => {
3119
3323
  setSelected(restored.input.items.map((item) => item.partyId));
3120
- if (restoreCurrency !== void 0 && restoreCurrency === restored.asset.currency) setAmounts(Object.fromEntries(restored.input.items.map((item) => [item.partyId, {
3121
- value: fromMinorUnits(BigInt(item.net), {
3122
- currency: restoreCurrency,
3123
- decimals: restored.asset.decimals
3124
- }).value,
3324
+ if (requireAsset(restored.input.asset).assetId === restored.input.asset) setAmounts(Object.fromEntries(restored.input.items.map((item) => [item.partyId, {
3325
+ value: item.amount.value,
3125
3326
  prefilled: false
3126
3327
  }])));
3127
- }, [restoreCurrency]));
3328
+ }, []));
3128
3329
  const frozen = draft !== null;
3129
3330
  const composerKey = composerIdentity(orgId, prefill, draftScope, frozen);
3130
3331
  const [seededKey, setSeededKey] = useState(composerKey);
@@ -3164,11 +3365,8 @@ function usePayrollRun(input) {
3164
3365
  draftError
3165
3366
  ]);
3166
3367
  const account = treasury.data ?? null;
3167
- const assetOptions = useMemo(() => account === null ? [] : [{
3168
- id: account.id,
3169
- symbol: account.available.currency,
3170
- availableDisplay: formatMoney(account.available, GRAMMAR)
3171
- }], [account]);
3368
+ const assetSelections = useMemo(() => assetSelectionsFromAccount(account), [account]);
3369
+ const assetOptions = useMemo(() => assetSelections.map((selection) => selection.option), [assetSelections]);
3172
3370
  const [selectedAssetId, setSelectedAssetId] = useState(null);
3173
3371
  useEffect(() => {
3174
3372
  if (!assetOptions.some((option) => option.id === selectedAssetId)) setSelectedAssetId(assetOptions[0]?.id ?? null);
@@ -3177,7 +3375,7 @@ function usePayrollRun(input) {
3177
3375
  const selectAsset = useCallback((id) => {
3178
3376
  if (!submitGate.current && !frozen) setSelectedAssetId((current) => assetOptions.some((option) => option.id === id) ? id : current);
3179
3377
  }, [assetOptions, frozen]);
3180
- const assetMoney = selectedAsset !== null && account !== null ? account.available : null;
3378
+ const assetMoney = assetSelections.find((selection) => selection.option.id === selectedAsset?.id)?.money ?? null;
3181
3379
  const named = useMemo(() => new Map(roster.options.map((option) => [option.id, option])), [roster.options]);
3182
3380
  const refs = roster.refs;
3183
3381
  const payableRefs = useMemo(() => {
@@ -3189,12 +3387,19 @@ function usePayrollRun(input) {
3189
3387
  }
3190
3388
  return resolved;
3191
3389
  }, [refs, selected]);
3192
- const seeds = useMemo(() => termsSeeds(terms.data ?? [], assetMoney, INSTANT_RUN_PERIOD), [assetMoney, terms.data]);
3390
+ const seeds = useMemo(() => {
3391
+ if (assetMoney === null) return /* @__PURE__ */ new Map();
3392
+ const metadata = requireAsset(assetMoney.asset);
3393
+ return termsSeeds(terms.data ?? [], metadata.peg === null ? null : {
3394
+ currency: metadata.peg,
3395
+ decimals: metadata.decimals
3396
+ }, INSTANT_RUN_PERIOD);
3397
+ }, [assetMoney, terms.data]);
3193
3398
  const entries = useMemo(() => {
3194
3399
  if (assetMoney === null) return [];
3195
3400
  return selected.map((recipientId) => {
3196
3401
  const value = amountText(amounts, seeds, recipientId);
3197
- const parsed = value.trim() === "" ? null : parseMoney(value, assetMoney);
3402
+ const parsed = value.trim() === "" ? null : parseAssetAmount(value, requireAsset(assetMoney.asset));
3198
3403
  return {
3199
3404
  recipientId,
3200
3405
  name: named.get(recipientId)?.name ?? recipientId,
@@ -3202,7 +3407,7 @@ function usePayrollRun(input) {
3202
3407
  change: (text) => change(recipientId, text),
3203
3408
  error: parsed !== null && isMoneyParseError(parsed) ? parsed.reason : null,
3204
3409
  prefilled: amounts[recipientId]?.prefilled ?? seeds.has(recipientId),
3205
- currency: assetMoney.currency
3410
+ currency: requireAsset(assetMoney.asset).symbol
3206
3411
  };
3207
3412
  });
3208
3413
  }, [
@@ -3217,7 +3422,7 @@ function usePayrollRun(input) {
3217
3422
  if (assetMoney === null || selected.length === 0) return null;
3218
3423
  const money = [];
3219
3424
  for (const recipientId of selected) {
3220
- const parsed = parseMoney(amountText(amounts, seeds, recipientId), assetMoney);
3425
+ const parsed = parseAssetAmount(amountText(amounts, seeds, recipientId), requireAsset(assetMoney.asset));
3221
3426
  if (isMoneyParseError(parsed)) return null;
3222
3427
  money.push(parsed);
3223
3428
  }
@@ -3228,16 +3433,25 @@ function usePayrollRun(input) {
3228
3433
  seeds,
3229
3434
  selected
3230
3435
  ]);
3231
- const total = useMemo(() => parsedAmounts === null || assetMoney === null ? null : fromMinorUnits(parsedAmounts.reduce((sum, money) => sum + toMinorUnits(money), 0n), assetMoney), [assetMoney, parsedAmounts]);
3232
- const availableRaw = assetMoney === null ? null : toMinorUnits(assetMoney);
3233
- const totalRaw = total === null ? null : toMinorUnits(total);
3436
+ const total = useMemo(() => parsedAmounts === null || assetMoney === null ? null : {
3437
+ asset: assetMoney.asset,
3438
+ value: assetFromMinorUnits(parsedAmounts.reduce((sum, amount) => sum + toRaw(amount, requireAsset(amount.asset)), 0n), {
3439
+ asset: assetMoney.asset,
3440
+ decimals: requireAsset(assetMoney.asset).decimals
3441
+ }).value
3442
+ }, [assetMoney, parsedAmounts]);
3443
+ const availableRaw = assetMoney === null ? null : toRaw(assetMoney, requireAsset(assetMoney.asset));
3444
+ const totalRaw = total === null ? null : toRaw(total, requireAsset(total.asset));
3234
3445
  const funds = availableRaw === null || totalRaw === null ? "unknown" : totalRaw > availableRaw ? "insufficient" : "available";
3235
3446
  const budgets = me.data?.budgets ?? [];
3447
+ const compensationTerms = useMemo(() => new Map((terms.data ?? []).filter((row) => row.unit === "paycheck" && /^[1-9][0-9]*$/u.test(row.rate)).map((row) => [row.partyId, row])), [terms.data]);
3448
+ const writerNamesSelectedAsset = writeNamesAsset();
3236
3449
  const blockedReason = firstFalseFact({
3237
3450
  signerReady: signerStatus === "ready",
3238
3451
  ...runAuthorityFacts(draft, budgets, selectedAsset, assetMoney, funds),
3239
3452
  recipientCount: payableRefs === null ? 0 : selected.length,
3240
- amountsParse: total !== null
3453
+ amountsParse: total !== null && hasRequiredCompensation(frozen, writerNamesSelectedAsset, selected, compensationTerms),
3454
+ writeNamesAsset: writerNamesSelectedAsset
3241
3455
  });
3242
3456
  const authorize = useMutation({
3243
3457
  mutationFn: async ({ candidate, options }) => {
@@ -3259,7 +3473,7 @@ function usePayrollRun(input) {
3259
3473
  if (candidate.scope === currentScope.current) restoreDraft(candidate.scope);
3260
3474
  }
3261
3475
  });
3262
- const totalDisplay = total === null ? null : formatMoney(total, GRAMMAR);
3476
+ const totalDisplay = total === null ? null : formatAssetAmount(total);
3263
3477
  const authorizeMutate = authorize.mutate;
3264
3478
  const submit = useCallback((options) => {
3265
3479
  const attempt = toInvocationOptions(options);
@@ -3268,41 +3482,35 @@ function usePayrollRun(input) {
3268
3482
  if (permissionId === void 0 || totalDisplay === null || parsedAmounts === null) return;
3269
3483
  if (payableRefs === null) return;
3270
3484
  const recipientCount = selected.length;
3271
- const items = [];
3272
- for (const [index, recipientId] of selected.entries()) {
3273
- const to = payableRefs[index];
3274
- const parsed = parsedAmounts[index];
3275
- if (to === void 0 || parsed === void 0) return;
3276
- const raw = toMinorUnits(parsed).toString();
3277
- items.push({
3278
- to: {
3279
- kind: "party",
3280
- partyId: toPartyId(recipientId)
3281
- },
3282
- partyId: recipientId,
3283
- gross: raw,
3284
- net: raw,
3285
- adjustments: []
3485
+ let candidate;
3486
+ if (frozen) candidate = draft;
3487
+ else {
3488
+ const items = freshRunItems({
3489
+ selected,
3490
+ payableRefs,
3491
+ amounts: parsedAmounts,
3492
+ compensationTerms
3286
3493
  });
3287
- }
3288
- const runAt = Date.now();
3289
- const candidate = frozen ? draft : {
3290
- version: 1,
3291
- scope: draftScope,
3292
- asset: {
3293
- id: selectedAsset.id,
3294
- currency: assetMoney.currency,
3295
- decimals: assetMoney.decimals
3296
- },
3297
- input: {
3298
- permissionId,
3299
- period: {
3300
- start: runAt,
3301
- end: runAt
3494
+ const runAt = Date.now();
3495
+ candidate = {
3496
+ version: 1,
3497
+ scope: draftScope,
3498
+ asset: {
3499
+ id: selectedAsset.id,
3500
+ currency: requireAsset(assetMoney.asset).symbol,
3501
+ decimals: requireAsset(assetMoney.asset).decimals
3302
3502
  },
3303
- items
3304
- }
3305
- };
3503
+ input: {
3504
+ permissionId,
3505
+ asset: selectedAsset.id,
3506
+ period: {
3507
+ start: runAt,
3508
+ end: runAt
3509
+ },
3510
+ items
3511
+ }
3512
+ };
3513
+ }
3306
3514
  submitGate.current = true;
3307
3515
  authorizeMutate({
3308
3516
  candidate,
@@ -3334,6 +3542,7 @@ function usePayrollRun(input) {
3334
3542
  selectedAsset,
3335
3543
  blockedReason,
3336
3544
  budgets,
3545
+ compensationTerms,
3337
3546
  onFailed,
3338
3547
  onRun,
3339
3548
  parsedAmounts,
@@ -3382,18 +3591,25 @@ function usePayrollRun(input) {
3382
3591
  };
3383
3592
  }
3384
3593
  function remainingBalanceDisplay(available, total, asset) {
3385
- return available === null || total === null || asset === null ? null : formatMoney(fromMinorUnits(available - total, asset), GRAMMAR);
3594
+ if (available === null || total === null || asset === null) return null;
3595
+ const remaining = available - total;
3596
+ const magnitude = remaining < 0n ? -remaining : remaining;
3597
+ const formatted = formatAssetAmount(assetFromMinorUnits(magnitude, {
3598
+ asset: asset.asset,
3599
+ decimals: requireAsset(asset.asset).decimals
3600
+ }));
3601
+ return remaining < 0n ? `-${formatted}` : formatted;
3386
3602
  }
3387
3603
  /** Persisted replay keeps its authorized Budget and asset; a prior debit cannot block its own recovery. */
3388
3604
  function runAuthorityFacts(draft, budgets, asset, money, funds) {
3389
3605
  return {
3390
3606
  budgetCount: draft !== null && !budgets.some((budget) => budget.id === draft.input.permissionId) ? 0 : budgets.length,
3391
- hasAsset: asset !== null && (draft === null || draft.asset.id === asset.id && draft.asset.currency === money?.currency && draft.asset.decimals === money?.decimals),
3607
+ hasAsset: asset !== null && (draft === null || draft.asset.id === asset.id && draft.input.asset === money?.asset),
3392
3608
  funds: draft?.input.requestKey === void 0 ? funds : "available"
3393
3609
  };
3394
3610
  }
3395
3611
  /**
3396
- * The seven facts, in the ONE order the contract fixes. Exactly one code comes
3612
+ * The facts, in the ONE order the contract fixes. Exactly one code comes
3397
3613
  * out, and it is always the first thing that is false — so two problems never
3398
3614
  * race to name the button.
3399
3615
  */
@@ -3404,7 +3620,8 @@ function firstFalseFact(facts) {
3404
3620
  if (!facts.hasAsset) return "no-asset";
3405
3621
  if (facts.recipientCount === 0) return "no-recipients";
3406
3622
  if (!facts.amountsParse) return "amount-invalid";
3407
- return facts.funds === "insufficient" ? "insufficient-funds" : null;
3623
+ if (facts.funds === "insufficient") return "insufficient-funds";
3624
+ return facts.writeNamesAsset ? null : "asset-unnamed";
3408
3625
  }
3409
3626
  //#endregion
3410
3627
  //#region src/headless/payroll/payroll-run.tsx
@@ -3414,7 +3631,7 @@ function usePayrollRunContext() {
3414
3631
  if (engine === null) throw new Error("CapxulPayrollRun parts must be used inside <CapxulPayrollRun>");
3415
3632
  return engine;
3416
3633
  }
3417
- function Root({ orgId, prefill, onRun, onFailed, children }) {
3634
+ function Root$1({ orgId, prefill, onRun, onFailed, children }) {
3418
3635
  const engine = usePayrollRun({
3419
3636
  orgId,
3420
3637
  prefill,
@@ -3444,7 +3661,7 @@ function Actions({ children }) {
3444
3661
  return /* @__PURE__ */ jsx(Fragment, { children: children(engine.actions) });
3445
3662
  }
3446
3663
  /** The compound shape the app's own design system already uses (`StatusTabs`). */
3447
- const CapxulPayrollRun = Object.assign(Root, {
3664
+ const CapxulPayrollRun = Object.assign(Root$1, {
3448
3665
  Recipients,
3449
3666
  Amounts,
3450
3667
  Asset,
@@ -3452,4 +3669,230 @@ const CapxulPayrollRun = Object.assign(Root, {
3452
3669
  Actions
3453
3670
  });
3454
3671
  //#endregion
3455
- export { CapxulActivity, CapxulAuthenticationController, CapxulContacts, CapxulDashboardAccess, CapxulOnboardingController, CapxulOrgMember, CapxulPayroll, CapxulPayrollRun, CapxulProvider, CapxulSendMoney, acknowledgeOnboardingDestination, activeOnboardingRecovery, capxulKeys, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHandleAvailability, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulInviteMember, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUploadImage };
3672
+ //#region src/headless/client-connection/use-client-connection.ts
3673
+ function useClientConnection(input) {
3674
+ const [view, setView] = useState(null);
3675
+ const [pending, setPending] = useState(false);
3676
+ const [error, setError] = useState(null);
3677
+ const [resendReady, setResendReady] = useState(false);
3678
+ const identityRef = useRef({
3679
+ client: input.client,
3680
+ requestId: input.requestId
3681
+ });
3682
+ identityRef.current = {
3683
+ client: input.client,
3684
+ requestId: input.requestId
3685
+ };
3686
+ const belongsHere = useCallback((client, requestId) => identityRef.current.client === client && identityRef.current.requestId === requestId, []);
3687
+ const refresh = useCallback(async (signal) => {
3688
+ const { client, requestId } = identityRef.current;
3689
+ const result = await client.auth.clientRequests.read({ requestId }, signal === void 0 ? void 0 : { signal });
3690
+ if (signal?.aborted || !belongsHere(client, requestId)) return;
3691
+ if (result.ok) {
3692
+ setView(result.value);
3693
+ setError(null);
3694
+ return;
3695
+ }
3696
+ setError(result.error);
3697
+ }, [belongsHere]);
3698
+ useEffect(() => {
3699
+ setView(null);
3700
+ setError(null);
3701
+ setPending(false);
3702
+ setResendReady(false);
3703
+ const controller = new AbortController();
3704
+ refresh(controller.signal);
3705
+ return () => {
3706
+ controller.abort();
3707
+ };
3708
+ }, [
3709
+ input.client,
3710
+ input.requestId,
3711
+ refresh
3712
+ ]);
3713
+ useEffect(() => {
3714
+ if (view?.phase !== "otp_required") {
3715
+ setResendReady(false);
3716
+ return;
3717
+ }
3718
+ const remaining = view.resendAt - Date.now();
3719
+ if (remaining <= 0) {
3720
+ setResendReady(true);
3721
+ return;
3722
+ }
3723
+ setResendReady(false);
3724
+ const timer = setTimeout(() => setResendReady(true), remaining);
3725
+ return () => clearTimeout(timer);
3726
+ }, [view]);
3727
+ const run = useCallback(async (work) => {
3728
+ const { client, requestId } = identityRef.current;
3729
+ setPending(true);
3730
+ try {
3731
+ const result = await work();
3732
+ if (!belongsHere(client, requestId)) return;
3733
+ if (!result.ok) {
3734
+ setError(result.error);
3735
+ return;
3736
+ }
3737
+ await refresh();
3738
+ } finally {
3739
+ if (belongsHere(client, requestId)) setPending(false);
3740
+ }
3741
+ }, [belongsHere, refresh]);
3742
+ return useMemo(() => {
3743
+ if (error !== null && (view === null || view.phase !== "refused")) return {
3744
+ phase: "failure",
3745
+ otp: null,
3746
+ consent: null,
3747
+ complete: null,
3748
+ failure: {
3749
+ code: error.code,
3750
+ retry: () => refresh(),
3751
+ cancel: async () => {
3752
+ if (view === null) return;
3753
+ const { client, requestId } = identityRef.current;
3754
+ await run(() => client.auth.clientRequests.cancel({
3755
+ requestId,
3756
+ revision: view.revision
3757
+ }));
3758
+ }
3759
+ }
3760
+ };
3761
+ if (view === null) return {
3762
+ phase: "checking",
3763
+ otp: null,
3764
+ consent: null,
3765
+ complete: null,
3766
+ failure: null
3767
+ };
3768
+ if (view.phase === "otp_required") return {
3769
+ phase: view.phase,
3770
+ otp: {
3771
+ email: view.email,
3772
+ pending,
3773
+ canResend: resendReady,
3774
+ verifyCode: async (code) => {
3775
+ const { client, requestId } = identityRef.current;
3776
+ await run(() => client.auth.clientRequests.verifyOtp({
3777
+ requestId,
3778
+ code
3779
+ }));
3780
+ },
3781
+ resend: async () => {
3782
+ const { client, requestId } = identityRef.current;
3783
+ await run(() => client.auth.clientRequests.sendOtp({ requestId }));
3784
+ }
3785
+ },
3786
+ consent: null,
3787
+ complete: null,
3788
+ failure: null
3789
+ };
3790
+ if (view.phase === "review") return {
3791
+ phase: view.phase,
3792
+ otp: null,
3793
+ consent: {
3794
+ verifiedEmail: view.email,
3795
+ clientLabel: view.clientLabel,
3796
+ scopeCodes: view.scopes,
3797
+ purpose: view.intent.mode,
3798
+ setupState: view.setupState,
3799
+ pending,
3800
+ accept: async () => {
3801
+ const { client, requestId } = identityRef.current;
3802
+ await run(() => client.auth.clientRequests.accept({
3803
+ requestId,
3804
+ revision: view.revision
3805
+ }));
3806
+ },
3807
+ cancel: async () => {
3808
+ const { client, requestId } = identityRef.current;
3809
+ await run(() => client.auth.clientRequests.cancel({
3810
+ requestId,
3811
+ revision: view.revision
3812
+ }));
3813
+ }
3814
+ },
3815
+ complete: null,
3816
+ failure: null
3817
+ };
3818
+ if (view.phase === "connected") return {
3819
+ phase: view.phase,
3820
+ otp: null,
3821
+ consent: null,
3822
+ complete: { outcome: "connected" },
3823
+ failure: null
3824
+ };
3825
+ if (view.phase === "refused") return {
3826
+ phase: view.phase,
3827
+ otp: null,
3828
+ consent: null,
3829
+ complete: null,
3830
+ failure: {
3831
+ code: view.code,
3832
+ ...view.field === void 0 ? {} : { field: view.field },
3833
+ retry: () => refresh(),
3834
+ cancel: async () => void 0
3835
+ }
3836
+ };
3837
+ return {
3838
+ phase: view.phase,
3839
+ otp: null,
3840
+ consent: null,
3841
+ complete: null,
3842
+ failure: null
3843
+ };
3844
+ }, [
3845
+ error,
3846
+ pending,
3847
+ refresh,
3848
+ resendReady,
3849
+ run,
3850
+ view
3851
+ ]);
3852
+ }
3853
+ //#endregion
3854
+ //#region src/headless/client-connection/client-connection.tsx
3855
+ const ConnectionContext = createContext(null);
3856
+ function useConnection() {
3857
+ const engine = useContext(ConnectionContext);
3858
+ if (engine === null) throw new Error("CapxulClientConnection parts must be used inside <CapxulClientConnection>");
3859
+ return engine;
3860
+ }
3861
+ function Root({ client, requestId, children }) {
3862
+ const engine = useClientConnection({
3863
+ client,
3864
+ requestId
3865
+ });
3866
+ return /* @__PURE__ */ jsx(ConnectionContext.Provider, {
3867
+ value: engine,
3868
+ children
3869
+ });
3870
+ }
3871
+ function Checking({ children }) {
3872
+ return useConnection().phase === "checking" ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
3873
+ }
3874
+ function Otp({ children }) {
3875
+ const otp = useConnection().otp;
3876
+ return otp === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(otp) });
3877
+ }
3878
+ function Consent({ children }) {
3879
+ const consent = useConnection().consent;
3880
+ return consent === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(consent) });
3881
+ }
3882
+ function Complete({ children }) {
3883
+ const complete = useConnection().complete;
3884
+ return complete === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(complete) });
3885
+ }
3886
+ function Failure({ children }) {
3887
+ const failure = useConnection().failure;
3888
+ return failure === null ? null : /* @__PURE__ */ jsx(Fragment, { children: children(failure) });
3889
+ }
3890
+ const CapxulClientConnection = Object.assign(Root, {
3891
+ Checking,
3892
+ Otp,
3893
+ Consent,
3894
+ Complete,
3895
+ Failure
3896
+ });
3897
+ //#endregion
3898
+ export { CapxulActivity, CapxulAuthenticationController, CapxulClientConnection, CapxulContacts, CapxulDashboardAccess, CapxulOnboardingController, CapxulOrgMember, CapxulPayroll, CapxulPayrollRun, CapxulProvider, CapxulSendMoney, acknowledgeOnboardingDestination, activeOnboardingRecovery, capxulKeys, clearOnboardingJourney, currentOnboardingJourneyId, entered, invalidateOnboardingJourneyObservation, isClaimed, isRestoring, loadOnboardingJourney, onboardingJourneyPosition, saveOnboardingJourney, startOnboardingJourney, useCapxul, useCapxulAccountBalance, useCapxulAccountFund, useCapxulActivity, useCapxulActivityDetail, useCapxulAnnotateMovement, useCapxulAuth, useCapxulCancelPayment, useCapxulClaimPayment, useCapxulClientOrNull, useCapxulCreateCommitment, useCapxulCreateOrg, useCapxulDestination, useCapxulHandleAvailability, useCapxulHoldings, useCapxulIdentity, useCapxulImageUpload, useCapxulOrgMembers, useCapxulOrgRoles, useCapxulOrgTreasury, useCapxulOrganizationPay, useCapxulOrganizationPayBatch, useCapxulOrgs, useCapxulPay, useCapxulPayment, useCapxulPayments, useCapxulPermission, useCapxulPermissionAssign, useCapxulPermissionChange, useCapxulPermissionCreate, useCapxulPermissionReplace, useCapxulPermissionRevoke, useCapxulPermissions, useCapxulProfile, useCapxulRedirectPayment, useCapxulSend, useCapxulTransitions, useCapxulUploadImage };