@oracle-agent/oracle 0.1.0
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/CONTRIBUTING.md +98 -0
- package/LICENSE +202 -0
- package/README.md +384 -0
- package/SECURITY.md +89 -0
- package/SETUP.md +235 -0
- package/artifacts/inscription/oracle-was-here-preview.png +0 -0
- package/artifacts/inscription/oracle-was-here.svg +1 -0
- package/artifacts/specialist-packs/oracle-full-crypto.json +273 -0
- package/bin/desk-server.mjs +438 -0
- package/bin/oracle-data-mcp.mjs +603 -0
- package/bin/oracle-init.mjs +423 -0
- package/bin/oracle-public-server.mjs +36 -0
- package/bin/oracle-route.mjs +254 -0
- package/bin/oracle-scan.mjs +192 -0
- package/docs/adding-a-chain.md +229 -0
- package/docs/architecture.md +135 -0
- package/docs/profiles.md +132 -0
- package/examples/add-a-chain.mjs +65 -0
- package/examples/research-a-token.mjs +70 -0
- package/package.json +90 -0
- package/profiles/_template/SOUL.md +47 -0
- package/profiles/_template/profile.json +22 -0
- package/profiles/bitcoin-agent/SOUL.md +31 -0
- package/profiles/bitcoin-agent/profile.json +32 -0
- package/profiles/hyperliquid-agent/SOUL.md +34 -0
- package/profiles/hyperliquid-agent/profile.json +37 -0
- package/profiles/oracle/SOUL.md +65 -0
- package/profiles/oracle/profile.json +35 -0
- package/profiles/polymarket-agent/SOUL.md +35 -0
- package/profiles/polymarket-agent/profile.json +34 -0
- package/profiles/profile.schema.json +90 -0
- package/profiles/protocol-builder/SOUL.md +50 -0
- package/profiles/protocol-builder/profile.json +37 -0
- package/profiles/robinhood-agent/SOUL.md +45 -0
- package/profiles/robinhood-agent/profile.json +39 -0
- package/profiles/solana-agent/SOUL.md +37 -0
- package/profiles/solana-agent/profile.json +37 -0
- package/profiles/stable-agent/SOUL.md +43 -0
- package/profiles/stable-agent/profile.json +37 -0
- package/public/oracle-console/app.js +272 -0
- package/public/oracle-console/bitcoin-wallets.js +206 -0
- package/public/oracle-console/index.html +91 -0
- package/public/oracle-console/styles.css +239 -0
- package/public/oracle-splash/index.html +931 -0
- package/scripts/build-inscription.py +230 -0
- package/scripts/check-test-count.mjs +105 -0
- package/scripts/e2e-hl-markets.mjs +21 -0
- package/scripts/e2e-hl-perps.mjs +48 -0
- package/scripts/e2e-hypercore-staking.mjs +128 -0
- package/scripts/e2e-solana-bitcoin.mjs +183 -0
- package/scripts/public-api-scan.mjs +23 -0
- package/scripts/secret-scan.mjs +181 -0
- package/scripts/verify-v3-venues.mjs +192 -0
- package/skills/oracle-best-execution/SKILL.md +127 -0
- package/skills/oracle-bitcoin/SKILL.md +53 -0
- package/skills/oracle-chain-graphs-telegram-cards/SKILL.md +59 -0
- package/skills/oracle-circuit-breaker/SKILL.md +51 -0
- package/skills/oracle-contract-research/SKILL.md +55 -0
- package/skills/oracle-desk/SKILL.md +58 -0
- package/skills/oracle-dex-launch/SKILL.md +38 -0
- package/skills/oracle-grants/SKILL.md +69 -0
- package/skills/oracle-hypercore-staking/SKILL.md +57 -0
- package/skills/oracle-hyperliquid/SKILL.md +56 -0
- package/skills/oracle-meme-token-sniper/SKILL.md +73 -0
- package/skills/oracle-nft-gacha-launch/SKILL.md +48 -0
- package/skills/oracle-nft-mint-gas-war/SKILL.md +63 -0
- package/skills/oracle-polymarket/SKILL.md +60 -0
- package/skills/oracle-protocol-builder/SKILL.md +38 -0
- package/skills/oracle-protocol-security/SKILL.md +60 -0
- package/skills/oracle-public-product/SKILL.md +44 -0
- package/skills/oracle-receipts/SKILL.md +52 -0
- package/skills/oracle-rfq-tokenized-assets/SKILL.md +69 -0
- package/skills/oracle-smart-wallet-scanner/SKILL.md +49 -0
- package/skills/oracle-solana/SKILL.md +65 -0
- package/skills/oracle-solana-nft/SKILL.md +54 -0
- package/skills/oracle-token-research/SKILL.md +67 -0
- package/src/agent-auth.mjs +191 -0
- package/src/approval-guard.mjs +282 -0
- package/src/attestation-secret.mjs +88 -0
- package/src/audit-log.mjs +196 -0
- package/src/auto-slippage.mjs +378 -0
- package/src/capability-posture.mjs +125 -0
- package/src/chains.mjs +62 -0
- package/src/data/catalog.mjs +495 -0
- package/src/data/desk-data.mjs +623 -0
- package/src/data/http.mjs +200 -0
- package/src/data/provider-endpoint.mjs +94 -0
- package/src/data/providers/aerodrome.mjs +244 -0
- package/src/data/providers/balancer.mjs +208 -0
- package/src/data/providers/bitcoin-esplora.mjs +230 -0
- package/src/data/providers/bitcoin-meta.mjs +378 -0
- package/src/data/providers/blockscout.mjs +14 -0
- package/src/data/providers/bridges.mjs +241 -0
- package/src/data/providers/cowswap.mjs +501 -0
- package/src/data/providers/curve.mjs +200 -0
- package/src/data/providers/defillama.mjs +88 -0
- package/src/data/providers/dexscreener.mjs +43 -0
- package/src/data/providers/evm-rpc.mjs +203 -0
- package/src/data/providers/geckoterminal.mjs +34 -0
- package/src/data/providers/gmx.mjs +495 -0
- package/src/data/providers/hl-info.mjs +102 -0
- package/src/data/providers/hl-markets.mjs +210 -0
- package/src/data/providers/hl-perps.mjs +382 -0
- package/src/data/providers/hl-staking.mjs +352 -0
- package/src/data/providers/hl-ws.mjs +119 -0
- package/src/data/providers/hyperevm-dex.mjs +49 -0
- package/src/data/providers/jupiter.mjs +182 -0
- package/src/data/providers/lifi.mjs +150 -0
- package/src/data/providers/magiceden-sol.mjs +355 -0
- package/src/data/providers/morpho.mjs +173 -0
- package/src/data/providers/odos.mjs +155 -0
- package/src/data/providers/oneinch.mjs +173 -0
- package/src/data/providers/opensea-multichain.mjs +136 -0
- package/src/data/providers/opensea-nft.mjs +99 -0
- package/src/data/providers/paraswap.mjs +117 -0
- package/src/data/providers/pendle.mjs +187 -0
- package/src/data/providers/poly-public.mjs +96 -0
- package/src/data/providers/poly-ws.mjs +103 -0
- package/src/data/providers/rh-agent.mjs +59 -0
- package/src/data/providers/satflow.mjs +336 -0
- package/src/data/providers/solana-rpc.mjs +186 -0
- package/src/data/providers/uniswap-v3.mjs +303 -0
- package/src/data/providers/zerox.mjs +166 -0
- package/src/data/public-api-scan.mjs +61 -0
- package/src/data/quote-placeholder.mjs +31 -0
- package/src/exact-integer.mjs +72 -0
- package/src/exec-policy.mjs +444 -0
- package/src/flags.mjs +15 -0
- package/src/fresh-window.mjs +76 -0
- package/src/gmx-attestation.mjs +175 -0
- package/src/index.mjs +50 -0
- package/src/nft-gas-war-guard.mjs +139 -0
- package/src/onboarding/agent-keys.mjs +157 -0
- package/src/onboarding/index.mjs +18 -0
- package/src/onboarding/tiers.mjs +139 -0
- package/src/oracle-env.mjs +38 -0
- package/src/protocol-execution.mjs +84 -0
- package/src/public-api/buzz-integration.mjs +256 -0
- package/src/public-api/connect-agent.mjs +397 -0
- package/src/public-api/grants.mjs +142 -0
- package/src/public-api/http.mjs +374 -0
- package/src/public-control/aa-adapter.mjs +402 -0
- package/src/public-control/build-registry.mjs +227 -0
- package/src/public-control/bundler-client.mjs +372 -0
- package/src/public-control/grant-indexer.mjs +296 -0
- package/src/public-control/policy-render.mjs +69 -0
- package/src/public-control/policy-schema.mjs +318 -0
- package/src/public-control/runtime-config.mjs +265 -0
- package/src/public-control/session-key-model.mjs +374 -0
- package/src/public-control/session-orchestrator.mjs +412 -0
- package/src/route-attestation.mjs +132 -0
- package/src/router/best-execution.mjs +221 -0
- package/src/router/index.mjs +185 -0
- package/src/router/prepare-bridge.mjs +288 -0
- package/src/router/prepare-route.mjs +341 -0
- package/src/router/proposal.mjs +311 -0
- package/src/router/risk-classifier.mjs +119 -0
- package/src/router/route-sources.mjs +292 -0
- package/src/scanner/chains.config.mjs +381 -0
- package/src/scanner/contract.mjs +270 -0
- package/src/scanner/evm-scanner.mjs +394 -0
- package/src/scanner/index.mjs +9 -0
- package/src/scanner/v2-venue.mjs +335 -0
- package/src/scanner/v3-venue.mjs +290 -0
- package/src/scopes.mjs +44 -0
- package/src/sell-simulation.mjs +167 -0
- package/src/token-transfer-guard.mjs +188 -0
- package/src/vault-attestation.mjs +145 -0
- package/src/venues.mjs +206 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Oracle Control — human-readable grant renderer (Slice A).
|
|
2
|
+
//
|
|
3
|
+
// Renders a validated public grant into deterministic plain text a user can
|
|
4
|
+
// review before approving. Rendering is a PRESENTATION of the deterministic
|
|
5
|
+
// policy only — it never adds, removes, or reinterprets authority, and it only
|
|
6
|
+
// ever surfaces public grant material (no keys, tokens, paths, or secrets).
|
|
7
|
+
//
|
|
8
|
+
// Output is byte-for-byte deterministic for a given grant: fixed field order,
|
|
9
|
+
// fixed labels, UTC timestamps derived purely from expiresAt, and the sha256
|
|
10
|
+
// grant id from the canonical form. Tests assert exact output.
|
|
11
|
+
|
|
12
|
+
import { normalizeGrant, grantId, isReadonlyAction, GRANT_VERSION } from "./policy-schema.mjs";
|
|
13
|
+
|
|
14
|
+
const LABEL_WIDTH = 14;
|
|
15
|
+
|
|
16
|
+
function row(label, value) {
|
|
17
|
+
return `${(label + ":").padEnd(LABEL_WIDTH)}${value}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Deterministic UTC ISO string from unix seconds. */
|
|
21
|
+
function isoUtc(unixSeconds) {
|
|
22
|
+
return new Date(unixSeconds * 1000).toISOString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** "50 bps (0.50%)" — deterministic fixed-2 percentage. */
|
|
26
|
+
export function formatBps(bps) {
|
|
27
|
+
return `${bps} bps (${(bps / 100).toFixed(2)}%)`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Group wei digits for readability: 1000000 -> "1,000,000". Deterministic. */
|
|
31
|
+
export function formatWei(weiString) {
|
|
32
|
+
const s = String(weiString);
|
|
33
|
+
return `${s.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} wei`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Render a grant as deterministic human-readable text.
|
|
38
|
+
* Validates/normalizes first (throws GrantValidationError on bad input), so a
|
|
39
|
+
* grant that cannot be rendered is a grant that cannot be authorized.
|
|
40
|
+
*/
|
|
41
|
+
export function renderGrant(input, opts = {}) {
|
|
42
|
+
const g = normalizeGrant(input, opts);
|
|
43
|
+
const id = grantId(g);
|
|
44
|
+
const readonlyOnly = g.actions.every((a) => isReadonlyAction(a));
|
|
45
|
+
|
|
46
|
+
const lines = [
|
|
47
|
+
"ORACLE CONTROL GRANT (deterministic authorization)",
|
|
48
|
+
"==================================================",
|
|
49
|
+
row("Grant ID", id),
|
|
50
|
+
row("Version", String(GRANT_VERSION)),
|
|
51
|
+
row("Chain", String(g.chainId)),
|
|
52
|
+
row("Agent", g.agentAddress),
|
|
53
|
+
row("Account", g.accountAddress),
|
|
54
|
+
row("Actions", g.actions.join(", ")),
|
|
55
|
+
row("Targets", g.targets.length ? g.targets.join(", ") : "(none — read/simulate only)"),
|
|
56
|
+
row("Max value", formatWei(g.maxValueWei)),
|
|
57
|
+
row("Max gas", formatWei(g.maxGasWei)),
|
|
58
|
+
row("Max slippage", formatBps(g.maxSlippageBps)),
|
|
59
|
+
row("Expires", `${isoUtc(g.expiresAt)} (unix ${g.expiresAt})`),
|
|
60
|
+
row("Nonce", g.nonce),
|
|
61
|
+
row("Revocation", g.revocationKey),
|
|
62
|
+
"--------------------------------------------------",
|
|
63
|
+
readonlyOnly
|
|
64
|
+
? "Scope: read/simulate only — this grant cannot move funds."
|
|
65
|
+
: "Scope: includes state-changing actions bounded by the caps above.",
|
|
66
|
+
"Self-custodial: this grant contains public data only and can be revoked at any time via the revocation key.",
|
|
67
|
+
];
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// Oracle Control — public grant policy schema (Slice A).
|
|
2
|
+
//
|
|
3
|
+
// Oracle Control is DETERMINISTIC AUTHORIZATION: a grant either validates or it
|
|
4
|
+
// does not, with no advisory judgement involved (that is Oracle Router's job,
|
|
5
|
+
// and Router output must never gate anything here).
|
|
6
|
+
//
|
|
7
|
+
// This module is dependency-free (node builtins only, no npm packages) and
|
|
8
|
+
// fully deterministic: same input object => same normalized grant => same
|
|
9
|
+
// canonical string => same grant id, regardless of key order, address casing,
|
|
10
|
+
// array ordering, or numeric representation (number / bigint / decimal string).
|
|
11
|
+
//
|
|
12
|
+
// Self-custody boundary: a grant only ever references PUBLIC material —
|
|
13
|
+
// chain id, the agent's public address, the user's own account address,
|
|
14
|
+
// action scopes, target contracts, spend/gas/slippage ceilings, expiry,
|
|
15
|
+
// nonce, and a public revocation key identifier. Nothing here may carry or
|
|
16
|
+
// derive private keys, bearer tokens, keystore paths, or session secrets.
|
|
17
|
+
//
|
|
18
|
+
// Fail-closed rules enforced by validateGrant / normalizeGrant:
|
|
19
|
+
// - every required field must be present (expiry, chain, agent, account, ...)
|
|
20
|
+
// - unknown fields are rejected (no smuggling extra authority)
|
|
21
|
+
// - broad wildcard actions are rejected by default ("*", "*:*", "verb:*")
|
|
22
|
+
// - empty targets are only allowed when EVERY action is read/simulate scoped
|
|
23
|
+
// - expiresAt is required and, when a reference `now` is supplied, must be
|
|
24
|
+
// strictly in the future
|
|
25
|
+
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
27
|
+
|
|
28
|
+
export const GRANT_VERSION = 1;
|
|
29
|
+
|
|
30
|
+
/** Every field is required. Order here is documentation only; canonical form
|
|
31
|
+
* always sorts keys alphabetically. */
|
|
32
|
+
export const REQUIRED_FIELDS = Object.freeze([
|
|
33
|
+
"chainId",
|
|
34
|
+
"agentAddress",
|
|
35
|
+
"accountAddress",
|
|
36
|
+
"actions",
|
|
37
|
+
"targets",
|
|
38
|
+
"maxValueWei",
|
|
39
|
+
"maxGasWei",
|
|
40
|
+
"maxSlippageBps",
|
|
41
|
+
"expiresAt",
|
|
42
|
+
"nonce",
|
|
43
|
+
"revocationKey",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/** Action verbs that never touch state; only these may have empty targets. */
|
|
47
|
+
export const READONLY_ACTION_VERBS = Object.freeze(["read", "simulate"]);
|
|
48
|
+
|
|
49
|
+
export const MAX_SLIPPAGE_BPS = 10_000;
|
|
50
|
+
export const MAX_NONCE_LENGTH = 128;
|
|
51
|
+
export const MAX_REVOCATION_KEY_LENGTH = 256;
|
|
52
|
+
export const MAX_ACTIONS = 64;
|
|
53
|
+
export const MAX_TARGETS = 256;
|
|
54
|
+
|
|
55
|
+
const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
56
|
+
// verb:resource — lowercase verb, resource may carry dots/dashes/underscores.
|
|
57
|
+
const ACTION_RE = /^[a-z][a-z0-9_-]*:[a-z0-9._-]+$/;
|
|
58
|
+
// wildcard-bearing action (only accepted when explicitly opted in, and never
|
|
59
|
+
// with a wildcard verb).
|
|
60
|
+
const ACTION_WILDCARD_RE = /^[a-z][a-z0-9_-]*:\*$/;
|
|
61
|
+
const NONCE_RE = /^[A-Za-z0-9._:-]+$/;
|
|
62
|
+
const REVOCATION_KEY_RE = /^[A-Za-z0-9._:-]+$/;
|
|
63
|
+
const DECIMAL_RE = /^(0|[1-9][0-9]*)$/;
|
|
64
|
+
|
|
65
|
+
export class GrantValidationError extends Error {
|
|
66
|
+
constructor(errors) {
|
|
67
|
+
const lines = errors.map((e) => `${e.field}: ${e.message}`);
|
|
68
|
+
super(`invalid grant (${errors.length} error${errors.length === 1 ? "" : "s"}): ${lines.join("; ")}`);
|
|
69
|
+
this.name = "GrantValidationError";
|
|
70
|
+
this.errors = errors;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPlainObject(v) {
|
|
75
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Parse a non-negative integer amount (number | bigint | decimal string)
|
|
79
|
+
* into a canonical decimal string. Returns null when unparseable. */
|
|
80
|
+
function toAmountString(v) {
|
|
81
|
+
if (typeof v === "bigint") {
|
|
82
|
+
return v >= 0n ? v.toString(10) : null;
|
|
83
|
+
}
|
|
84
|
+
if (typeof v === "number") {
|
|
85
|
+
if (!Number.isSafeInteger(v) || v < 0) return null;
|
|
86
|
+
return String(v);
|
|
87
|
+
}
|
|
88
|
+
if (typeof v === "string") {
|
|
89
|
+
const s = v.trim();
|
|
90
|
+
if (!DECIMAL_RE.test(s)) return null;
|
|
91
|
+
return s;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toSafeInt(v) {
|
|
97
|
+
if (typeof v === "bigint") {
|
|
98
|
+
if (v < 0n || v > BigInt(Number.MAX_SAFE_INTEGER)) return null;
|
|
99
|
+
return Number(v);
|
|
100
|
+
}
|
|
101
|
+
if (typeof v === "number") {
|
|
102
|
+
if (!Number.isSafeInteger(v) || v < 0) return null;
|
|
103
|
+
return v;
|
|
104
|
+
}
|
|
105
|
+
if (typeof v === "string") {
|
|
106
|
+
const s = v.trim();
|
|
107
|
+
if (!DECIMAL_RE.test(s)) return null;
|
|
108
|
+
const n = Number(s);
|
|
109
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** True when the action verb is read-only (read/simulate). */
|
|
115
|
+
export function isReadonlyAction(action) {
|
|
116
|
+
const verb = String(action).split(":")[0];
|
|
117
|
+
return READONLY_ACTION_VERBS.includes(verb);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate a grant. Never throws; returns { ok, errors, grant } where `grant`
|
|
122
|
+
* is the normalized form when ok, else null.
|
|
123
|
+
*
|
|
124
|
+
* Options:
|
|
125
|
+
* allowWildcardActions (default false) — permit "verb:*" actions. Broad
|
|
126
|
+
* wildcards ("*", "*:*", wildcard verbs) are ALWAYS
|
|
127
|
+
* rejected regardless of this flag.
|
|
128
|
+
* now (unix seconds) — when provided, expiresAt must be
|
|
129
|
+
* strictly greater than it. Pass explicitly for
|
|
130
|
+
* deterministic validation; omitted means no liveness
|
|
131
|
+
* check (pure shape validation).
|
|
132
|
+
*/
|
|
133
|
+
export function validateGrant(input, opts = {}) {
|
|
134
|
+
const errors = [];
|
|
135
|
+
const push = (field, message) => errors.push({ field, message });
|
|
136
|
+
|
|
137
|
+
if (!isPlainObject(input)) {
|
|
138
|
+
push("grant", "grant must be a plain object");
|
|
139
|
+
return { ok: false, errors, grant: null };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fail closed on unknown fields.
|
|
143
|
+
for (const key of Object.keys(input)) {
|
|
144
|
+
if (!REQUIRED_FIELDS.includes(key)) push(key, "unknown field is not allowed");
|
|
145
|
+
}
|
|
146
|
+
for (const key of REQUIRED_FIELDS) {
|
|
147
|
+
if (!(key in input) || input[key] == null) push(key, "required field is missing");
|
|
148
|
+
}
|
|
149
|
+
if (errors.length) return { ok: false, errors, grant: null };
|
|
150
|
+
|
|
151
|
+
const out = {};
|
|
152
|
+
|
|
153
|
+
// chainId
|
|
154
|
+
const chainId = toSafeInt(input.chainId);
|
|
155
|
+
if (chainId == null || chainId <= 0) push("chainId", "must be a positive integer chain id");
|
|
156
|
+
else out.chainId = chainId;
|
|
157
|
+
|
|
158
|
+
// addresses
|
|
159
|
+
for (const field of ["agentAddress", "accountAddress"]) {
|
|
160
|
+
const v = input[field];
|
|
161
|
+
if (typeof v !== "string" || !ADDRESS_RE.test(v.trim())) {
|
|
162
|
+
push(field, "must be a 0x-prefixed 20-byte hex address");
|
|
163
|
+
} else {
|
|
164
|
+
out[field] = v.trim().toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// actions
|
|
169
|
+
if (!Array.isArray(input.actions) || input.actions.length === 0) {
|
|
170
|
+
push("actions", "must be a non-empty array of action scopes");
|
|
171
|
+
} else if (input.actions.length > MAX_ACTIONS) {
|
|
172
|
+
push("actions", `too many actions (max ${MAX_ACTIONS})`);
|
|
173
|
+
} else {
|
|
174
|
+
const actions = [];
|
|
175
|
+
for (const raw of input.actions) {
|
|
176
|
+
const a = typeof raw === "string" ? raw.trim() : null;
|
|
177
|
+
if (!a) {
|
|
178
|
+
push("actions", "action entries must be non-empty strings");
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (a.includes("*")) {
|
|
182
|
+
if (opts.allowWildcardActions === true && ACTION_WILDCARD_RE.test(a)) {
|
|
183
|
+
actions.push(a);
|
|
184
|
+
} else {
|
|
185
|
+
push("actions", `broad wildcard action "${a}" is rejected (fail closed)`);
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (!ACTION_RE.test(a)) {
|
|
190
|
+
push("actions", `malformed action "${a}" (expected verb:resource)`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
actions.push(a);
|
|
194
|
+
}
|
|
195
|
+
out.actions = [...new Set(actions)].sort();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// targets
|
|
199
|
+
if (!Array.isArray(input.targets)) {
|
|
200
|
+
push("targets", "must be an array of contract addresses");
|
|
201
|
+
} else if (input.targets.length > MAX_TARGETS) {
|
|
202
|
+
push("targets", `too many targets (max ${MAX_TARGETS})`);
|
|
203
|
+
} else {
|
|
204
|
+
const targets = [];
|
|
205
|
+
for (const raw of input.targets) {
|
|
206
|
+
const t = typeof raw === "string" ? raw.trim() : null;
|
|
207
|
+
if (!t || !ADDRESS_RE.test(t)) {
|
|
208
|
+
push("targets", `target "${raw}" must be a 0x-prefixed 20-byte hex address (wildcards not allowed)`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
targets.push(t.toLowerCase());
|
|
212
|
+
}
|
|
213
|
+
out.targets = [...new Set(targets)].sort();
|
|
214
|
+
if (out.targets.length === 0 && Array.isArray(out.actions)) {
|
|
215
|
+
const nonReadonly = out.actions.filter((a) => !isReadonlyAction(a));
|
|
216
|
+
if (nonReadonly.length > 0 || out.actions.length === 0) {
|
|
217
|
+
push(
|
|
218
|
+
"targets",
|
|
219
|
+
`empty targets are only allowed for read/simulate scopes (blocked by: ${nonReadonly.join(", ") || "no valid actions"})`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// amounts
|
|
226
|
+
for (const field of ["maxValueWei", "maxGasWei"]) {
|
|
227
|
+
const amt = toAmountString(input[field]);
|
|
228
|
+
if (amt == null) push(field, "must be a non-negative integer wei amount");
|
|
229
|
+
else out[field] = amt;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// slippage
|
|
233
|
+
const bps = toSafeInt(input.maxSlippageBps);
|
|
234
|
+
if (bps == null || bps > MAX_SLIPPAGE_BPS) {
|
|
235
|
+
push("maxSlippageBps", `must be an integer between 0 and ${MAX_SLIPPAGE_BPS}`);
|
|
236
|
+
} else {
|
|
237
|
+
out.maxSlippageBps = bps;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// expiry — required (checked above); must be a positive unix-seconds int and,
|
|
241
|
+
// when `now` is supplied, strictly in the future.
|
|
242
|
+
// Date-renderable ceiling in unix seconds. JS Date maxes at 8.64e15 ms, so a
|
|
243
|
+
// seconds value above 8.64e12 overflows the renderer (isoUtc does *1000).
|
|
244
|
+
// Also well within the AA lane's uint48 bound, so a schema-valid expiry always
|
|
245
|
+
// fits both the on-chain validUntil field and the human render. Reject rather
|
|
246
|
+
// than render a bogus far-future date (fail closed on the seconds-vs-ms mistake).
|
|
247
|
+
const MAX_UNIX_SECONDS = 8_640_000_000_000;
|
|
248
|
+
const expiresAt = toSafeInt(input.expiresAt);
|
|
249
|
+
if (expiresAt == null || expiresAt <= 0) {
|
|
250
|
+
push("expiresAt", "must be a positive unix-seconds timestamp");
|
|
251
|
+
} else if (expiresAt > MAX_UNIX_SECONDS) {
|
|
252
|
+
push("expiresAt", "exceeds the max unix-seconds timestamp (pass seconds, not milliseconds)");
|
|
253
|
+
} else {
|
|
254
|
+
out.expiresAt = expiresAt;
|
|
255
|
+
if (opts.now != null) {
|
|
256
|
+
const now = toSafeInt(opts.now);
|
|
257
|
+
if (now == null) push("expiresAt", "opts.now must be a unix-seconds integer");
|
|
258
|
+
else if (expiresAt <= now) push("expiresAt", `grant is expired (expiresAt ${expiresAt} <= now ${now})`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// nonce
|
|
263
|
+
const nonce =
|
|
264
|
+
typeof input.nonce === "string"
|
|
265
|
+
? input.nonce.trim()
|
|
266
|
+
: toSafeInt(input.nonce) != null
|
|
267
|
+
? String(toSafeInt(input.nonce))
|
|
268
|
+
: null;
|
|
269
|
+
if (!nonce || nonce.length > MAX_NONCE_LENGTH || !NONCE_RE.test(nonce)) {
|
|
270
|
+
push("nonce", `must be a non-empty string (max ${MAX_NONCE_LENGTH} chars, [A-Za-z0-9._:-])`);
|
|
271
|
+
} else {
|
|
272
|
+
out.nonce = nonce;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// revocationKey — a PUBLIC identifier used to revoke this grant; never a secret.
|
|
276
|
+
const rk = typeof input.revocationKey === "string" ? input.revocationKey.trim() : null;
|
|
277
|
+
if (!rk || rk.length > MAX_REVOCATION_KEY_LENGTH || !REVOCATION_KEY_RE.test(rk)) {
|
|
278
|
+
push("revocationKey", `must be a non-empty public identifier (max ${MAX_REVOCATION_KEY_LENGTH} chars, [A-Za-z0-9._:-])`);
|
|
279
|
+
} else {
|
|
280
|
+
out.revocationKey = rk;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (errors.length) return { ok: false, errors, grant: null };
|
|
284
|
+
return { ok: true, errors: [], grant: out };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Validate + normalize; throws GrantValidationError on any failure. */
|
|
288
|
+
export function normalizeGrant(input, opts = {}) {
|
|
289
|
+
const { ok, errors, grant } = validateGrant(input, opts);
|
|
290
|
+
if (!ok) throw new GrantValidationError(errors);
|
|
291
|
+
return grant;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Deterministic canonical JSON for a grant: validates/normalizes first, then
|
|
296
|
+
* serializes with alphabetically sorted keys (arrays already sorted + deduped
|
|
297
|
+
* by normalization). Identical grants — regardless of input key order, address
|
|
298
|
+
* casing, or numeric representation — always produce the identical string.
|
|
299
|
+
*/
|
|
300
|
+
export function canonicalizeGrant(input, opts = {}) {
|
|
301
|
+
const g = normalizeGrant(input, opts);
|
|
302
|
+
const keys = Object.keys(g).sort();
|
|
303
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(g[k])}`);
|
|
304
|
+
return `{"version":${GRANT_VERSION},${parts.join(",")}}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** sha256 hex of the canonical form — stable public identifier for a grant. */
|
|
308
|
+
export function grantId(input, opts = {}) {
|
|
309
|
+
return createHash("sha256").update(canonicalizeGrant(input, opts), "utf8").digest("hex");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Deterministic expiry check against an explicit unix-seconds `now`. */
|
|
313
|
+
export function isExpired(grant, nowSeconds) {
|
|
314
|
+
const now = toSafeInt(nowSeconds);
|
|
315
|
+
const exp = toSafeInt(isPlainObject(grant) ? grant.expiresAt : null);
|
|
316
|
+
if (now == null || exp == null) return true; // fail closed
|
|
317
|
+
return exp <= now;
|
|
318
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Oracle Control — public runtime config: chain/bundler/paymaster registry
|
|
2
|
+
// (Slice K).
|
|
3
|
+
//
|
|
4
|
+
// This module is pure config parsing. It never makes a live network call and
|
|
5
|
+
// never imports the private executor stack — its only job is to turn env
|
|
6
|
+
// vars into a validated, in-memory registry of PUBLIC endpoints (chain
|
|
7
|
+
// metadata, bundler URLs, paymaster URLs, and the public HTTP bind) that the
|
|
8
|
+
// rest of Oracle Control (aa-adapter.mjs, bundler-client.mjs, http.mjs) can
|
|
9
|
+
// read from.
|
|
10
|
+
//
|
|
11
|
+
// Hard boundaries for this file:
|
|
12
|
+
// - Imports ONLY from ./aa-adapter.mjs (ENTRYPOINT_V07 — the single source
|
|
13
|
+
// of truth for the deployed EntryPoint address this codebase targets)
|
|
14
|
+
// and node builtins. NEVER from the private executor stack
|
|
15
|
+
// (get-signer.mjs, keystore.mjs, exec-policy.mjs, local-signer/*,
|
|
16
|
+
// adapters/*, mint-capability.mjs) — this module must never be wired to
|
|
17
|
+
// private operator wallet, and holds no secrets of any kind.
|
|
18
|
+
// - bundlerUrl/paymasterUrl are treated as PUBLIC endpoints (the same way a
|
|
19
|
+
// public RPC URL is public) — but they are still scanned for anything
|
|
20
|
+
// secret-SHAPED (a bearer token, an api-key-looking query param, a raw
|
|
21
|
+
// private-key hex blob, a PEM block) and rejected outright if found.
|
|
22
|
+
// Fail closed: a config that looks like it's carrying a credential in a
|
|
23
|
+
// URL must never load.
|
|
24
|
+
// - resolveChain() fails closed on any chainId not in the registry — no
|
|
25
|
+
// silent fallback to a made-up chain.
|
|
26
|
+
// - entryPoint is never read from env or invented; it is always pinned to
|
|
27
|
+
// aa-adapter.mjs's ENTRYPOINT_V07 and asserted equal for every seeded
|
|
28
|
+
// chain, so a typo/drift in this file can never point a chain at the
|
|
29
|
+
// wrong EntryPoint deployment.
|
|
30
|
+
// - redactedConfig() strips the query string (and any embedded userinfo)
|
|
31
|
+
// off every bundler/paymaster URL before it is considered "safe to log"
|
|
32
|
+
// — even though the full URL already passed the secret-shape scan at
|
|
33
|
+
// load time, a log line is a wider blast radius than an in-memory
|
|
34
|
+
// config object, so this view is deliberately more conservative.
|
|
35
|
+
|
|
36
|
+
import { ENTRYPOINT_V07 } from "./aa-adapter.mjs";
|
|
37
|
+
|
|
38
|
+
export class RuntimeConfigError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(`runtime-config: ${message}`);
|
|
41
|
+
this.name = "RuntimeConfigError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fail(message) {
|
|
46
|
+
throw new RuntimeConfigError(message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Secret-shape guard for public endpoint URLs (fail closed)
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/** String-value shapes that must never appear in a bundler/paymaster URL.
|
|
54
|
+
* Mirrors the posture of connect-agent.mjs's FORBIDDEN_VALUE_RULES /
|
|
55
|
+
* bundler-client.mjs's SecretLeakError checks, scoped to what a URL could
|
|
56
|
+
* plausibly carry (query-string credentials, embedded key material). */
|
|
57
|
+
const SECRET_URL_RULES = Object.freeze([
|
|
58
|
+
// 32-byte hex — the shape of a raw EVM private key / session secret.
|
|
59
|
+
{ rule: "raw-32-byte-hex-key", re: /0x[0-9a-fA-F]{64}/ },
|
|
60
|
+
{ rule: "bearer-token", re: /bearer\s+[A-Za-z0-9._~+/=-]+/i },
|
|
61
|
+
{ rule: "pem-private-key", re: /-----BEGIN[A-Z ]*PRIVATE KEY-----/ },
|
|
62
|
+
{ rule: "authorization-header", re: /authorization\s*:/i },
|
|
63
|
+
// Credential-shaped query/path segment: ?api_key=..., ?token=..., ?secret=...,
|
|
64
|
+
// ?password=..., ?auth=..., ?access_token=..., ?bearer=..., or userinfo
|
|
65
|
+
// (user:pass@host) embedded in the URL itself.
|
|
66
|
+
{
|
|
67
|
+
rule: "credential-shaped-param",
|
|
68
|
+
re: /[?&](api[_-]?key|apikey|key|token|access[_-]?token|secret|password|passwd|auth|bearer)=/i,
|
|
69
|
+
},
|
|
70
|
+
{ rule: "embedded-userinfo", re: /^[a-z][a-z0-9+.-]*:\/\/[^/@\s]+:[^/@\s]+@/i },
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
function assertPublicUrlSafe(value, label) {
|
|
74
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
75
|
+
fail(`${label} must be a non-empty string URL`);
|
|
76
|
+
}
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = new URL(value);
|
|
80
|
+
} catch {
|
|
81
|
+
fail(`${label} is not a valid URL: ${value}`);
|
|
82
|
+
}
|
|
83
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
84
|
+
fail(`${label} must be an http(s) URL, got scheme ${parsed.protocol}`);
|
|
85
|
+
}
|
|
86
|
+
for (const { rule, re } of SECRET_URL_RULES) {
|
|
87
|
+
if (re.test(value)) {
|
|
88
|
+
fail(`${label} looks like it carries secret material (rule: ${rule}) — refusing to load a credential-shaped public endpoint`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Chain registry seed
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/** Base metadata for every chain Oracle Control's public runtime config
|
|
99
|
+
* knows about out of the box. entryPoint is intentionally NOT stored here —
|
|
100
|
+
* it is always ENTRYPOINT_V07, asserted below, so there is exactly one
|
|
101
|
+
* place (aa-adapter.mjs) that can ever define it. */
|
|
102
|
+
const CHAIN_SEEDS = Object.freeze([
|
|
103
|
+
{ chainId: 8453, name: "Base" },
|
|
104
|
+
{ chainId: 42161, name: "Arbitrum" },
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
export const DEFAULT_CHAIN_ID = 8453;
|
|
108
|
+
export const DEFAULT_PUBLIC_HOST = "127.0.0.1";
|
|
109
|
+
export const DEFAULT_PUBLIC_PORT = 8799;
|
|
110
|
+
|
|
111
|
+
function buildChainEntry(seed, env) {
|
|
112
|
+
const entry = {
|
|
113
|
+
name: seed.name,
|
|
114
|
+
entryPoint: ENTRYPOINT_V07,
|
|
115
|
+
};
|
|
116
|
+
// entryPoint must always equal aa-adapter's ENTRYPOINT_V07 for every
|
|
117
|
+
// supported chain — asserted here (not just "set here") so a future edit
|
|
118
|
+
// that tries to override it per-chain is caught immediately.
|
|
119
|
+
if (entry.entryPoint !== ENTRYPOINT_V07) {
|
|
120
|
+
fail(`chain ${seed.chainId} entryPoint drifted from aa-adapter ENTRYPOINT_V07`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const bundlerVar = `ORACLE_BUNDLER_URL_${seed.chainId}`;
|
|
124
|
+
const paymasterVar = `ORACLE_PAYMASTER_URL_${seed.chainId}`;
|
|
125
|
+
const bundlerRaw = env[bundlerVar];
|
|
126
|
+
const paymasterRaw = env[paymasterVar];
|
|
127
|
+
|
|
128
|
+
if (bundlerRaw != null && String(bundlerRaw).trim() !== "") {
|
|
129
|
+
entry.bundlerUrl = assertPublicUrlSafe(String(bundlerRaw), bundlerVar);
|
|
130
|
+
}
|
|
131
|
+
if (paymasterRaw != null && String(paymasterRaw).trim() !== "") {
|
|
132
|
+
entry.paymasterUrl = assertPublicUrlSafe(String(paymasterRaw), paymasterVar);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return entry;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parsePort(raw, label) {
|
|
139
|
+
const n = Number(raw);
|
|
140
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
141
|
+
fail(`${label} must be an integer port 0-65535, got ${JSON.stringify(raw)}`);
|
|
142
|
+
}
|
|
143
|
+
return n;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Parse the public runtime config (chain/bundler/paymaster registry + public
|
|
148
|
+
* HTTP bind) from an env-like object. Pure function, no I/O, no network.
|
|
149
|
+
*
|
|
150
|
+
* @param {object} [env] defaults to process.env; any plain object of
|
|
151
|
+
* string-ish values works (this is what makes it deterministically
|
|
152
|
+
* testable without touching real process.env).
|
|
153
|
+
* @returns {{
|
|
154
|
+
* chains: Record<string, {name:string, entryPoint:string, bundlerUrl?:string, paymasterUrl?:string}>,
|
|
155
|
+
* defaultChainId: number,
|
|
156
|
+
* publicHost: string,
|
|
157
|
+
* publicPort: number,
|
|
158
|
+
* }}
|
|
159
|
+
*/
|
|
160
|
+
export function loadPublicConfig(env = process.env) {
|
|
161
|
+
if (env == null || typeof env !== "object") {
|
|
162
|
+
fail("env must be an object");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const chains = {};
|
|
166
|
+
for (const seed of CHAIN_SEEDS) {
|
|
167
|
+
chains[String(seed.chainId)] = buildChainEntry(seed, env);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const rawDefaultChainId = env.ORACLE_DEFAULT_CHAIN_ID;
|
|
171
|
+
let defaultChainId = DEFAULT_CHAIN_ID;
|
|
172
|
+
if (rawDefaultChainId != null && String(rawDefaultChainId).trim() !== "") {
|
|
173
|
+
const n = Number(rawDefaultChainId);
|
|
174
|
+
if (!Number.isInteger(n)) {
|
|
175
|
+
fail(`ORACLE_DEFAULT_CHAIN_ID must be an integer chain id, got ${JSON.stringify(rawDefaultChainId)}`);
|
|
176
|
+
}
|
|
177
|
+
if (chains[String(n)] == null) {
|
|
178
|
+
fail(`ORACLE_DEFAULT_CHAIN_ID ${n} is not a registered chain (known: ${Object.keys(chains).join(", ")})`);
|
|
179
|
+
}
|
|
180
|
+
defaultChainId = n;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const publicHost = (() => {
|
|
184
|
+
const v = env.ORACLE_PUBLIC_HOST ?? env.MAD_PUBLIC_HOST;
|
|
185
|
+
return v != null && String(v).trim() !== "" ? String(v) : DEFAULT_PUBLIC_HOST;
|
|
186
|
+
})();
|
|
187
|
+
|
|
188
|
+
const publicPort = (() => {
|
|
189
|
+
const v = env.ORACLE_PUBLIC_PORT ?? env.MAD_PUBLIC_PORT;
|
|
190
|
+
if (v == null || String(v).trim() === "") return DEFAULT_PUBLIC_PORT;
|
|
191
|
+
return parsePort(v, "ORACLE_PUBLIC_PORT");
|
|
192
|
+
})();
|
|
193
|
+
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
chains: Object.freeze(chains),
|
|
196
|
+
defaultChainId,
|
|
197
|
+
publicHost,
|
|
198
|
+
publicPort,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve a single chain entry from a loaded config. Fails closed on any
|
|
204
|
+
* chainId not present in the registry — never falls back to a guessed/
|
|
205
|
+
* default chain.
|
|
206
|
+
*
|
|
207
|
+
* @param {ReturnType<typeof loadPublicConfig>} config
|
|
208
|
+
* @param {number|string} chainId
|
|
209
|
+
*/
|
|
210
|
+
export function resolveChain(config, chainId) {
|
|
211
|
+
if (config == null || typeof config.chains !== "object") {
|
|
212
|
+
fail("resolveChain: config must be a loadPublicConfig() result");
|
|
213
|
+
}
|
|
214
|
+
const key = String(chainId);
|
|
215
|
+
const entry = config.chains[key];
|
|
216
|
+
if (entry == null) {
|
|
217
|
+
fail(`unsupported chainId ${key} (registered: ${Object.keys(config.chains).join(", ") || "none"})`);
|
|
218
|
+
}
|
|
219
|
+
return entry;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Drop the query string and any userinfo from a URL, keeping only
|
|
223
|
+
* scheme://host:port/path — safe to write to a log line even though the
|
|
224
|
+
* full URL already passed the load-time secret-shape scan. */
|
|
225
|
+
function redactUrl(value) {
|
|
226
|
+
try {
|
|
227
|
+
const u = new URL(value);
|
|
228
|
+
u.search = "";
|
|
229
|
+
u.username = "";
|
|
230
|
+
u.password = "";
|
|
231
|
+
return u.toString();
|
|
232
|
+
} catch {
|
|
233
|
+
return "[unparseable-url-redacted]";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Safe-to-log view of a loaded config: chain names/entryPoints/host/port
|
|
239
|
+
* pass through unchanged (none of that is secret-shaped), bundler/paymaster
|
|
240
|
+
* URLs are stripped down to origin+path only.
|
|
241
|
+
*
|
|
242
|
+
* @param {ReturnType<typeof loadPublicConfig>} config
|
|
243
|
+
*/
|
|
244
|
+
export function redactedConfig(config) {
|
|
245
|
+
if (config == null || typeof config.chains !== "object") {
|
|
246
|
+
fail("redactedConfig: config must be a loadPublicConfig() result");
|
|
247
|
+
}
|
|
248
|
+
const chains = {};
|
|
249
|
+
for (const [chainId, entry] of Object.entries(config.chains)) {
|
|
250
|
+
chains[chainId] = {
|
|
251
|
+
name: entry.name,
|
|
252
|
+
entryPoint: entry.entryPoint,
|
|
253
|
+
...(entry.bundlerUrl != null ? { bundlerUrl: redactUrl(entry.bundlerUrl) } : {}),
|
|
254
|
+
...(entry.paymasterUrl != null ? { paymasterUrl: redactUrl(entry.paymasterUrl) } : {}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return Object.freeze({
|
|
258
|
+
chains: Object.freeze(chains),
|
|
259
|
+
defaultChainId: config.defaultChainId,
|
|
260
|
+
publicHost: config.publicHost,
|
|
261
|
+
publicPort: config.publicPort,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const _internal = { assertPublicUrlSafe, SECRET_URL_RULES, CHAIN_SEEDS };
|