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

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/README.md CHANGED
@@ -19,51 +19,44 @@ const client = created.value;
19
19
  Every public operation resolves a `Promise<CapxulResult<T>>`. Domain failures
20
20
  are values, not thrown exceptions.
21
21
 
22
- ## Money input and display
22
+ ## Asset amount input and display
23
23
 
24
- Use the public money helpers at form and display boundaries. They do not
25
- convert a money value to a JavaScript number.
24
+ Use the asset helpers at Payment form and display boundaries. They keep the
25
+ exact AssetId and do not convert a value to a JavaScript number.
26
26
 
27
27
  ```ts
28
- import { formatMoney, parseMoney } from "@capxul/sdk";
28
+ import { formatAssetAmount, parseAssetAmount, requireAsset, TOKENS } from "@capxul/sdk";
29
29
 
30
- const amount = parseMoney("1,000.25", {
31
- currency: account.available.currency,
32
- decimals: account.available.decimals,
33
- });
30
+ const amount = parseAssetAmount("1,000.25", requireAsset(TOKENS.BASE_SEPOLIA.CAPXUL_TEST_USDC));
34
31
 
35
32
  if ("kind" in amount) {
36
33
  console.error(amount.reason); // closed code — the app owns the sentence (ADR-0023)
37
34
  } else {
38
- console.log(formatMoney(amount)); // $1,000.25
39
- console.log(formatMoney(amount, { grammar: "code" })); // 1,000.25 USD
35
+ console.log(formatAssetAmount(amount)); // 1,000.25 USDC
40
36
  }
41
37
  ```
42
38
 
43
- `parseMoney` returns validation errors as values. `formatMoney` rejects a
44
- malformed `Money` record with `INVALID_INPUT`.
39
+ `parseAssetAmount` returns validation errors as values. Payment, request,
40
+ Budget, Payroll, and activity amounts require AssetAmount. Fiat Money remains
41
+ separate valuation data.
45
42
 
46
- ## The asset a screen shows
43
+ ## Value all asset positions
47
44
 
48
- `holdings.primary` picks the one asset a hero balance or a summary total
49
- should show. It returns the renderable holding with the largest available
50
- balance. A chain row that carries no `decimals`, no `symbol`, or a symbol that
51
- stands for no supported currency is skipped, because it cannot be rendered.
45
+ Use `holdings.current` for raw positions. Use `valueIn` to value exact asset
46
+ quantities in a display currency. A token symbol does not identify an asset.
47
+ Quantities from different assets cannot be compared as a headline balance.
52
48
 
53
49
  ```ts
54
- import { formatMoney, type Holding } from "@capxul/sdk";
55
-
56
- const result = await client.holdings.primary();
50
+ import { formatMoney, valueIn } from "@capxul/sdk";
57
51
 
58
- if (result.ok && result.value !== null) {
59
- const holding: Holding = result.value;
60
- console.log(holding.symbol); // "USDX" — the token symbol the chain reported
61
- console.log(formatMoney(holding.available, { grammar: "code" })); // e.g. "1,250.50 USD"
52
+ const valuation = valueIn(amounts, displayCurrency, rates);
53
+ if (valuation.total !== null) {
54
+ console.log(formatMoney(valuation.total));
62
55
  }
63
56
  ```
64
57
 
65
- `null` means no row is renderable. It is a value, not an error, so give the
66
- screen its own rule for showing nothing.
58
+ Keep `valuation.unrated` visible beside the known subtotal. A missing rate
59
+ does not mean a zero balance.
67
60
 
68
61
  ## Observability
69
62
 
@@ -105,6 +98,11 @@ The backend remains the authority for reserved names and global uniqueness.
105
98
  The method returns only after the client requirement reaches a ready Account.
106
99
  The React frontend does not use this journey yet.
107
100
 
101
+ A Node host that keeps login outside the browser can pass `openfortSignerFactory`.
102
+ The factory receives only the validated Openfort and auth bootstrap fields. Its
103
+ browser page can use `createOpenfortBrowserSigner` with host-owned access-token
104
+ and encryption-session callbacks. It does not need a second login or private SDK import.
105
+
108
106
  ## Published entry points
109
107
 
110
108
  The packed npm package exposes only:
@@ -173,3 +171,36 @@ operator secrets and prove only the path named by the command. See
173
171
  The browser production path owns its Openfort signer when no signer is supplied.
174
172
  Current integration recovery evidence and its published-package boundary are
