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: 2 additions & 1 deletion workspace/data-proxy/src/config/pyth-lazer-module-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ export interface PythLazerModuleConfig
export const PythLazerModuleRouteSchema = v.strictObject({
...RouteSchema.entries,
moduleName: v.string(),
fetchFromModule: v.string(),
// Present: path template. Omitted: POST body is expected instead.
fetchFromModule: v.optional(v.string()),
channel: v.optional(PythLazerChannelSchema, PYTH_LAZER_DEFAULT_CHANNEL),
type: v.literal("pyth-lazer"),
});
Expand Down
31 changes: 28 additions & 3 deletions workspace/data-proxy/src/modules/pyth-lazer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ On startup the module:
3. Caches inbound `streamUpdated` messages keyed by `(channel, priceFeedId)`. Ticks for feeds that are no longer in the desired set are ignored.
4. A forked cleanup daemon removes from the desired set feeds that have not been requested within `priceFeedsCleanupTtl`. It deletes their cache entries and updates the desired set.

HTTP requests resolve `fetchFromModule` to one or more comma-separated feed IDs or symbols, add any new feeds to the desired set, send a subscribe of the full set for that channel if it grew, and return the latest cached price for each. If a price is not yet available, the handler waits briefly for an update (shared price-cache timeout: 3 seconds).
This module supports two different types of HTTP requests:

- **Standard Request** (`fetchFromModule` set): resolve the template to one or more comma-separated feed IDs or symbols, add any new feeds to the desired set for the route channel, send a subscribe of the full set for that channel if it grew, and return the latest cached price for each as a JSON array. If a price is not yet available, the handler waits briefly for an update (shared price-cache timeout: 3 seconds).
- **POST Body-Based Request** (`fetchFromModule` omitted): parse a JSON body (`priceFeedIds` / `priceFeedSymbols`, optional `channel`), subscribe on the selected channel (body overrides route default), and return `{ parsed: { timestampUs, priceFeeds } }`. All-or-fail if any feed misses.

Pyth cannot append feeds to an existing subscription id, so a grow sends a **new** subscribe with a new id and the full desired set for that channel. Each channel has at most one **active** (acked) subscription, plus any in-flight ids still waiting for an ack. The module promotes an id only after Pyth acks it (`subscribed` or `subscribedWithInvalidFeedIdsIgnored`) and only if it is higher than the channel’s current active id; older outstanding ids on that channel are then unsubscribed.

Expand All @@ -37,8 +40,8 @@ Pyth cannot append feeds to an existing subscription id, so a grow sends a **new
| `moduleName` | yes | — | Name of a configured Pyth Lazer module. |
| `path` | yes | — | Proxy path (supports `{:param}` path params). |
| `method` | no | `GET` | HTTP method(s). |
| `fetchFromModule` | yes | — | Template producing one or more comma-separated feed IDs or symbols. |
| `channel` | no | `fixed_rate@200ms` | Channel for this route’s subscriptions and cache lookups (`real_time`, `fixed_rate@50ms`, `fixed_rate@200ms`, `fixed_rate@1000ms`). |
| `fetchFromModule` | no | — | When set: Template producing one or more comma-separated feed IDs or symbols. When omitted: `POST` body is expected instead. |
| `channel` | no | `fixed_rate@200ms` | Default channel for this route’s subscriptions and cache lookups (`real_time`, `fixed_rate@50ms`, `fixed_rate@200ms`, `fixed_rate@1000ms`). On the Pro body surface, a request may override this with a `channel` field. |

### Example Configuration

Expand Down Expand Up @@ -71,6 +74,13 @@ Pyth cannot append feeds to an existing subscription id, so a grow sends a **new
"method": "GET",
"fetchFromModule": "{:symbol}",
"channel": "real_time"
},
{
"type": "pyth-lazer",
"moduleName": "pyth",
"path": "/v1/latest_price",
"method": "POST",
"channel": "fixed_rate@200ms"
}
]
}
Expand Down Expand Up @@ -151,6 +161,21 @@ On a wait timeout, the entry still appears with `__sedaHasPrice: false` and with

Requests with more feeds than `maxFeedsPerRequest`, or a numeric token that is not a u32, return HTTP 400.

### POST Body-Based Request (Latest Price Request)

When a route omits `fetchFromModule`, the module accepts a JSON body (extra Pyth Pro fields such as `formats` / `properties` are ignored):

```json
{
"priceFeedIds": [1, 2],
"channel": "real_time"
}
```

Use `priceFeedSymbols` instead of `priceFeedIds` when requesting by symbol. `channel` is optional and falls back to the route’s `channel`.

The response matches Pyth Pro’s `{ parsed: { timestampUs, priceFeeds } }` envelope. Missing optional feed fields are emitted as `null`. Unlike the path surface, this surface is all-or-fail: if any requested feed never produces a price, the whole request errors.


## Notes

Expand Down
Loading
Loading