@blazephoenix/sdk 1.0.0 → 1.0.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/README.md CHANGED
@@ -64,7 +64,7 @@ new BlazePhoenix(); // reads BLAZEPHOENIX_RPC_BASE / _ETHEREUM / _OPTIMISM / _
64
64
  - A chain without a configured node throws `rpc_required` — nothing silently falls back
65
65
  to someone else's infrastructure.
66
66
  - `https://` and `wss://` only (`http://` allowed for `localhost` — your anvil/geth).
67
- URLs are redacted in every error message, because keys live in them.
67
+ URLs are redacted in every error message and in its `cause` chain, because keys live in them.
68
68
 
69
69
  ## Versions — pick yours
70
70
 
@@ -147,7 +147,7 @@ guesses a token from a ticker.
147
147
 
148
148
  ## Errors
149
149
 
150
- Every failure is a `BlazeError` with a stable `code`; protocol reverts carry `revert`
150
+ Every failure, `solvency()` included, is a `BlazeError` with a stable `code`; protocol reverts carry `revert`
151
151
  (`RouterE` / `QuoterE` / `SolverE` / `HubE` + the reason from the contract's own source):
152
152
 
153
153
  | code | meaning |
@@ -167,6 +167,8 @@ Every failure is a `BlazeError` with a stable `code`; protocol reverts carry `re
167
167
  [`@blazephoenix/mcp`](https://github.com/blazephoenixxyz-crypto/blazephoenix-mcp) wraps this SDK as
168
168
  a local MCP server — the agent's tools run on your machine, on your RPC.
169
169
 
170
+ For agents, [`llms.txt`](llms.txt) describes this package in the [llmstxt.org](https://llmstxt.org) format.
171
+
170
172
  ## Examples
171
173
 
172
174
  [`examples/`](examples): `quote.ts`, `watch-fills.ts`, `telegram-bot.ts`, and
package/dist/index.cjs CHANGED
@@ -4716,12 +4716,278 @@ function phoenixCheck(pv, tokenIn, tokenOut) {
4716
4716
  disclaimer: 'Deterministic checks derived from on-chain state, not financial advice. "blocked" is a real answer \u2014 we fail closed rather than guess.'
4717
4717
  };
4718
4718
  }
4719
+
4720
+ // src/constants.ts
4721
+ var API_BASE = "https://blazephoenix.xyz";
4722
+ var DEPLOYMENTS_URL = `${API_BASE}/api/deployments`;
4723
+ var SUPPORTED_CHAIN_IDS = [8453, 1, 10, 42161, 4663];
4724
+ var CHAINS = {
4725
+ 1: {
4726
+ chainId: 1,
4727
+ name: "Ethereum",
4728
+ key: "ETHEREUM",
4729
+ explorer: "https://etherscan.io",
4730
+ weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
4731
+ usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
4732
+ blockTime: 12
4733
+ },
4734
+ 8453: {
4735
+ chainId: 8453,
4736
+ name: "Base",
4737
+ key: "BASE",
4738
+ explorer: "https://basescan.org",
4739
+ weth: "0x4200000000000000000000000000000000000006",
4740
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
4741
+ bzpx: "0x23113e72165a034265Ab8Bf2277CCB7a85Cb7483",
4742
+ staking: "0x3f60C7aa0c36a78D200405feBE143d2Cf3fA0c77",
4743
+ blockTime: 2
4744
+ },
4745
+ 10: {
4746
+ chainId: 10,
4747
+ name: "Optimism",
4748
+ key: "OPTIMISM",
4749
+ explorer: "https://optimistic.etherscan.io",
4750
+ weth: "0x4200000000000000000000000000000000000006",
4751
+ usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
4752
+ blockTime: 2
4753
+ },
4754
+ 42161: {
4755
+ chainId: 42161,
4756
+ name: "Arbitrum",
4757
+ key: "ARBITRUM",
4758
+ explorer: "https://arbiscan.io",
4759
+ weth: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
4760
+ usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
4761
+ blockTime: 0.25
4762
+ },
4763
+ // Robinhood Chain — the dollar asset is USDG, not USDC; the `usdc` field
4764
+ // carries the chain's canonical dollar token. Explorer is Blockscout.
4765
+ 4663: {
4766
+ chainId: 4663,
4767
+ name: "Robinhood",
4768
+ key: "ROBINHOOD",
4769
+ explorer: "https://robinhoodchain.blockscout.com",
4770
+ weth: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
4771
+ usdc: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
4772
+ blockTime: 2
4773
+ }
4774
+ };
4775
+ var ALIASES = {
4776
+ "1": 1,
4777
+ eth: 1,
4778
+ ethereum: 1,
4779
+ mainnet: 1,
4780
+ "8453": 8453,
4781
+ base: 8453,
4782
+ "10": 10,
4783
+ op: 10,
4784
+ optimism: 10,
4785
+ "42161": 42161,
4786
+ arb: 42161,
4787
+ arbitrum: 42161,
4788
+ "arbitrum-one": 42161,
4789
+ "4663": 4663,
4790
+ rh: 4663,
4791
+ robinhood: 4663,
4792
+ "robinhood-chain": 4663
4793
+ };
4794
+ function resolveChain(chain) {
4795
+ const id = ALIASES[String(chain).trim().toLowerCase()];
4796
+ if (!id) {
4797
+ throw new Error(
4798
+ `Unsupported chain "${chain}" \u2014 use 8453/base, 1/eth, 10/optimism, 42161/arbitrum or 4663/robinhood`
4799
+ );
4800
+ }
4801
+ return id;
4802
+ }
4803
+ function tryResolveChain(chain) {
4804
+ return ALIASES[String(chain).trim().toLowerCase()];
4805
+ }
4806
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
4807
+ var NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
4808
+ var PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4809
+ var FEE_BPS = 28;
4810
+ var SWAP_TOPIC0 = "0xd6d34547c69c5ee3d2667625c188acf1006abb93e0ee7cf03925c67cf7760413";
4811
+ var SURPLUS_TOPIC0 = "0x1a3afef0f067eb51a6bfec6ab3625a4408dd8c3571b72836483e68feb70d1026";
4812
+ var EXECUTION_PROOF_TOPIC0 = "0xb5a62bcb8753e37706da20862f2a32a16831ef0144567eb15b1da49547e9f84c";
4813
+ var FEE_TOPIC0 = "0x18fa25e22e0f1f39ef2130081363ba86a1259e598e669558683dabfc15bfe731";
4814
+
4815
+ // src/rpc.ts
4816
+ var isTransport = (v) => typeof v === "function";
4817
+ var isEip1193 = (v) => !!v && typeof v === "object" && typeof v.request === "function";
4818
+ var isSource = (v) => typeof v === "string" || Array.isArray(v) || isTransport(v) || isEip1193(v);
4819
+ function isAllowedRpcUrl(url) {
4820
+ let u;
4821
+ try {
4822
+ u = new URL(url.trim());
4823
+ } catch {
4824
+ return false;
4825
+ }
4826
+ if (u.protocol === "https:" || u.protocol === "wss:") return true;
4827
+ if (u.protocol === "http:" || u.protocol === "ws:") {
4828
+ return u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
4829
+ }
4830
+ return false;
4831
+ }
4832
+ function urlTransport(url, o) {
4833
+ if (!isAllowedRpcUrl(url)) {
4834
+ throw new BlazeError("bad_request", `refusing RPC URL "${redact(url)}": use https:// or wss:// (http/ws only for localhost)`);
4835
+ }
4836
+ const u = url.trim();
4837
+ return u.startsWith("ws") ? viem.webSocket(u, { timeout: o.timeoutMs, retryCount: o.retries }) : viem.http(u, { timeout: o.timeoutMs, retryCount: o.retries, batch: o.batch });
4838
+ }
4839
+ function redact(url) {
4840
+ try {
4841
+ const u = new URL(url);
4842
+ const path = u.pathname.length > 1 ? "/\u2026" : "";
4843
+ return `${u.protocol}//${u.host}${path}`;
4844
+ } catch {
4845
+ return "<invalid url>";
4846
+ }
4847
+ }
4848
+ function scrubUrls(text) {
4849
+ return text.replace(/\b(?:https?|wss?):\/\/[^\s"'<>]+/gi, (m) => redact(m));
4850
+ }
4851
+ function transportFrom(src, opts = {}) {
4852
+ const o = {
4853
+ timeoutMs: opts.timeoutMs ?? 1e4,
4854
+ retries: opts.retries ?? 2,
4855
+ batch: opts.batch ?? false
4856
+ };
4857
+ if (isTransport(src)) return src;
4858
+ if (isEip1193(src)) return viem.custom(src, { retryCount: o.retries });
4859
+ if (typeof src === "string") return urlTransport(src, o);
4860
+ if (Array.isArray(src)) {
4861
+ const list = src.map((u) => u.trim()).filter(Boolean);
4862
+ if (list.length === 0) throw new BlazeError("rpc_required", "empty RPC list");
4863
+ if (list.length === 1) return urlTransport(list[0], o);
4864
+ return viem.fallback(list.map((u) => urlTransport(u, { ...o, retries: 0 })), { retryCount: o.retries });
4865
+ }
4866
+ throw new BlazeError("bad_request", "unrecognised rpc value");
4867
+ }
4868
+ var RPC_HELP = "BlazePhoenix SDK 1.x runs 100% on YOUR RPC \u2014 it ships no endpoints and never reads through our servers. Pass `rpc` (a URL, a list of URLs, a wallet provider or a viem transport), e.g. new BlazePhoenix({ rpc: { base: process.env.BASE_RPC_URL } }). Any provider's free tier is enough.";
4869
+ var RpcRouter = class {
4870
+ perChain = /* @__PURE__ */ new Map();
4871
+ single;
4872
+ opts;
4873
+ constructor(config, opts = {}) {
4874
+ this.opts = opts;
4875
+ if (config === void 0 || config === null || config === "") return;
4876
+ if (isSource(config)) {
4877
+ this.single = { source: config };
4878
+ return;
4879
+ }
4880
+ if (typeof config !== "object") throw new BlazeError("bad_request", "rpc must be a URL, a list, a provider, a transport or a per-chain map");
4881
+ for (const [k, v] of Object.entries(config)) {
4882
+ if (v === void 0 || v === null || v === "") continue;
4883
+ const id = tryResolveChain(k);
4884
+ if (!id) throw new BlazeError("bad_request", `rpc map: unknown chain "${k}"`);
4885
+ if (!isSource(v)) throw new BlazeError("bad_request", `rpc map: bad value for chain "${k}"`);
4886
+ this.perChain.set(id, { source: v, chainId: id });
4887
+ }
4888
+ }
4889
+ /** Chains with an explicitly configured RPC (a single source reports none). */
4890
+ configuredChains() {
4891
+ return SUPPORTED_CHAIN_IDS.filter((id) => this.perChain.has(id));
4892
+ }
4893
+ hasAny() {
4894
+ return !!this.single || this.perChain.size > 0;
4895
+ }
4896
+ /** The chain to use when a request names none: the only configured one. */
4897
+ async defaultChain() {
4898
+ if (this.perChain.size === 1) return [...this.perChain.keys()][0];
4899
+ if (this.single && this.perChain.size === 0) {
4900
+ const id = await this.verify(this.single);
4901
+ const c = tryResolveChain(id);
4902
+ if (!c) throw new BlazeError("rpc_chain_mismatch", `your RPC serves chain ${id}, which BlazePhoenix is not deployed on`);
4903
+ return c;
4904
+ }
4905
+ throw new BlazeError("bad_request", "this request needs `chain` (several RPCs are configured)");
4906
+ }
4907
+ slotFor(chainId) {
4908
+ const s = this.perChain.get(chainId) ?? this.single;
4909
+ if (!s) {
4910
+ throw new BlazeError(
4911
+ "rpc_required",
4912
+ `no RPC configured for ${CHAINS[chainId].name} (${chainId}). ${RPC_HELP}`,
4913
+ { details: { chainId } }
4914
+ );
4915
+ }
4916
+ return s;
4917
+ }
4918
+ clientOf(s) {
4919
+ if (!s.client) {
4920
+ s.client = viem.createPublicClient({ transport: transportFrom(s.source, this.opts) });
4921
+ }
4922
+ return s.client;
4923
+ }
4924
+ verify(s) {
4925
+ if (!s.verified) {
4926
+ const c = this.clientOf(s);
4927
+ s.verified = c.getChainId().catch((e) => {
4928
+ s.verified = void 0;
4929
+ throw new BlazeError("rpc_error", `your RPC did not answer eth_chainId: ${scrubUrls(String(e?.message ?? e))}`, { cause: e });
4930
+ });
4931
+ }
4932
+ return s.verified;
4933
+ }
4934
+ /** A PublicClient for `chain`, over YOUR node, after checking it really is that chain. */
4935
+ async client(chain) {
4936
+ const chainId = resolveChain(chain);
4937
+ const s = this.slotFor(chainId);
4938
+ const actual = await this.verify(s);
4939
+ if (actual !== chainId) {
4940
+ throw new BlazeError(
4941
+ "rpc_chain_mismatch",
4942
+ `your RPC serves chain ${actual}, not ${CHAINS[chainId].name} (${chainId}) \u2014 configure one per chain: rpc: { base: "\u2026", eth: "\u2026" }`,
4943
+ { details: { expected: chainId, actual } }
4944
+ );
4945
+ }
4946
+ return this.clientOf(s);
4947
+ }
4948
+ };
4949
+ function rpcFromEnv(env = globalEnv()) {
4950
+ const split = (v) => {
4951
+ const list = v.split(",").map((s) => s.trim()).filter(Boolean);
4952
+ return list.length === 1 ? list[0] : list;
4953
+ };
4954
+ const map = {};
4955
+ for (const id of SUPPORTED_CHAIN_IDS) {
4956
+ const v = env[`BLAZEPHOENIX_RPC_${CHAINS[id].key}`] ?? env[`BLAZEPHOENIX_RPC_${id}`];
4957
+ if (v && v.trim()) map[String(id)] = split(v);
4958
+ }
4959
+ const single = env.BLAZEPHOENIX_RPC_URL;
4960
+ if (Object.keys(map).length > 0) {
4961
+ if (single && single.trim()) {
4962
+ throw new BlazeError(
4963
+ "bad_request",
4964
+ "set either BLAZEPHOENIX_RPC_URL (one node) or the per-chain BLAZEPHOENIX_RPC_<CHAIN> variables, not both"
4965
+ );
4966
+ }
4967
+ return map;
4968
+ }
4969
+ return single && single.trim() ? split(single) : void 0;
4970
+ }
4971
+ function globalEnv() {
4972
+ const p = globalThis.process;
4973
+ return p?.env ?? {};
4974
+ }
4975
+
4976
+ // src/errors.ts
4977
+ function scrubCause(c, depth = 0) {
4978
+ if (c === void 0 || c === null || depth > 8) return void 0;
4979
+ if (!(c instanceof Error)) return scrubUrls(String(c));
4980
+ const inner = scrubCause(c.cause, depth + 1);
4981
+ const out = new Error(scrubUrls(c.message), inner !== void 0 ? { cause: inner } : void 0);
4982
+ out.name = c.name;
4983
+ return out;
4984
+ }
4719
4985
  var BlazeError = class extends Error {
4720
4986
  code;
4721
4987
  details;
4722
4988
  revert;
4723
4989
  constructor(code, message, opts = {}) {
4724
- super(message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
4990
+ super(message, opts.cause !== void 0 ? { cause: scrubCause(opts.cause) } : void 0);
4725
4991
  this.name = "BlazeError";
4726
4992
  this.code = code;
4727
4993
  this.details = opts.details;
@@ -4884,101 +5150,6 @@ function minOutFor(netOut, slippageBps, floor) {
4884
5150
  return bySlip > floor ? bySlip : floor;
4885
5151
  }
4886
5152
 
4887
- // src/constants.ts
4888
- var API_BASE = "https://blazephoenix.xyz";
4889
- var DEPLOYMENTS_URL = `${API_BASE}/api/deployments`;
4890
- var SUPPORTED_CHAIN_IDS = [8453, 1, 10, 42161, 4663];
4891
- var CHAINS = {
4892
- 1: {
4893
- chainId: 1,
4894
- name: "Ethereum",
4895
- key: "ETHEREUM",
4896
- explorer: "https://etherscan.io",
4897
- weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
4898
- usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
4899
- blockTime: 12
4900
- },
4901
- 8453: {
4902
- chainId: 8453,
4903
- name: "Base",
4904
- key: "BASE",
4905
- explorer: "https://basescan.org",
4906
- weth: "0x4200000000000000000000000000000000000006",
4907
- usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
4908
- bzpx: "0x23113e72165a034265Ab8Bf2277CCB7a85Cb7483",
4909
- staking: "0x3f60C7aa0c36a78D200405feBE143d2Cf3fA0c77",
4910
- blockTime: 2
4911
- },
4912
- 10: {
4913
- chainId: 10,
4914
- name: "Optimism",
4915
- key: "OPTIMISM",
4916
- explorer: "https://optimistic.etherscan.io",
4917
- weth: "0x4200000000000000000000000000000000000006",
4918
- usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
4919
- blockTime: 2
4920
- },
4921
- 42161: {
4922
- chainId: 42161,
4923
- name: "Arbitrum",
4924
- key: "ARBITRUM",
4925
- explorer: "https://arbiscan.io",
4926
- weth: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
4927
- usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
4928
- blockTime: 0.25
4929
- },
4930
- // Robinhood Chain — the dollar asset is USDG, not USDC; the `usdc` field
4931
- // carries the chain's canonical dollar token. Explorer is Blockscout.
4932
- 4663: {
4933
- chainId: 4663,
4934
- name: "Robinhood",
4935
- key: "ROBINHOOD",
4936
- explorer: "https://robinhoodchain.blockscout.com",
4937
- weth: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
4938
- usdc: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
4939
- blockTime: 2
4940
- }
4941
- };
4942
- var ALIASES = {
4943
- "1": 1,
4944
- eth: 1,
4945
- ethereum: 1,
4946
- mainnet: 1,
4947
- "8453": 8453,
4948
- base: 8453,
4949
- "10": 10,
4950
- op: 10,
4951
- optimism: 10,
4952
- "42161": 42161,
4953
- arb: 42161,
4954
- arbitrum: 42161,
4955
- "arbitrum-one": 42161,
4956
- "4663": 4663,
4957
- rh: 4663,
4958
- robinhood: 4663,
4959
- "robinhood-chain": 4663
4960
- };
4961
- function resolveChain(chain) {
4962
- const id = ALIASES[String(chain).trim().toLowerCase()];
4963
- if (!id) {
4964
- throw new Error(
4965
- `Unsupported chain "${chain}" \u2014 use 8453/base, 1/eth, 10/optimism, 42161/arbitrum or 4663/robinhood`
4966
- );
4967
- }
4968
- return id;
4969
- }
4970
- function tryResolveChain(chain) {
4971
- return ALIASES[String(chain).trim().toLowerCase()];
4972
- }
4973
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
4974
- var NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
4975
- var PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
4976
- var FEE_BPS = 28;
4977
- var SWAP_TOPIC0 = "0xd6d34547c69c5ee3d2667625c188acf1006abb93e0ee7cf03925c67cf7760413";
4978
- var SURPLUS_TOPIC0 = "0x1a3afef0f067eb51a6bfec6ab3625a4408dd8c3571b72836483e68feb70d1026";
4979
- var EXECUTION_PROOF_TOPIC0 = "0xb5a62bcb8753e37706da20862f2a32a16831ef0144567eb15b1da49547e9f84c";
4980
- var FEE_TOPIC0 = "0x18fa25e22e0f1f39ef2130081363ba86a1259e598e669558683dabfc15bfe731";
4981
-
4982
5153
  // src/deployments.ts
4983
5154
  var Z = ZERO_ADDRESS;
4984
5155
  var pending = () => ({ core: Z, hub: Z, solver: Z, router: Z, quoter: Z });
@@ -5471,162 +5642,6 @@ function fromBaseUnits(v, decimals, maxDp) {
5471
5642
  frac = frac.replace(/0+$/, "");
5472
5643
  return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
5473
5644
  }
5474
- var isTransport = (v) => typeof v === "function";
5475
- var isEip1193 = (v) => !!v && typeof v === "object" && typeof v.request === "function";
5476
- var isSource = (v) => typeof v === "string" || Array.isArray(v) || isTransport(v) || isEip1193(v);
5477
- function isAllowedRpcUrl(url) {
5478
- let u;
5479
- try {
5480
- u = new URL(url.trim());
5481
- } catch {
5482
- return false;
5483
- }
5484
- if (u.protocol === "https:" || u.protocol === "wss:") return true;
5485
- if (u.protocol === "http:" || u.protocol === "ws:") {
5486
- return u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
5487
- }
5488
- return false;
5489
- }
5490
- function urlTransport(url, o) {
5491
- if (!isAllowedRpcUrl(url)) {
5492
- throw new BlazeError("bad_request", `refusing RPC URL "${redact(url)}": use https:// or wss:// (http/ws only for localhost)`);
5493
- }
5494
- const u = url.trim();
5495
- return u.startsWith("ws") ? viem.webSocket(u, { timeout: o.timeoutMs, retryCount: o.retries }) : viem.http(u, { timeout: o.timeoutMs, retryCount: o.retries, batch: o.batch });
5496
- }
5497
- function redact(url) {
5498
- try {
5499
- const u = new URL(url);
5500
- const path = u.pathname.length > 1 ? "/\u2026" : "";
5501
- return `${u.protocol}//${u.host}${path}`;
5502
- } catch {
5503
- return "<invalid url>";
5504
- }
5505
- }
5506
- function transportFrom(src, opts = {}) {
5507
- const o = {
5508
- timeoutMs: opts.timeoutMs ?? 1e4,
5509
- retries: opts.retries ?? 2,
5510
- batch: opts.batch ?? false
5511
- };
5512
- if (isTransport(src)) return src;
5513
- if (isEip1193(src)) return viem.custom(src, { retryCount: o.retries });
5514
- if (typeof src === "string") return urlTransport(src, o);
5515
- if (Array.isArray(src)) {
5516
- const list = src.map((u) => u.trim()).filter(Boolean);
5517
- if (list.length === 0) throw new BlazeError("rpc_required", "empty RPC list");
5518
- if (list.length === 1) return urlTransport(list[0], o);
5519
- return viem.fallback(list.map((u) => urlTransport(u, { ...o, retries: 0 })), { retryCount: o.retries });
5520
- }
5521
- throw new BlazeError("bad_request", "unrecognised rpc value");
5522
- }
5523
- var RPC_HELP = "BlazePhoenix SDK 1.x runs 100% on YOUR RPC \u2014 it ships no endpoints and never reads through our servers. Pass `rpc` (a URL, a list of URLs, a wallet provider or a viem transport), e.g. new BlazePhoenix({ rpc: { base: process.env.BASE_RPC_URL } }). Any provider's free tier is enough.";
5524
- var RpcRouter = class {
5525
- perChain = /* @__PURE__ */ new Map();
5526
- single;
5527
- opts;
5528
- constructor(config, opts = {}) {
5529
- this.opts = opts;
5530
- if (config === void 0 || config === null || config === "") return;
5531
- if (isSource(config)) {
5532
- this.single = { source: config };
5533
- return;
5534
- }
5535
- if (typeof config !== "object") throw new BlazeError("bad_request", "rpc must be a URL, a list, a provider, a transport or a per-chain map");
5536
- for (const [k, v] of Object.entries(config)) {
5537
- if (v === void 0 || v === null || v === "") continue;
5538
- const id = tryResolveChain(k);
5539
- if (!id) throw new BlazeError("bad_request", `rpc map: unknown chain "${k}"`);
5540
- if (!isSource(v)) throw new BlazeError("bad_request", `rpc map: bad value for chain "${k}"`);
5541
- this.perChain.set(id, { source: v, chainId: id });
5542
- }
5543
- }
5544
- /** Chains with an explicitly configured RPC (a single source reports none). */
5545
- configuredChains() {
5546
- return SUPPORTED_CHAIN_IDS.filter((id) => this.perChain.has(id));
5547
- }
5548
- hasAny() {
5549
- return !!this.single || this.perChain.size > 0;
5550
- }
5551
- /** The chain to use when a request names none: the only configured one. */
5552
- async defaultChain() {
5553
- if (this.perChain.size === 1) return [...this.perChain.keys()][0];
5554
- if (this.single && this.perChain.size === 0) {
5555
- const id = await this.verify(this.single);
5556
- const c = tryResolveChain(id);
5557
- if (!c) throw new BlazeError("rpc_chain_mismatch", `your RPC serves chain ${id}, which BlazePhoenix is not deployed on`);
5558
- return c;
5559
- }
5560
- throw new BlazeError("bad_request", "this request needs `chain` (several RPCs are configured)");
5561
- }
5562
- slotFor(chainId) {
5563
- const s = this.perChain.get(chainId) ?? this.single;
5564
- if (!s) {
5565
- throw new BlazeError(
5566
- "rpc_required",
5567
- `no RPC configured for ${CHAINS[chainId].name} (${chainId}). ${RPC_HELP}`,
5568
- { details: { chainId } }
5569
- );
5570
- }
5571
- return s;
5572
- }
5573
- clientOf(s) {
5574
- if (!s.client) {
5575
- s.client = viem.createPublicClient({ transport: transportFrom(s.source, this.opts) });
5576
- }
5577
- return s.client;
5578
- }
5579
- verify(s) {
5580
- if (!s.verified) {
5581
- const c = this.clientOf(s);
5582
- s.verified = c.getChainId().catch((e) => {
5583
- s.verified = void 0;
5584
- throw new BlazeError("rpc_error", `your RPC did not answer eth_chainId: ${e?.message ?? e}`, { cause: e });
5585
- });
5586
- }
5587
- return s.verified;
5588
- }
5589
- /** A PublicClient for `chain`, over YOUR node, after checking it really is that chain. */
5590
- async client(chain) {
5591
- const chainId = resolveChain(chain);
5592
- const s = this.slotFor(chainId);
5593
- const actual = await this.verify(s);
5594
- if (actual !== chainId) {
5595
- throw new BlazeError(
5596
- "rpc_chain_mismatch",
5597
- `your RPC serves chain ${actual}, not ${CHAINS[chainId].name} (${chainId}) \u2014 configure one per chain: rpc: { base: "\u2026", eth: "\u2026" }`,
5598
- { details: { expected: chainId, actual } }
5599
- );
5600
- }
5601
- return this.clientOf(s);
5602
- }
5603
- };
5604
- function rpcFromEnv(env = globalEnv()) {
5605
- const split = (v) => {
5606
- const list = v.split(",").map((s) => s.trim()).filter(Boolean);
5607
- return list.length === 1 ? list[0] : list;
5608
- };
5609
- const map = {};
5610
- for (const id of SUPPORTED_CHAIN_IDS) {
5611
- const v = env[`BLAZEPHOENIX_RPC_${CHAINS[id].key}`] ?? env[`BLAZEPHOENIX_RPC_${id}`];
5612
- if (v && v.trim()) map[String(id)] = split(v);
5613
- }
5614
- const single = env.BLAZEPHOENIX_RPC_URL;
5615
- if (Object.keys(map).length > 0) {
5616
- if (single && single.trim()) {
5617
- throw new BlazeError(
5618
- "bad_request",
5619
- "set either BLAZEPHOENIX_RPC_URL (one node) or the per-chain BLAZEPHOENIX_RPC_<CHAIN> variables, not both"
5620
- );
5621
- }
5622
- return map;
5623
- }
5624
- return single && single.trim() ? split(single) : void 0;
5625
- }
5626
- function globalEnv() {
5627
- const p = globalThis.process;
5628
- return p?.env ?? {};
5629
- }
5630
5645
 
5631
5646
  // src/client.ts
5632
5647
  var HEX_ADDR2 = /^0x[0-9a-fA-F]{40}$/;
@@ -5702,6 +5717,8 @@ var BlazePhoenix = class {
5702
5717
  checks.push({ name: `code:${k}`, ok: !!code && code !== "0x", detail: code && code !== "0x" ? `${(code.length - 2) / 2} bytes` : "no code at address" });
5703
5718
  });
5704
5719
  if (featuresOf(dep.version).introspection) {
5720
+ if (isZero(c.hub)) checks.push({ name: "hub", ok: false, detail: "zero address" });
5721
+ if (isZero(c.solver)) checks.push({ name: "solver", ok: false, detail: "zero address" });
5705
5722
  const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
5706
5723
  const read = async (fn) => {
5707
5724
  try {
@@ -5912,7 +5929,7 @@ var BlazePhoenix = class {
5912
5929
  async quoteBatch(reqs) {
5913
5930
  const fail = (e) => ({
5914
5931
  ok: false,
5915
- error: { code: e instanceof BlazeError ? e.code : "error", message: e?.message ?? String(e) }
5932
+ error: { code: e instanceof BlazeError ? e.code : "error", message: scrubUrls(e?.message ?? String(e)) }
5916
5933
  });
5917
5934
  const out = new Array(reqs.length);
5918
5935
  const groups = /* @__PURE__ */ new Map();
@@ -6203,11 +6220,16 @@ var BlazePhoenix = class {
6203
6220
  const staking = CHAINS[chainId].staking;
6204
6221
  if (!staking) throw new BlazeError("not_deployed", `no BlazePhoenix staking engine on ${CHAINS[chainId].name}`);
6205
6222
  const client = await this.rpc.client(chainId);
6206
- const blockNumber = await client.getBlockNumber();
6207
- const [r, isSolvent] = await Promise.all([
6208
- client.readContract({ address: staking, abi: STAKING_SOLVENCY_ABI, functionName: "solvency", blockNumber }),
6209
- client.readContract({ address: staking, abi: STAKING_SOLVENCY_ABI, functionName: "isSolvent", blockNumber })
6210
- ]);
6223
+ let blockNumber, r, isSolvent;
6224
+ try {
6225
+ blockNumber = await client.getBlockNumber();
6226
+ [r, isSolvent] = await Promise.all([
6227
+ client.readContract({ address: staking, abi: STAKING_SOLVENCY_ABI, functionName: "solvency", blockNumber }),
6228
+ client.readContract({ address: staking, abi: STAKING_SOLVENCY_ABI, functionName: "isSolvent", blockNumber })
6229
+ ]);
6230
+ } catch (e) {
6231
+ throw new BlazeError("rpc_error", `solvency read failed on your RPC: ${scrubUrls(String(e?.message ?? e))}`, { cause: e });
6232
+ }
6211
6233
  const s = r;
6212
6234
  return {
6213
6235
  chainId,