175
173
  tracked in the [#870 receipt](../../docs/receipts/issue-870-openfort-signer-recovery-RECEIPT-2026-07-19.md).
174
+
175
+ ### Node session storage
176
+
177
+ A Node host can pass `nodeSessionStorage: { load, save }` to
178
+ `createCapxulClient`. Both methods return promises. `load` returns an opaque
179
+ BetterAuth credential or `null`; `save` receives the credential or `null` on
180
+ logout. Store it with owner-only access and isolate it by application and
181
+ environment. Do not print it or use a cached `AuthSession` as backend authority.
182
+ The SDK validates the credential and expiry with BetterAuth on process restart.
183
+ With durable storage, each adapter serializes session reads, OTP verification,
184
+ sign-out, and Convex token reads. A delayed response cannot clear or rotate a
185
+ credential from a later operation on that adapter. Cancellation while queued
186
+ returns immediately and prevents that operation from reaching the provider.
187
+ Hosts that share storage across clients or processes must fence writes, for
188
+ example with an atomic compare-and-set; the adapter cannot order other clients.
189
+
190
+ For separate OTP request and verification processes, retain the email and
191
+ original request timestamp. After `auth.getSession()` confirms signed-out state,
192
+ call `auth.resumeOtp({ email, requestedAt })`, then `auth.verifyOtp({ email,
193
+ code })`. Resume restores pending input only. It does not send another OTP or
194
+ authenticate the caller. Keep OTP values out of arguments, logs, and stored data.
195
+
196
+ ### Existing financial documents
197
+
198
+ Use `paymentDocuments.verify({ documentHash, contentHash })` to verify stored
199
+ content. Use `paymentDocuments.render` with the same pair for safe semantic HTML.
200
+ Call `paymentDocuments.get` only for a deliberate download. Its `bytesBase64`
201
+ contains the exact stored bytes. Do not log document results or error payloads.
202
+ Get references from Payment or Movement activity detail. References include
203
+ kind, format, and role. A hash is not permission to read a document.
204
+ Reads check current Payment participants or current Movement actor access.
205
+ Missing and unauthorized pairs return the same refusal. These methods do not
206
+ create documents or add Payment links.
@@ -254,6 +254,7 @@ declare const Errors: {
254
254
  readonly method: string;
255
255
  readonly currentState: string;
256
256
  readonly validStates: readonly string[];
257
+ readonly organizationId?: string;
257
258
  }) => CapxulError;
258
259
  };
259
260
  //#endregion
