Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ lens/
│ ├── db.ts # Postgres pool / Prisma client
│ ├── api/ # REST + GraphQL route implementations
│ ├── routes/ # Additional REST route modules
│ ├── ingesters/ # SDEX + AMM ingester loops
│ ├── ingest/ # Venue/oracle adapters (Horizon AMM, Aquarius, Reflector)
│ ├── ingest/ # Venue/oracle adapters + ingester loops (SDEX, Horizon AMM, Soroswap, Aquarius, Reflector)
│ ├── publish/ # SEP-40 publisher: encoding, publication policy, on-chain feed
│ ├── aggregator/ # VWAP + best-route calculation
│ ├── pricing/ # TWAP, depth and aggregate helpers
Expand Down
93 changes: 93 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,10 +705,60 @@ export interface paths {
patch?: never;
trace?: never;
};
"/rates": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Query latest lending and borrow rates across pools
* @description Returns the latest supply and borrow rates, utilisation, and reserves for lending markets.
*/
get: operations["getLendingRates"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
LendingRateEntry: {
id: string;
/** @example blend */
venue: string;
/** @example blend */
source: string;
/** @example mainnet */
network: string;
poolId: string;
poolName?: string | null;
/** @example USDC */
asset: string;
assetAddress?: string | null;
supplyApy: number;
borrowApy: number;
supplyApr?: number | null;
borrowApr?: number | null;
/** @description Borrowed / supplied as a fraction. Null for venues that publish rates only. */
utilization?: number | null;
totalSupply?: number | null;
totalBorrowed?: number | null;
reserves?: number | null;
ledger?: number;
/** Format: date-time */
timestamp: string;
};
LendingRatesResponse: {
network: string;
rates: components["schemas"]["LendingRateEntry"][];
};
InternalErrorResponse: {
/**
* @example internal_error
Expand Down Expand Up @@ -1456,6 +1506,18 @@ export interface components {
};
};
responses: {
/** @description Bad request — a parameter failed validation. */
BadRequest: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
error?: string;
code?: string;
};
};
};
/** @description Payment Required */
PaymentRequired: {
headers: {
Expand Down Expand Up @@ -3118,4 +3180,35 @@ export interface operations {
402: components["responses"]["PaymentRequired"];
};
};
getLendingRates: {
parameters: {
query?: {
/** @description Filter by asset symbol (e.g. USDC, XLM) */
asset?: string;
/** @description Filter by pool contract ID */
poolId?: string;
/** @description Stellar network to query (testnet or mainnet) */
network?: "testnet" | "mainnet";
/** @description Maximum number of rate records to return */
limit?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Latest lending rates */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["LendingRatesResponse"];
};
};
400: components["responses"]["BadRequest"];
429: components["responses"]["TooManyRequests"];
};
};
}
2 changes: 1 addition & 1 deletion docs/RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ worker, ingester or scheduler process:
| HTTP API | REST routes, `/graphql` + GraphiQL, webhooks, x402 facilitator, admin/usage routes | `buildApp()` in `src/app.ts`, routes under `src/api/` and `src/routes/` |
| WebSocket server | `graphql-transport-ws` subscriptions on `/graphql`; local fan-out plus cross-instance fan-out over Redis pub/sub | `src/ws/fanout.ts`, `src/ws/` |
| Venue ingesters | One infinite poll loop per (venue × enabled network) for **SDEX**, **Horizon AMM**, **Soroswap**, **Aquarius** | registry `src/ingest/venues/index.ts`, started in `main()` (`src/index.ts`) via `startVenueAdapter` (`src/ingest/venues/runner.ts`) |
| Snapshot ingester | Appends one `price_snapshots` row per active pair at every minute boundary, per network | `src/ingesters/snapshot.ts` |
| Snapshot ingester | Appends one `price_snapshots` row per active pair at every minute boundary, per network | `src/ingest/snapshot.ts` |
| BullMQ workers | `aggregate-refresh` (one queue/worker per network), `snapshot-retention` (hourly), `price_points` partition maintenance (hourly) — **in the same process** | `src/jobs/aggregateRefresh.ts`, `src/jobs/snapshotRetention.ts`, `src/jobs/pricePointsPartitionJob.ts` |
| Webhook dispatcher | Threshold alerts, delivered inline after each ingested price write | `src/webhookDispatcher.ts`, `src/alerts/delivery.ts` |
| Schema migration | `prisma migrate deploy` runs **at every boot, before the API listens** — committed migrations only, never a diff of the live schema (#233). On a database that predates the migration history the baseline is recorded as applied first, without running it | `applyMigrations()` in `src/db/migrate.ts`, called from `main()` (`src/index.ts`) — doc: [`docs/database-migrations.md`](database-migrations.md) |
Expand Down
2 changes: 1 addition & 1 deletion docs/ingest-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ queryable at all.

`last_trade_timestamp` was worse. It is a `Gauge.set()`, not an increment, so
whichever loop ticked last won. The testnet loop at
[`src/ingesters/sdex.ts`](../src/ingesters/sdex.ts) overwrote mainnet's value
[`src/ingest/venues/sdex.ts`](../src/ingest/venues/sdex.ts) overwrote mainnet's value
for the same pair every poll interval, and the series reported a timestamp
belonging to exactly one network while looking network-agnostic. That is the
only staleness signal Lens exports, and on a dual-network deployment it was
Expand Down
18 changes: 18 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2904,6 +2904,24 @@
}
},
"responses": {
"BadRequest": {
"description": "Bad request — a parameter failed validation.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"code": {
"type": "string"
}
}
}
}
}
},
"PaymentRequired": {
"description": "Payment Required",
"content": {
Expand Down
11 changes: 11 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1974,6 +1974,17 @@ components:
default: testnet

responses:
BadRequest:
description: Bad request — a parameter failed validation.
content:
application/json:
schema:
type: object
properties:
error:
type: string
code:
type: string
PaymentRequired:
description: Payment Required
content:
Expand Down
Loading
Loading