Repository navigation
ci(release): version packages - #156
Open
github-actions[bot] wants to merge 1 commit into
Open
github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
15 times, most recently
from
September 8, 2026 11:42
8357a61 to
6720052
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
15 times, most recently
from
September 30, 2026 18:31
1c269ef to
0e23ec8
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
29 times, most recently
from
October 19, 2026 14:07
1dcf366 to
c7be0ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
lens@0.3.0
Minor Changes
ad9152e: Add a network-aware aggregated
/book/:assetA/:assetBendpoint with live SDEX levels, synthetic AMM reserve steps, cumulative depth, and source provenance.36eb950: Automatic cataloging of resources from discovery extension upon settlement (Bazaar: automatic cataloging from the discovery extension #130)
81d8e59: Add seller-side Bazaar discovery helpers (
src/bazaar/declare.ts):param.*constructors that make per-parameter descriptions a required positional
argument,
declareHttpResource/declareMcpToolbuilders that assemble theextensions.bazaardeclaration and its JSON Schema, andvalidateDeclaration/assertDeclarationwhich run the catalog's ownvalidateListingplus seller-side metadata-quality rules so malformed listingsfail at development time rather than in production. Lens's own gated routes
(
/price,/candles,/pools,/price/twap, and the MCP price tool) aredeclared with them in
src/bazaar/lensListings.ts.059ca5a: Add a
priceUpdated(pair: String!, network: String)GraphQL subscription that streams live prices over the existing/graphqlendpoint (graphql-transport-ws protocol). Every ingester (SDEX, Horizon AMM, Soroswap) publishes{ pair, price, ts, network }on each new price; subscribers receive only the pair they request.networkis optional and narrows the stream to one chain — omitting it delivers every enabled network, which is only safe if the subscriber reads thenetworkfield on each message, since a dual-network deployment otherwise interleaves two chains' prices for the same pair.39f565f: Add
/liveand/ready, so a platform can tell "kill this one" apart from"stop sending traffic here".
/statusanswered neither question. It readsindexer_state, so it fails whenPostgres is unreachable — useless as a liveness signal — and it says nothing
about whether the process has finished booting.
app.listen()runs part-waythrough
main(), before the ingesters, the retention worker and the websocketfan-out start, so between listening and those loops running the process accepts
requests it cannot yet answer.
GET /livetouches no dependency and stays healthy while Postgres is down orthe app is still booting. That is the point: a liveness probe that fails during
a database outage has the orchestrator restarting every instance at once.
GET /readyreports503withok: falseuntilmain()finishes booting, andwhile Postgres is unreachable or unmigrated, or Redis is unreachable when Redis
is part of readiness (
READINESS_REDIS_CHECK, default true). The Postgres checkis one
to_regclassquery answering both reachability and whether migrationswere applied — reachability alone would report a process with no tables as
ready. Probes are bounded by
READINESS_TIMEOUT_MSand answers cached forREADINESS_CACHE_MS, so polling every few seconds costs at most one round tripper dependency per interval.
Both are public, allow-listed from rate limiting next to
/status, sentCache-Control: no-store, and excluded fromhttp_requests_total— afixed-interval poller would otherwise put a floor under the request rate that no
amount of real traffic dropping could go below.
/statusis unchanged.The deployed smoke check now requires both probes instead of skipping
/readywhen it is absent, and fails on a failing
/live.3b86612: Add the
lens_get_priceMCP tool, so an agent connected over MCP can ask Lens foran aggregated price instead of only searching and paying other people's Bazaar
nodes. Assets are
CODEorCODE:ISSUER, the issuer binds when both sides nameone (a same-named asset is refused with
ERR_ASSET_ISSUER_MISMATCH, neverpriced), the network defaults to the one the server runs, and the answer is
structured content carrying the price, venues, source count, confidence, the age
of the last trade and the best route.
94bd8ea: Gate
paid_callbehind an operator allow-list of hosts (paid_callwill pay whatever URL the agent names #404). The tool payswhatever URL it is given, up to
MCP_MAX_PAYMENT_PRICE, from a live key — andan agent that reads attacker-influenced text is one sentence from being told
which URL.
paidCallAllowlist(server config,MCP_PAID_CALL_ALLOWED_HOSTS)lists origins or URL prefixes; an unlisted, non-http(s) or credential-bearing
URL is refused with
ERR_HOST_NOT_ALLOWEDnaming the host, before any requestis sent, and the list cannot be widened by a tool argument. The default is
empty, which pays nobody.
7d187ea: Add
GET /snapshot: one cached, network-scoped summary of a network's venues(liveness), AMM liquidity (TVL, per-asset reserves and per-pool detail), 24h
traded volume (per venue, with a single-unit total only when the pairs share a
unit), the busiest pairs with their price direction, stablecoin peg readings,
and data freshness. All figures come from a single database statement with one
as_ofclock, and unknowns are reported asnullwith a reason rather thaninvented. The endpoint is public and never 404s.
5ea669e: Bound what a slow websocket client can cost the process.
WebSocket#send()neither blocks nor throws when a peer is slow, it queues.Nothing inspected
bufferedAmount, so one subscriber that stopped reading — asuspended mobile client, a wedged TCP window — accumulated price updates in its
socket buffer without limit, and the message was counted as sent the moment it
was queued. With the feed pushing continuously, one stuck subscriber was enough
to grow the process until it was OOM-killed, taking every other subscriber and
every HTTP client with it.
FanOutManagernow applies a per-client policy insrc/ws/backpressure.ts:past
WS_SLOW_CLIENT_BUFFER_BYTES(256 KiB) updates are coalesced to the newestone, past
WS_MAX_BUFFERED_BYTES(1 MiB) — or congested for longer thanWS_MAX_CONGESTED_MS(30s) — the client is closed with1013and counted inws_clients_dropped_total. Superseded updates count inws_messages_dropped_total, so a truncated feed is visible rather than silent,and
ws_buffered_bytesreports total queued bytes. All the limits areconfigurable, and none of them can slow down delivery to any other client.
ws_messages_sent_totalnow increments when the socket reports the writecompleted rather than when
send()returned, so it counts what was writtenrather than what was queued. The websocket metrics also moved from
prom-client's global default registry onto the one
/metricsrenders, which iswhere they were supposed to be visible all along.
Patch Changes
e091749: Partition the aggregation layer by network. Every function in
src/aggregator/vwap.tsnow takes a requirednetworkand filtersprice_points/pool_snapshotson it, andgetAMMPricefilters both thepool_snapshotsscan and thepool_idsubquery. Previously an unfiltered readreturned a volume-weighted blend of both chains' rows for a pair key that
exists on both, so
/price/:a/:banswered with a number nobody traded at andstamped it
"network":"mainnet"./price/:assetA/:assetBpassesreq.networkthrough to the aggregator, so thenetworkfield in the response is the one the numbers came from. The aggregaterefresh worker now runs once per enabled network — its own queue name, its own
pair list, and
price_aggregatesupserts stamped with its network — where itpreviously pinned all three to
STELLAR_NETWORK, leaving the second network's/price/*/historyand/screenerpermanently empty. The Redis price key itwrites is network-prefixed, matching what the REST route reads.
90f3269:
bazaar_searchno longer fetches any URL it is handed (bazaar_searchwill fetch any URL it is given #407). The MCP server nowparses the target, refuses anything that is not
http/https, resolves thehostname and rejects it if any address is private, loopback, link-local, or a
cloud metadata endpoint (e.g.
169.254.169.254), and follows redirects manuallyso a permitted host cannot bounce the request to a forbidden address. Operators
can constrain reachable hosts further with the shared
OUTBOUND_ALLOWED_HOSTSlist, which narrows access but never re-opens a blocked address. Blocked requests
return the new
ERR_URL_BLOCKEDcode.7ba1f47: Preserve TWAP timestamp alignment when outlier prices are rejected.
a6dcd93:
GET /prices/historyaccepts?format=csv, returning the points as atext/csvbody (ts,price,volume, RFC 4180 quoting, LF endings, one trailingnewline); JSON stays the default and an unknown format is rejected with 400
before the database is queried. The serializer is
historyToCsv()insrc/api/history.ts, andtests/historical.golden.test.tsbyte-compares it —plus the HTTP response — against
tests/fixtures/historical-*.csv, so columnorder, timestamp and number formatting, and line endings cannot drift without
failing the suite.
d546b0b:
GET /prices/historynow honours?network=/x-networkinstead of alwaysquerying the deployment's default network, and echoes the resolved
networkin the response.
queryHistory()takesnetworkas a parameter.5ef1591: Add a
networklabel to the four ingest metrics —trades_ingested_total,amm_snapshots_total,price_snapshots_totalandlast_trade_timestamp— so adual-network deployment (
ENABLED_NETWORKS=testnet,mainnet) reports eachnetwork separately instead of folding them together.
last_trade_timestampis the one that mattered: it is a gauge, so the testnetingester's
set()was overwriting mainnet's value for the same pair every pollinterval, making Lens's only staleness signal silently unusable — it still
looked like a plausible timestamp. The counters were less broken but equally
unqueryable per network, since the two loops summed into one series.
Cardinality is unchanged in character:
pairs x networks(pluspoolsonamm_snapshots_total), all bounded byWATCHED_PAIRSandENABLED_NETWORKS.No
issuerorpoollabel was added. Existing queries that did not filter bynetworkkeep working and now return one series per network instead of amerged one, so a recording rule that aggregated across networks before will see
one additional series per pair.
Documented in
docs/ingest-metrics.md.3c43cfe: Fix network-blind webhooks and price tracking across SDEX, Horizon AMM, Soroswap, and Aquarius ingesters:
lastPricemaps by(network, pairKey)so price ticks on one chain do not supply the previous price for another chain.POST /webhooks) with validation.2ea353b: Document the full public route surface in
openapi.yaml(params, the shared?network=query, and response schemas) and addtests/openapi.test.ts, whichboots the app, enumerates every registered route and fails when a non-internal
route is missing from the spec. Operator-only and non-HTTP routes are excluded
through an explicit, commented allow-list in
src/openapi/coverage.ts. Thegenerator is now importable and deterministic, and a test asserts the committed
openapi.jsonmatches it.7ecd06d:
GET /pairsandGET /poolsno longer scan the whole history of every networkon each request. Both now filter by network (
req.network, default the activenetwork) and read one index entry per pair / pool, so cost no longer grows with
table size. Response shape is unchanged. A pair or pool that has gone quiet is
still listed with its real last-update timestamp (it is never dropped).
2c9d8a4: Snapshot retention now prunes
price_snapshotsfor every network inENABLED_NETWORKS, not just the active one, so the second network of adual-network process no longer grows unbounded. The startup prune, the BullMQ
worker and the in-process fallback timer all use the new
pruneAllNetworks(),and the pruned row count is logged per network.
pruneOldSnapshots()gains anoptional
networkargument (defaulting to the active network).9abd1f0:
/screenerno longer reportsmarket_cap. It was a copy ofliquidityunder another name, so sorting or filtering by market cap silently sorted or filtered liquidity. Lens has no circulating-supply data to compute a real one, so the field is removed from the response and the sort allowlist, and?market_cap=and?sortBy=market_capnow return a 400 instead of being ignored. The three CTEs behind/screenerare also now scoped to one network (?network=/x-network, defaulting to the active network) instead of pooling testnet and mainnet rows.d53ac14: Fix
slippagePcton/price/:a/:b/route, which was always exactly 0 becausethe execution price was compared with itself. It is now the shortfall of the
execution price against the AMM reserve-ratio spot price, so it grows with
order size against a fixed pool. Route selection and
estimatedOutputareunchanged. Slippage is only reported for an AMM route; SDEX and SPLIT routes
(and pairs with no AMM pool) have no size-independent spot reference, so the
value stays 0 rather than reporting a cross-venue spread as slippage.
61c1854: Fix
GET /status: scopeindexer_stateto the requested network (?network=/
x-network, defaulting toSTELLAR_NETWORK) instead of returning whicheverchain wrote most recently, and report the answering network and its watched
pairs in the response.
The SDEX and AMM ingesters now derive each trade's ledger from the TOID prefix
of its Horizon id — Horizon sends no
ledgerfield on a trade — so price pointscarry a real ledger again and
lastIndexedLedgeris no longer permanently null.A malformed id falls back to the last stored ledger, and the batch is deferred
if no ledger can be established. A new
ingestLagSecondsfield surfaces astalled ingester without Prometheus.
3cdb4bd: Route all logging through pino (
src/logger.ts) instead ofconsole.*. Everymodule now logs through a component-scoped child logger —
ingester:sdex,job:aggregate-refresh,ws:fanout,redis,config,webhook,route:price, … — emitted as a structuredcomponentfield rather than amessage prefix, so operators can filter by component without regexing text.
Fastify's request logger is built from the same options, so
req.logcarriesthe same redaction and formatting.
The level is configurable via
LOG_LEVEL(defaultinfo, documented in.env.example): logs are newline-delimited JSON whenNODE_ENV=productionandpretty-printed for humans otherwise. Values of secret-looking environment
variables (
DATABASE_URL,DIRECT_DATABASE_URL,REDIS_URL, and any namecontaining
KEY/SECRET/TOKEN/PASSWORD) are scrubbed from everyserialized line, including the password inside a connection string and driver
error messages that echo it, while request auth headers are redacted via
pino's
redact. A test asserts noconsole.*survives in non-test source andthat a connection string or API key value never reaches the output.
29e834a: Config now validates every Soroban contract id it hands out (Soroswap factory,
Reflector oracle) with
StrKey.isValidContract. A malformed id no longer bootsthe feature "enabled" and fails on every RPC call: it logs a warning naming the
env var and disables that feature on that network. Two built-in defaults were
55 characters and therefore invalid; the testnet Soroswap factory default is
replaced with the address from Soroswap's published testnet deployment, and the
mainnet Reflector default is removed, so the oracle stays off until
REFLECTOR_CONTRACT_ID_MAINNETis set..env.exampleand the deploy docs areupdated.
789b054:
GET /volumes/:assetnow filtersprice_pointsby network instead of summingtestnet and mainnet volume together. It accepts
?network=testnet|mainnet(default: the active network), returns 400 for any other value, and echoes the
resolved
networkin the response body.dca5ff3: Add
docs/x402-conformance.md: a conformance baseline for the publicx402.orgfacilitator on
stellar:testnet, established by settling a real payment throughit with an unmodified
@stellar/stellar-sdkclient and feeding it deliberatelybad input. Records
/supportedverbatim, a settled transaction hash, areason-per-rejection table, and five divergences between the reference
implementation's advertised and actual behaviour. This is the baseline the Lens
facilitator (x402 facilitator: implement GET /supported #124, x402 facilitator: implement POST /verify (including __check_auth contract accounts) #125, x402 facilitator: implement POST /settle (design note first) #126) is measured against.
94bd8ea: Add an end-to-end test for the successful x402 402 payment loop (No test drives a real 402 payment loop #423). Drives the real
@x402/fetchtransport against a stubbed resource + fake facilitator, signing a real Sorobantransferwith@stellar/stellar-sdkusing throwaway testnet keys. Asserts on what was signed —amount, destination and network passphrase (valid on testnet, invalid on mainnet) — not just the
response body, and covers the failure branches: amount above maxPrice refused before submitting,
a network the client is not registered for, and an expired signature that the facilitator rejects.