@capxul/sdk 4.20.0-beta.4 → 4.20.0-beta.5

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.
@@ -1,4 +1,4 @@
1
- import { $ as toPartyId, A as SUPPORTED_CURRENCY_CODES, B as toAuthUserId, C as ASSET_ID_RE, Ct as isChainUpstream, D as COUNTRY_CODE_RE, Dt as isCredentialField, E as CLIENT_REQUEST_ID_RE, Et as containsSensitiveMaterial, F as toAccountId, G as toDurationMs, H as toChainId, I as toAddress, J as toEpochSeconds, K as toEmail, L as toAllowedOrigin, M as ZERO_BYTES32, O as EMAIL_RE$1, Ot as redactSecrets, Q as toOrgId, S as APP_ID_RE, St as failureFingerprint, T as CLIENT_GRANT_ID_RE, Tt as revertSummaryText, U as toCountryCode, V as toBudgetId, W as toCurrencyCode, X as toJwtToken, Y as toHandle, Z as toKycTier, _ as normalizeBindingEmail, _t as FAILURE_MODES$1, at as toTesterKind, b as configuredMoneyAssetById, bt as chainEvidenceLabel, c as AuthCachePortTag, ct as validateHandle, d as authClientPortFromPromiseAdapter, dt as CAPXUL_ERROR_CODES, et as toPayrollGroupId, f as AuthClientError, ft as CapxulError, gt as CHAIN_UPSTREAMS, h as orgRoleKeyForLabel, ht as isCapxulError, i as BrowserAuthCacheAdapter, it as toSessionToken, j as WEI_RE, k as EVM_ADDRESS_RE$1, kt as redactUrlSecrets, l as SystemClockLayer, lt as HANDLE_RE$1, m as ADMIN_ROLE_LABEL, mt as Errors, nt as toPublishableKey, ot as toTxHash, p as AuthClientPortTag, pt as EXPECTED_OPERATION_OUTCOMES, q as toEpochMs, r as InMemoryAuthCacheAdapter, rt as toRoleKey, st as toWeiAmount, tt as toPayrollRunId, u as ClockPortTag, ut as decodeConvexError, v as BASE_SEPOLIA_CHAIN_ID, vt as boundedResponseHeaders, w as BYTES32_RE, wt as isFailureMode, x as ACCOUNT_ID_RE, xt as decodeChainCause, y as deriveCapxulSafeAddress, yt as chainCauseProperties, z as toAssetId } from "./OAuthBearerAuthClient-DWkofxdZ.mjs";
1
+ import { $ as toOrgId, A as EVM_ADDRESS_RE$1, At as redactUrlSecrets, B as toAssetId, C as APP_ID_RE, Ct as failureFingerprint, D as CLIENT_REQUEST_ID_RE, Dt as containsSensitiveMaterial, E as CLIENT_GRANT_ID_RE, Et as revertSummaryText, G as toCurrencyCode, H as toBudgetId, I as toAccountId, J as toEpochMs, K as toDurationMs, L as toAddress, M as WEI_RE, N as ZERO_BYTES32, O as COUNTRY_CODE_RE, Ot as isCredentialField, P as assetIdFor, Q as toKycTier, R as toAllowedOrigin, S as ACCOUNT_ID_RE, St as decodeChainCause, T as BYTES32_RE, Tt as isFailureMode, U as toChainId, V as toAuthUserId, W as toCountryCode, X as toHandle, Y as toEpochSeconds, Z as toJwtToken, _ as normalizeBindingEmail, _t as CHAIN_UPSTREAMS, at as toSessionToken, b as CONFIGURED_MONEY_ASSETS, bt as chainCauseProperties, c as AuthCachePortTag, ct as toWeiAmount, d as authClientPortFromPromiseAdapter, dt as decodeConvexError, et as toPartyId, f as AuthClientError, ft as CAPXUL_ERROR_CODES, gt as isCapxulError, h as orgRoleKeyForLabel, ht as Errors, i as BrowserAuthCacheAdapter, it as toRoleKey, j as SUPPORTED_CURRENCY_CODES, k as EMAIL_RE$1, kt as redactSecrets, l as SystemClockLayer, lt as validateHandle, m as ADMIN_ROLE_LABEL, mt as EXPECTED_OPERATION_OUTCOMES, nt as toPayrollRunId, ot as toTesterKind, p as AuthClientPortTag, pt as CapxulError, q as toEmail, r as InMemoryAuthCacheAdapter, rt as toPublishableKey, st as toTxHash, tt as toPayrollGroupId, u as ClockPortTag, ut as HANDLE_RE$1, v as BASE_SEPOLIA_CHAIN_ID, vt as FAILURE_MODES$1, w as ASSET_ID_RE, wt as isChainUpstream, x as configuredMoneyAssetById, xt as chainEvidenceLabel, y as deriveCapxulSafeAddress, yt as boundedResponseHeaders } from "./OAuthBearerAuthClient-BqQz-bnO.mjs";
2
2
  import { formatUnits, keccak256, parseUnits, recoverAddress, stringToHex, toBytes } from "viem";
3
3
  import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, Layer, Logger, Metric, Option, Queue, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
4
  import { getFunctionName, makeFunctionReference } from "convex/server";
