@blazephoenix/sdk 1.2.0 → 1.3.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/README.md CHANGED
@@ -15,7 +15,7 @@ npm i @blazephoenix/sdk viem
15
15
 
16
16
  ## BlazePhoenix in one sentence
17
17
 
18
- BlazePhoenix is the fully on-chain DEX aggregator — the route is computed inside the transaction that executes it — paired with an oracle-free staking engine that proves its own solvency on every transaction. It runs on Base, Ethereum, Optimism, Arbitrum and Robinhood Chain.
18
+ BlazePhoenix is the fully on-chain DEX aggregator — the route is computed inside the transaction that executes it — paired with an oracle-free staking engine that proves its own solvency on every transaction. V2, the generation now in development, targets Base, Optimism, Arbitrum, Robinhood Chain and further L2s and is not planned for Ethereum L1. The V1 generation deployed today runs on Base, Ethereum, Optimism, Arbitrum and Robinhood Chain.
19
19
 
20
20
  **Swap** in the [app](https://blazephoenix.xyz/?tab=swap) · **integrate** with the [SDK](https://github.com/blazephoenixxyz-crypto/SDK) (`npm i @blazephoenix/sdk`) · **connect an agent** to the [MCP server](https://github.com/blazephoenixxyz-crypto/blazephoenix-mcp) (`https://blazephoenix.xyz/mcp`, no key)
21
21
 
@@ -53,12 +53,14 @@ q.amountOut; // net output after the 0.28% fee (bigint, base units)
53
53
  q.checks.verdict; // Phoenix Check: ok | caution | danger | blocked (fails closed)
54
54
  q.version; // protocol version that answered ('1.0.0' today, '2.0.0' once deployed)
55
55
 
56
- // 2) the swap transaction(s): verified calldata + the approval step if needed
56
+ // 2) the swap transaction(s): calldata verified against the request (swap math; `simulate: true`
57
+ // asks the Router from `from` whether it settles) + the approval step if needed
57
58
  const plan = await blaze.buildSwap({
58
59
  chain: 'base', tokenIn: 'WETH', tokenOut: 'USDC', amount: '1.5',
59
60
  recipient: me, from: me, // `from` enables the allowance check
60
61
  slippageBps: 50, // default 50; never below the on-chain floor
61
62
  exact: true, // optional: minOut from previewPlanExact (one more eth_call)
63
+ simulate: true, // optional: eth_call the swap from `from`; a revert throws not_executable
62
64
  });
63
65
  plan.steps; // [approve?, swap] — { chainId, to, data, value }
64
66
  plan.minOut; // the minimum the Router will enforce for you
@@ -71,6 +73,12 @@ const res = await blaze.execute({ wallet, plan }); // approve → simulate → s
71
73
  res.amountOut; // realised, from the Router's Swap event
72
74
  ```
73
75
 
76
+ The Quoter's `canExecute` is swap math: it does not see what the tokens do to the payer (a transfer tax,
77
+ a per-account cap, the payer's own balance). Whether the bytes settle is answered only by the Router's own
78
+ call from the account that will send them: `simulate(plan, from)`, `buildSwap({ simulate: true })` or
79
+ `execute`. With `simulate: true` and the approval still missing, no eth_call is made: `plan.simulation` is
80
+ `{ ok: false, error.name: 'allowance' }`; send the approval, then simulate again before the swap.
81
+
74
82
  `execute` takes a viem `WalletClient`. The SDK never asks for, stores or touches a key.
75
83
 
76
84
  ## Your RPC, your way
@@ -100,7 +108,7 @@ Protocol versions are deployments, not SDK versions:
100
108
  | version | status | what it is |
101
109
  |---|---|---|
102
110
  | `1.0.0` | **live** on all five chains | the generation deployed at launch |
103
- | `2.0.0` | pending | the final Core / Hub / Solver / Quoter / Router from [BlazePhoenix-Dex](https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex) (`VERSION()` = `"2.0.0"`) |
111
+ | `2.0.0` | pending | the generation in development (Router, Solver, Hub, Quoter and Gate, with the Core and Math libraries; Balancer V2/V3 and Curve NG are reached through the Gate), targeting Base, Optimism, Arbitrum, Robinhood Chain and further L2s and not Ethereum L1, from [BlazePhoenix-Dex](https://github.com/blazephoenixxyz-crypto/Blaze-Phoenix-Dex) (`VERSION()` = `"2.0.0"`) |
104
112
 
105
113
  ```ts
106
114
  new BlazePhoenix({ rpc, version: 'latest' }); // default: newest version deployed on each chain
@@ -158,7 +166,9 @@ on-chain floor all match what you asked. Your slippage then only ever **tightens
158
166
  ## More
159
167
 
160
168
  ```ts
161
- await blaze.quoteExact(req); // previewPlanExact: every concentrated leg dry-run on the pool
169
+ await blaze.quoteExact(req); // previewPlanExact: every concentrated leg dry-run on its venue.
170
+ // `exactOut` is net of the protocol fee on every Quoter; `quoterOut` is the
171
+ // scalar exactly as the Quoter returned it (gross of the fee on a 1.x Quoter)
162
172
  await blaze.quoteBatch([req1, req2]); // per-item { ok, quote } | { ok:false, error }
163
173
  await blaze.getFills({ chain: 'base', lookbackBlocks: 5_000n }); // Swap (+ ExecutionProof on 2.x)
164
174
  const stop = await blaze.watchFills({ chain: 'base', onFill: console.log });
@@ -168,7 +178,8 @@ toJSON(quote); // bigint → string, for HTTP / queues /
168
178
  ```
169
179
 
170
180
  Pure helpers (no RPC): `buildApproveTx`, `buildWrapTx`, `buildUnwrapTx`, `permit2TypedData`,
171
- `encodeSwapExactInWithPermit2`, `encodeSwapExactIn`, `verifySwapExactIn`, `minOutFor`,
181
+ `encodeSwapExactInWithPermit2`, `encodeSwapExactIn`, `encodeSwapExactInNative`, `encodeSwapBestExactIn`,
182
+ `decodeSwapExactIn`, `verifySwapExactIn`, `tightenMinOut`, `minOutFor`, `phoenixCheck`,
172
183
  `decodeBlazeError`, `toBaseUnits`, `fromBaseUnits`, `deepLink`.
173
184
 
174
185
  Tokens: `0x` addresses, or the universal symbols `ETH` (native), `WETH`, `USDC` (the chain's
@@ -186,7 +197,7 @@ Every failure, `solvency()` included, is a `BlazeError` with a stable `code`; pr
186
197
  | `rpc_chain_mismatch` | the node serves a different chain |
187
198
  | `rpc_error` | your node failed (network, rate limit) |
188
199
  | `no_route` | the Solver found no executable path (`SolverE(5)`) or the quote reverted |
189
- | `not_executable` | the Quoter says the route cannot settle now (`canExecute = false`) |
200
+ | `not_executable` | the Quoter's swap math says the route cannot execute now (`canExecute = false`), or, with `simulate: true`, the Router's eth_call from `from` reverts |
190
201
  | `calldata_mismatch` | Quoter bytes did not match your request — refused |
191
202
  | `min_implausible` | `buildSwap`: `userMinOut` below the quote net of `slippageBps` (or the on-chain floor) |
192
203
  | `min_above_exact` | `buildSwap({ exact: true })`: `userMinOut` above `exactOut` less the 50 bps safety margin |
package/dist/index.cjs CHANGED
@@ -4768,7 +4768,8 @@ var CHAINS = {
4768
4768
  explorer: "https://robinhoodchain.blockscout.com",
4769
4769
  weth: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
4770
4770
  usdc: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
4771
- blockTime: 2
4771
+ blockTime: 0.104
4772
+ // measured ~0.104 s/block
4772
4773
  }
4773
4774
  };
4774
4775
  var ALIASES = {
@@ -4833,7 +4834,7 @@ function urlTransport(url, o) {
4833
4834
  throw new BlazeError("bad_request", `refusing RPC URL "${redact(url)}": use https:// or wss:// (http/ws only for localhost)`);
4834
4835
  }
4835
4836
  const u = url.trim();
4836
- 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 });
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, fetchOptions: { redirect: "error" } });
4837
4838
  }
