@capxul/sdk 4.1.4 → 4.2.0-rc.2
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/{clock-C2AUlq1V.mjs → OAuthBearerAuthClient-BrbXrndM.mjs} +525 -52
- package/dist/{signer-C0hZ6Kiy.d.mts → OAuthBearerAuthClient-C-ip-z8M.d.mts} +30 -6
- package/dist/capxul-source.json +11 -0
- package/dist/index.d.mts +53 -51
- package/dist/index.mjs +144 -3505
- package/dist/node/index.d.mts +2 -3
- package/dist/node/index.mjs +1 -2
- package/dist/{observation-Bhdq9-l6.d.mts → production-CBKHzqYP.d.mts} +97 -4
- package/dist/{create-capxul-client-B3ET402J.mjs → production-WbMhv2Fb.mjs} +4098 -181
- package/dist/testing/index.d.mts +7 -2
- package/dist/testing/index.mjs +10 -4
- package/package.json +3 -3
- package/dist/OAuthBearerAuthClient-BHvDR8_0.mjs +0 -211
- package/dist/OAuthBearerAuthClient-D-DLYlKj.d.mts +0 -19
|
@@ -1,39 +1,275 @@
|
|
|
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
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
const CREDENTIAL_FIELDS = /* @__PURE__ */ new Set([
|
|
5
|
+
"password",
|
|
6
|
+
"passwd",
|
|
7
|
+
"pwd",
|
|
8
|
+
"pass",
|
|
9
|
+
"passphrase",
|
|
10
|
+
"currentpassword",
|
|
11
|
+
"newpassword",
|
|
12
|
+
"confirmpassword",
|
|
13
|
+
"passwordconfirmation",
|
|
14
|
+
"passwordhash",
|
|
15
|
+
"hashedpassword",
|
|
16
|
+
"pin",
|
|
17
|
+
"privatekey",
|
|
18
|
+
"walletprivatekey",
|
|
19
|
+
"secretkey",
|
|
20
|
+
"signingkey",
|
|
21
|
+
"signingsecret",
|
|
22
|
+
"seed",
|
|
23
|
+
"seedphrase",
|
|
24
|
+
"mnemonic",
|
|
25
|
+
"recoveryphrase",
|
|
26
|
+
"recovery",
|
|
27
|
+
"recoverycode",
|
|
28
|
+
"recoverycodes",
|
|
29
|
+
"keyshare",
|
|
30
|
+
"signingmaterial",
|
|
31
|
+
"recoverymaterial",
|
|
32
|
+
"privatekeybytes",
|
|
33
|
+
"signingprivatekey",
|
|
34
|
+
"encryptionkey",
|
|
35
|
+
"decryptionkey",
|
|
36
|
+
"authtoken",
|
|
37
|
+
"apitoken",
|
|
38
|
+
"bearertoken",
|
|
39
|
+
"oauthtoken",
|
|
40
|
+
"csrftoken",
|
|
41
|
+
"clientassertion",
|
|
42
|
+
"token",
|
|
43
|
+
"accesstoken",
|
|
44
|
+
"refreshtoken",
|
|
45
|
+
"idtoken",
|
|
46
|
+
"sessiontoken",
|
|
47
|
+
"session",
|
|
48
|
+
"sessionkey",
|
|
49
|
+
"sessionsecret",
|
|
50
|
+
"authsessionid",
|
|
51
|
+
"sessioncredential",
|
|
52
|
+
"sessioncredentials",
|
|
53
|
+
"sessioncookie",
|
|
54
|
+
"auth",
|
|
55
|
+
"authentication",
|
|
56
|
+
"authorization",
|
|
57
|
+
"proxyauthorization",
|
|
58
|
+
"cookie",
|
|
59
|
+
"cookies",
|
|
60
|
+
"setcookie",
|
|
61
|
+
"apikey",
|
|
62
|
+
"xapikey",
|
|
63
|
+
"xauthtoken",
|
|
64
|
+
"clientsecret",
|
|
65
|
+
"secret",
|
|
66
|
+
"secrets",
|
|
67
|
+
"credential",
|
|
68
|
+
"credentials",
|
|
69
|
+
"signature",
|
|
70
|
+
"otp",
|
|
71
|
+
"otpcode",
|
|
72
|
+
"totpsecret",
|
|
73
|
+
"onetimecode",
|
|
74
|
+
"onetimepassword",
|
|
75
|
+
"verificationcode",
|
|
76
|
+
"authcode",
|
|
77
|
+
"authorizationcode",
|
|
78
|
+
"oauthcode",
|
|
79
|
+
"codeverifier"
|
|
80
|
+
]);
|
|
81
|
+
/** Match credential fields by meaning, without hiding token addresses or replay IDs. */
|
|
82
|
+
function isCredentialField(name, context) {
|
|
83
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9]/gu, "");
|
|
84
|
+
return CREDENTIAL_FIELDS.has(normalized) || /(?:password(?:hash)?|passwd|passphrase|privatekey|signingkey|apikey|clientsecret|accesstoken|refreshtoken|sessiontoken)$/u.test(normalized) || context === "authentication" && (normalized === "code" || normalized === "sessionid") || context === "header" && /(?:token|secret|apikey|privatekey|signingkey|password|authorization|cookie|session)$/u.test(normalized);
|
|
85
|
+
}
|
|
86
|
+
function decodeUrlComponent(text) {
|
|
87
|
+
try {
|
|
88
|
+
return decodeURIComponent(text);
|
|
89
|
+
} catch {
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Preserve URL diagnostics while removing credentials from their declared components. */
|
|
94
|
+
function redactUrlSecrets(value, source) {
|
|
95
|
+
try {
|
|
96
|
+
const absolute = /^[a-z][a-z0-9+.-]*:/iu.test(value);
|
|
97
|
+
const protocolRelative = value.startsWith("//");
|
|
98
|
+
const url = new URL(value, "https://redaction.invalid");
|
|
99
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return maskText(value);
|
|
100
|
+
let changed = url.username.length > 0 || url.password.length > 0;
|
|
101
|
+
const maskParams = (params) => new URLSearchParams(Array.from(params, ([name, entry]) => {
|
|
102
|
+
const safeName = maskText(name);
|
|
103
|
+
const safeEntry = isCredentialField(name) || /^(?:code|draft|__posthog)$/iu.test(name) ? "[REDACTED]" : maskText(entry);
|
|
104
|
+
changed ||= safeName !== name || safeEntry !== entry;
|
|
105
|
+
return [safeName, safeEntry];
|
|
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) => {
|
|
110
|
+
const decoded = decodeUrlComponent(segment);
|
|
111
|
+
const masked = provider && /^v[23]$/u.test(decodeUrlComponent(segments[index - 1] ?? "")) && decoded.length > 0 ? "[REDACTED]" : maskText(decoded);
|
|
112
|
+
changed ||= masked !== decoded;
|
|
113
|
+
return masked === decoded ? segment : encodeURIComponent(masked);
|
|
114
|
+
}).join("/");
|
|
115
|
+
const query = maskParams(url.searchParams);
|
|
116
|
+
const fragment = url.hash.slice(1);
|
|
117
|
+
const decodedFragment = decodeUrlComponent(fragment);
|
|
118
|
+
const maskedFragment = maskText(decodedFragment);
|
|
119
|
+
changed ||= maskedFragment !== decodedFragment;
|
|
120
|
+
const fragmentQuery = fragment.indexOf("?");
|
|
121
|
+
const fragmentPath = fragment.slice(0, fragmentQuery);
|
|
122
|
+
const maskedFragmentPath = maskText(decodeUrlComponent(fragmentPath));
|
|
123
|
+
changed ||= maskedFragmentPath !== decodeUrlComponent(fragmentPath);
|
|
124
|
+
const hash = fragmentQuery >= 0 ? `${maskedFragmentPath === decodeUrlComponent(fragmentPath) ? fragmentPath : encodeURIComponent(maskedFragmentPath)}?${maskParams(new URLSearchParams(fragment.slice(fragmentQuery + 1)))}` : fragment.includes("=") ? maskParams(new URLSearchParams(fragment)) : maskedFragment === decodedFragment ? fragment : encodeURIComponent(maskedFragment);
|
|
125
|
+
const origin = absolute ? url.origin : protocolRelative ? `//${url.host}` : "";
|
|
126
|
+
if (!changed) return value;
|
|
127
|
+
return `${origin}${path}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
|
|
128
|
+
} catch {
|
|
129
|
+
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
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const JSON_FIELD = /("(?:[^"\\]|\\.)*")(\s*:\s*)("(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/gu;
|
|
133
|
+
const PROSE_FIELD = /(?=(\b([A-Za-z][A-Za-z0-9_-]*(?:[ \t]+[A-Za-z][A-Za-z0-9_-]*){0,3})\s*[:=]\s*((?:Bearer|Basic)[ \t]+[^\s,;"']+|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&"']+)))/gu;
|
|
134
|
+
const EMBEDDED_URL = /(https?:\/\/[^\s"<>]*)/giu;
|
|
135
|
+
function isSecretLabel(name) {
|
|
136
|
+
return isCredentialField(name) || /^request[ _-]?body$/iu.test(name);
|
|
137
|
+
}
|
|
138
|
+
const JSON_ENCODING_MAX = 8;
|
|
139
|
+
const JSON_NESTING_MAX = 64;
|
|
140
|
+
const JSON_KEY = /("(?:[^"\\]|\\.)*")\s*:/gu;
|
|
141
|
+
const JSON_CONTAINER_START = /(?:\{\s*["}]|\[\s*[[\]{}"0-9tfn-])/uy;
|
|
142
|
+
function hasSecretJsonKey(value) {
|
|
143
|
+
for (const match of value.matchAll(JSON_KEY)) try {
|
|
144
|
+
if (match[1] !== void 0 && isSecretLabel(JSON.parse(match[1]))) return true;
|
|
145
|
+
} catch {}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
/** Project a complete fragment once. Never retry all of its nested substrings. */
|
|
149
|
+
function projectJsonFragment(value, encodingDepth, overDepth) {
|
|
150
|
+
if (overDepth || encodingDepth >= JSON_ENCODING_MAX) return "[REDACTED]";
|
|
151
|
+
let parsed;
|
|
152
|
+
try {
|
|
153
|
+
parsed = JSON.parse(value);
|
|
154
|
+
} catch {
|
|
155
|
+
return hasSecretJsonKey(value) ? "[REDACTED]" : value;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
let changed = false;
|
|
159
|
+
const masked = JSON.stringify(parsed, (name, nested) => {
|
|
160
|
+
if (isSecretLabel(name)) {
|
|
161
|
+
changed ||= nested !== "[REDACTED]";
|
|
162
|
+
return "[REDACTED]";
|
|
163
|
+
}
|
|
164
|
+
if (typeof nested === "string") {
|
|
165
|
+
const projected = maskJsonFragments(nested, encodingDepth + 1);
|
|
166
|
+
changed ||= projected !== nested;
|
|
167
|
+
return projected;
|
|
168
|
+
}
|
|
169
|
+
return nested;
|
|
170
|
+
});
|
|
171
|
+
return changed ? masked : value;
|
|
172
|
+
} catch {
|
|
173
|
+
return "[REDACTED]";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Scan balanced object/array fragments once, respecting JSON quotes and escapes. */
|
|
177
|
+
function maskJsonFragments(value, encodingDepth = 0) {
|
|
178
|
+
if (value.trimStart().startsWith("\"")) try {
|
|
179
|
+
const decoded = JSON.parse(value);
|
|
180
|
+
if (typeof decoded === "string") {
|
|
181
|
+
if (encodingDepth >= JSON_ENCODING_MAX) return "\"[REDACTED]\"";
|
|
182
|
+
const masked = maskJsonFragments(decoded, encodingDepth + 1);
|
|
183
|
+
return masked === decoded ? value : JSON.stringify(masked);
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
let output = "";
|
|
187
|
+
let cursor = 0;
|
|
188
|
+
let start = -1;
|
|
189
|
+
let depth = 0;
|
|
190
|
+
let overDepth = false;
|
|
191
|
+
let quoted = false;
|
|
192
|
+
let escaped = false;
|
|
193
|
+
for (let index = 0; index < value.length; index++) {
|
|
194
|
+
const character = value[index];
|
|
195
|
+
if (start < 0) {
|
|
196
|
+
JSON_CONTAINER_START.lastIndex = index;
|
|
197
|
+
if (!JSON_CONTAINER_START.test(value)) continue;
|
|
198
|
+
start = index;
|
|
199
|
+
depth = 1;
|
|
200
|
+
overDepth = false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (quoted) {
|
|
204
|
+
if (escaped) escaped = false;
|
|
205
|
+
else if (character === "\\") escaped = true;
|
|
206
|
+
else if (character === "\"") quoted = false;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (character === "\"") quoted = true;
|
|
210
|
+
else if (character === "{" || character === "[") {
|
|
211
|
+
depth++;
|
|
212
|
+
overDepth ||= depth > JSON_NESTING_MAX;
|
|
213
|
+
} else if (character === "}" || character === "]") {
|
|
214
|
+
depth--;
|
|
215
|
+
if (depth !== 0) continue;
|
|
216
|
+
output += value.slice(cursor, start) + projectJsonFragment(value.slice(start, index + 1), encodingDepth, overDepth);
|
|
217
|
+
cursor = index + 1;
|
|
218
|
+
start = -1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (start >= 0) {
|
|
222
|
+
const fragment = value.slice(start);
|
|
223
|
+
output += value.slice(cursor, start) + (overDepth || hasSecretJsonKey(fragment) ? "[REDACTED]" : fragment);
|
|
224
|
+
cursor = value.length;
|
|
225
|
+
}
|
|
226
|
+
return output + value.slice(cursor);
|
|
227
|
+
}
|
|
228
|
+
function maskLabelledText(value) {
|
|
229
|
+
const jsonMasked = maskJsonFragments(value).replace(JSON_FIELD, (whole, quotedName, separator) => {
|
|
230
|
+
try {
|
|
231
|
+
return isSecretLabel(JSON.parse(quotedName)) ? `${quotedName}${separator}"[REDACTED]"` : whole;
|
|
232
|
+
} catch {
|
|
233
|
+
return whole;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
let output = "";
|
|
237
|
+
let cursor = 0;
|
|
238
|
+
for (const match of jsonMasked.matchAll(PROSE_FIELD)) {
|
|
239
|
+
const [, whole, label, entry] = match;
|
|
240
|
+
if (whole === void 0 || label === void 0 || entry === void 0 || match.index < cursor || /^Bearer[ \t]+/iu.test(entry)) continue;
|
|
241
|
+
const words = label.split(/[ \t]+/u);
|
|
242
|
+
for (let index = 0; index < words.length; index++) {
|
|
243
|
+
const name = words.slice(index).join(" ");
|
|
244
|
+
if (isSecretLabel(name)) {
|
|
245
|
+
const prefix = words.slice(0, index).join(" ");
|
|
246
|
+
output += jsonMasked.slice(cursor, match.index) + `${prefix ? `${prefix} ` : ""}${name}=[REDACTED]`;
|
|
247
|
+
cursor = match.index + whole.length;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return output + jsonMasked.slice(cursor);
|
|
253
|
+
}
|
|
254
|
+
const TOKEN_MASKS = [
|
|
255
|
+
[/\bBearer\s+(?!(?:realm|error|error_description|scope|authorization_uri|resource|claims)\s*=)[^\s,;"']+/giu, "Bearer [REDACTED]"],
|
|
14
256
|
[/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]"],
|
|
15
257
|
[/(^|[^a-fA-F0-9])(?<!0[xX])[a-fA-F0-9]{64}(?=$|[^a-fA-F0-9])/gu, "$1[REDACTED]"],
|
|
16
|
-
[/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"]
|
|
17
|
-
[/(https?:\/\/[^\s"']+?\/v[0-9]\/)[A-Za-z0-9_-]{16,}/giu, "$1[REDACTED]"],
|
|
18
|
-
[/\b(api[ _-]?key|access[ _-]?token|token|secret|password|passphrase|private[ _-]?key|authorization|cookie|signature|request[ _-]?body|credential|otp|one[ _-]?time[ _-]?(?:password|code)|verification[ _-]?code)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;&]+)/giu, "$1=[REDACTED]"]
|
|
258
|
+
[/(?:(?:sk|pk|rk)_(?:live|test)|(?:phc|phx|ghp|gho|ghu|ghs|ghr|whsec)_|github_pat_|xox[baprs]-|AKIA[0-9A-Z]{16}|AIza)[A-Za-z0-9_-]*/giu, "[REDACTED]"]
|
|
19
259
|
];
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
260
|
+
/** URL components and malformed URLs use this non-recursive masking layer. */
|
|
261
|
+
function maskText(value) {
|
|
262
|
+
let text = maskLabelledText(value);
|
|
263
|
+
for (const [pattern, replacement] of TOKEN_MASKS) text = text.replace(pattern, replacement);
|
|
264
|
+
return text;
|
|
265
|
+
}
|
|
266
|
+
/** Reject only when the same semantic sanitizer removes credential material. */
|
|
25
267
|
function containsSensitiveMaterial(value) {
|
|
26
|
-
return
|
|
268
|
+
return redactSecrets(value) !== value;
|
|
27
269
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* lines, provider messages, PostHog rows) but must not carry live credentials.
|
|
32
|
-
*/
|
|
33
|
-
function redactSecrets(value) {
|
|
34
|
-
let text = value;
|
|
35
|
-
for (const [pattern, replacement] of MASKS) text = text.replace(pattern, replacement);
|
|
36
|
-
return text;
|
|
270
|
+
/** Mask credential fields and embedded URLs while preserving surrounding evidence. */
|
|
271
|
+
function redactSecrets(value, source) {
|
|
272
|
+
return maskLabelledText(value).split(EMBEDDED_URL).map((part, index) => index % 2 === 1 ? redactUrlSecrets(part, source).replaceAll("%5BREDACTED%5D", "[REDACTED]") : maskText(part)).join("");
|
|
37
273
|
}
|
|
38
274
|
//#endregion
|
|
39
275
|
//#region ../errors/src/chain-cause.ts
|
|
@@ -605,7 +841,29 @@ function decodeConvexError(err) {
|
|
|
605
841
|
/** Canonical handle syntax shared by validation and branded constructors. */
|
|
606
842
|
const HANDLE_RE = /^[a-z0-9_-]{3,30}$/;
|
|
607
843
|
//#endregion
|
|
844
|
+
//#region ../config/src/handle.ts
|
|
845
|
+
const RESERVED_HANDLES = /* @__PURE__ */ new Set([
|
|
846
|
+
"admin",
|
|
847
|
+
"api",
|
|
848
|
+
"capxul",
|
|
849
|
+
"help",
|
|
850
|
+
"root",
|
|
851
|
+
"support",
|
|
852
|
+
"system",
|
|
853
|
+
"www"
|
|
854
|
+
]);
|
|
855
|
+
function normalizeHandle(raw) {
|
|
856
|
+
return raw.trim().toLowerCase().replace(/^@/, "");
|
|
857
|
+
}
|
|
858
|
+
function validateHandle(raw) {
|
|
859
|
+
const handle = normalizeHandle(raw);
|
|
860
|
+
if (!HANDLE_RE.test(handle)) throw Errors.invalidInput("handle", "must be 3-30 characters of a-z, 0-9, underscore, or hyphen");
|
|
861
|
+
if (RESERVED_HANDLES.has(handle)) throw Errors.invalidInput("handle", "handle is reserved");
|
|
862
|
+
return handle;
|
|
863
|
+
}
|
|
864
|
+
//#endregion
|
|
608
865
|
//#region ../types/src/index.ts
|
|
866
|
+
const ASSET_ID_RE = /^eip155:([1-9][0-9]*)\/erc20:(0x[0-9a-f]{40})$/;
|
|
609
867
|
const EVM_ADDRESS_RE = /^0x[0-9a-f]{40}$/i;
|
|
610
868
|
const BYTES32_RE = /^0x[0-9a-f]{64}$/i;
|
|
611
869
|
const ZERO_BYTES32 = `0x${"0".repeat(64)}`;
|
|
@@ -707,6 +965,17 @@ function toEpochSeconds(raw) {
|
|
|
707
965
|
assertSafeNonNegativeInteger(raw, "epochSeconds");
|
|
708
966
|
return raw;
|
|
709
967
|
}
|
|
968
|
+
/** Validate an already canonical identity without changing signed input. */
|
|
969
|
+
function toAssetId(raw) {
|
|
970
|
+
const match = typeof raw === "string" ? ASSET_ID_RE.exec(raw) : null;
|
|
971
|
+
if (match === null || match[0] !== raw || !Number.isSafeInteger(Number(match[1]))) throw Errors.invalidInput("assetId", "must be a canonical ERC-20 CAIP-19 identifier");
|
|
972
|
+
if (match[2] === "0x0000000000000000000000000000000000000000") throw Errors.invalidInput("assetId", "must identify a nonzero token contract");
|
|
973
|
+
return raw;
|
|
974
|
+
}
|
|
975
|
+
/** Build an identity from verified chain facts. Normalize the token address. */
|
|
976
|
+
function assetIdFor(chainId, tokenAddress) {
|
|
977
|
+
return toAssetId(`eip155:${toChainId(chainId)}/erc20:${toAddress(tokenAddress)}`);
|
|
978
|
+
}
|
|
710
979
|
function toChainId(raw) {
|
|
711
980
|
if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) throw Errors.invalidInput("chainId", invalidValueReason("must be a positive safe integer", raw));
|
|
712
981
|
return raw;
|
|
@@ -769,31 +1038,27 @@ function invalidValueReason(prefix, raw) {
|
|
|
769
1038
|
return `${prefix}: ${String(raw)}`;
|
|
770
1039
|
}
|
|
771
1040
|
//#endregion
|
|
772
|
-
//#region ../config/src/handle.ts
|
|
773
|
-
const RESERVED_HANDLES = /* @__PURE__ */ new Set([
|
|
774
|
-
"admin",
|
|
775
|
-
"api",
|
|
776
|
-
"capxul",
|
|
777
|
-
"help",
|
|
778
|
-
"root",
|
|
779
|
-
"support",
|
|
780
|
-
"system",
|
|
781
|
-
"www"
|
|
782
|
-
]);
|
|
783
|
-
function normalizeHandle(raw) {
|
|
784
|
-
return raw.trim().toLowerCase().replace(/^@/, "");
|
|
785
|
-
}
|
|
786
|
-
function validateHandle(raw) {
|
|
787
|
-
const handle = normalizeHandle(raw);
|
|
788
|
-
if (!HANDLE_RE.test(handle)) throw Errors.invalidInput("handle", "must be 3-30 characters of a-z, 0-9, underscore, or hyphen");
|
|
789
|
-
if (RESERVED_HANDLES.has(handle)) throw Errors.invalidInput("handle", "handle is reserved");
|
|
790
|
-
return handle;
|
|
791
|
-
}
|
|
792
|
-
//#endregion
|
|
793
1041
|
//#region ../config/src/tokens.ts
|
|
794
1042
|
/** `TestUSDC` ("USDX") — Base Sepolia, 6 decimals, open `mint`. (Canon §1.) */
|
|
795
1043
|
const USDX_ADDRESS_BASE_SEPOLIA = "0xf09fcbb6df9f8f918e58f9c8ab15a76ade862b77";
|
|
796
|
-
|
|
1044
|
+
const USDX_ASSET = {
|
|
1045
|
+
assetId: assetIdFor(84532, USDX_ADDRESS_BASE_SEPOLIA),
|
|
1046
|
+
chainId: toChainId(84532),
|
|
1047
|
+
symbol: "USDX",
|
|
1048
|
+
peg: toCurrencyCode("USD"),
|
|
1049
|
+
tokenAddress: USDX_ADDRESS_BASE_SEPOLIA,
|
|
1050
|
+
deploymentStartBlock: 39860173,
|
|
1051
|
+
decimals: 6
|
|
1052
|
+
};
|
|
1053
|
+
const CONFIGURED_MONEY_ASSETS = [{
|
|
1054
|
+
...USDX_ASSET,
|
|
1055
|
+
currency: USDX_ASSET.peg
|
|
1056
|
+
}];
|
|
1057
|
+
CONFIGURED_MONEY_ASSETS.map((asset) => asset.tokenAddress.toLowerCase());
|
|
1058
|
+
function configuredMoneyAssetById(assetId) {
|
|
1059
|
+
const canonical = toAssetId(assetId);
|
|
1060
|
+
return CONFIGURED_MONEY_ASSETS.find((asset) => asset.assetId === canonical) ?? null;
|
|
1061
|
+
}
|
|
797
1062
|
//#endregion
|
|
798
1063
|
//#region ../config/src/org-payments.ts
|
|
799
1064
|
/** Zodiac MultiSendCallOnly module on Base Sepolia. */
|
|
@@ -1762,6 +2027,26 @@ function errorKind(error) {
|
|
|
1762
2027
|
var ClockError = class extends Data.TaggedError("ClockError") {};
|
|
1763
2028
|
var ClockPortTag = class extends Context.Service()("@capxul/sdk/ports/ClockPort") {};
|
|
1764
2029
|
//#endregion
|
|
2030
|
+
//#region src/adapters/clock/SystemClockAdapter.ts
|
|
2031
|
+
var SystemClockAdapter = class {
|
|
2032
|
+
now = Effect.try({
|
|
2033
|
+
try: () => toEpochMs(Date.now()),
|
|
2034
|
+
catch: (cause) => new ClockError({
|
|
2035
|
+
operation: "now",
|
|
2036
|
+
cause
|
|
2037
|
+
})
|
|
2038
|
+
});
|
|
2039
|
+
sleep(duration) {
|
|
2040
|
+
return Effect.callback((resume) => {
|
|
2041
|
+
const timeout = setTimeout(() => resume(Effect.void), duration);
|
|
2042
|
+
return Effect.sync(() => clearTimeout(timeout));
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
function SystemClockLayer() {
|
|
2047
|
+
return Layer.succeed(ClockPortTag, new SystemClockAdapter());
|
|
2048
|
+
}
|
|
2049
|
+
//#endregion
|
|
1765
2050
|
//#region src/ports/auth-cache.ts
|
|
1766
2051
|
var AuthCacheError = class extends Data.TaggedError("AuthCacheError") {};
|
|
1767
2052
|
var AuthCachePortTag = class extends Context.Service()("@capxul/sdk/ports/AuthCachePort") {};
|
|
@@ -1890,4 +2175,192 @@ async function readClockNow(clock) {
|
|
|
1890
2175
|
}
|
|
1891
2176
|
}
|
|
1892
2177
|
//#endregion
|
|
1893
|
-
|
|
2178
|
+
//#region src/adapters/auth-client/OAuthBearerAuthClient.ts
|
|
2179
|
+
const PROVIDER = "agent-exchange";
|
|
2180
|
+
/** Re-exchange this far ahead of expiry, matching the JWT cache eviction margin. */
|
|
2181
|
+
const REFRESH_MARGIN_MS = 3e4;
|
|
2182
|
+
/**
|
|
2183
|
+
* `AuthSession.token` is a Better Auth session handle. This flow has none, and
|
|
2184
|
+
* the access token must never be copied into it, so the slot holds a placeholder.
|
|
2185
|
+
*/
|
|
2186
|
+
const OAUTH_SESSION_TOKEN = toSessionToken("oauth-bearer");
|
|
2187
|
+
var OAuthBearerAuthClient = class {
|
|
2188
|
+
#exchangeUrl;
|
|
2189
|
+
#exchangeSecret;
|
|
2190
|
+
#accessToken;
|
|
2191
|
+
#fetchImpl;
|
|
2192
|
+
#clock;
|
|
2193
|
+
#cached = null;
|
|
2194
|
+
constructor(input) {
|
|
2195
|
+
this.#exchangeUrl = input.exchangeUrl;
|
|
2196
|
+
this.#exchangeSecret = input.exchangeSecret;
|
|
2197
|
+
this.#accessToken = input.accessToken;
|
|
2198
|
+
this.#fetchImpl = input.fetch ?? fetch;
|
|
2199
|
+
this.#clock = input.clock ?? new SystemClockAdapter();
|
|
2200
|
+
}
|
|
2201
|
+
async canSendOtp() {
|
|
2202
|
+
return refuse("canSendOtp");
|
|
2203
|
+
}
|
|
2204
|
+
async sendOtp() {
|
|
2205
|
+
return refuse("sendOtp");
|
|
2206
|
+
}
|
|
2207
|
+
async verifyOtp() {
|
|
2208
|
+
return refuse("verifyOtp");
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* The authorization server owns the token's lifetime, so revoking it there is
|
|
2212
|
+
* the sign-out. Reporting success here would claim a token was dropped.
|
|
2213
|
+
*/
|
|
2214
|
+
async signOut() {
|
|
2215
|
+
return refuse("signOut");
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* The exchanged identity. Unlike the Better Auth adapters this never answers
|
|
2219
|
+
* `null`: the consumer holds a usable access token or a refused one, and a
|
|
2220
|
+
* refused one is a NOT_AUTHENTICATED failure, not the absence of a session.
|
|
2221
|
+
*/
|
|
2222
|
+
async getSession(options) {
|
|
2223
|
+
const exchange = await this.#exchange("getSession", options);
|
|
2224
|
+
if (!exchange.ok) return exchange;
|
|
2225
|
+
return {
|
|
2226
|
+
ok: true,
|
|
2227
|
+
value: {
|
|
2228
|
+
authUserId: toAuthUserId(exchange.value.authUserId),
|
|
2229
|
+
email: toEmail(exchange.value.email),
|
|
2230
|
+
token: OAUTH_SESSION_TOKEN,
|
|
2231
|
+
expiresAt: toEpochMs(exchange.value.expiresAt)
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
async getConvexJwt(options) {
|
|
2236
|
+
const exchange = await this.#exchange("getConvexJwt", options);
|
|
2237
|
+
if (!exchange.ok) return exchange;
|
|
2238
|
+
return {
|
|
2239
|
+
ok: true,
|
|
2240
|
+
value: {
|
|
2241
|
+
token: toJwtToken(exchange.value.token),
|
|
2242
|
+
expEpochSeconds: toEpochSeconds(Math.floor(exchange.value.expiresAt / 1e3))
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* The cached exchange while it is fresh, otherwise a new one. A failed
|
|
2248
|
+
* exchange never replaces the cache, so a stale token is never served.
|
|
2249
|
+
*
|
|
2250
|
+
* lazy: no in-flight de-duplication — two concurrent reads past the margin
|
|
2251
|
+
* each mint a JWT. Minting is idempotent, so the cost is one extra request.
|
|
2252
|
+
*/
|
|
2253
|
+
async #exchange(operation, options) {
|
|
2254
|
+
if (options?.signal?.aborted) return {
|
|
2255
|
+
ok: false,
|
|
2256
|
+
error: Errors.cancelled({ operation })
|
|
2257
|
+
};
|
|
2258
|
+
const cached = this.#cached;
|
|
2259
|
+
if (cached !== null && options?.forceRefresh !== true) {
|
|
2260
|
+
const now = await readClockNow(this.#clock);
|
|
2261
|
+
if (!now.ok) return now;
|
|
2262
|
+
if (now.value < cached.expiresAt - REFRESH_MARGIN_MS) return {
|
|
2263
|
+
ok: true,
|
|
2264
|
+
value: cached
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
return this.#postExchange(operation, options?.signal);
|
|
2268
|
+
}
|
|
2269
|
+
async #postExchange(operation, signal) {
|
|
2270
|
+
let res;
|
|
2271
|
+
try {
|
|
2272
|
+
res = await this.#fetchImpl(this.#exchangeUrl, {
|
|
2273
|
+
method: "POST",
|
|
2274
|
+
headers: {
|
|
2275
|
+
"content-type": "application/json",
|
|
2276
|
+
authorization: `Bearer ${this.#exchangeSecret}`
|
|
2277
|
+
},
|
|
2278
|
+
body: JSON.stringify({ accessToken: this.#accessToken }),
|
|
2279
|
+
...signal === void 0 ? {} : { signal }
|
|
2280
|
+
});
|
|
2281
|
+
} catch (cause) {
|
|
2282
|
+
return {
|
|
2283
|
+
ok: false,
|
|
2284
|
+
error: signal?.aborted === true || isAbortError(cause) ? Errors.cancelled({ operation }) : Errors.networkError(operation, cause)
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
if (signal?.aborted === true) return {
|
|
2288
|
+
ok: false,
|
|
2289
|
+
error: Errors.cancelled({ operation })
|
|
2290
|
+
};
|
|
2291
|
+
const body = await res.json().catch(() => null);
|
|
2292
|
+
if (!res.ok) return {
|
|
2293
|
+
ok: false,
|
|
2294
|
+
error: exchangeRefused(operation, res.status, readErrorCode(body))
|
|
2295
|
+
};
|
|
2296
|
+
const exchange = readExchange(body);
|
|
2297
|
+
if (exchange === null) return {
|
|
2298
|
+
ok: false,
|
|
2299
|
+
error: Errors.providerError(PROVIDER, operation, /* @__PURE__ */ new Error("unexpected body"), { httpStatus: res.status })
|
|
2300
|
+
};
|
|
2301
|
+
this.#cached = exchange;
|
|
2302
|
+
return {
|
|
2303
|
+
ok: true,
|
|
2304
|
+
value: exchange
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
};
|
|
2308
|
+
/**
|
|
2309
|
+
* `AuthClientPort` over the backend agent-token exchange. Build one per OAuth
|
|
2310
|
+
* access token and hand it to `createCapxulClient({ authClient })`.
|
|
2311
|
+
*/
|
|
2312
|
+
function oauthBearerAuthClient(input) {
|
|
2313
|
+
return authClientPortFromPromiseAdapter(new OAuthBearerAuthClient(input));
|
|
2314
|
+
}
|
|
2315
|
+
/** The OTP verbs belong to the email flow; `validStates` names it for the caller. */
|
|
2316
|
+
function refuse(method) {
|
|
2317
|
+
return {
|
|
2318
|
+
ok: false,
|
|
2319
|
+
error: Errors.wrongState({
|
|
2320
|
+
method,
|
|
2321
|
+
currentState: "oauth-bearer",
|
|
2322
|
+
validStates: ["email-otp"]
|
|
2323
|
+
})
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* The error catalog has no FORBIDDEN code, so 403 (`insufficient_scope`) and
|
|
2328
|
+
* 503 (`exchange_unconfigured`) both land on PROVIDER_ERROR. `httpStatus` and
|
|
2329
|
+
* `details.reason` carry which one the backend reported. Only 401 becomes
|
|
2330
|
+
* NOT_AUTHENTICATED, because only 401 is answered by a new access token.
|
|
2331
|
+
*/
|
|
2332
|
+
function exchangeRefused(operation, status, reason) {
|
|
2333
|
+
if (status === 401) return Errors.notAuthenticated("The exchange refused this OAuth access token");
|
|
2334
|
+
return Errors.providerError(PROVIDER, operation, /* @__PURE__ */ new Error(`HTTP ${status}`), {
|
|
2335
|
+
httpStatus: status,
|
|
2336
|
+
...reason === void 0 ? {} : { details: { reason } }
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
/** An abort raised by something other than the caller's own signal. */
|
|
2340
|
+
function isAbortError(cause) {
|
|
2341
|
+
return readField(cause, "name") === "AbortError";
|
|
2342
|
+
}
|
|
2343
|
+
function readErrorCode(body) {
|
|
2344
|
+
const error = readField(body, "error");
|
|
2345
|
+
return typeof error === "string" ? error : void 0;
|
|
2346
|
+
}
|
|
2347
|
+
function readField(value, key) {
|
|
2348
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2349
|
+
return value[key];
|
|
2350
|
+
}
|
|
2351
|
+
function readExchange(body) {
|
|
2352
|
+
if (typeof body !== "object" || body === null) return null;
|
|
2353
|
+
const { token, expiresAt, authUserId, email } = body;
|
|
2354
|
+
if (typeof token !== "string" || token.length === 0) return null;
|
|
2355
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null;
|
|
2356
|
+
if (typeof authUserId !== "string" || authUserId.length === 0) return null;
|
|
2357
|
+
if (typeof email !== "string" || email.length === 0) return null;
|
|
2358
|
+
return {
|
|
2359
|
+
token,
|
|
2360
|
+
expiresAt,
|
|
2361
|
+
authUserId,
|
|
2362
|
+
email
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
//#endregion
|
|
2366
|
+
export { toTesterKind as $, toAccountId as A, toEmail as B, BYTES32_RE as C, ZERO_BYTES32 as D, WEI_RE as E, toBudgetId as F, toKycTier as G, toEpochSeconds as H, toChainId as I, toPayrollGroupId as J, toOrgId as K, toCountryCode as L, toAllowedOrigin as M, toAppId as N, assetIdFor as O, toAuthUserId as P, toSessionToken as Q, toCurrencyCode as R, ASSET_ID_RE as S, SUPPORTED_CURRENCY_CODES as T, toHandle as U, toEpochMs as V, toJwtToken as W, toPublishableKey as X, toPayrollRunId as Y, toRoleKey as Z, BASE_SEPOLIA_CHAIN_ID as _, revertSummaryText as _t, parseAuthSession as a, EXPECTED_OPERATION_OUTCOMES as at, ACCOUNT_ID_RE as b, redactSecrets as bt, AuthCachePortTag as c, CHAIN_UPSTREAMS as ct, authClientPortFromPromiseAdapter as d, chainCauseProperties as dt, validateHandle as et, AuthClientError as f, chainEvidenceLabel as ft, normalizeBindingEmail as g, isFailureMode as gt, CAPXUL_PAYMENTS_V2_ADDRESS as h, isChainUpstream as ht, BrowserAuthCacheAdapter as i, CapxulError as it, toAddress as j, currencySymbolFor as k, SystemClockLayer as l, FAILURE_MODES as lt, orgRoleKeyForLabel as m, failureFingerprint as mt, readClockNow as n, decodeConvexError as nt, parseCachedJwt as o, Errors as ot, AuthClientPortTag as p, decodeChainCause as pt, toPartyId as q, InMemoryAuthCacheAdapter as r, CAPXUL_ERROR_CODES as rt, AuthCacheError as s, isCapxulError as st, oauthBearerAuthClient as t, HANDLE_RE as tt, ClockPortTag as u, boundedResponseHeaders as ut, deriveCapxulSafeAddress as v, containsSensitiveMaterial as vt, EVM_ADDRESS_RE as w, APP_ID_RE as x, redactUrlSecrets as xt, configuredMoneyAssetById as y, isCredentialField as yt, toDurationMs as z };
|