@@ -311,9 +312,9 @@ type Money = {
311
312
  type Account$1 = {
312
313
  readonly id: AccountId;
313
314
  readonly balances: readonly AccountPosition[];
314
- /** Fiat valuation of `balances`. Never a token quantity. */
315
- readonly balance: Money;
316
- readonly available: Money;
315
+ /** Fiat valuation of `balances`. `null` means at least one position has no rate. */
316
+ readonly balance: Money | null;
317
+ readonly available: Money | null;
317
318
  };
318
319
  /**
319
320
  * One asset position on an Account. `asset` is the identity; `symbol` and
@@ -391,8 +392,12 @@ declare const SUPPORTED_CURRENCIES: readonly [{
391
392
  }];
392
393
  type SupportedCurrencyCode = (typeof SUPPORTED_CURRENCIES)[number]["code"];
393
394
  declare function toAddress(raw: unknown): Address$1;
395
+ declare function toAccountId(raw: unknown): AccountId;
394
396
  declare function toPartyId(raw: unknown): PartyId;
395
397
  declare function toHandle(raw: unknown): Handle;
398
+ declare function toPermissionId(raw: unknown): PermissionId;
399
+ declare function toBudgetId(raw: unknown): BudgetId;
400
+ declare function toOrgId(raw: unknown): OrgId;
396
401
  declare function toCountryCode(raw: unknown): CountryCode;
397
402
  //#endregion
398
403
  //#region ../config/src/capxul-payments-v2.d.ts
@@ -642,4 +647,4 @@ interface OAuthBearerAuthClientInput {
642
647
  */
643
648
  declare function oauthBearerAuthClient(input: OAuthBearerAuthClientInput): AuthClientPort;
644
649
  //#endregion
645
- export { OrgId as $, Address$1 as A, RevertSummary as At, BudgetId as B, AuthCachePort as C, Failure as Ct, Account$1 as D, ChainUpstream as Dt, CAPXUL_PAYMENTS_V2_ADDRESS as E, ChainCause as Et, AssetAmount as F, CurrencyCode as G, ClientGrantId as H, AssetId as I, EVM_ADDRESS_RE as J, DocumentHash as K, AuthSession as L, AllowedOrigin as M, AnonymousDistinctId as N, AccountId as O, FAILURE_MODES as Ot, AppId as P, Money as Q, AuthUserId as R, AuthCacheError as S, Errors as St, CachedJwt as T, CHAIN_UPSTREAMS as Tt, ClientRequestId as U, ChainId as V, CountryCode as W, EpochMs as X, Email as Y, Handle as Z, Session as _, toPartyId as _t, Eip1193RequestProvider as a, PermissionId as at, AuthClientPort as b, CapxulErrorCode as bt, injectedWalletSigner as c, RoleKey as ct, AccountRequirement as d, TesterKind as dt, PartyId as et, Eip1193Provider as f, TxHash as ft, Profile as g, toHandle as gt, CapxulResult as h, toCountryCode as ht, CapxulSigner as i, PermissionAssignmentId as it, AllowanceKey as j, AccountPosition as k, FailureMode as kt, AccountProvider as l, SessionToken as lt, localPrivateKeyAccountProvider as m, toAddress as mt, oauthBearerAuthClient as n, PayrollGroupId as nt, SignerStatus as o, Profile$1 as ot, eip1193AccountProvider as p, WeiAmount as pt, DurationMs as q, CapxulDigestSigner as r, PayrollRunId as rt, SignerStatusStore as s, PublishableKey as st, OAuthBearerAuthClientInput as t, PaymentCommandId as tt, AccountProviderSource as u, SmartAccount$1 as ut, SmartAccount as v, CAPXUL_ERROR_CODES as vt, AuthCachePortTag as w, isCapxulError as wt, CanSendOtpStatus as x, CapxulErrorDetails as xt, ClockPort as y, CapxulError as yt, BlockNumber as z };
650
+ export { OrgId as $, Address$1 as A, ChainCause as At, BudgetId as B, AuthCachePort as C, CapxulError as Ct, Account$1 as D, Failure as Dt, CAPXUL_PAYMENTS_V2_ADDRESS as E, Errors as Et, AssetAmount as F, CurrencyCode as G, ClientGrantId as H, AssetId as I, EVM_ADDRESS_RE as J, DocumentHash as K, AuthSession as L, AllowedOrigin as M, FAILURE_MODES as Mt, AnonymousDistinctId as N, FailureMode as Nt, AccountId as O, isCapxulError as Ot, AppId as P, RevertSummary as Pt, Money as Q, AuthUserId as R, AuthCacheError as S, CAPXUL_ERROR_CODES as St, CachedJwt as T, CapxulErrorDetails as Tt, ClientRequestId as U, ChainId as V, CountryCode as W, EpochMs as X, Email as Y, Handle as Z, Session as _, toCountryCode as _t, Eip1193RequestProvider as a, PermissionId as at, AuthClientPort as b, toPartyId as bt, injectedWalletSigner as c, RoleKey as ct, AccountRequirement as d, TesterKind as dt, PartyId as et, Eip1193Provider as f, TxHash as ft, Profile as g, toBudgetId as gt, CapxulResult as h, toAddress as ht, CapxulSigner as i, PermissionAssignmentId as it, AllowanceKey as j, ChainUpstream as jt, AccountPosition as k, CHAIN_UPSTREAMS as kt, AccountProvider as l, SessionToken as lt, localPrivateKeyAccountProvider as m, toAccountId as mt, oauthBearerAuthClient as n, PayrollGroupId as nt, SignerStatus as o, Profile$1 as ot, eip1193AccountProvider as p, WeiAmount as pt, DurationMs as q, CapxulDigestSigner as r, PayrollRunId as rt, SignerStatusStore as s, PublishableKey as st, OAuthBearerAuthClientInput as t, PaymentCommandId as tt, AccountProviderSource as u, SmartAccount$1 as ut, SmartAccount as v, toHandle as vt, AuthCachePortTag as w, CapxulErrorCode as wt, CanSendOtpStatus as x, toPermissionId as xt, ClockPort as y, toOrgId as yt, BlockNumber as z };
@@ -1,4 +1,4 @@
1
- import { concat, encodeAbiParameters, encodeFunctionData, encodePacked, getContractAddress, keccak256, padHex, stringToHex, toBytes, toEventSelector, toFunctionSelector, toFunctionSignature } from "viem";
1
+ import { concat, encodeAbiParameters, encodeFunctionData, encodePacked, getContractAddress, keccak256, padHex, toBytes, toEventSelector, toFunctionSelector, toFunctionSignature } from "viem";
2
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([
@@ -807,8 +807,10 @@ const Errors = {
807
807
  * enumerates the states the method accepts.
808
808
  */
809
809
  wrongState: (details) => new CapxulError("WRONG_STATE", `${details.method} called from state '${details.currentState}'; valid states: ${details.validStates.join(", ")}`, { details: {
810
- ...details,
811
- validStates: [...details.validStates]
810
+ method: details.method,
811
+ currentState: details.currentState,
812
+ validStates: [...details.validStates],
813
+ ...details.organizationId === void 0 ? {} : { organizationId: details.organizationId }
812
814
  } })
813
815
  };
814
816
  //#endregion
@@ -941,6 +943,12 @@ function toHandle(raw) {
941
943
  if (typeof raw !== "string" || !HANDLE_RE.test(raw)) throw Errors.invalidInput("handle", invalidValueReason("must be 3-30 characters of a-z, 0-9, underscore, or hyphen", raw));
942
944
  return raw;
943
945
  }
946
+ function toPermissionId(raw) {
947
+ return toNonEmptyStringBrand(raw, "permissionId");
948
+ }
949
+ function toPermissionAssignmentId(raw) {
950
+ return toNonEmptyStringBrand(raw, "permissionAssignmentId");
951
+ }
944
952
  function toBudgetId(raw) {
945
953
  return toNonEmptyStringBrand(raw, "budgetId");
946
954
  }
@@ -1061,33 +1069,11 @@ function invalidValueReason(prefix, raw) {
1061
1069
  }
1062
1070
  //#endregion
1063
1071
  //#region ../config/src/tokens.ts
1064
- /** `TestUSDC` ("USDX") — Base Sepolia, 6 decimals, open `mint`. (Canon §1.) */
1065
- const USDX_ADDRESS_BASE_SEPOLIA = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
1066
- const USDX_DEPLOYMENT_START_BLOCK = 39860173;
1067
- /**
1068
- * The ERC-20 `symbol()` the deployed `TestUSDC` contract returns
1069
- * (`packages/contracts/src/TestUSDC.sol:10`). It travels with the address and
1070
- * the decimals so a holdings row can never carry one token's address with
1071
- * another token's display symbol. This is the display symbol, not the
1072
- * currency: see `USDX_CURRENCY` below.
1073
- */
1074
- const USDX_SYMBOL = "USDX";
1075
1072
  const SYNTHETIC_TEST_ASSET_SOURCE = "synthetic-test-liquidity";
1076
- const USDX_ASSET = {
1077
- assetId: assetIdFor(84532, USDX_ADDRESS_BASE_SEPOLIA),
1078
- chainId: toChainId(84532),
1079
- symbol: USDX_SYMBOL,
1080
- peg: toCurrencyCode("USD"),
1081
- tokenAddress: USDX_ADDRESS_BASE_SEPOLIA,
1082
- deploymentStartBlock: USDX_DEPLOYMENT_START_BLOCK,
1083
- decimals: 6,
1084
- source: "legacy"
1085
- };
1086
- ({ ...USDX_ASSET }), USDX_ASSET.peg;
1087
1073
  /**
1088
1074
  * The candidate tier: five owned test assets, every field read back from Base
1089
- * Sepolia after the deployment that created it (chain 84532, blocks
1090
- * 46,465,978–46,465,982). Real contracts, authored liquidity, no settlement
1075
+ * Sepolia after the deployment that created it (chain 84532, block
1076
+ * 46,690,061). Real contracts, authored liquidity, no settlement
1091
1077
  * admission — so rollback is removing consumers, never relabelling a
1092
1078
  * deployed address or deleting its history.
1093
1079
  *
@@ -1096,84 +1082,95 @@ const USDX_ASSET = {
1096
1082
  * address returning the same symbol, which is why the resolvers below key on
1097
1083
  * `assetId`, or on chain plus address, and never on a symbol.
1098
1084
  */
1099
- const SYNTHETIC_TEST_ASSETS = [
1100
- {
1101
- assetId: assetIdFor(84532, "0xb3d8566fb90f7df939f4bc08a09b19047117f549"),
1085
+ const SYNTHETIC_TEST_ASSET_FIXTURES = {
1086
+ usdc: {
1087
+ assetId: assetIdFor(84532, "0x06ba8b863ca77490b0e7008427562bdbfcff730e"),
1102
1088
  chainId: toChainId(84532),
1103
- tokenAddress: "0xb3d8566fb90f7df939f4bc08a09b19047117f549",
1089
+ tokenAddress: "0x06ba8b863ca77490b0e7008427562bdbfcff730e",
1104
1090
  symbol: "USDC",
1105
1091
  decimals: 6,
1106
1092
  peg: toCurrencyCode("USD"),
1107
- deploymentStartBlock: 46465978,
1108
- deploymentTransaction: "0xa7fb8bb9d1fd1cc5fe2fc42fa4cdccace7742efae8ea99f091a283e5709343de",
1093
+ deploymentStartBlock: 46690061,
1094
+ deploymentTransaction: "0x2661b7af5f62b9e31c707a59746cda65b90ba4448852317ac1968162fb94ff52",
1109
1095
  source: SYNTHETIC_TEST_ASSET_SOURCE
1110
1096
  },
1111
- {
1112
- assetId: assetIdFor(84532, "0x7ddcfb6aaffb9908c4c2db308b784c269eec8a01"),
1097
+ usdt: {
1098
+ assetId: assetIdFor(84532, "0xcb5f361ae070e75d573941b8cd54995fa3876d75"),
1113
1099
  chainId: toChainId(84532),
1114
- tokenAddress: "0x7ddcfb6aaffb9908c4c2db308b784c269eec8a01",
1100
+ tokenAddress: "0xcb5f361ae070e75d573941b8cd54995fa3876d75",
1115
1101
  symbol: "USDT",
1116
1102
  decimals: 6,
1117
1103
  peg: toCurrencyCode("USD"),
1118
- deploymentStartBlock: 46465979,
1119
- deploymentTransaction: "0xa17ad76fe8b24cdf1fbcff245c3eb022444710cf9fa7b5ee59818be654f4ae10",
1104
+ deploymentStartBlock: 46690061,
1105
+ deploymentTransaction: "0x98e5303f97974a31eddb61ad030072d900ce7283ce8080bfe0b9e5ea6f297749",
1120
1106
  source: SYNTHETIC_TEST_ASSET_SOURCE
1121
1107
  },
1122
- {
1123
- assetId: assetIdFor(84532, "0x1d93d525f73453fe18eebf044e8a3954bfe3721e"),
1108
+ weth: {
1109
+ assetId: assetIdFor(84532, "0x85b7bd39ce3df9a85135e1e307225dd435f67e47"),
1124
1110
  chainId: toChainId(84532),
1125
- tokenAddress: "0x1d93d525f73453fe18eebf044e8a3954bfe3721e",
1111
+ tokenAddress: "0x85b7bd39ce3df9a85135e1e307225dd435f67e47",
1126
1112
  symbol: "WETH",
1127
1113
  decimals: 18,
1128
1114
  peg: null,
1129
- deploymentStartBlock: 46465980,
1130
- deploymentTransaction: "0xcb9b2d024686fac93ec1243ccf02d3567fd531da46176115660cba2ba644b09d",
1115
+ deploymentStartBlock: 46690061,
1116
+ deploymentTransaction: "0x48dfacc01c15adc34f6c629aec5c9ff7bb84176d448017da0d0d4fdaf8b5a68a",
1131
1117
  source: SYNTHETIC_TEST_ASSET_SOURCE
1132
1118
  },
1133
- {
1134
- assetId: assetIdFor(84532, "0x8fe52c70aa9f7b6d74d8bb26eca33d87413cb624"),
1119
+ wbtc: {
1120
+ assetId: assetIdFor(84532, "0xcf433a81f507dcc88c2c404650ffafb40750b451"),
1135
1121
  chainId: toChainId(84532),
1136
- tokenAddress: "0x8fe52c70aa9f7b6d74d8bb26eca33d87413cb624",
1122
+ tokenAddress: "0xcf433a81f507dcc88c2c404650ffafb40750b451",
1137
1123
  symbol: "WBTC",
1138
1124
  decimals: 8,
1139
1125
  peg: null,
1140
- deploymentStartBlock: 46465981,
1141
- deploymentTransaction: "0x728ba7b430ffd141f83d0ed5ce429d050049d7b097c936fd11e9c572d1243854",
1126
+ deploymentStartBlock: 46690061,
1127
+ deploymentTransaction: "0xa975fcda0a67509742243bbfc386032d2e0d6ca78404c255fae2e4ffae1c7f4d",
1142
1128
  source: SYNTHETIC_TEST_ASSET_SOURCE
1143
1129
  },
1144
- {
1145
- assetId: assetIdFor(84532, "0x5865fe9787ac214feb5facaebaec06969ac0a9a3"),
1130
+ cngn: {
1131
+ assetId: assetIdFor(84532, "0xda515a7267e190710f0003f951c62bcfd70d6b2a"),
1146
1132
  chainId: toChainId(84532),
1147
- tokenAddress: "0x5865fe9787ac214feb5facaebaec06969ac0a9a3",
1133
+ tokenAddress: "0xda515a7267e190710f0003f951c62bcfd70d6b2a",
1148
1134
  symbol: "cNGN",
1149
1135
  decimals: 6,
1150
1136
  peg: toCurrencyCode("NGN"),
1151
- deploymentStartBlock: 46465982,
1152
- deploymentTransaction: "0xcedf254bcef57fc64189261a5ab1161bc4fde370e470933f627c4d8ddb5a2e26",
1137
+ deploymentStartBlock: 46690061,
1138
+ deploymentTransaction: "0x6f6b0302448c7cd7db5c118a8250e548c6e72821df373e5a27550e5cdfa635a1",
1153
1139
  source: SYNTHETIC_TEST_ASSET_SOURCE
1154
1140
  }
1141
+ };
1142
+ const SYNTHETIC_TEST_ASSETS = [
1143
+ SYNTHETIC_TEST_ASSET_FIXTURES.usdc,
1144
+ SYNTHETIC_TEST_ASSET_FIXTURES.usdt,
1145
+ SYNTHETIC_TEST_ASSET_FIXTURES.weth,
1146
+ SYNTHETIC_TEST_ASSET_FIXTURES.wbtc,
1147
+ SYNTHETIC_TEST_ASSET_FIXTURES.cngn
1155
1148
  ];
1156
1149
  toChainId(84532);
1157
1150
  /**
1158
1151
  * The settlement tier: the owned test assets admitted to the exact-transfer
1159
- * path and to the treasury reads. USDC, USDT and WETH are equal assets. There
1160
- * is no default token and no successor to USDX; USDX survives only in
1161
- * `LEGACY_MONEY_ASSETS` and is not a current treasury row.
1152
+ * path and to the treasury reads. USDC, USDT and WETH are equal assets.
1153
+ * There is no default token.
1162
1154
  */
1163
- const SETTLEMENT_ASSET_SYMBOLS = [
1164
- "USDC",
1165
- "USDT",
1166
- "WETH"
1167
- ];
1168
- const CONFIGURED_MONEY_ASSETS = SYNTHETIC_TEST_ASSETS.filter((asset) => SETTLEMENT_ASSET_SYMBOLS.includes(asset.symbol));
1155
+ const SETTLEMENT_ASSETS_BASE_SEPOLIA = Object.freeze({
1156
+ CAPXUL_TEST_USDC: SYNTHETIC_TEST_ASSET_FIXTURES.usdc,
1157
+ CAPXUL_TEST_USDT: SYNTHETIC_TEST_ASSET_FIXTURES.usdt,
1158
+ CAPXUL_TEST_WETH: SYNTHETIC_TEST_ASSET_FIXTURES.weth
1159
+ });
1160
+ const CONFIGURED_MONEY_ASSETS = Object.freeze(Object.values(SETTLEMENT_ASSETS_BASE_SEPOLIA));
1161
+ /** Public AssetIds for the admitted Base Sepolia test assets. */
1162
+ const TOKENS = Object.freeze({ BASE_SEPOLIA: Object.freeze({
1163
+ CAPXUL_TEST_USDC: SETTLEMENT_ASSETS_BASE_SEPOLIA.CAPXUL_TEST_USDC.assetId,
1164
+ CAPXUL_TEST_USDT: SETTLEMENT_ASSETS_BASE_SEPOLIA.CAPXUL_TEST_USDT.assetId,
1165
+ CAPXUL_TEST_WETH: SETTLEMENT_ASSETS_BASE_SEPOLIA.CAPXUL_TEST_WETH.assetId
1166
+ }) });
1169
1167
  /**
1170
1168
  * Every known asset in ONE identity space. The tiers above declare admission;
1171
1169
  * this is the only list an identity resolves against, so an asset is never
1172
1170
  * two different things depending on which list a caller happened to read.
1173
1171
  */
1174
- const MONEY_ASSET_REGISTRY = [USDX_ASSET, ...SYNTHETIC_TEST_ASSETS];
1172
+ const MONEY_ASSET_REGISTRY = [...SYNTHETIC_TEST_ASSETS];
1175
1173
  CONFIGURED_MONEY_ASSETS.map((asset) => asset.tokenAddress.toLowerCase());
1176
- USDX_ADDRESS_BASE_SEPOLIA.toLowerCase();
1177
1174
  /** The registry row for `assetId` across every tier, or `null`. */
1178
1175
  function configuredMoneyAssetById(assetId) {
1179
1176
  const canonical = toAssetId(assetId);
@@ -1183,6 +1180,28 @@ function configuredMoneyAssetById(assetId) {
1183
1180
  //#region ../config/src/org-payments.ts
1184
1181
  /** Zodiac MultiSendCallOnly module on Base Sepolia. */
1185
1182
  const MULTI_SEND_CALL_ONLY = "0x9641d764fc13c8b624c04430c7356c1c7c8102e2";
1183
+ /** Supported Organization permission identities, newest first. */
1184
+ const ORG_PERMISSION_IDENTITY_PROFILES = [{
1185
+ id: "zodiac-roles-2.1.1",
1186
+ moduleFactoryRuntimeKeccak256: "0x01623cbcf010a1c326230f1b2d5f48a66b440232ee49096102bc84967dc5f21e",
1187
+ mastercopy: "0xf2964ce6161ce0e75964fe7927ce114cb0b283d5",
1188
+ mastercopyRuntimeKeccak256: "0x471d8b3b419f1eb955230c0326c8812176df49bf3c7b414a563fda5a3c6c10b6",
1189
+ proxyRuntimeKeccak256: "0x248f9591f94c24c199ade3e73feb477cf02d387b2bea91f2f931a0deb094f163",
1190
+ multiSendUnwrapper: "0xb4cd4bb764c089f20da18700ce8bc5e49f369efd",
1191
+ multiSendUnwrapperRuntimeKeccak256: "0x1f6e088be5e6ef9d0fbe0547d3fa9a9e40d823433fd8a4449215b5663209a1eb"
1192
+ }, {
1193
+ id: "zodiac-roles-2.1.0",
1194
+ moduleFactoryRuntimeKeccak256: "0x01623cbcf010a1c326230f1b2d5f48a66b440232ee49096102bc84967dc5f21e",
1195
+ mastercopy: "0x9646fdad06d3e24444381f44362a3b0eb343d337",
1196
+ mastercopyRuntimeKeccak256: "0x87911cbc6aa0496e6bcb07dab2462b9c76daea130dede6dcc57d0adf307fa7ec",
1197
+ proxyRuntimeKeccak256: "0x9d4059abf46db50e4b48d9b6404b1f200cb5a1efeb3725be2de630a2a9001944",
1198
+ multiSendUnwrapper: "0x93b7fcbc63ed8a3a24b59e1c3e6649d50b7427c0",
1199
+ multiSendUnwrapperRuntimeKeccak256: "0x4d8d8608b70a6e2f40726b9dd74406bbf73e90cac58e23ee953e2b45ad7dc3df"
1200
+ }];
1201
+ const CURRENT_ORG_PERMISSION_IDENTITY = ORG_PERMISSION_IDENTITY_PROFILES[0];
1202
+ ORG_PERMISSION_IDENTITY_PROFILES[1];
1203
+ CURRENT_ORG_PERMISSION_IDENTITY.mastercopy;
1204
+ CURRENT_ORG_PERMISSION_IDENTITY.multiSendUnwrapper;
1186
1205
  //#endregion
1187
1206
  //#region ../config/src/safe.ts
1188
1207
  const BASE_SEPOLIA_CHAIN_ID = 84532;
@@ -1226,7 +1245,7 @@ function normalizeSafeSaltEmail(email) {
1226
1245
  * `keccak256(namespace + ":" + normalizeSafeSaltEmail(email))`.
1227
1246
  *
1228
1247
  * Namespace defaults to `CAPXUL_SAFE_SALT_NAMESPACE`
1229
- * (`"capxul:staging:email:v1"`) — the current Capxul scheme. Pass a
1248
+ * (`"capxul:staging:email:v2"`) — the current Capxul scheme. Pass a
1230
1249
  * different namespace only for pinned pre-v2 artifacts
1231
1250
  * (`CAPXUL_SAFE_SALT_NAMESPACE_V1`, faucet Safe) or when bumping the scheme.
1232
1251
  *
@@ -1235,7 +1254,7 @@ function normalizeSafeSaltEmail(email) {
1235
1254
  * @returns The Capxul-namespaced salt nonce as a `bigint`
1236
1255
  */
1237
1256
  function computeCapxulSafeSaltNonce(input) {
1238
- const namespace = input.namespace ?? "capxul:staging:email:v1";
1257
+ const namespace = input.namespace ?? "capxul:staging:email:v2";
1239
1258
  const normalizedEmail = normalizeSafeSaltEmail(input.email);
1240
1259
  return BigInt(keccak256(toBytes(`${namespace}:${normalizedEmail}`)));
1241
1260
  }
@@ -1411,6 +1430,9 @@ function normalizeBindingEmail(email) {
1411
1430
  return normalizeSafeSaltEmail(email);
1412
1431
  }
1413
1432
  //#endregion
1433
+ //#region ../config/src/role-dsl.ts
1434
+ const ADMIN_ROLE_LABEL = "Admin";
1435
+ //#endregion
1414
1436
  //#region ../config/src/capxul-payments-v2.ts
1415
1437
  /** The exact compiled CapxulPaymentsV2 ABI. */
1416
1438
  const CAPXUL_PAYMENTS_V2_ABI = [
@@ -2056,38 +2078,6 @@ const CAPXUL_PAYMENTS_V2_ABI = [
2056
2078
  ];
2057
2079
  /** Immutable CapxulPaymentsV2 deployment on Base Sepolia. */
2058
2080
  const CAPXUL_PAYMENTS_V2_ADDRESS = "0x7a5c25aB7ce91d7a475B71cd1ec33E230291f8a4";
2059
- //#endregion
2060
- //#region ../config/src/route.ts
2061
- /** USDX on Base Sepolia — the only settlement token in v1. */
2062
- const USDX_BASE_SEPOLIA_TOKEN = {
2063
- chainId: BASE_SEPOLIA_CHAIN_ID,
2064
- address: USDX_ADDRESS_BASE_SEPOLIA,
2065
- decimals: 6
2066
- };
2067
- USDX_BASE_SEPOLIA_TOKEN.chainId;
2068
- USDX_BASE_SEPOLIA_TOKEN.chainId;
2069
- 10n ** BigInt(6);
2070
- /**
2071
- * The bootstrap manage role. It administers people and roles, cannot spend,
2072
- * and is not a signer of the Organization Safe; "Owner" implied the latter,
2073
- * so the label is Admin (ruled 2026-09-06).
2074
- */
2075
- const ADMIN_ROLE_LABEL = "Admin";
2076
- function normalizeOrgRoleLabel(label) {
2077
- const normalized = label.trim().replace(/\s+/g, " ");
2078
- if (normalized.length === 0) throw Errors.invalidInput("role.label", "must be a non-empty role label");
2079
- return normalized;
2080
- }
2081
- function orgRoleKeyForLabel(label) {
2082
- return keccak256(toBytes(normalizeOrgRoleLabel(label).toLowerCase()));
2083
- }
2084
- padHex(stringToHex("FM_DAILY"), {
2085
- size: 32,
2086
- dir: "right"
2087
- }), padHex(stringToHex("TL_DAILY"), {
2088
- size: 32,
2089
- dir: "right"
2090
- });
2091
2081
  padHex(concat([MULTI_SEND_CALL_ONLY, "0x8d80ff0a"]), {
2092
2082
  dir: "right",
2093
2083
  size: 32
@@ -2100,9 +2090,9 @@ function paymentsV2Signature(name) {
2100
2090
  paymentsV2Signature("send");
2101
2091
  paymentsV2Signature("sendBatch");
2102
2092
  paymentsV2Signature("createCommitment");
2103
- paymentsV2Signature("cancel");
2104
- paymentsV2Signature("redirect");
2105
- new Map([
2093
+ //#endregion
2094
+ //#region ../config/src/org-bootstrap.ts
2095
+ const ROLE_AUTHORITY_EVENT_BY_FUNCTION_SELECTOR = new Map([
2106
2096
  ["assignRoles(address,bytes32[],bool[])", "AssignRoles(address,bytes32[],bool[])"],
2107
2097
  ["allowTarget(bytes32,address,uint8)", "AllowTarget(bytes32,address,uint8)"],
2108
2098
  ["scopeTarget(bytes32,address)", "ScopeTarget(bytes32,address)"],
@@ -2112,6 +2102,8 @@ new Map([
2112
2102
  ["revokeFunction(bytes32,address,bytes4)", "RevokeFunction(bytes32,address,bytes4)"],
2113
2103
  ["setAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)", "SetAllowance(bytes32,uint128,uint128,uint128,uint64,uint64)"]
2114
2104
  ].map(([functionSignature, eventSignature]) => [toFunctionSelector(functionSignature), toEventSelector(eventSignature)]));
2105
+ toEventSelector("AssignRoles(address,bytes32[],bool[])");
2106
+ new Set([...ROLE_AUTHORITY_EVENT_BY_FUNCTION_SELECTOR.values()].map((topic) => topic.toLowerCase()));
2115
2107
  //#endregion
2116
2108
  //#region src/ports/auth-client.ts
2117
2109
  var AuthClientError = class extends Data.TaggedError("AuthClientError") {};
@@ -2489,4 +2481,4 @@ function readExchange(body) {
2489
2481
  };
2490
2482
  }
2491
2483
  //#endregion
2492
- export { toOrgId as $, EVM_ADDRESS_RE as A, redactUrlSecrets as At, toAssetId as B, APP_ID_RE as C, failureFingerprint as Ct, CLIENT_REQUEST_ID_RE as D, containsSensitiveMaterial as Dt, CLIENT_GRANT_ID_RE as E, revertSummaryText as Et, currencySymbolFor as F, toCurrencyCode as G, toBudgetId as H, toAccountId as I, toEpochMs as J, toDurationMs as K, toAddress as L, WEI_RE as M, ZERO_BYTES32 as N, COUNTRY_CODE_RE as O, isCredentialField as Ot, assetIdFor as P, toKycTier as Q, toAllowedOrigin as R, ACCOUNT_ID_RE as S, decodeChainCause as St, BYTES32_RE as T, isFailureMode as Tt, toChainId as U, toAuthUserId as V, toCountryCode as W, toHandle as X, toEpochSeconds as Y, toJwtToken as Z, normalizeBindingEmail as _, CHAIN_UPSTREAMS as _t, parseAuthSession as a, toSessionToken as at, CONFIGURED_MONEY_ASSETS as b, chainCauseProperties as bt, AuthCachePortTag as c, toWeiAmount as ct, authClientPortFromPromiseAdapter as d, decodeConvexError as dt, toPartyId as et, AuthClientError as f, CAPXUL_ERROR_CODES as ft, CAPXUL_PAYMENTS_V2_ADDRESS as g, isCapxulError as gt, orgRoleKeyForLabel as h, Errors as ht, BrowserAuthCacheAdapter as i, toRoleKey as it, SUPPORTED_CURRENCY_CODES as j, EMAIL_RE as k, redactSecrets as kt, SystemClockLayer as l, validateHandle as lt, ADMIN_ROLE_LABEL as m, EXPECTED_OPERATION_OUTCOMES as mt, readClockNow as n, toPayrollRunId as nt, parseCachedJwt as o, toTesterKind as ot, AuthClientPortTag as p, CapxulError as pt, toEmail as q, InMemoryAuthCacheAdapter as r, toPublishableKey as rt, AuthCacheError as s, toTxHash as st, oauthBearerAuthClient as t, toPayrollGroupId as tt, ClockPortTag as u, HANDLE_RE as ut, BASE_SEPOLIA_CHAIN_ID as v, FAILURE_MODES as vt, ASSET_ID_RE as w, isChainUpstream as wt, configuredMoneyAssetById as x, chainEvidenceLabel as xt, deriveCapxulSafeAddress as y, boundedResponseHeaders as yt, toAppId as z };
2484
+ export { toKycTier as $, EMAIL_RE as A, containsSensitiveMaterial as At, toAppId as B, ACCOUNT_ID_RE as C, chainCauseProperties as Ct, CLIENT_GRANT_ID_RE as D, isChainUpstream as Dt, BYTES32_RE as E, failureFingerprint as Et, assetIdFor as F, toCountryCode as G, toAuthUserId as H, currencySymbolFor as I, toEmail as J, toCurrencyCode as K, toAccountId as L, SUPPORTED_CURRENCY_CODES as M, redactSecrets as Mt, WEI_RE as N, redactUrlSecrets as Nt, CLIENT_REQUEST_ID_RE as O, isFailureMode as Ot, ZERO_BYTES32 as P, toJwtToken as Q, toAddress as R, configuredMoneyAssetById as S, boundedResponseHeaders as St, ASSET_ID_RE as T, decodeChainCause as Tt, toBudgetId as U, toAssetId as V, toChainId as W, toEpochSeconds as X, toEpochMs as Y, toHandle as Z, BASE_SEPOLIA_CHAIN_ID as _, EXPECTED_OPERATION_OUTCOMES as _t, parseAuthSession as a, toPermissionId as at, SYNTHETIC_TEST_ASSET_FIXTURES as b, CHAIN_UPSTREAMS as bt, AuthCachePortTag as c, toSessionToken as ct, authClientPortFromPromiseAdapter as d, toWeiAmount as dt, toOrgId as et, AuthClientError as f, validateHandle as ft, normalizeBindingEmail as g, CapxulError as gt, ADMIN_ROLE_LABEL as h, CAPXUL_ERROR_CODES as ht, BrowserAuthCacheAdapter as i, toPermissionAssignmentId as it, EVM_ADDRESS_RE as j, isCredentialField as jt, COUNTRY_CODE_RE as k, revertSummaryText as kt, SystemClockLayer as l, toTesterKind as lt, CAPXUL_PAYMENTS_V2_ADDRESS as m, decodeConvexError as mt, readClockNow as n, toPayrollGroupId as nt, parseCachedJwt as o, toPublishableKey as ot, AuthClientPortTag as p, HANDLE_RE as pt, toDurationMs as q, InMemoryAuthCacheAdapter as r, toPayrollRunId as rt, AuthCacheError as s, toRoleKey as st, oauthBearerAuthClient as t, toPartyId as tt, ClockPortTag as u, toTxHash as ut, deriveCapxulSafeAddress as v, Errors as vt, APP_ID_RE as w, chainEvidenceLabel as wt, TOKENS as x, FAILURE_MODES as xt, CONFIGURED_MONEY_ASSETS as y, isCapxulError as yt, toAllowedOrigin as z };