@@ -406,6 +406,259 @@ const isRestoring = (state) => {
406
406
  * it in #1521; this ticket ships the selector they move onto. */
407
407
  const isClaimed = (state) => state.phase === "authenticated" && state.account.at === "claimed";
408
408
  //#endregion
409
+ //#region src/domain/money/parse-money.ts
410
+ /**
411
+ * ADR-0023 R1: the reason is a closed code and the ONLY failure vocabulary —
412
+ * the consuming app maps each code to its own sentence. The SDK ships no
413
+ * English for user-text failure.
414
+ */
415
+ function isMoneyParseError(value) {
416
+ return "kind" in value;
417
+ }
418
+ const AMOUNT_PATTERN = /^(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?$/;
419
+ const ZERO_PATTERN = /^0+(?:\.0+)?$/;
420
+ function parseMoney(input, asset) {
421
+ const parsed = parseDecimalInput(input, asset.decimals, 24);
422
+ return "kind" in parsed ? parsed : {
423
+ currency: asset.currency,
424
+ value: parsed.value,
425
+ decimals: asset.decimals
426
+ };
427
+ }
428
+ /** Shared input grammar. Callers retain their own amount size limits. */
429
+ function parseDecimalInput(input, decimals, maximumLength) {
430
+ if (!Number.isInteger(decimals) || decimals < 0) throw Errors.invalidInput("asset.decimals", "must be a non-negative integer");
431
+ const value = input.trim();
432
+ if (value.length === 0) return {
433
+ kind: "error",
434
+ reason: "required"
435
+ };
436
+ if (value.length > maximumLength) return {
437
+ kind: "error",
438
+ reason: "too-long"
439
+ };
440
+ const negative = value.startsWith("-");
441
+ const unsigned = negative ? value.slice(1) : value;
442
+ if (!AMOUNT_PATTERN.test(unsigned)) return {
443
+ kind: "error",
444
+ reason: "invalid-format"
445
+ };
446
+ if (negative) return {
447
+ kind: "error",
448
+ reason: "non-positive"
449
+ };
450
+ const normalized = unsigned.replaceAll(",", "");
451
+ const fraction = normalized.split(".")[1];
452
+ if (fraction !== void 0 && fraction.length > decimals) return {
453
+ kind: "error",
454
+ reason: "too-many-decimals"
455
+ };
456
+ if (ZERO_PATTERN.test(normalized)) return {
457
+ kind: "error",
458
+ reason: "non-positive"
459
+ };
460
+ return { value: normalized };
461
+ }
462
+ //#endregion
463
+ //#region src/domain/money/asset-amount.ts
464
+ /** Convert raw units without rounding or changing asset identity. */
465
+ function assetAmountFromRaw(raw, asset) {
466
+ validateMetadata(asset);
467
+ return {
468
+ asset: asset.assetId,
469
+ value: formatUnits(decimalToRaw(toWeiAmount(raw), 0), asset.decimals)
470
+ };
471
+ }
472
+ /** Exact scaling. This rejects excess precision instead of rounding it. */
473
+ function toRaw(amount, asset) {
474
+ validateMetadata(asset);
475
+ if (amount.asset !== asset.assetId) throw Errors.invalidInput("amount.asset", "must match the selected asset on its chain");
476
+ return decimalToRaw(amount.value, asset.decimals);
477
+ }
478
+ function parseAssetAmount(input, asset) {
479
+ validateMetadata(asset);
480
+ const parsed = parseDecimalInput(input, asset.decimals, 100);
481
+ return "kind" in parsed ? parsed : {
482
+ asset: asset.assetId,
483
+ value: parsed.value.replace(/^0+(?=\d)/, "")
484
+ };
485
+ }
486
+ /** Display the full quantity. Only fiat formatting rounds for display. */
487
+ function formatAssetAmount(amount) {
488
+ const asset = requireAsset(amount.asset);
489
+ const [integer = "0", fraction] = formatUnits(toRaw(amount, asset), asset.decimals).split(".");
490
+ return `${integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}${fraction === void 0 ? "" : `.${fraction}`} ${asset.symbol}`;
491
+ }
492
+ function assetSymbolFor(asset) {
493
+ return requireAsset(asset).symbol;
494
+ }
495
+ function requireAsset(id) {
496
+ const asset = configuredMoneyAssetById(id);
497
+ if (asset === null) throw Errors.invalidInput("amount.asset", "is not a registered asset");
498
+ return asset;
499
+ }
500
+ function decimalToRaw(value, decimals) {
501
+ validatePrecision(decimals);
502
+ if (!/^\d+(?:\.\d+)?$/.test(value) || value.trim() !== value || value.length > 512) throw Errors.invalidInput("amount.value", "must be a non-negative decimal string of at most 512 characters");
503
+ const [integer = "0", fraction = ""] = value.split(".");
504
+ if (fraction.length > decimals) throw Errors.invalidInput("amount.value", "fraction exceeds asset precision");
505
+ return BigInt(integer) * 10n ** BigInt(decimals) + BigInt(fraction.padEnd(decimals, "0") || "0");
506
+ }
507
+ function validatePrecision(decimals) {
508
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255) throw Errors.invalidInput("decimals", "must be an integer from 0 to 255");
509
+ }
510
+ function validateMetadata(asset) {
511
+ validatePrecision(asset.decimals);
512
+ if (asset.assetId !== assetIdFor(asset.chainId, asset.tokenAddress)) throw Errors.invalidInput("amount.asset", "metadata must match its chain and token");
513
+ }
514
+ //#endregion
515
+ //#region src/domain/money/valuation.ts
516
+ const PEG_RATES = (source, into) => (typeof source === "string" ? source : source.peg) === into ? {
517
+ value: "1",
518
+ decimals: 0
519
+ } : null;
520
+ /**
521
+ * The oracle the money READS price with: the account headline, the org
522
+ * treasury headline, and every later total over the same seam (D6).
523
+ *
524
+ * It is `PEG_RATES` today because no price feed exists yet, and a peg is a
525
+ * relationship, not a market price — so an unpegged asset such as WETH reads
526
+ * as unrated rather than as a fabricated number. This binding is the ONE place
527
+ * a real feed arrives; no call site changes when it does.
528
+ */
529
+ const MONEY_READ_RATES = PEG_RATES;
530
+ /** Sum exact fixed-point values. Missing rates never contribute zero. */
531
+ function valueIn(amounts, displayCurrency, rates = PEG_RATES) {
532
+ const currency = toCurrencyCode(displayCurrency);
533
+ const counted = [];
534
+ const unrated = [];
535
+ let sum = 0n;
536
+ let scale = 0;
537
+ for (const amount of amounts) {
538
+ let source;
539
+ let raw;
540
+ let decimals;
541
+ if ("asset" in amount) {
542
+ source = requireAsset(amount.asset);
543
+ raw = toRaw(amount, source);
544
+ decimals = source.decimals;
545
+ } else {
546
+ source = toCurrencyCode(amount.currency);
547
+ raw = decimalToRaw(amount.value, amount.decimals);
548
+ decimals = amount.decimals;
549
+ }
550
+ const rate = rates(source, currency);
551
+ if (rate === null) {
552
+ unrated.push(amount);
553
+ continue;
554
+ }
555
+ validatePrecision(rate.decimals);
556
+ if (!/^\d+$/.test(rate.value) || rate.value.trim() !== rate.value || rate.value.length > 256 || BigInt(rate.value) <= 0n) throw Errors.invalidInput("rate.value", "must be a positive fixed-point integer");
557
+ const termScale = decimals + rate.decimals;
558
+ const nextScale = Math.max(scale, termScale);
559
+ sum = sum * 10n ** BigInt(nextScale - scale) + raw * BigInt(rate.value) * 10n ** BigInt(nextScale - termScale);
560
+ scale = nextScale;
561
+ counted.push(amount);
562
+ }
563
+ return {
564
+ total: counted.length === 0 ? null : {
565
+ currency,
566
+ value: formatUnits(sum, scale),
567
+ decimals: scale
568
+ },
569
+ counted,
570
+ unrated
571
+ };
572
+ }
573
+ //#endregion
574
+ //#region src/domain/money/account-positions.ts
575
+ /**
576
+ * Lift an Account's raw per-asset positions to the SDK's `AccountPosition`.
577
+ *
578
+ * A position whose `assetId` is not a registered asset refuses. An unregistered
579
+ * token has no identity this SDK can state, so it must never be relabelled as
580
+ * one — the failure is the point, not a row to skip.
581
+ */
582
+ function accountPositionsFromWire(positions) {
583
+ return positions.map((position) => {
584
+ const asset = configuredMoneyAssetById(toAssetId(position.assetId));
585
+ if (asset === null) throw Errors.invalidInput("assetId", "is not a registered asset");
586
+ const raw = toWeiAmount(position.rawBalance);
587
+ const rawAvailable = toWeiAmount(position.rawAvailableBalance ?? position.rawBalance);
588
+ return {
589
+ asset: asset.assetId,
590
+ symbol: asset.symbol,
591
+ decimals: asset.decimals,
592
+ balance: assetAmountFromRaw(raw, asset).value,
593
+ available: assetAmountFromRaw(rawAvailable, asset).value
594
+ };
595
+ });
596
+ }
597
+ /**
598
+ * The fiat currency a position is denominated in: the registry peg of its
599
+ * asset, or `null` when that asset carries no peg (ADR-0028 R3/R4).
600
+ *
601
+ * A consumer must denominate a token amount in the position's OWN peg. The
602
+ * Account's `available.currency` is the display currency of a multi-asset
603
+ * VALUATION (ADR-0028 R6). It is `"USD"` for every Account regardless of what
604
+ * is held, so reading it as a position's denomination labels a token with the
605
+ * headline's fiat — the false identity this cut removes.
606
+ */
607
+ function positionPeg(asset) {
608
+ return configuredMoneyAssetById(asset)?.peg ?? null;
609
+ }
610
+ /**
611
+ * The fiat headline of a set of positions: the sum of the positions the given
612
+ * oracle can rate into the display currency (ADR-0028 R6, D6). It never picks
613
+ * a token and never labels one as fiat.
614
+ *
615
+ * The oracle is a PARAMETER (D6 obligation 4). It defaults to `PEG_RATES`, which
616
+ * only rates an asset whose peg equals the display currency, so an unpegged
617
+ * asset such as WETH stays out of the total and out of `counted`:
618
+ * a WETH-only Account reads a `$0.00` headline under that default. Hand it a
619
+ * real oracle and the same Account prices (WETH at `$2000` under
620
+ * `createMockRates`, a live feed later).
621
+ *
622
+ * Either way the unrated position is never zeroed and never dropped: it stays
623
+ * in `Account.balances`, and `valueIn` reports it in its own `unrated` list so
624
+ * a caller can say how complete the total is.
625
+ */
626
+ function headlineValuation(balances, displayCurrency, rates = PEG_RATES) {
627
+ const total = (pick) => valueIn(balances.map((position) => ({
628
+ asset: position.asset,
629
+ value: pick(position)
630
+ })), displayCurrency, rates).total ?? {
631
+ currency: displayCurrency,
632
+ value: "0",
633
+ decimals: 0
634
+ };
635
+ return {
636
+ balance: total((position) => position.balance),
637
+ available: total((p) => p.available)
638
+ };
639
+ }
640
+ /**
641
+ * The Account: its per-asset truth plus the fiat headline valuation. The oracle
642
+ * is threaded from the read that built it, never defaulted past this point
643
+ * (D6): the account read passes the configured one.
644
+ */
645
+ function accountFromPositions(id, positions, displayCurrency = toCurrencyCode("USD"), rates = PEG_RATES) {
646
+ const balances = accountPositionsFromWire(positions);
647
+ return {
648
+ id,
649
+ balances,
650
+ ...headlineValuation(balances, displayCurrency, rates)
651
+ };
652
+ }
653
+ /** The same lift for positions a caller already holds as `AccountPosition`. */
654
+ function accountFromAccountPositions(id, balances, displayCurrency = toCurrencyCode("USD"), rates = PEG_RATES) {
655
+ return {
656
+ id,
657
+ balances,
658
+ ...headlineValuation(balances, displayCurrency, rates)
659
+ };
660
+ }
661
+ //#endregion
409
662
  //#region ../observability/src/operations.ts
410
663
  const CAPXUL_OPERATIONS = {
411
664
  account: {
@@ -474,6 +727,26 @@ const CAPXUL_OPERATIONS = {
474
727
  createOrg: "createOrg",
475
728
  orgs: "orgs"
476
729
  },
730
+ /** AUTH A3 (#2073): the SDK facade over the native continuation endpoints. */
731
+ clientRequests: {
732
+ decide: "clientRequests.decide",
733
+ read: "clientRequests.read"
734
+ },
735
+ /** AUTH A2 (#2072): the native client authorization issuer and its browser
736
+ * continuation. One name per durable transition the endpoints can fail on. */
737
+ clientAuthorization: {
738
+ acceptRequest: "clientAuthorization.acceptRequest",
739
+ cancelRequest: "clientAuthorization.cancelRequest",
740
+ consumeRequest: "clientAuthorization.consumeRequest",
741
+ denyRequest: "clientAuthorization.denyRequest",
742
+ invalidateContext: "clientAuthorization.invalidateContext",
743
+ markConsented: "clientAuthorization.markConsented",
744
+ readMetadata: "clientAuthorization.readMetadata",
745
+ revokeGrant: "clientAuthorization.revokeGrant",
746
+ rotateGrant: "clientAuthorization.rotateGrant",
747
+ startRequest: "clientAuthorization.startRequest",
748
+ sweepExpired: "clientAuthorization.sweepExpired"
749
+ },
477
750
  credentials: {
478
751
  bootstrapClient: "credentials.bootstrapClient",
479
752
  mintQuickstartKey: "credentials.mintQuickstartKey"
@@ -972,6 +1245,44 @@ const IdentityRefusedProps = Schema.Struct({
972
1245
  state: OptionalString
973
1246
  });
974
1247
  const AuthSignedOutProps = Schema.Struct(TelemetryEnvelopeProps);
1248
+ const NativeAuthorizationMode = Schema.Literals(["login", "signup"]);
1249
+ const NativeAuthorizationStartedProps = Schema.Struct({
1250
+ ...TelemetryEnvelopeProps,
1251
+ mode: NativeAuthorizationMode
1252
+ });
1253
+ const NativeAuthorizationReviewedProps = Schema.Struct({
1254
+ ...TelemetryEnvelopeProps,
1255
+ mode: NativeAuthorizationMode
1256
+ });
1257
+ const NativeAuthorizationAcceptedProps = Schema.Struct({
1258
+ ...TelemetryEnvelopeProps,
1259
+ mode: NativeAuthorizationMode,
1260
+ /** Whether consent also had to finish account setup first. */
1261
+ setup_state: Schema.optional(Schema.Literals([
1262
+ "not_required",
1263
+ "new",
1264
+ "resumable"
1265
+ ]))
1266
+ });
1267
+ const NativeAuthorizationDeniedProps = Schema.Struct({
1268
+ ...TelemetryEnvelopeProps,
1269
+ mode: NativeAuthorizationMode
1270
+ });
1271
+ const NativeAuthorizationConnectedProps = Schema.Struct({
1272
+ ...TelemetryEnvelopeProps,
1273
+ mode: NativeAuthorizationMode
1274
+ });
1275
+ const NativeAuthorizationFailedProps = Schema.Struct({
1276
+ ...TelemetryEnvelopeProps,
1277
+ mode: Schema.optional(NativeAuthorizationMode),
1278
+ /** The refusal word the store returned, for example `identity_mismatch` or
1279
+ * `request_expired`. These are the closed `CLIENT_AUTHORIZATION_REFUSALS` from
1280
+ * `@capxul/wire` plus the store-internal `fresh_session_required` (ruling R3),
1281
+ * which the continuation maps to `identity_mismatch` on the wire but reports
1282
+ * truthfully here. Kept as `OptionalString` rather than an imported literal
1283
+ * set: this package must not depend on the transport contract. */
1284
+ reason: OptionalString
1285
+ });
975
1286
  const ProvisioningSafeCreatedProps = Schema.Struct({
976
1287
  ...TelemetryEnvelopeProps,
977
1288
  safe_address: OptionalAddress
@@ -1014,6 +1325,12 @@ const BootstrapFailedProps = Schema.Struct({
1014
1325
  * Organization row's chain, the configured token address — never from a label.
1015
1326
  * The verified `organization_id` and its group arrive from the backend
1016
1327
  * attribution, so they are not repeated here.
1328
+ *
1329
+ * D1 (2026-09-10): `asset_id` carries the canonical branded identity the read
1330
+ * resolved, `eip155:<chainId>/erc20:<lowercase-address>`, from the registry
1331
+ * row. `token_address`/`chain_id` are its parts; `currency` is the peg and a
1332
+ * display code. The producer emits the branded id so the live read can be
1333
+ * grouped by ASSET, never by symbol or peg.
1017
1334
  */
1018
1335
  const AccountBalanceReadProps = Schema.Struct({
1019
1336
  ...TelemetryEnvelopeProps,
@@ -1021,6 +1338,7 @@ const AccountBalanceReadProps = Schema.Struct({
1021
1338
  account_kind: OptionalAccountKind,
1022
1339
  chain_id: OptionalChainId,
1023
1340
  token_address: OptionalAddress,
1341
+ asset_id: OptionalString,
1024
1342
  currency: OptionalCurrencyCode,
1025
1343
  balance_bucket: OptionalBalanceBucket,
1026
1344
  durationMs: OptionalDurationMs
@@ -1489,6 +1807,30 @@ Schema.Struct({
1489
1807
  name: Schema.Literal("auth_signed_out"),
1490
1808
  props: AuthSignedOutProps
1491
1809
  });
1810
+ Schema.Struct({
1811
+ name: Schema.Literal("native_authorization_started"),
1812
+ props: NativeAuthorizationStartedProps
1813
+ });
1814
+ Schema.Struct({
1815
+ name: Schema.Literal("native_authorization_reviewed"),
1816
+ props: NativeAuthorizationReviewedProps
1817
+ });
1818
+ Schema.Struct({
1819
+ name: Schema.Literal("native_authorization_accepted"),
1820
+ props: NativeAuthorizationAcceptedProps
1821
+ });
1822
+ Schema.Struct({
1823
+ name: Schema.Literal("native_authorization_denied"),
1824
+ props: NativeAuthorizationDeniedProps
1825
+ });
1826
+ Schema.Struct({
1827
+ name: Schema.Literal("native_authorization_connected"),
1828
+ props: NativeAuthorizationConnectedProps
1829
+ });
1830
+ Schema.Struct({
1831
+ name: Schema.Literal("native_authorization_failed"),
1832
+ props: NativeAuthorizationFailedProps
1833
+ });
1492
1834
  Schema.Struct({
1493
1835
  name: Schema.Literal("onboarding_intent_selected"),
1494
1836
  props: OnboardingIntentSelectedProps
@@ -1743,12 +2085,18 @@ const CLI_COMMAND_NAMES = [
1743
2085
  "telemetry.status",
1744
2086
  "telemetry.enable",
1745
2087
  "telemetry.disable",
2088
+ "auth",
2089
+ "auth.login",
2090
+ "auth.signup",
2091
+ "auth.status",
2092
+ "auth.logout",
1746
2093
  "unknown"
1747
2094
  ];
1748
2095
  const CLI_CAPABILITIES = [
1749
2096
  "cli",
1750
2097
  "diagnostics",
1751
- "collection"
2098
+ "collection",
2099
+ "authentication"
1752
2100
  ];
1753
2101
  const CLI_INVOCATION_KINDS = [
1754
2102
  "command",
@@ -1766,8 +2114,9 @@ const CliInvocationProps = {
1766
2114
  capability: Schema.Literals(CLI_CAPABILITIES),
1767
2115
  invocation_kind: Schema.Literals(CLI_INVOCATION_KINDS),
1768
2116
  invocation_id: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u)),
1769
- cli_identity_kind: Schema.optional(Schema.Literal("anonymous")),
2117
+ cli_identity_kind: Schema.optional(Schema.Literals(["anonymous", "identified"])),
1770
2118
  anonymous_id: Schema.optional(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u))),
2119
+ auth_user_id: Schema.optional(Schema.String.check(Schema.isPattern(/^[^\s]{1,256}$/u))),
1771
2120
  occurred_at: Schema.Number,
1772
2121
  cli_version: CliRelease,
1773
2122
  sdk_version: CliRelease,
@@ -4446,8 +4795,7 @@ const country = normalize((s) => s.toUpperCase()).pipe(Schema.refine((s) => COUN
4446
4795
  const credential = Schema.RedactedFromValue(Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(8192), Schema.isPattern(/^[A-Za-z0-9._~-]+$/u)), { label: "native authorization credential" });
4447
4796
  const otp = Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[0-9]{6}$/u)), { label: "OTP" });
4448
4797
  const scopes = Schema.Tuple([Schema.Literal(NATIVE_CLIENT_SCOPE)]);
4449
- /** Exact HTTPS origin, no credentials/path/query and no retired cloud deployment. */
4450
- const NativeHttpsOriginSchema = Schema.String.check(Schema.makeFilter((value) => {
4798
+ const isNativeHttpsOriginValue = (value) => {
4451
4799
  try {
4452
4800
  const url = new URL(value);
4453
4801
  const host = url.hostname.toLowerCase().replace(/\.$/u, "");
@@ -4455,20 +4803,43 @@ const NativeHttpsOriginSchema = Schema.String.check(Schema.makeFilter((value) =>
4455
4803
  } catch {
4456
4804
  return false;
4457
4805
  }
4458
- }, { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts" }));
4806
+ };
4807
+ Schema.String.check(Schema.makeFilter(isNativeHttpsOriginValue, { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts" }));
4808
+ const isLoopbackOriginValue = (value) => {
4809
+ try {
4810
+ const url = new URL(value);
4811
+ const host = url.hostname.toLowerCase().replace(/\.$/u, "");
4812
+ return url.protocol === "http:" && value === url.origin && !url.username && !url.password && !url.search && !url.hash && (host === "127.0.0.1" || host === "[::1]" || host === "localhost");
4813
+ } catch {
4814
+ return false;
4815
+ }
4816
+ };
4817
+ Schema.String.check(Schema.makeFilter(isLoopbackOriginValue, { message: "must be a canonical credential-free loopback HTTP origin" }));
4818
+ /**
4819
+ * Either a standing-tier HTTPS origin or a devnet loopback origin. A `Union` of
4820
+ * the two refined string schemas does not type-check in this Effect version —
4821
+ * it widens both fields to `unknown` — so the disjunction lives in one filter
4822
+ * and the struct check below is what binds a loopback origin to
4823
+ * `environment: "local"`. Loopback is never admissible on a standing tier.
4824
+ */
4825
+ const NativeOriginSchema = Schema.String.check(Schema.makeFilter((value) => isNativeHttpsOriginValue(value) || isLoopbackOriginValue(value), { message: "must be a canonical credential-free HTTPS origin outside retired Convex cloud hosts, or a loopback HTTP origin for the local devnet" }));
4459
4826
  const NativeClientConfigurationSchema = Schema.Struct({
4460
4827
  clientId: Schema.Literal(NATIVE_CLIENT_ID),
4461
4828
  applicationId: AppIdSchema,
4462
4829
  keyId,
4463
4830
  environment: Schema.Literals([
4831
+ "local",
4464
4832
  "development",
4465
4833
  "staging",
4466
4834
  "production"
4467
4835
  ]),
4468
4836
  keyEnvironment: Schema.Literals(["test", "live"]),
4469
- issuer: NativeHttpsOriginSchema,
4470
- verificationOrigin: NativeHttpsOriginSchema
4471
- }).annotate(strict).check(Schema.makeFilter((c) => c.keyEnvironment === (c.environment === "production" ? "live" : "test"), { message: "key environment must match the configured deployment" })).annotate(strict);
4837
+ issuer: NativeOriginSchema,
4838
+ verificationOrigin: NativeOriginSchema
4839
+ }).annotate(strict).check(Schema.makeFilter((c) => c.keyEnvironment === (c.environment === "production" ? "live" : "test"), { message: "key environment must match the configured deployment" })).check(Schema.makeFilter((c) => {
4840
+ const loopback = isLoopbackOriginValue(c.issuer) || isLoopbackOriginValue(c.verificationOrigin);
4841
+ return c.environment === "local" ? isLoopbackOriginValue(c.issuer) && isLoopbackOriginValue(c.verificationOrigin) : !loopback;
4842
+ }, { message: "loopback origins belong to the local devnet environment alone" })).annotate(strict);
4472
4843
  const NativeSignupProfileSchema = Schema.Struct({
4473
4844
  displayName: normalize((s) => s.trim()).check(Schema.isMinLength(1), Schema.isMaxLength(200)),
4474
4845
  country,
@@ -4522,7 +4893,7 @@ const profileSummary = {
4522
4893
  handle,
4523
4894
  country
4524
4895
  };
4525
- Schema.Union([
4896
+ const ClientRequestViewSchema = Schema.Union([
4526
4897
  Schema.Struct({
4527
4898
  ...request,
4528
4899
  phase: Schema.Literal(CLIENT_AUTHORIZATION_PHASE.OTP_REQUIRED),
@@ -4589,8 +4960,7 @@ Schema.Struct({
4589
4960
  Schema.Struct({
4590
4961
  client_id: Schema.Literal(NATIVE_CLIENT_ID),
4591
4962
  grant_type: Schema.Literal("refresh_token"),
4592
- refresh_token: credential,
4593
- refreshAttemptId: Schema.RedactedFromValue(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{32,128}$/u)), { label: "refresh attempt" })
4963
+ refresh_token: credential
4594
4964
  }).annotate(strict);
4595
4965
  Schema.Struct({
4596
4966
  client_id: Schema.Literal(NATIVE_CLIENT_ID),
@@ -4662,6 +5032,33 @@ Schema.Struct({
4662
5032
  maximum: NATIVE_CLIENT_LIMITS.requestMs / 1e3
4663
5033
  }))
4664
5034
  }).annotate(strict);
5035
+ Schema.Struct({
5036
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
5037
+ token: credential,
5038
+ token_type_hint: Schema.optional(Schema.Literals([
5039
+ "access_token",
5040
+ "refresh_token",
5041
+ "device_code"
5042
+ ]))
5043
+ }).annotate(strict);
5044
+ Schema.Struct({
5045
+ client_id: Schema.Literal(NATIVE_CLIENT_ID),
5046
+ token: credential
5047
+ }).annotate(strict);
5048
+ Schema.Struct({
5049
+ error: Schema.Literals([
5050
+ "authorization_pending",
5051
+ "slow_down",
5052
+ "access_denied",
5053
+ "invalid_grant",
5054
+ "invalid_request",
5055
+ "expired_token",
5056
+ "server_error"
5057
+ ]),
5058
+ refusal: Schema.optional(Schema.Literals(CLIENT_AUTHORIZATION_REFUSALS)),
5059
+ retryable: Schema.Boolean,
5060
+ retry_after: Schema.optional(nonNegativeInt)
5061
+ }).annotate(strict);
4665
5062
  //#endregion
4666
5063
  //#region src/internal/invocation-observation.ts
4667
5064
  const INVOCATION_OBSERVATION = Symbol("capxul.invocation-observation");
@@ -6822,7 +7219,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
6822
7219
  }
6823
7220
  //#endregion
6824
7221
  //#region package.json
6825
- var version = "4.20.0-beta.4";
7222
+ var version = "4.20.0-beta.5";
6826
7223
  //#endregion
6827
7224
  //#region src/telemetry/exception-projection.ts
6828
7225
  /** Fixed fallback for failures that have no safe message. */
@@ -9411,7 +9808,7 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
9411
9808
  ["command", CLI_COMMAND_NAMES],
9412
9809
  ["capability", CLI_CAPABILITIES],
9413
9810
  ["invocation_kind", CLI_INVOCATION_KINDS],
9414
- ["cli_identity_kind", ["anonymous"]],
9811
+ ["cli_identity_kind", ["anonymous", "identified"]],
9415
9812
  ["output_mode", ["human", "json"]],
9416
9813
  ["unit", [
9417
9814
  "1",
@@ -11333,13 +11730,15 @@ var ConvexAccountAdapter = class {
11333
11730
  #convex;
11334
11731
  #fns;
11335
11732
  #telemetry;
11733
+ #rates;
11336
11734
  constructor(deps) {
11337
11735
  this.#convex = deps.convex;
11338
11736
  this.#fns = deps.functions ?? DEFAULT_FUNCTIONS$3;
11339
11737
  this.#telemetry = deps.telemetry;
11738
+ this.#rates = deps.rates ?? MONEY_READ_RATES;
11340
11739
  }
11341
11740
  readBalance(input) {
11342
- return retryIdempotentRead(this.#convex.action(this.#fns.readBalance, copyInvocationObservation(input, { chainId: wireChainId(input.chainId) })), CAPXUL_OPERATIONS.accounts.read, this.#telemetry, input).pipe(Effect.mapError((error) => accountReadErrorFromCapxul("readBalance", error.publicError, error)), Effect.flatMap((wire) => brandAccountEffect("readBalance", wire)), Effect.catchDefect((cause) => Effect.fail(accountReadErrorFromUnknown("readBalance", cause))));
11741
+ return retryIdempotentRead(this.#convex.action(this.#fns.readBalance, copyInvocationObservation(input, { chainId: wireChainId(input.chainId) })), CAPXUL_OPERATIONS.accounts.read, this.#telemetry, input).pipe(Effect.mapError((error) => accountReadErrorFromCapxul("readBalance", error.publicError, error)), Effect.flatMap((wire) => brandAccountEffect("readBalance", wire, this.#rates)), Effect.catchDefect((cause) => Effect.fail(accountReadErrorFromUnknown("readBalance", cause))));
11343
11742
  }
11344
11743
  fundFromFaucet(input) {
11345
11744
  const operation = "fundFromFaucet";
@@ -11352,26 +11751,21 @@ var ConvexAccountAdapter = class {
11352
11751
  }).pipe(Effect.mapError((error) => accountReadErrorFromCapxul(operation, error.publicError, error)), Effect.map((wire) => ({ txHash: wire.txHash })), Effect.catchDefect((cause) => Effect.fail(accountReadErrorFromUnknown(operation, cause))));
11353
11752
  }
11354
11753
  };
11355
- function ConvexAccountLayer() {
11754
+ function ConvexAccountLayer(input = {}) {
11356
11755
  return Layer.effect(AccountReadPortTag, Effect.all([ConvexCallPortTag, TelemetryPortTag]).pipe(Effect.map(([convex, telemetry]) => new ConvexAccountAdapter({
11357
11756
  convex,
11358
- telemetry
11757
+ telemetry,
11758
+ ...input
11359
11759
  }))));
11360
11760
  }
11361
- function brandAccountEffect(operation, wire) {
11761
+ function brandAccountEffect(operation, wire, rates) {
11362
11762
  return Effect.try({
11363
- try: () => brandAccount(wire),
11763
+ try: () => brandAccount(wire, rates),
11364
11764
  catch: (cause) => accountReadErrorFromUnknown(operation, cause)
11365
11765
  });
11366
11766
  }
11367
- function brandAccount(wire) {
11368
- const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
11369
- const available = fromWei(wire.rawAvailableBalance ?? wire.rawBalance, wire.decimals, wire.currency);
11370
- return {
11371
- id: toAccountId(wire.accountId),
11372
- balance,
11373
- available
11374
- };
11767
+ function brandAccount(wire, rates) {
11768
+ return accountFromPositions(toAccountId(wire.accountId), wire.positions, void 0, rates);
11375
11769
  }
11376
11770
  function accountReadErrorFromUnknown(operation, cause) {
11377
11771
  if (cause instanceof CapxulError) return accountReadErrorFromCapxul(operation, cause);
@@ -11510,16 +11904,16 @@ function brandOrgView(wire, treasury, viewerRole) {
11510
11904
  };
11511
11905
  }
11512
11906
  /**
11513
- * Build the Org treasury `Account` from the raw on-chain `balanceOf` integer
11514
- * (the D3 RPC read). `available === balance` for a treasury with no envelope
11515
- * partition yet (a fresh Org reads back $0).
11907
+ * Build the Org treasury `Account` from the raw per-asset `balanceOf` reads
11908
+ * (the D3 RPC pull). `available === balance` for a treasury with no envelope
11909
+ * partition yet (a fresh Org reads back a zero position per asset).
11910
+ *
11911
+ * `rates` is the oracle the headline prices with (D6). The live read passes the
11912
+ * configured one; it defaults to `PEG_RATES` so an unpegged holding reads as
11913
+ * unrated rather than as a number nobody priced.
11516
11914
  */
11517
11915
  function brandOrgTreasury(input) {
11518
- return {
11519
- id: toAccountId(`account_${orgIdBody(input.orgId)}`),
11520
- balance: input.money,
11521
- available: input.money
11522
- };
11916
+ return accountFromAccountPositions(toAccountId(`account_${orgIdBody(input.orgId)}`), input.balances, void 0, input.rates);
11523
11917
  }
11524
11918
  function brandOrgRole(wire) {
11525
11919
  return {
@@ -11606,10 +12000,12 @@ var ConvexOrganizationAdapter = class {
11606
12000
  #convex;
11607
12001
  #fns;
11608
12002
  #telemetry;
12003
+ #rates;
11609
12004
  constructor(input) {
11610
12005
  this.#convex = input.convex;
11611
12006
  this.#fns = input.functions ?? DEFAULT_FUNCTIONS$1;
11612
12007
  this.#telemetry = input.telemetry;
12008
+ this.#rates = input.rates ?? MONEY_READ_RATES;
11613
12009
  }
11614
12010
  createOrg(_input) {
11615
12011
  return Effect.fail(orgErrorFromCapxul("createOrg", Errors.notImplemented("organizationSetup", "use onboarding.completeOrganization")));
@@ -11657,15 +12053,11 @@ var ConvexOrganizationAdapter = class {
11657
12053
  const orgId = String(input.orgId);
11658
12054
  return retryIdempotentRead(this.#convex.action(this.#fns.readTreasury, copyInvocationObservation(input, { orgId })), CAPXUL_OPERATIONS.org.treasury, this.#telemetry, input).pipe(Effect.mapError((error) => orgErrorFromCapxul("readTreasury", error.publicError, error)), Effect.map((wire) => {
11659
12055
  if (wire.orgId !== orgId) throw Errors.invalidInput("orgId", "Organization treasury scope does not match");
11660
- const balance = fromWei(wire.rawBalance, wire.decimals, wire.currency);
11661
- const available = fromWei(wire.rawAvailableBalance, wire.decimals, wire.currency);
11662
- return {
11663
- ...brandOrgTreasury({
11664
- orgId: wire.orgId,
11665
- money: balance
11666
- }),
11667
- available
11668
- };
12056
+ return brandOrgTreasury({
12057
+ orgId: wire.orgId,
12058
+ balances: accountPositionsFromWire(wire.positions),
12059
+ rates: this.#rates
12060
+ });
11669
12061
  }));
11670
12062
  }
11671
12063
  };
@@ -13306,6 +13698,157 @@ function makeAuthMethods(deps) {
13306
13698
  };
13307
13699
  }
13308
13700
  //#endregion
13701
+ //#region src/surface/client-requests.ts
13702
+ function decodeView(body) {
13703
+ try {
13704
+ return {
13705
+ ok: true,
13706
+ value: Schema.decodeUnknownSync(ClientRequestViewSchema)(body)
13707
+ };
13708
+ } catch {
13709
+ return {
13710
+ ok: false,
13711
+ error: Errors.invalidInput("clientRequest", "view is not a closed phase")
13712
+ };
13713
+ }
13714
+ }
13715
+ function httpError$1(status) {
13716
+ if (status === 401) return {
13717
+ ok: false,
13718
+ error: Errors.notAuthenticated()
13719
+ };
13720
+ if (status === 404) return {
13721
+ ok: false,
13722
+ error: Errors.invalidInput("requestId", "is not a live request")
13723
+ };
13724
+ if (status === 429) return {
13725
+ ok: false,
13726
+ error: Errors.rateLimited({ resource: "clientRequest" })
13727
+ };
13728
+ return {
13729
+ ok: false,
13730
+ error: Errors.networkError("clientRequests")
13731
+ };
13732
+ }
13733
+ function makeClientRequestMethods(deps) {
13734
+ const fetchImpl = deps.fetch ?? fetch;
13735
+ const urlFor = (path, query) => {
13736
+ const resolved = resolveAuthClientUrl(deps.authBaseUrl, path);
13737
+ return query === void 0 ? resolved : `${resolved}?${query}`;
13738
+ };
13739
+ async function readView(requestId, options) {
13740
+ if (options?.signal?.aborted) return {
13741
+ ok: false,
13742
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.clientRequests.read })
13743
+ };
13744
+ try {
13745
+ const response = await fetchImpl(urlFor("/api/auth/native/request", `requestId=${encodeURIComponent(requestId)}`), {
13746
+ method: "GET",
13747
+ credentials: "include",
13748
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
13749
+ });
13750
+ const body = await response.json().catch(() => null);
13751
+ if (!response.ok) {
13752
+ const viewed = decodeView(body);
13753
+ return viewed.ok ? viewed : httpError$1(response.status);
13754
+ }
13755
+ return decodeView(body);
13756
+ } catch (cause) {
13757
+ if (options?.signal?.aborted) return {
13758
+ ok: false,
13759
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.clientRequests.read })
13760
+ };
13761
+ if (isCapxulError(cause)) return {
13762
+ ok: false,
13763
+ error: cause
13764
+ };
13765
+ return {
13766
+ ok: false,
13767
+ error: Errors.networkError("native request read failed")
13768
+ };
13769
+ }
13770
+ }
13771
+ async function postDecision(path, input, options) {
13772
+ if (options?.signal?.aborted) return {
13773
+ ok: false,
13774
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.clientRequests.decide })
13775
+ };
13776
+ try {
13777
+ const response = await fetchImpl(urlFor(path), {
13778
+ method: "POST",
13779
+ credentials: "include",
13780
+ headers: { "content-type": "application/json" },
13781
+ body: JSON.stringify({
13782
+ requestId: input.requestId,
13783
+ revision: input.revision
13784
+ }),
13785
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
13786
+ });
13787
+ const viewed = decodeView(await response.json().catch(() => null));
13788
+ if (viewed.ok) return viewed;
13789
+ if (!response.ok) return httpError$1(response.status);
13790
+ return viewed;
13791
+ } catch (cause) {
13792
+ if (options?.signal?.aborted) return {
13793
+ ok: false,
13794
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.clientRequests.decide })
13795
+ };
13796
+ if (isCapxulError(cause)) return {
13797
+ ok: false,
13798
+ error: cause
13799
+ };
13800
+ return {
13801
+ ok: false,
13802
+ error: Errors.networkError("native request decision failed")
13803
+ };
13804
+ }
13805
+ }
13806
+ return {
13807
+ read: (input, options) => readView(input.requestId, options),
13808
+ async sendOtp(input, options) {
13809
+ const view = await readView(input.requestId, options);
13810
+ if (!view.ok) return view;
13811
+ if (view.value.phase !== "otp_required" && view.value.phase !== "review") return {
13812
+ ok: false,
13813
+ error: Errors.wrongState({
13814
+ method: "clientRequests.sendOtp",
13815
+ currentState: view.value.phase,
13816
+ validStates: ["otp_required", "review"]
13817
+ })
13818
+ };
13819
+ const sent = await deps.signIn({ email: view.value.email }, options);
13820
+ if (!sent.ok) return sent;
13821
+ return {
13822
+ ok: true,
13823
+ value: void 0
13824
+ };
13825
+ },
13826
+ async verifyOtp(input, options) {
13827
+ const view = await readView(input.requestId, options);
13828
+ if (!view.ok) return view;
13829
+ if (view.value.phase !== "otp_required" && view.value.phase !== "review") return {
13830
+ ok: false,
13831
+ error: Errors.wrongState({
13832
+ method: "clientRequests.verifyOtp",
13833
+ currentState: view.value.phase,
13834
+ validStates: ["otp_required", "review"]
13835
+ })
13836
+ };
13837
+ const verified = await deps.verifyOtp({
13838
+ email: view.value.email,
13839
+ code: input.code
13840
+ }, options);
13841
+ if (!verified.ok) return verified;
13842
+ return {
13843
+ ok: true,
13844
+ value: void 0
13845
+ };
13846
+ },
13847
+ accept: (input, options) => postDecision("/api/auth/native/request/accept", input, options),
13848
+ cancel: (input, options) => postDecision("/api/auth/native/request/deny", input, options)
13849
+ };
13850
+ }
13851
+ //#endregion
13309
13852
  //#region src/surface/smart-account-deps.ts
13310
13853
  /** The single Context tag the `smartAccount.*` methods resolve. */
13311
13854
  var SmartAccountDepsTag = class extends Context.Service()("@capxul/sdk/SmartAccountDeps") {};
@@ -14678,18 +15221,15 @@ var OrgDepsTag = class extends Context.Service()("@capxul/sdk/OrgDeps") {};
14678
15221
  function orgDepsLayer(deps) {
14679
15222
  return Layer.succeed(OrgDepsTag, deps);
14680
15223
  }
14681
- /** Zero `Money` in the USDX-backed display currency (a fresh Org's treasury). */
14682
- function zeroMoney() {
14683
- return fromWei("0", 6, "USD");
14684
- }
14685
- /** A fresh Org's treasury Account: `$0` balance, `available === balance`. */
15224
+ /** A fresh Org's treasury Account: a zero position per settlement asset. */
14686
15225
  function zeroTreasury(orgId) {
14687
- const balance = zeroMoney();
14688
- return {
14689
- id: toAccountId(`account_${stripPrefix(orgId)}`),
14690
- balance,
14691
- available: balance
14692
- };
15226
+ return accountFromAccountPositions(toAccountId(`account_${stripPrefix(orgId)}`), CONFIGURED_MONEY_ASSETS.map((asset) => ({
15227
+ asset: asset.assetId,
15228
+ symbol: asset.symbol,
15229
+ decimals: asset.decimals,
15230
+ balance: "0",
15231
+ available: "0"
15232
+ })), void 0, MONEY_READ_RATES);
14693
15233
  }
14694
15234
  /** Drop a known `<prefix>_` so the remainder is reusable as another brand seed. */
14695
15235
  function stripPrefix(value) {
@@ -15097,8 +15637,7 @@ function makeOrgMethods(deps) {
15097
15637
  return {
15098
15638
  id: treasuryResult.value.id,
15099
15639
  address: org?.safeAddress ?? null,
15100
- balance: treasuryResult.value.balance,
15101
- available: treasuryResult.value.available
15640
+ balances: treasuryResult.value.balances
15102
15641
  };
15103
15642
  }), observationOnlyControls(controls), CAPXUL_OPERATIONS.org.account.get, deps.runPromise);
15104
15643
  } },
@@ -15202,23 +15741,27 @@ const organizationPaymentExecutionUnavailable = () => Promise.resolve({
15202
15741
  const holdingsContract = { current: makeFunctionReference(CAPXUL_FUNCTIONS["holdings/actions"].current) };
15203
15742
  //#endregion
15204
15743
  //#region src/domain/money/primary-holding.ts
15205
- const CURRENCY_BY_TOKEN_SYMBOL = /* @__PURE__ */ new Map([["USDX", toCurrencyCode("USD")]]);
15206
15744
  /**
15207
- * The designated display asset: the payable row holding the most money, or
15208
- * `null` when no row is payable.
15745
+ * The designated display asset: the LOWEST `id` among the renderable rows, or
15746
+ * `null` when no row is renderable.
15209
15747
  *
15210
- * A `HoldingRow` (`packages/wire/src/money.ts`) is a RAW CHAIN ROW, and three
15211
- * of its shapes cannot become a `Holding`. Each is SKIPPED rather than
15212
- * reported, because a row nobody can render is not a failure of the read:
15748
+ * A `HoldingRow` (`packages/wire/src/money.ts`) is a RAW CHAIN ROW. A native
15749
+ * row has no ERC-20 identity; a row with no `decimals` cannot be lifted into
15750
+ * major units; a row with no `symbol` cannot be named. Each is SKIPPED rather
15751
+ * than reported — a row nobody can render is not a failure of the read.
15213
15752
  *
15214
- * - no `decimals` the raw balance cannot be lifted into major units;
15215
- * - no `symbol` the asset cannot be named;
15216
- * - a symbol that stands for no supported `CurrencyCode` the balance
15217
- * cannot be stated as `Money` without inventing a currency for it.
15753
+ * WHICH row is named is decided by ASSET IDENTITY ascending `id` — never by
15754
+ * quantity. Two rows with different assets are not comparable money, so no
15755
+ * quantity can order them: 100 USDC is not "more" than 1 WETH without a rate,
15756
+ * and ranking major units would show whatever asset happens to have the most
15757
+ * decimal places as "primary". An identity order is arbitrary as well, but it
15758
+ * is stable and claims nothing about value. ADR-0028 R6 retires
15759
+ * `holdings.primary` outright; this export stays only until that retirement
15760
+ * lands with its remaining consumers, so no new policy is invented here.
15218
15761
  *
15219
15762
  * This replaces the app's hand pick in `formatCurrentHoldingsBalance`
15220
15763
  * (`dashboard-formatters.ts`): a `.find()` over a hardcoded `["USDX","USDC"]`
15221
- * list falling back to `rows[0]`. Ties keep the earlier row, so the same
15764
+ * list falling back to `rows[0]`. Duplicate ids keep the first row, so the same
15222
15765
  * snapshot always yields the same answer.
15223
15766
  */
15224
15767
  function primaryHolding(rows) {
@@ -15226,37 +15769,21 @@ function primaryHolding(rows) {
15226
15769
  for (const row of rows) {
15227
15770
  const holding = toHolding(row);
15228
15771
  if (holding === null) continue;
15229
- if (primary === null || compareDecimal(holding.available.value, primary.available.value) > 0) primary = holding;
15772
+ if (primary === null || holding.id < primary.id) primary = holding;
15230
15773
  }
15231
15774
  return primary;
15232
15775
  }
15233
15776
  function toHolding(row) {
15777
+ if (row.assetKind !== "erc20") return null;
15234
15778
  if (row.symbol === void 0 || row.decimals === void 0) return null;
15235
- const currency = currencyForTokenSymbol(row.symbol);
15236
- if (currency === null) return null;
15237
15779
  return {
15238
- id: row.assetKind === "native" ? "native" : row.tokenAddress.toLowerCase(),
15780
+ id: row.tokenAddress.toLowerCase(),
15781
+ assetKind: row.assetKind,
15239
15782
  symbol: row.symbol,
15240
- available: fromWei(row.rawBalance, row.decimals, currency)
15783
+ decimals: row.decimals,
15784
+ value: formatUnits(BigInt(row.rawBalance), row.decimals)
15241
15785
  };
15242
15786
  }
15243
- function currencyForTokenSymbol(symbol) {
15244
- return CURRENCY_BY_TOKEN_SYMBOL.get(symbol.toUpperCase()) ?? null;
15245
- }
15246
- /**
15247
- * Order two non-negative major-unit decimal strings without converting either
15248
- * to a JavaScript number. Rows carry different `decimals`, so the raw integers
15249
- * are not comparable: 1999999999 at 9 decimals is LESS than 2000000 at 6.
15250
- */
15251
- function compareDecimal(first, second) {
15252
- const [firstWhole = "0", firstFraction = ""] = first.split(".");
15253
- const [secondWhole = "0", secondFraction = ""] = second.split(".");
15254
- const width = Math.max(firstFraction.length, secondFraction.length);
15255
- const firstScaled = BigInt(firstWhole + firstFraction.padEnd(width, "0"));
15256
- const secondScaled = BigInt(secondWhole + secondFraction.padEnd(width, "0"));
15257
- if (firstScaled === secondScaled) return 0;
15258
- return firstScaled < secondScaled ? -1 : 1;
15259
- }
15260
15787
  //#endregion
15261
15788
  //#region src/surface/holdings.ts
15262
15789
  /** SPEC Freshness: past this block age the read is labelled stale. Nothing refuses. */
@@ -15597,6 +16124,7 @@ const closeClientResources = (close) => toCapxulResult(Effect.promise(close), La
15597
16124
  function assembleCapxulClient(input) {
15598
16125
  const observation = input;
15599
16126
  const authCache = input.authCache ?? detectAuthCacheAdapter();
16127
+ const accountSetup = input.accountSetup ?? "automatic";
15600
16128
  const effectRunner = input.effectRunner ?? {
15601
16129
  runSync: Effect.runSync,
15602
16130
  runPromise: Effect.runPromise
@@ -15667,7 +16195,7 @@ function assembleCapxulClient(input) {
15667
16195
  });
15668
16196
  kickProvisioning = accountBundle.kickProvisioning;
15669
16197
  const account = accountBundle.methods;
15670
- const auth = makeAuthMethods({
16198
+ const authCore = makeAuthMethods({
15671
16199
  actor,
15672
16200
  authClient: input.ports.authClient,
15673
16201
  authCache,
@@ -15685,10 +16213,19 @@ function assembleCapxulClient(input) {
15685
16213
  }
15686
16214
  if (browserSigner.statusStore?.status() === "unknown" && typeof browserSigner.ensureWalletReady === "function") browserSigner.ensureWalletReady().catch(() => {});
15687
16215
  }
16216
+ if (accountSetup === "explicit") return;
15688
16217
  await detectPendingOrgInvitations?.();
15689
16218
  kickProvisioning?.();
15690
16219
  }
15691
16220
  });
16221
+ const auth = {
16222
+ ...authCore,
16223
+ clientRequests: makeClientRequestMethods({
16224
+ authBaseUrl: input.bootstrap.authBaseUrl,
16225
+ signIn: authCore.signIn,
16226
+ verifyOtp: authCore.verifyOtp
16227
+ })
16228
+ };
15692
16229
  const smartAccount = makeSmartAccountMethods({
15693
16230
  actor,
15694
16231
  smartAccountPort: input.ports.smartAccount,
@@ -15852,6 +16389,7 @@ function assembleCapxulClient(input) {
15852
16389
  ...input.ports.indexerRead === void 0 ? {} : { indexer: input.ports.indexerRead },
15853
16390
  organizationSetup: { getProofReceipt: (orgId) => runPortEffect(input.ports.convexCall.query(organizationSetupProofReceiptQuery, { orgId })) },
15854
16391
  telemetry: input.ports.telemetry,
16392
+ accountSetup,
15855
16393
  captureExternalAddressAcknowledged: (address, organizationId) => {
15856
16394
  const telemetry = input.ports.telemetry;
15857
16395
  if (telemetry === void 0) return;
@@ -16917,6 +17455,7 @@ async function createCapxulClientWithSignerControls(input, controls) {
16917
17455
  bootstrap: adapters.value.bootstrap,
16918
17456
  authCache: adapters.value.ports.authCache,
16919
17457
  requirement: input.requirement ?? "none",
17458
+ accountSetup: input.accountSetup,
16920
17459
  ...signer === void 0 ? {} : { signer },
16921
17460
  orgPort: Context.get(adapters.value.context, OrgPortTag),
16922
17461
  ...signer === void 0 ? {} : { organizationSetup: new ConvexOrganizationSetupAdapter({
@@ -17128,4 +17667,4 @@ function productionIndexerReadPort(input, ports, observation) {
17128
17667
  });
17129
17668
  }
17130
17669
  //#endregion
17131
- export { signerFailure as A, isRestoring as B, normalizeExceptionErrorKind as C, fromWei as D, safeExceptionLabel as E, CAPXUL_OPERATIONS as F, isCapxulOperation as I, normalizeCapxulOperation as L, PAYMENT_DIRECTIONS as M, PAYMENT_STATUSES as N, isSettingUpLifecycle as O, redactTelemetryEvent as P, destination as R, SDK_VERSION$1 as S, projectSdkException as T, fingerprintPaymentIntent as _, smartAccountErrorFromCapxul as a, failureDetail as b, identityErrorFromCapxul as c, embeddedSigner as d, openfortEmbeddedSigner as f, devPrivateKeySigner as g, deriveDevPrivateKey as h, assembleCapxulClient as i, resolveFailureMode as j, injectedWalletSigner as k, convexCallErrorFromCapxul as l, openfortEmbeddedWalletPort as m, createCapxulClientWithSignerControls as n, accountReadErrorFromCapxul as o, openfortEmbeddedSignerFromWallet as p, postHogObservability as r, wireChainId as s, createCapxulClient as t, bootstrapErrorFromCapxul as u, toWei as v, normalizeExceptionOperation as w, EXCEPTION_MESSAGE as x, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT as y, isClaimed as z };
17670
+ export { resolveFailureMode as A, valueIn as B, normalizeExceptionErrorKind as C, isSettingUpLifecycle as D, safeExceptionLabel as E, isCapxulOperation as F, isMoneyParseError as G, assetSymbolFor as H, normalizeCapxulOperation as I, isClaimed as J, parseMoney as K, accountFromAccountPositions as L, PAYMENT_STATUSES as M, redactTelemetryEvent as N, injectedWalletSigner as O, CAPXUL_OPERATIONS as P, positionPeg as R, SDK_VERSION$1 as S, projectSdkException as T, formatAssetAmount as U, assetAmountFromRaw as V, parseAssetAmount as W, isRestoring as Y, fingerprintPaymentIntent as _, smartAccountErrorFromCapxul as a, failureDetail as b, identityErrorFromCapxul as c, embeddedSigner as d, openfortEmbeddedSigner as f, devPrivateKeySigner as g, deriveDevPrivateKey as h, assembleCapxulClient as i, PAYMENT_DIRECTIONS as j, signerFailure as k, convexCallErrorFromCapxul as l, openfortEmbeddedWalletPort as m, createCapxulClientWithSignerControls as n, accountReadErrorFromCapxul as o, openfortEmbeddedSignerFromWallet as p, destination as q, postHogObservability as r, wireChainId as s, createCapxulClient as t, bootstrapErrorFromCapxul as u, toWei as v, normalizeExceptionOperation as w, EXCEPTION_MESSAGE as x, CAPXUL_SDK_EXPECTED_OUTCOME_EVENT as y, PEG_RATES as z };