Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 8 additions & 8 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
10 changes: 5 additions & 5 deletions .changeset/admin-structural-auth.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': patch
---

Guard admin endpoints structurally (#514). An `onRequest` hook enforces `ADMIN_TOKEN` over every route that declares `{ admin: true, auth: 'admin' }` or is registered under `/admin/`, so a new admin endpoint is protected by where it lives rather than by its handler remembering to call `isAdminAuthorized`. `public: true` now means genuinely unauthenticated, and all seven admin routes declare the admin tier instead. The guard reads the matched route pattern, not `req.url`, so a percent-encoded path cannot route to an admin handler while reading as non-admin.
---
'lens': patch
---
Guard admin endpoints structurally (#514). An `onRequest` hook enforces `ADMIN_TOKEN` over every route that declares `{ admin: true, auth: 'admin' }` or is registered under `/admin/`, so a new admin endpoint is protected by where it lives rather than by its handler remembering to call `isAdminAuthorized`. `public: true` now means genuinely unauthenticated, and all seven admin routes declare the admin tier instead. The guard reads the matched route pattern, not `req.url`, so a percent-encoded path cannot route to an admin handler while reading as non-admin.
10 changes: 5 additions & 5 deletions .changeset/admin-token-diagnostics.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': patch
---

Distinguish an unconfigured admin surface from a rejected token (#508). `checkAdminAuth` returns `authorized` / `unauthorized` / `not_configured`, and admin routes answer 503 `admin_not_configured` when `ADMIN_TOKEN` is unset, blank or an obvious placeholder, instead of a 401 that tells an operator their correct token is wrong. The structural admin hook makes the same three-way distinction, startup logs a warning when the surface is disabled, and `GET /status` reports `adminConfigured` β€” whether a token is set, never any part of its value.
---
'lens': patch
---
Distinguish an unconfigured admin surface from a rejected token (#508). `checkAdminAuth` returns `authorized` / `unauthorized` / `not_configured`, and admin routes answer 503 `admin_not_configured` when `ADMIN_TOKEN` is unset, blank or an obvious placeholder, instead of a 401 that tells an operator their correct token is wrong. The structural admin hook makes the same three-way distinction, startup logs a warning when the surface is disabled, and `GET /status` reports `adminConfigured` β€” whether a token is set, never any part of its value.
10 changes: 5 additions & 5 deletions .changeset/aggregated-book-depth.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Add a network-aware aggregated `/book/:assetA/:assetB` endpoint with live SDEX levels, synthetic AMM reserve steps, cumulative depth, and source provenance.
---
'lens': minor
---
Add a network-aware aggregated `/book/:assetA/:assetB` endpoint with live SDEX levels, synthetic AMM reserve steps, cumulative depth, and source provenance.
38 changes: 19 additions & 19 deletions .changeset/aggregator-network-partitioning.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
'lens': patch
---

Partition the aggregation layer by network. Every function in
`src/aggregator/vwap.ts` now takes a required `network` and filters
`price_points`/`pool_snapshots` on it, and `getAMMPrice` filters both the
`pool_snapshots` scan and the `pool_id` subquery. Previously an unfiltered read
returned a volume-weighted blend of both chains' rows for a pair key that
exists on both, so `/price/:a/:b` answered with a number nobody traded at and
stamped it `"network":"mainnet"`.

`/price/:assetA/:assetB` passes `req.network` through to the aggregator, so the
`network` field in the response is the one the numbers came from. The aggregate
refresh worker now runs once per enabled network β€” its own queue name, its own
pair list, and `price_aggregates` upserts stamped with its network β€” where it
previously pinned all three to `STELLAR_NETWORK`, leaving the second network's
`/price/*/history` and `/screener` permanently empty. The Redis price key it
writes is network-prefixed, matching what the REST route reads.
---
'lens': patch
---
Partition the aggregation layer by network. Every function in
`src/aggregator/vwap.ts` now takes a required `network` and filters
`price_points`/`pool_snapshots` on it, and `getAMMPrice` filters both the
`pool_snapshots` scan and the `pool_id` subquery. Previously an unfiltered read
returned a volume-weighted blend of both chains' rows for a pair key that
exists on both, so `/price/:a/:b` answered with a number nobody traded at and
stamped it `"network":"mainnet"`.
`/price/:assetA/:assetB` passes `req.network` through to the aggregator, so the
`network` field in the response is the one the numbers came from. The aggregate
refresh worker now runs once per enabled network β€” its own queue name, its own
pair list, and `price_aggregates` upserts stamped with its network β€” where it
previously pinned all three to `STELLAR_NETWORK`, leaving the second network's
`/price/*/history` and `/screener` permanently empty. The Redis price key it
writes is network-prefixed, matching what the REST route reads.
10 changes: 5 additions & 5 deletions .changeset/api-key-last-used.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Record when an API key was last used to authenticate (#500). `api_keys.last_used_at` is stamped on each successful authentication, throttled to one write per key per `KEY_LAST_USED_THROTTLE_SECONDS` (default 60) so authentication stays a read for busy keys, and surfaced on `GET /admin/keys` β€” null meaning the key has not authenticated since the column existed, which is what makes an abandoned key visible. A failed write never refuses a valid request, but is logged once per key rather than swallowed.
---
'lens': minor
---
Record when an API key was last used to authenticate (#500). `api_keys.last_used_at` is stamped on each successful authentication, throttled to one write per key per `KEY_LAST_USED_THROTTLE_SECONDS` (default 60) so authentication stays a read for busy keys, and surfaced on `GET /admin/keys` β€” null meaning the key has not authenticated since the column existed, which is what makes an abandoned key visible. A failed write never refuses a valid request, but is logged once per key rather than swallowed.
10 changes: 5 additions & 5 deletions .changeset/api-key-lookup-cache.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Cache API-key lookups in-process so authentication is no longer a Postgres round-trip per request (#495). Positive and negative lookups are held for `API_KEY_CACHE_TTL` seconds (default 30), the cache is bounded at 10,000 entries so invalid keys cannot grow it without limit, hit/miss is exported as `api_key_cache_lookups_total`, and a key's own `expiresAt` is re-checked on every hit so the TTL can never outlive the key. Revoking or rotating a key drops its cached entry immediately on the instance handling the request; another instance picks it up within the TTL. Rotation now also carries the old key's scopes to the replacement.
---
'lens': minor
---
Cache API-key lookups in-process so authentication is no longer a Postgres round-trip per request (#495). Positive and negative lookups are held for `API_KEY_CACHE_TTL` seconds (default 30), the cache is bounded at 10,000 entries so invalid keys cannot grow it without limit, hit/miss is exported as `api_key_cache_lookups_total`, and a key's own `expiresAt` is re-checked on every hit so the TTL can never outlive the key. Revoking or rotating a key drops its cached entry immediately on the instance handling the request; another instance picks it up within the TTL. Rotation now also carries the old key's scopes to the replacement.
10 changes: 5 additions & 5 deletions .changeset/bazaar-automatic-cataloging-130.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Automatic cataloging of resources from discovery extension upon settlement (#130)
---
'lens': minor
---
Automatic cataloging of resources from discovery extension upon settlement (#130)
24 changes: 12 additions & 12 deletions .changeset/bazaar-search-ssrf-guard.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
'lens': patch
---

`bazaar_search` no longer fetches any URL it is handed (#407). The MCP server now
parses the target, refuses anything that is not `http`/`https`, resolves the
hostname 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 manually
so a permitted host cannot bounce the request to a forbidden address. Operators
can constrain reachable hosts further with the shared `OUTBOUND_ALLOWED_HOSTS`
list, which narrows access but never re-opens a blocked address. Blocked requests
return the new `ERR_URL_BLOCKED` code.
---
'lens': patch
---
`bazaar_search` no longer fetches any URL it is handed (#407). The MCP server now
parses the target, refuses anything that is not `http`/`https`, resolves the
hostname 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 manually
so a permitted host cannot bounce the request to a forbidden address. Operators
can constrain reachable hosts further with the shared `OUTBOUND_ALLOWED_HOSTS`
list, which narrows access but never re-opens a blocked address. Blocked requests
return the new `ERR_URL_BLOCKED` code.
26 changes: 13 additions & 13 deletions .changeset/bazaar-seller-helpers.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
'lens': minor
---

Add seller-side Bazaar discovery helpers (`src/bazaar/declare.ts`): `param.*`
constructors that make per-parameter descriptions a required positional
argument, `declareHttpResource` / `declareMcpTool` builders that assemble the
`extensions.bazaar` declaration and its JSON Schema, and
`validateDeclaration` / `assertDeclaration` which run the catalog's own
`validateListing` plus seller-side metadata-quality rules so malformed listings
fail at development time rather than in production. Lens's own gated routes
(`/price`, `/candles`, `/pools`, `/price/twap`, and the MCP price tool) are
declared with them in `src/bazaar/lensListings.ts`.
---
'lens': minor
---
Add seller-side Bazaar discovery helpers (`src/bazaar/declare.ts`): `param.*`
constructors that make per-parameter descriptions a required positional
argument, `declareHttpResource` / `declareMcpTool` builders that assemble the
`extensions.bazaar` declaration and its JSON Schema, and
`validateDeclaration` / `assertDeclaration` which run the catalog's own
`validateListing` plus seller-side metadata-quality rules so malformed listings
fail at development time rather than in production. Lens's own gated routes
(`/price`, `/candles`, `/pools`, `/price/twap`, and the MCP price tool) are
declared with them in `src/bazaar/lensListings.ts`.
14 changes: 7 additions & 7 deletions .changeset/coinbase-cex-adapter-215.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
"lens": minor
---

Ship the first real centralized-exchange adapter in `src/ingest/cex/` (issue #215). The directory previously held only the `CexAdapter` interface and a mock, so every cross-venue comparison compared Stellar venues against nothing. `CoinbaseAdapter` implements `CexAdapter` in full β€” `fetchTicker` and `fetchOrderBook` against Coinbase Exchange's public market-data endpoints β€” and is built and registered through a new exchange-agnostic `createCexAdapter(name)` factory (`registerCexAdapterFactory` wires concrete exchanges in).

It normalises pairs in both directions, mapping the Lens canonical form (`XLM/USD`) to the identifier the exchange actually uses (`XLM-USD`) and back, since a slash-form request 404s. Requests are throttled to Coinbase's published public limit (10 req/s per IP, burst up to 15) with a token bucket; a `429` honours `Retry-After` when present and otherwise backs off exponentially. Any outage, timeout, non-2xx, or malformed body degrades to a `null` return β€” the adapter never throws, so a dead exchange cannot take down the process or surface an error response. Prices carry a distinct `source` (`coinbase_cex`) and take the `network` from the caller rather than hard-coding it, so a CEX number is never mistaken for, or silently blended with, an on-chain one.
---
"lens": minor
---
Ship the first real centralized-exchange adapter in `src/ingest/cex/` (issue #215). The directory previously held only the `CexAdapter` interface and a mock, so every cross-venue comparison compared Stellar venues against nothing. `CoinbaseAdapter` implements `CexAdapter` in full β€” `fetchTicker` and `fetchOrderBook` against Coinbase Exchange's public market-data endpoints β€” and is built and registered through a new exchange-agnostic `createCexAdapter(name)` factory (`registerCexAdapterFactory` wires concrete exchanges in).
It normalises pairs in both directions, mapping the Lens canonical form (`XLM/USD`) to the identifier the exchange actually uses (`XLM-USD`) and back, since a slash-form request 404s. Requests are throttled to Coinbase's published public limit (10 req/s per IP, burst up to 15) with a token bucket; a `429` honours `Retry-After` when present and otherwise backs off exponentially. Any outage, timeout, non-2xx, or malformed body degrades to a `null` return β€” the adapter never throws, so a dead exchange cannot take down the process or surface an error response. Prices carry a distinct `source` (`coinbase_cex`) and take the `network` from the caller rather than hard-coding it, so a CEX number is never mistaken for, or silently blended with, an on-chain one.
10 changes: 5 additions & 5 deletions .changeset/decimal-strings-boundary.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Serialise decimal quantities as exact strings across the API boundary via header/query/Accept negotiation to preserve 18-decimal precision without IEEE-754 floating-point loss.
---
'lens': minor
---
Serialise decimal quantities as exact strings across the API boundary via header/query/Accept negotiation to preserve 18-decimal precision without IEEE-754 floating-point loss.
30 changes: 15 additions & 15 deletions .changeset/defindex-vault-adapter.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
'lens': minor
---

DeFindex vault shares are now priced. A new `DeFindex` venue adapter
(`src/ingest/venues/defindex.ts`) enumerates every vault from the protocol's
on-chain factory registry, reads each vault's total managed assets and total
share supply straight from the chain, and derives the share price as
`totalAssets / totalShares` β€” never from a published figure that could be stale.
A vault with zero shares yields no point rather than dividing by zero. Rows are
written with `source = 'defindex_vault'` so a derived vault price is never
silently blended with a traded one, and each row carries the network it was read
from, so the ingester runs once per `ENABLED_NETWORKS` entry without merging
mainnet and testnet series. The factory contract ids are taken whole from DeFindex's
published `mainnet.contracts.json` / `testnet.contracts.json`.
---
'lens': minor
---
DeFindex vault shares are now priced. A new `DeFindex` venue adapter
(`src/ingest/venues/defindex.ts`) enumerates every vault from the protocol's
on-chain factory registry, reads each vault's total managed assets and total
share supply straight from the chain, and derives the share price as
`totalAssets / totalShares` β€” never from a published figure that could be stale.
A vault with zero shares yields no point rather than dividing by zero. Rows are
written with `source = 'defindex_vault'` so a derived vault price is never
silently blended with a traded one, and each row carries the network it was read
from, so the ingester runs once per `ENABLED_NETWORKS` entry without merging
mainnet and testnet series. The factory contract ids are taken whole from DeFindex's
published `mainnet.contracts.json` / `testnet.contracts.json`.
10 changes: 5 additions & 5 deletions .changeset/discovery-search-129.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Add GET /discovery/search with relevance ranking and quality evaluation (#129)
---
'lens': minor
---
Add GET /discovery/search with relevance ranking and quality evaluation (#129)
10 changes: 5 additions & 5 deletions .changeset/enforce-rate-per-day-175.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': patch
---

Enforce ratePerDay daily quota on authenticated API keys (#175)
---
'lens': patch
---
Enforce ratePerDay daily quota on authenticated API keys (#175)
10 changes: 5 additions & 5 deletions .changeset/fiat-pricing-naira-290.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': minor
---

Add GET /price/:asset/fiat for local currency conversion and naira valuation (#290)
---
'lens': minor
---
Add GET /price/:asset/fiat for local currency conversion and naira valuation (#290)
10 changes: 5 additions & 5 deletions .changeset/fix-twap-outlier-alignment.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
'lens': patch
---

Preserve TWAP timestamp alignment when outlier prices are rejected.
---
'lens': patch
---
Preserve TWAP timestamp alignment when outlier prices are rejected.
10 changes: 5 additions & 5 deletions .changeset/graphql-price-subscriptions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"lens": minor
---

Add a `priceUpdated(pair: String!, network: String)` GraphQL subscription that streams live prices over the existing `/graphql` endpoint (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. `network` is optional and narrows the stream to one chain β€” omitting it delivers every enabled network, which is only safe if the subscriber reads the `network` field on each message, since a dual-network deployment otherwise interleaves two chains' prices for the same pair.
---
"lens": minor
---
Add a `priceUpdated(pair: String!, network: String)` GraphQL subscription that streams live prices over the existing `/graphql` endpoint (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. `network` is optional and narrows the stream to one chain β€” omitting it delivers every enabled network, which is only safe if the subscriber reads the `network` field on each message, since a dual-network deployment otherwise interleaves two chains' prices for the same pair.
Loading
Loading