@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.
- package/dist/{signer-C0hZ6Kiy.d.mts → OAuthBearerAuthClient-C-ip-z8M.d.mts} +30 -6
- package/dist/{clock-DIfTX44d.mjs → OAuthBearerAuthClient-DDD0JlaI.mjs} +379 -27
- package/dist/index.d.mts +75 -51
- package/dist/index.mjs +144 -3569
- package/dist/node/index.d.mts +2 -3
- package/dist/node/index.mjs +1 -2
- package/dist/{create-capxul-client-B6PUjdld.mjs → production-BmsFhDUr.mjs} +8998 -3076
- package/dist/{observation-vegBPfXj.d.mts → production-DvGDNIwu.d.mts} +298 -84
- package/dist/testing/index.d.mts +82 -2
- package/dist/testing/index.mjs +101 -4
- package/package.json +7 -7
- package/dist/OAuthBearerAuthClient-ByPzJZr8.mjs +0 -211
- package/dist/OAuthBearerAuthClient-D-DLYlKj.d.mts +0 -19
|
@@ -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
|
-
|
|
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
|
|
108
|
-
const
|
|
109
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|