4838
4839
  function redact(url) {
4839
4840
  try {
@@ -4981,6 +4982,9 @@ function scrubCause(c, depth = 0) {
4981
4982
  out.name = c.name;
4982
4983
  return out;
4983
4984
  }
4985
+ function untrustedText(t, max = 120) {
4986
+ return (t ?? "").replace(/[^\x20-\x7e]+/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
4987
+ }
4984
4988
  var BlazeError = class extends Error {
4985
4989
  code;
4986
4990
  details;
@@ -5159,9 +5163,9 @@ var EMBEDDED_DEPLOYMENTS = {
5159
5163
  {
5160
5164
  version: "2.0.0",
5161
5165
  status: "pending",
5162
- source: "blazephoenixxyz-crypto/Blaze-Phoenix-Dex@07c8563",
5166
+ source: "blazephoenixxyz-crypto/Blaze-Phoenix-Dex@f746a48",
5163
5167
  notes: "Final generation (Core/Hub/Solver/Quoter/Router). Addresses arrive through the registry the day they are deployed.",
5164
- chains: { 8453: pending(), 1: pending(), 10: pending(), 42161: pending(), 4663: pending() }
5168
+ chains: { 8453: pending(), 10: pending(), 42161: pending(), 4663: pending() }
5165
5169
  },
5166
5170
  {
5167
5171
  version: "1.0.0",
@@ -5654,6 +5658,9 @@ function fromBaseUnits(v, decimals, maxDp) {
5654
5658
  var HEX_ADDR2 = /^0x[0-9a-fA-F]{40}$/;
5655
5659
  var INT = /^\d{1,78}$/;
5656
5660
  var UINT128_MAX = (1n << 128n) - 1n;
5661
+ function netExactOutput(version, exactOut) {
5662
+ return featuresOf(version).previewAndEncode ? exactOut : exactOut - (exactOut * BigInt(FEE_BPS) + 9999n) / 10000n;
5663
+ }
5657
5664
  var BlazePhoenix = class {
5658
5665
  registry;
5659
5666
  rpc;
@@ -5812,6 +5819,20 @@ var BlazePhoenix = class {
5812
5819
  if (typeof req.amountIn === "bigint") v = req.amountIn;
5813
5820
  else if (INT.test(String(req.amountIn).trim())) v = BigInt(String(req.amountIn).trim());
5814
5821
  else throw new BlazeError("bad_request", 'amountIn must be an integer in base units \u2014 use `amount` for human units ("1.5")');
5822
+ if (req.amount !== void 0 && req.amount !== null && req.amount !== "") {
5823
+ const d = await this.decimalsOf(client, chainId, tIn).catch((e) => {
5824
+ throw new BlazeError("bad_request", "amount and amountIn are both set and the decimals could not be read to compare them \u2014 pass only one", { cause: e });
5825
+ });
5826
+ let human;
5827
+ try {
5828
+ human = toBaseUnits(req.amount, d);
5829
+ } catch (e) {
5830
+ throw new BlazeError("bad_request", e.message);
5831
+ }
5832
+ if (human !== v) {
5833
+ throw new BlazeError("bad_request", `amount and amountIn disagree (${human} vs ${v} base units): pass only one`);
5834
+ }
5835
+ }
5815
5836
  } else if (req.amount !== void 0) {
5816
5837
  const d = await this.decimalsOf(client, chainId, tIn).catch((e) => {
5817
5838
  throw new BlazeError("bad_request", `could not read decimals of ${tIn.address} on your RPC \u2014 pass amountIn in base units`, { cause: e });
@@ -5919,13 +5940,15 @@ var BlazePhoenix = class {
5919
5940
  args: [tIn.address, tOut.address, amountIn],
5920
5941
  ...req.blockNumber !== void 0 ? { blockNumber: req.blockNumber } : {}
5921
5942
  });
5943
+ const net = netExactOutput(ctx.dep.version, exactOut);
5922
5944
  return {
5923
5945
  chainId: ctx.chainId,
5924
5946
  version: ctx.dep.version,
5925
5947
  tokenIn: tIn.address,
5926
5948
  tokenOut: tOut.address,
5927
5949
  amountIn,
5928
- exactOut,
5950
+ exactOut: net,
5951
+ quoterOut: exactOut,
5929
5952
  route,
5930
5953
  quotedAt: Date.now()
5931
5954
  };
@@ -6060,8 +6083,8 @@ var BlazePhoenix = class {
6060
6083
  if (!samePath(ex[0], route)) {
6061
6084
  throw new BlazeError("calldata_mismatch", "previewPlanExact priced a different route than the one to execute");
6062
6085
  }
6063
- exactOut = ex[1];
6064
- return ex[1];
6086
+ exactOut = netExactOutput(ctx.dep.version, ex[1]);
6087
+ return exactOut;
6065
6088
  };
6066
6089
  const settleMin = async (p) => {
6067
6090
  let floor = minOutFor(p.netOut, slippageBps, p.ironFloor);
@@ -6161,7 +6184,7 @@ var BlazePhoenix = class {
6161
6184
  }
6162
6185
  }
6163
6186
  const quote = this.buildQuote(ctx, tIn, tOut, amountIn, pv, fb);
6164
- return {
6187
+ const plan = {
6165
6188
  quote,
6166
6189
  entry,
6167
6190
  encodedBy,
@@ -6175,6 +6198,18 @@ var BlazePhoenix = class {
6175
6198
  steps: approval ? [approval.tx, tx] : [tx],
6176
6199
  ...exactOut !== void 0 ? { exactOut } : {}
6177
6200
  };
6201
+ if (!req.simulate) return plan;
6202
+ if (!from) throw new BlazeError("bad_request", "simulate: true needs `from` (the account that will send the swap)");
6203
+ const simulation = await this.simulate(plan, from);
6204
+ if (!simulation.ok && simulation.error?.name !== "allowance") {
6205
+ const err = simulation.error;
6206
+ throw new BlazeError(
6207
+ "not_executable",
6208
+ `the Router refuses these bytes from ${from}: ${err?.name ?? "revert"}${err?.code !== void 0 ? `(${err.code})` : ""} \u2014 ${err?.reason ?? "execution reverted"}`,
6209
+ { details: { from, error: err } }
6210
+ );
6211
+ }
6212
+ return { ...plan, simulation };
6178
6213
  }
6179
6214
  /** eth_call the swap from `from` on your RPC: the realised amountOut if it
6180
6215
  * would settle now, or the decoded reason it would revert. */
@@ -6190,7 +6225,7 @@ var BlazePhoenix = class {
6190
6225
  } catch (e) {
6191
6226
  if (isTransportError(e)) throw new BlazeError("rpc_error", `simulation: your RPC failed (${e?.message?.split("\n")[0]})`, { cause: e });
6192
6227
  const d = decodeBlazeError(e);
6193
- return { ok: false, error: d ?? { contract: "unknown", name: "revert", reason: e?.message?.split("\n")[0] ?? "execution reverted" } };
6228
+ return { ok: false, error: d ?? { contract: "unknown", name: "revert", reason: untrustedText(e?.message?.split("\n")[0]) || "execution reverted" } };
6194
6229
  }
6195
6230
  }
6196
6231
  /**