@capxul/sdk 4.2.0 → 4.20.0-beta.1

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,5 +1,5 @@
1
- import { Context, Effect } from "effect";
2
1
  import { Account, Address, Hex } from "viem";
2
+ import { Context, Effect } from "effect";
3
3
  //#region ../errors/src/chain-cause.d.ts
4
4
  /**
5
5
  * Why an operation failed — the one flat vocabulary. The first four are the
@@ -257,10 +257,6 @@ declare const Errors: {
257
257
  }) => CapxulError;
258
258
  };
259
259
  //#endregion
260
- //#region ../config/src/capxul-payments-v2.d.ts
261
- /** Immutable CapxulPaymentsV2 deployment on Base Sepolia. */
262
- declare const CAPXUL_PAYMENTS_V2_ADDRESS: Address;
263
- //#endregion
264
260
  //#region ../types/src/brand.d.ts
265
261
  /**
266
262
  * Nominal type helper. `Brand<T, B>` is structurally a `T` at runtime but
@@ -275,6 +271,13 @@ type Brand<T, B extends string> = T & {
275
271
  //#endregion
276
272
  //#region ../types/src/index.d.ts
277
273
  type Address$1 = Brand<string, "Address">;
274
+ /** Canonical ERC-20 CAIP-19 identity. Currency is not asset identity. */
275
+ type AssetId = Brand<string, "AssetId">;
276
+ /** One asset quantity in major units. Registry metadata supplies precision. */
277
+ type AssetAmount = {
278
+ readonly asset: AssetId;
279
+ readonly value: string;
280
+ };
278
281
  type Email = Brand<string, "Email">;
279
282
  type TesterKind = "human" | "e2e" | "agent";
280
283
  type Profile$1 = {
@@ -375,6 +378,10 @@ declare function toPartyId(raw: unknown): PartyId;
375
378
  declare function toHandle(raw: unknown): Handle;
376
379
  declare function toCountryCode(raw: unknown): CountryCode;
377
380
  //#endregion
381
+ //#region ../config/src/capxul-payments-v2.d.ts
382
+ /** Immutable CapxulPaymentsV2 deployment on Base Sepolia. */
383
+ declare const CAPXUL_PAYMENTS_V2_ADDRESS: Address;
384
+ //#endregion
378
385
  //#region src/ports/auth-cache.d.ts
379
386
  /**
380
387
  * AuthCachePort stores the user session and the cached
@@ -601,4 +608,21 @@ interface Eip1193RequestProvider {
601
608
  */
602
609
  declare function injectedWalletSigner(provider: Eip1193RequestProvider): CapxulSigner;
603
610
  //#endregion
604
- export { PublishableKey as $, AppId as A, EVM_ADDRESS_RE as B, CachedJwt as C, RevertSummary as Ct, AllowanceKey as D, Address$1 as E, ChainId as F, OrgId as G, EpochMs as H, CountryCode as I, PayrollGroupId as J, PartyId as K, CurrencyCode as L, AuthUserId as M, BlockNumber as N, AllowedOrigin as O, BudgetId as P, Profile$1 as Q, DocumentHash as R, AuthCachePortTag as S, FailureMode as St, AccountId as T, Handle as U, Email as V, Money as W, PermissionAssignmentId as X, PayrollRunId as Y, PermissionId as Z, ClockPort as _, isCapxulError as _t, SignerStatusStore as a, WeiAmount as at, AuthCacheError as b, ChainUpstream as bt, AccountProviderSource as c, toHandle as ct, eip1193AccountProvider as d, CAPXUL_ERROR_CODES as dt, RoleKey as et, localPrivateKeyAccountProvider as f, CapxulError as ft, SmartAccount as g, Failure as gt, Session as h, Errors as ht, SignerStatus as i, TxHash as it, AuthSession as j, AnonymousDistinctId as k, AccountRequirement as l, toPartyId as lt, Profile as m, CapxulErrorDetails as mt, CapxulSigner as n, SmartAccount$1 as nt, injectedWalletSigner as o, toAddress as ot, CapxulResult as p, CapxulErrorCode as pt, PaymentCommandId as q, Eip1193RequestProvider as r, TesterKind as rt, AccountProvider as s, toCountryCode as st, CapxulDigestSigner as t, SessionToken as tt, Eip1193Provider as u, CAPXUL_PAYMENTS_V2_ADDRESS as ut, AuthClientPort as v, CHAIN_UPSTREAMS as vt, Account$1 as w, AuthCachePort as x, FAILURE_MODES as xt, CanSendOtpStatus as y, ChainCause as yt, DurationMs as z };
611
+ //#region src/adapters/auth-client/OAuthBearerAuthClient.d.ts
612
+ interface OAuthBearerAuthClientInput {
613
+ /** The exchange endpoint: `{authBaseUrl}/api/auth/agent/exchange`. */
614
+ readonly exchangeUrl: string;
615
+ /** The shared secret the exchange endpoint checks. The caller holds it. */
616
+ readonly exchangeSecret: string;
617
+ /** The agent's OAuth access token. */
618
+ readonly accessToken: string;
619
+ readonly fetch?: typeof fetch;
620
+ readonly clock?: ClockPort;
621
+ }
622
+ /**
623
+ * `AuthClientPort` over the backend agent-token exchange. Build one per OAuth
624
+ * access token and hand it to `createCapxulClient({ authClient })`.
625
+ */
626
+ declare function oauthBearerAuthClient(input: OAuthBearerAuthClientInput): AuthClientPort;
627
+ //#endregion
628
+ export { PayrollGroupId as $, AllowanceKey as A, ChainId as B, AuthCachePort as C, ChainCause as Ct, Account$1 as D, RevertSummary as Dt, CAPXUL_PAYMENTS_V2_ADDRESS as E, FailureMode as Et, AssetId as F, EVM_ADDRESS_RE as G, CurrencyCode as H, AuthSession as I, Handle as J, Email as K, AuthUserId as L, AnonymousDistinctId as M, AppId as N, AccountId as O, AssetAmount as P, PaymentCommandId as Q, BlockNumber as R, AuthCacheError as S, CHAIN_UPSTREAMS as St, CachedJwt as T, FAILURE_MODES as Tt, DocumentHash as U, CountryCode as V, DurationMs as W, OrgId as X, Money as Y, PartyId as Z, Session as _, CapxulErrorCode as _t, Eip1193RequestProvider as a, RoleKey as at, AuthClientPort as b, Failure as bt, injectedWalletSigner as c, TesterKind as ct, AccountRequirement as d, toAddress as dt, PayrollRunId as et, Eip1193Provider as f, toCountryCode as ft, Profile as g, CapxulError as gt, CapxulResult as h, CAPXUL_ERROR_CODES as ht, CapxulSigner as i, PublishableKey as it, AllowedOrigin as j, Address$1 as k, AccountProvider as l, TxHash as lt, localPrivateKeyAccountProvider as m, toPartyId as mt, oauthBearerAuthClient as n, PermissionId as nt, SignerStatus as o, SessionToken as ot, eip1193AccountProvider as p, toHandle as pt, EpochMs as q, CapxulDigestSigner as r, Profile$1 as rt, SignerStatusStore as s, SmartAccount$1 as st, OAuthBearerAuthClientInput as t, PermissionAssignmentId as tt, AccountProviderSource as u, WeiAmount as ut, SmartAccount as v, CapxulErrorDetails as vt, AuthCachePortTag as w, ChainUpstream as wt, CanSendOtpStatus as x, isCapxulError as xt, ClockPort as y, Errors as yt, BudgetId as z };
@@ -1,5 +1,5 @@
1
- import { Context, Data, Effect, Layer, Result } from "effect";
2
1
  import { concat, encodeAbiParameters, encodeFunctionData, encodePacked, getContractAddress, keccak256, padHex, stringToHex, toBytes, toEventSelector, toFunctionSelector, toFunctionSignature } from "viem";
2
+ import { Context, Data, Effect, Layer, Result } from "effect";
3
3
  //#region ../errors/src/secret-material.ts
4
4
  const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
5
5
  "password",
@@ -104,14 +104,19 @@ function redactUrlSecrets(value, source) {
104
104
  changed ||= safeName !== name || safeEntry !== entry;
105
105
  return [safeName, safeEntry];
106
106
  })).toString();
107
- const provider = source !== void 0 || /(?:^|\.)(?:alchemy\.com|alchemyapi\.io|infura\.io)$/iu.test(url.hostname);
108
- const segments = (absolute || protocolRelative ? url.pathname : value.split(/[?#]/u)[0] ?? "").split("/");
109
- const path = segments.map((segment, index) => {
107
+ const provider = source === "alchemy" || source === "infura" || /(?:^|\.)(?:alchemy\.com|alchemyapi\.io|infura\.io)$/iu.test(url.hostname);
108
+ const rawPath = absolute || protocolRelative ? url.pathname : value.split(/[?#]/u)[0] ?? "";
109
+ const segments = rawPath.split("/");
110
+ let path = segments.map((segment, index) => {
110
111
  const decoded = decodeUrlComponent(segment);
111
112
  const masked = provider && /^v[23]$/u.test(decodeUrlComponent(segments[index - 1] ?? "")) && decoded.length > 0 ? "[REDACTED]" : maskText(decoded);
112
113
  changed ||= masked !== decoded;
113
114
  return masked === decoded ? segment : encodeURIComponent(masked);
114
115
  }).join("/");
116
+ if (source === "gateway" && rawPath.length > 0) {
117
+ path = `${rawPath.startsWith("/") ? "/" : ""}${encodeURIComponent("[REDACTED]")}`;
118
+ changed = true;
119
+ }
115
120
  const query = maskParams(url.searchParams);
116
121
  const fragment = url.hash.slice(1);
117
122
  const decodedFragment = decodeUrlComponent(fragment);
@@ -126,6 +131,7 @@ function redactUrlSecrets(value, source) {
126
131
  if (!changed) return value;
127
132
  return `${origin}${path}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
128
133
  } catch {
134
+ if (source === "gateway") return "[REDACTED]";
129
135
  return maskText(value.replace(/(https?:\/\/)[^/\s@]+@/giu, "$1[REDACTED]@").replace(/([?&#])([^=&#]+)=([^&#]*)/gu, (whole, separator, name) => isCredentialField(decodeUrlComponent(name)) || /^(?:code|draft|__posthog)$/iu.test(decodeUrlComponent(name)) ? `${separator}${name}=[REDACTED]` : whole));
130
136
  }
131
137
  }
@@ -841,7 +847,29 @@ function decodeConvexError(err) {
841
847
  /** Canonical handle syntax shared by validation and branded constructors. */
842
848
  const HANDLE_RE = /^[a-z0-9_-]{3,30}$/;
843
849
  //#endregion
850
+ //#region ../config/src/handle.ts
851
+ const RESERVED_HANDLES = /* @__PURE__ */ new Set([
852
+ "admin",
853
+ "api",
854
+ "capxul",
855
+ "help",
856
+ "root",
857
+ "support",
858
+ "system",
859
+ "www"
860
+ ]);
861
+ function normalizeHandle(raw) {
862
+ return raw.trim().toLowerCase().replace(/^@/, "");
863
+ }
864
+ function validateHandle(raw) {
865
+ const handle = normalizeHandle(raw);
866
+ if (!HANDLE_RE.test(handle)) throw Errors.invalidInput("handle", "must be 3-30 characters of a-z, 0-9, underscore, or hyphen");
867
+ if (RESERVED_HANDLES.has(handle)) throw Errors.invalidInput("handle", "handle is reserved");
868
+ return handle;
869
+ }
870
+ //#endregion
844
871
  //#region ../types/src/index.ts
872
+ const ASSET_ID_RE = /^eip155:([1-9][0-9]*)\/erc20:(0x[0-9a-f]{40})$/;
845
873
  const EVM_ADDRESS_RE = /^0x[0-9a-f]{40}$/i;
846
874
  const BYTES32_RE = /^0x[0-9a-f]{64}$/i;
847
875
  const ZERO_BYTES32 = `0x${"0".repeat(64)}`;
@@ -880,6 +908,7 @@ const COUNTRY_CODE_RE = /^[A-Z]{2}$/;
880
908
  const ACCOUNT_ID_RE = /^account_[0-9A-Za-z]+$/;
881
909
  const PARTY_ID_RE = /^party_[0-9A-Za-z]+$/;
882
910
  const APP_ID_RE = /^app_[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
911
+ const TX_HASH_RE = /^0x[0-9a-f]{64}$/i;
883
912
  const WEI_RE = /^[0-9]+$/;
884
913
  Math.floor(Number.MAX_SAFE_INTEGER / 1e3);
885
914
  function toAddress(raw) {
@@ -935,6 +964,10 @@ function toPublishableKey(raw) {
935
964
  if (typeof raw !== "string" || !PUBLISHABLE_KEY_PATTERN.test(raw)) throw Errors.invalidInput("publishableKey", invalidValueReason("must match cap_pk_(test|live) plus 32 Crockford base32 chars", raw));
936
965
  return raw;
937
966
  }
967
+ function toTxHash(raw) {
968
+ if (typeof raw !== "string" || !TX_HASH_RE.test(raw)) throw Errors.invalidInput("txHash", invalidValueReason("must be 0x + 64 hex chars", raw));
969
+ return raw.toLowerCase();
970
+ }
938
971
  function toEpochMs(raw) {
939
972
  assertSafeNonNegativeInteger(raw, "epochMs");
940
973
  return raw;
@@ -943,10 +976,25 @@ function toEpochSeconds(raw) {
943
976
  assertSafeNonNegativeInteger(raw, "epochSeconds");
944
977
  return raw;
945
978
  }
979
+ /** Validate an already canonical identity without changing signed input. */
980
+ function toAssetId(raw) {
981
+ const match = typeof raw === "string" ? ASSET_ID_RE.exec(raw) : null;
982
+ if (match === null || match[0] !== raw || !Number.isSafeInteger(Number(match[1]))) throw Errors.invalidInput("assetId", "must be a canonical ERC-20 CAIP-19 identifier");
983
+ if (match[2] === "0x0000000000000000000000000000000000000000") throw Errors.invalidInput("assetId", "must identify a nonzero token contract");
984
+ return raw;
985
+ }
986
+ /** Build an identity from verified chain facts. Normalize the token address. */
987
+ function assetIdFor(chainId, tokenAddress) {
988
+ return toAssetId(`eip155:${toChainId(chainId)}/erc20:${toAddress(tokenAddress)}`);
989
+ }
946
990
  function toChainId(raw) {
947
991
  if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) throw Errors.invalidInput("chainId", invalidValueReason("must be a positive safe integer", raw));
948
992
  return raw;
949
993
  }
994
+ function toWeiAmount(raw) {
995
+ if (typeof raw !== "string" || !WEI_RE.test(raw)) throw Errors.invalidInput("weiAmount", invalidValueReason("must be a non-negative integer string", raw));
996
+ return raw;
997
+ }
950
998
  function toCountryCode(raw) {
951
999
  if (typeof raw !== "string") throw Errors.invalidInput("countryCode", "must be a string");
952
1000
  const upper = raw.toUpperCase();
@@ -1005,31 +1053,121 @@ function invalidValueReason(prefix, raw) {
1005
1053
  return `${prefix}: ${String(raw)}`;
1006
1054
  }
1007
1055
  //#endregion
1008
- //#region ../config/src/handle.ts
1009
- const RESERVED_HANDLES = /* @__PURE__ */ new Set([
1010
- "admin",
1011
- "api",
1012
- "capxul",
1013
- "help",
1014
- "root",
1015
- "support",
1016
- "system",
1017
- "www"
1018
- ]);
1019
- function normalizeHandle(raw) {
1020
- return raw.trim().toLowerCase().replace(/^@/, "");
1021
- }
1022
- function validateHandle(raw) {
1023
- const handle = normalizeHandle(raw);
1024
- if (!HANDLE_RE.test(handle)) throw Errors.invalidInput("handle", "must be 3-30 characters of a-z, 0-9, underscore, or hyphen");
1025
- if (RESERVED_HANDLES.has(handle)) throw Errors.invalidInput("handle", "handle is reserved");
1026
- return handle;
1027
- }
1028
- //#endregion
1029
1056
  //#region ../config/src/tokens.ts
1030
1057
  /** `TestUSDC` ("USDX") — Base Sepolia, 6 decimals, open `mint`. (Canon §1.) */
1031
1058
  const USDX_ADDRESS_BASE_SEPOLIA = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
1032
- USDX_ADDRESS_BASE_SEPOLIA.toLowerCase();
1059
+ const USDX_DEPLOYMENT_START_BLOCK = 39860173;
1060
+ /**
1061
+ * The ERC-20 `symbol()` the deployed `TestUSDC` contract returns
1062
+ * (`packages/contracts/src/TestUSDC.sol:10`). It travels with the address and
1063
+ * the decimals so a holdings row can never carry one token's address with
1064
+ * another token's display symbol. This is the display symbol, not the
1065
+ * currency: see `USDX_CURRENCY` below.
1066
+ */
1067
+ const USDX_SYMBOL = "USDX";
1068
+ const SYNTHETIC_TEST_ASSET_SOURCE = "synthetic-test-liquidity";
1069
+ const USDX_ASSET = {
1070
+ assetId: assetIdFor(84532, USDX_ADDRESS_BASE_SEPOLIA),
1071
+ chainId: toChainId(84532),
1072
+ symbol: USDX_SYMBOL,
1073
+ peg: toCurrencyCode("USD"),
1074
+ tokenAddress: USDX_ADDRESS_BASE_SEPOLIA,
1075
+ deploymentStartBlock: USDX_DEPLOYMENT_START_BLOCK,
1076
+ decimals: 6,
1077
+ source: "settlement"
1078
+ };
1079
+ /**
1080
+ * The settlement tier: rows the exact-transfer path and the Movement scanner
1081
+ * admit, each carrying the legacy `currency` projection those readers expect.
1082
+ * Adding an owned test asset here would put candidate liquidity on the
1083
+ * money-write path, so M03's rows go to the candidate tier below instead.
1084
+ */
1085
+ const CONFIGURED_MONEY_ASSETS = [{
1086
+ ...USDX_ASSET,
1087
+ currency: USDX_ASSET.peg
1088
+ }];
1089
+ /**
1090
+ * The candidate tier: five owned test assets, every field read back from Base
1091
+ * Sepolia after the deployment that created it (chain 84532, blocks
1092
+ * 46,465,978–46,465,982). Real contracts, authored liquidity, no settlement
1093
+ * admission — so rollback is removing consumers, never relabelling a
1094
+ * deployed address or deleting its history.
1095
+ *
1096
+ * `symbol` is the display string the owned contract returns. A
1097
+ * provider-supported USDC would be a different contract at a different
1098
+ * address returning the same symbol, which is why the resolvers below key on
1099
+ * `assetId`, or on chain plus address, and never on a symbol.
1100
+ */
1101
+ const SYNTHETIC_TEST_ASSETS = [
1102
+ {
1103
+ assetId: assetIdFor(84532, "0xb3d8566fb90f7df939f4bc08a09b19047117f549"),
1104
+ chainId: toChainId(84532),
1105
+ tokenAddress: "0xb3d8566fb90f7df939f4bc08a09b19047117f549",
1106
+ symbol: "USDC",
1107
+ decimals: 6,
1108
+ peg: toCurrencyCode("USD"),
1109
+ deploymentStartBlock: 46465978,
1110
+ deploymentTransaction: "0xa7fb8bb9d1fd1cc5fe2fc42fa4cdccace7742efae8ea99f091a283e5709343de",
1111
+ source: SYNTHETIC_TEST_ASSET_SOURCE
1112
+ },
1113
+ {
1114
+ assetId: assetIdFor(84532, "0x7ddcfb6aaffb9908c4c2db308b784c269eec8a01"),
1115
+ chainId: toChainId(84532),
1116
+ tokenAddress: "0x7ddcfb6aaffb9908c4c2db308b784c269eec8a01",
1117
+ symbol: "USDT",
1118
+ decimals: 6,
1119
+ peg: toCurrencyCode("USD"),
1120
+ deploymentStartBlock: 46465979,
1121
+ deploymentTransaction: "0xa17ad76fe8b24cdf1fbcff245c3eb022444710cf9fa7b5ee59818be654f4ae10",
1122
+ source: SYNTHETIC_TEST_ASSET_SOURCE
1123
+ },
1124
+ {
1125
+ assetId: assetIdFor(84532, "0x1d93d525f73453fe18eebf044e8a3954bfe3721e"),
1126
+ chainId: toChainId(84532),
1127
+ tokenAddress: "0x1d93d525f73453fe18eebf044e8a3954bfe3721e",
1128
+ symbol: "WETH",
1129
+ decimals: 18,
1130
+ peg: null,
1131
+ deploymentStartBlock: 46465980,
1132
+ deploymentTransaction: "0xcb9b2d024686fac93ec1243ccf02d3567fd531da46176115660cba2ba644b09d",
1133
+ source: SYNTHETIC_TEST_ASSET_SOURCE
1134
+ },
1135
+ {
1136
+ assetId: assetIdFor(84532, "0x8fe52c70aa9f7b6d74d8bb26eca33d87413cb624"),
1137
+ chainId: toChainId(84532),
1138
+ tokenAddress: "0x8fe52c70aa9f7b6d74d8bb26eca33d87413cb624",
1139
+ symbol: "WBTC",
1140
+ decimals: 8,
1141
+ peg: null,
1142
+ deploymentStartBlock: 46465981,
1143
+ deploymentTransaction: "0x728ba7b430ffd141f83d0ed5ce429d050049d7b097c936fd11e9c572d1243854",
1144
+ source: SYNTHETIC_TEST_ASSET_SOURCE
1145
+ },
1146
+ {
1147
+ assetId: assetIdFor(84532, "0x5865fe9787ac214feb5facaebaec06969ac0a9a3"),
1148
+ chainId: toChainId(84532),
1149
+ tokenAddress: "0x5865fe9787ac214feb5facaebaec06969ac0a9a3",
1150
+ symbol: "cNGN",
1151
+ decimals: 6,
1152
+ peg: toCurrencyCode("NGN"),
1153
+ deploymentStartBlock: 46465982,
1154
+ deploymentTransaction: "0xcedf254bcef57fc64189261a5ab1161bc4fde370e470933f627c4d8ddb5a2e26",
1155
+ source: SYNTHETIC_TEST_ASSET_SOURCE
1156
+ }
1157
+ ];
1158
+ toChainId(84532);
1159
+ /**
1160
+ * Every known asset in ONE identity space. The tiers above declare admission;
1161
+ * this is the only list an identity resolves against, so an asset is never
1162
+ * two different things depending on which list a caller happened to read.
1163
+ */
1164
+ const MONEY_ASSET_REGISTRY = [...CONFIGURED_MONEY_ASSETS, ...SYNTHETIC_TEST_ASSETS];
1165
+ CONFIGURED_MONEY_ASSETS.map((asset) => asset.tokenAddress.toLowerCase());
1166
+ /** The registry row for `assetId` across every tier, or `null`. */
1167
+ function configuredMoneyAssetById(assetId) {
1168
+ const canonical = toAssetId(assetId);
1169
+ return MONEY_ASSET_REGISTRY.find((asset) => asset.assetId === canonical) ?? null;
1170
+ }
1033
1171
  //#endregion
1034
1172
  //#region ../config/src/org-payments.ts
1035
1173
  /** Zodiac MultiSendCallOnly module on Base Sepolia. */
@@ -1918,6 +2056,12 @@ const USDX_BASE_SEPOLIA_TOKEN = {
1918
2056
  USDX_BASE_SEPOLIA_TOKEN.chainId;
1919
2057
  USDX_BASE_SEPOLIA_TOKEN.chainId;
1920
2058
  10n ** BigInt(6);
2059
+ /**
2060
+ * The bootstrap manage role. It administers people and roles, cannot spend,
2061
+ * and is not a signer of the Organization Safe; "Owner" implied the latter,
2062
+ * so the label is Admin (ruled 2026-09-06).
2063
+ */
2064
+ const ADMIN_ROLE_LABEL = "Admin";
1921
2065
  function normalizeOrgRoleLabel(label) {
1922
2066
  const normalized = label.trim().replace(/\s+/g, " ");
1923
2067
  if (normalized.length === 0) throw Errors.invalidInput("role.label", "must be a non-empty role label");
@@ -1998,6 +2142,26 @@ function errorKind(error) {
1998
2142
  var ClockError = class extends Data.TaggedError("ClockError") {};
1999
2143
  var ClockPortTag = class extends Context.Service()("@capxul/sdk/ports/ClockPort") {};
2000
2144
  //#endregion
2145
+ //#region src/adapters/clock/SystemClockAdapter.ts
2146
+ var SystemClockAdapter = class {
2147
+ now = Effect.try({
2148
+ try: () => toEpochMs(Date.now()),
2149
+ catch: (cause) => new ClockError({
2150
+ operation: "now",
2151
+ cause
2152
+ })
2153
+ });
2154
+ sleep(duration) {
2155
+ return Effect.callback((resume) => {
2156
+ const timeout = setTimeout(() => resume(Effect.void), duration);
2157
+ return Effect.sync(() => clearTimeout(timeout));
2158
+ });
2159
+ }
2160
+ };
2161
+ function SystemClockLayer() {
2162
+ return Layer.succeed(ClockPortTag, new SystemClockAdapter());
2163
+ }
2164
+ //#endregion
2001
2165
  //#region src/ports/auth-cache.ts
2002
2166
  var AuthCacheError = class extends Data.TaggedError("AuthCacheError") {};
2003
2167
  var AuthCachePortTag = class extends Context.Service()("@capxul/sdk/ports/AuthCachePort") {};
@@ -2126,4 +2290,192 @@ async function readClockNow(clock) {
2126
2290
  }
2127
2291
  }
2128
2292
  //#endregion
2129
- export { CapxulError as $, toAuthUserId as A, toJwtToken as B, WEI_RE as C, toAddress as D, toAccountId as E, toDurationMs as F, toPayrollRunId as G, toOrgId as H, toEmail as I, toSessionToken as J, toPublishableKey as K, toEpochMs as L, toChainId as M, toCountryCode as N, toAllowedOrigin as O, toCurrencyCode as P, CAPXUL_ERROR_CODES as Q, toEpochSeconds as R, SUPPORTED_CURRENCY_CODES as S, currencySymbolFor as T, toPartyId as U, toKycTier as V, toPayrollGroupId as W, HANDLE_RE as X, toTesterKind as Y, decodeConvexError as Z, deriveCapxulSafeAddress as _, parseCachedJwt as a, boundedResponseHeaders as at, BYTES32_RE as b, ClockError as c, decodeChainCause as ct, AuthClientError as d, isFailureMode as dt, EXPECTED_OPERATION_OUTCOMES as et, AuthClientPortTag as f, revertSummaryText as ft, BASE_SEPOLIA_CHAIN_ID as g, redactUrlSecrets as gt, normalizeBindingEmail as h, redactSecrets as ht, parseAuthSession as i, FAILURE_MODES as it, toBudgetId as j, toAppId as k, ClockPortTag as l, failureFingerprint as lt, CAPXUL_PAYMENTS_V2_ADDRESS as m, isCredentialField as mt, InMemoryAuthCacheAdapter as n, isCapxulError as nt, AuthCacheError as o, chainCauseProperties as ot, orgRoleKeyForLabel as p, containsSensitiveMaterial as pt, toRoleKey as q, BrowserAuthCacheAdapter as r, CHAIN_UPSTREAMS as rt, AuthCachePortTag as s, chainEvidenceLabel as st, readClockNow as t, Errors as tt, authClientPortFromPromiseAdapter as u, isChainUpstream as ut, validateHandle as v, ZERO_BYTES32 as w, EVM_ADDRESS_RE as x, APP_ID_RE as y, toHandle as z };
2293
+ //#region src/adapters/auth-client/OAuthBearerAuthClient.ts
2294
+ const PROVIDER = "agent-exchange";
2295
+ /** Re-exchange this far ahead of expiry, matching the JWT cache eviction margin. */
2296
+ const REFRESH_MARGIN_MS = 3e4;
2297
+ /**
2298
+ * `AuthSession.token` is a Better Auth session handle. This flow has none, and
2299
+ * the access token must never be copied into it, so the slot holds a placeholder.
2300
+ */
2301
+ const OAUTH_SESSION_TOKEN = toSessionToken("oauth-bearer");
2302
+ var OAuthBearerAuthClient = class {
2303
+ #exchangeUrl;
2304
+ #exchangeSecret;
2305
+ #accessToken;
2306
+ #fetchImpl;
2307
+ #clock;
2308
+ #cached = null;
2309
+ constructor(input) {
2310
+ this.#exchangeUrl = input.exchangeUrl;
2311
+ this.#exchangeSecret = input.exchangeSecret;
2312
+ this.#accessToken = input.accessToken;
2313
+ this.#fetchImpl = input.fetch ?? fetch;
2314
+ this.#clock = input.clock ?? new SystemClockAdapter();
2315
+ }
2316
+ async canSendOtp() {
2317
+ return refuse("canSendOtp");
2318
+ }
2319
+ async sendOtp() {
2320
+ return refuse("sendOtp");
2321
+ }
2322
+ async verifyOtp() {
2323
+ return refuse("verifyOtp");
2324
+ }
2325
+ /**
2326
+ * The authorization server owns the token's lifetime, so revoking it there is
2327
+ * the sign-out. Reporting success here would claim a token was dropped.
2328
+ */
2329
+ async signOut() {
2330
+ return refuse("signOut");
2331
+ }
2332
+ /**
2333
+ * The exchanged identity. Unlike the Better Auth adapters this never answers
2334
+ * `null`: the consumer holds a usable access token or a refused one, and a
2335
+ * refused one is a NOT_AUTHENTICATED failure, not the absence of a session.
2336
+ */
2337
+ async getSession(options) {
2338
+ const exchange = await this.#exchange("getSession", options);
2339
+ if (!exchange.ok) return exchange;
2340
+ return {
2341
+ ok: true,
2342
+ value: {
2343
+ authUserId: toAuthUserId(exchange.value.authUserId),
2344
+ email: toEmail(exchange.value.email),
2345
+ token: OAUTH_SESSION_TOKEN,
2346
+ expiresAt: toEpochMs(exchange.value.expiresAt)
2347
+ }
2348
+ };
2349
+ }
2350
+ async getConvexJwt(options) {
2351
+ const exchange = await this.#exchange("getConvexJwt", options);
2352
+ if (!exchange.ok) return exchange;
2353
+ return {
2354
+ ok: true,
2355
+ value: {
2356
+ token: toJwtToken(exchange.value.token),
2357
+ expEpochSeconds: toEpochSeconds(Math.floor(exchange.value.expiresAt / 1e3))
2358
+ }
2359
+ };
2360
+ }
2361
+ /**
2362
+ * The cached exchange while it is fresh, otherwise a new one. A failed
2363
+ * exchange never replaces the cache, so a stale token is never served.
2364
+ *
2365
+ * lazy: no in-flight de-duplication — two concurrent reads past the margin
2366
+ * each mint a JWT. Minting is idempotent, so the cost is one extra request.
2367
+ */
2368
+ async #exchange(operation, options) {
2369
+ if (options?.signal?.aborted) return {
2370
+ ok: false,
2371
+ error: Errors.cancelled({ operation })
2372
+ };
2373
+ const cached = this.#cached;
2374
+ if (cached !== null && options?.forceRefresh !== true) {
2375
+ const now = await readClockNow(this.#clock);
2376
+ if (!now.ok) return now;
2377
+ if (now.value < cached.expiresAt - REFRESH_MARGIN_MS) return {
2378
+ ok: true,
2379
+ value: cached
2380
+ };
2381
+ }
2382
+ return this.#postExchange(operation, options?.signal);
2383
+ }
2384
+ async #postExchange(operation, signal) {
2385
+ let res;
2386
+ try {
2387
+ res = await this.#fetchImpl(this.#exchangeUrl, {
2388
+ method: "POST",
2389
+ headers: {
2390
+ "content-type": "application/json",
2391
+ authorization: `Bearer ${this.#exchangeSecret}`
2392
+ },
2393
+ body: JSON.stringify({ accessToken: this.#accessToken }),
2394
+ ...signal === void 0 ? {} : { signal }
2395
+ });
2396
+ } catch (cause) {
2397
+ return {
2398
+ ok: false,
2399
+ error: signal?.aborted === true || isAbortError(cause) ? Errors.cancelled({ operation }) : Errors.networkError(operation, cause)
2400
+ };
2401
+ }
2402
+ if (signal?.aborted === true) return {
2403
+ ok: false,
2404
+ error: Errors.cancelled({ operation })
2405
+ };
2406
+ const body = await res.json().catch(() => null);
2407
+ if (!res.ok) return {
2408
+ ok: false,
2409
+ error: exchangeRefused(operation, res.status, readErrorCode(body))
2410
+ };
2411
+ const exchange = readExchange(body);
2412
+ if (exchange === null) return {
2413
+ ok: false,
2414
+ error: Errors.providerError(PROVIDER, operation, /* @__PURE__ */ new Error("unexpected body"), { httpStatus: res.status })
2415
+ };
2416
+ this.#cached = exchange;
2417
+ return {
2418
+ ok: true,
2419
+ value: exchange
2420
+ };
2421
+ }
2422
+ };
2423
+ /**
2424
+ * `AuthClientPort` over the backend agent-token exchange. Build one per OAuth
2425
+ * access token and hand it to `createCapxulClient({ authClient })`.
2426
+ */
2427
+ function oauthBearerAuthClient(input) {
2428
+ return authClientPortFromPromiseAdapter(new OAuthBearerAuthClient(input));
2429
+ }
2430
+ /** The OTP verbs belong to the email flow; `validStates` names it for the caller. */
2431
+ function refuse(method) {
2432
+ return {
2433
+ ok: false,
2434
+ error: Errors.wrongState({
2435
+ method,
2436
+ currentState: "oauth-bearer",
2437
+ validStates: ["email-otp"]
2438
+ })
2439
+ };
2440
+ }
2441
+ /**
2442
+ * The error catalog has no FORBIDDEN code, so 403 (`insufficient_scope`) and
2443
+ * 503 (`exchange_unconfigured`) both land on PROVIDER_ERROR. `httpStatus` and
2444
+ * `details.reason` carry which one the backend reported. Only 401 becomes
2445
+ * NOT_AUTHENTICATED, because only 401 is answered by a new access token.
2446
+ */
2447
+ function exchangeRefused(operation, status, reason) {
2448
+ if (status === 401) return Errors.notAuthenticated("The exchange refused this OAuth access token");
2449
+ return Errors.providerError(PROVIDER, operation, /* @__PURE__ */ new Error(`HTTP ${status}`), {
2450
+ httpStatus: status,
2451
+ ...reason === void 0 ? {} : { details: { reason } }
2452
+ });
2453
+ }
2454
+ /** An abort raised by something other than the caller's own signal. */
2455
+ function isAbortError(cause) {
2456
+ return readField(cause, "name") === "AbortError";
2457
+ }
2458
+ function readErrorCode(body) {
2459
+ const error = readField(body, "error");
2460
+ return typeof error === "string" ? error : void 0;
2461
+ }
2462
+ function readField(value, key) {
2463
+ if (typeof value !== "object" || value === null) return void 0;
2464
+ return value[key];
2465
+ }
2466
+ function readExchange(body) {
2467
+ if (typeof body !== "object" || body === null) return null;
2468
+ const { token, expiresAt, authUserId, email } = body;
2469
+ if (typeof token !== "string" || token.length === 0) return null;
2470
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null;
2471
+ if (typeof authUserId !== "string" || authUserId.length === 0) return null;
2472
+ if (typeof email !== "string" || email.length === 0) return null;
2473
+ return {
2474
+ token,
2475
+ expiresAt,
2476
+ authUserId,
2477
+ email
2478
+ };
2479
+ }
2480
+ //#endregion
2481
+ export { toRoleKey as $, currencySymbolFor as A, toCurrencyCode as B, ASSET_ID_RE as C, isCredentialField as Ct, WEI_RE as D, SUPPORTED_CURRENCY_CODES as E, toAssetId as F, toHandle as G, toEmail as H, toAuthUserId as I, toOrgId as J, toJwtToken as K, toBudgetId as L, toAddress as M, toAllowedOrigin as N, ZERO_BYTES32 as O, toAppId as P, toPublishableKey as Q, toChainId as R, APP_ID_RE as S, containsSensitiveMaterial as St, EVM_ADDRESS_RE as T, redactUrlSecrets as Tt, toEpochMs as U, toDurationMs as V, toEpochSeconds as W, toPayrollGroupId as X, toPartyId as Y, toPayrollRunId as Z, normalizeBindingEmail as _, decodeChainCause as _t, parseAuthSession as a, HANDLE_RE as at, configuredMoneyAssetById as b, isFailureMode as bt, AuthCachePortTag as c, CapxulError as ct, authClientPortFromPromiseAdapter as d, isCapxulError as dt, toSessionToken as et, AuthClientError as f, CHAIN_UPSTREAMS as ft, CAPXUL_PAYMENTS_V2_ADDRESS as g, chainEvidenceLabel as gt, orgRoleKeyForLabel as h, chainCauseProperties as ht, BrowserAuthCacheAdapter as i, validateHandle as it, toAccountId as j, assetIdFor as k, SystemClockLayer as l, EXPECTED_OPERATION_OUTCOMES as lt, ADMIN_ROLE_LABEL as m, boundedResponseHeaders as mt, readClockNow as n, toTxHash as nt, parseCachedJwt as o, decodeConvexError as ot, AuthClientPortTag as p, FAILURE_MODES as pt, toKycTier as q, InMemoryAuthCacheAdapter as r, toWeiAmount as rt, AuthCacheError as s, CAPXUL_ERROR_CODES as st, oauthBearerAuthClient as t, toTesterKind as tt, ClockPortTag as u, Errors as ut, BASE_SEPOLIA_CHAIN_ID as v, failureFingerprint as vt, BYTES32_RE as w, redactSecrets as wt, ACCOUNT_ID_RE as x, revertSummaryText as xt, deriveCapxulSafeAddress as y, isChainUpstream as yt, toCountryCode as z };