Activity feeds
How to load discrete market events (trades, liquidity changes, and other venue activity) for an activity panel and keep the feed updated without re-downloading full history on every poll.
OHLCV candles use Chart windows (CandleQuery, bucket time). Activity feeds use FeedQuery and FeedCursor (time + eventId).
Venue feed reads use the same window options on:
easy.read.getTradeFeed— you pass a pair label (e.g.42-USDT)telemetry.getVenueFeed— any supported eventkindwhen you already know the pooltelemetry.getTrades— shorthand for trades only (kinds: ['trade']implicit)
All return a FeedSeries. Trade-specific calls (getTradeFeed, getTrades) use the TradeFeed alias — a FeedSeries whose events are TradeView items.
What is a feed event?
Each event is one discrete market fact — e.g. one executed trade. It has:
eventId— stable, opaque ID (unique across the deployment; do not parse it)time— when the event occurred (Unix timestamp in seconds)kind— what happened ('trade','liquidity_add','liquidity_remove', …)ledgerId— which chain the event belongs totxHash— transaction hash (build explorer URLs in your app; Nutrimatic does not return explorer links)
Trades add venueId, price, baseAmount, quoteAmount, and side. Every event also has actor (flow-relevant address). See TradeView.
Events in events are ascending by total order: time first, then eventId as tie-break. Show “newest on top” in your UI by reversing the array — the API and SDK keep canonical ASC order.
Four ways to fetch data
| Pattern | When you need it | What to call |
|---|---|---|
| Bootstrap | User opens the activity panel, or changes pair / venue | getTradeFeed({ pair, limit: 50 }) |
| Tail refresh | New events may have arrived since last load | getTradeFeed({ pair, after: … }) |
| Head only | Panel is open; cheap poll for the live edge | getTradeFeed({ pair, scope: 'headOnly' }) |
| Tick | Pool summary only — spot, TVL, volume, not individual events | getPoolView({ venue }) |
Bootstrap = first load of recent history. Tail refresh = append new events since your last load. Head only = minimal poll at the feed edge. Tick = aggregated pool state, not the event feed.
Do not repeat bootstrap on a timer. Use head only for frequent cheap polls and tail refresh when you need the full list of new events (often less frequent than head only).
Window parameters (FeedQuery)
Optional fields on feed reads: limit, after, before, scope, kinds.
If you omit all of them, Nutrimatic returns a default slice of recent events — the same as omitting only limit (default 50 events). That is scope: 'default'.
You can combine before + limit when loading older feed history. Do not pass after and before together unless you need a narrow slice between two cursors.
Do not combine scope: 'headOnly' with limit, before, or bootstrap — it is only for polling the live edge when history is already loaded.
limit
What it means: “Return at most this many events.” Nutrimatic picks the most recent events up to that count (returned in ASC order).
Optional — default 50 if omitted. Responses cap at 200; if you ask for more, you receive at most the cap.
| Situation | Should you pass limit? | Example |
|---|---|---|
| First panel load (bootstrap) | Yes | limit: 50 |
Tail refresh (you already passed after) | Usually no | after already limits to new events |
| User scrolls up for older events | Yes, with before | before: { time, eventId }, limit: 50 |
| Pool summary without events | Not used | getPoolView |
after
What it means: “I already have events up to this cursor — only send me newer ones.”
after is a FeedCursor: { time, eventId }. Nutrimatic returns events strictly after this point in total order (time, then eventId).
When to use it: after bootstrap, on each feed refresh.
What value to pass: the cursor of the last event you already have.
import { lastFeedCursor } from '@nutrimatic/sdk'
const after = lastFeedCursor(feed.events)
// Pass `after` on the next getTradeFeed call
What you do with the response: merge into your existing feed — do not discard previous events. Use mergeFeedSeries.
Why eventId matters: several trades can share the same time. The cursor includes both fields so you never skip or duplicate events in the same second.
before
What it means: “I already have recent events — send me older ones.”
before is a FeedCursor. Nutrimatic returns events strictly before this point in total order.
When to use it: the user scrolls up in the activity list.
What value to pass: the cursor of the oldest event you already display.
import { firstFeedCursor } from '@nutrimatic/sdk'
const before = firstFeedCursor(feed.events)
// Pass `before` together with `limit`
Always pair with limit: e.g. before: oldestCursor, limit: 50.
What you do with the response: prepend merged events (older data first).
kinds
What it means: filter which event types to return.
| Value | Use |
|---|---|
'trade' | Trade tape, buy/sell activity |
'liquidity_add' | 'liquidity_remove' | LP add/remove monitoring |
Omit kinds to receive all kinds the venue supports. getTrades is equivalent to getVenueFeed with kinds: ['trade'].
scope
| Value | events in response | When to use |
|---|---|---|
'default' or omit | Events matching window params | Bootstrap, tail refresh, page backward |
'headOnly' | Always [] (empty) | Frequent poll while panel is visible |
When to use 'headOnly': you already ran bootstrap, the panel is on screen, and you want a cheap poll without reloading the full event history. For full new events, use tail refresh with after.
Do not use 'headOnly' with: limit, before, or the first panel load.
Response fields
Every successful feed call returns a FeedSeries. Important fields:
| Field | What it is | What you do with it |
|---|---|---|
scopeId | ID of the venue (pool) this feed belongs to | Compare against your panel’s current venue so you don’t render a stale feed after a pair or venue switch |
scopeKind | Always 'venue' for feed reads | Confirms the scope type; no action needed unless your code branches on it |
events | Array of events, oldest → newest | Render list; reverse for “newest on top” |
asOf | Unix timestamp in seconds for when Nutrimatic built this snapshot | Show a “data as of …” label, or use it to detect a stale feed |
ordering | Always 'asc' | Contract guarantee — do not assume DESC from API |
Recommended refresh loop
Copy this shape for a minimal live trade panel:
import {
createNutrimaticSdk,
lastFeedCursor,
mergeFeedSeries,
} from '@nutrimatic/sdk'
const sdk = createNutrimaticSdk({ /* … */ })
// Step 1 — Bootstrap (once when panel opens, or pair changes)
let feed = await sdk.easy.read.getTradeFeed({
pair: '42-USDT',
limit: 50,
})
// Render feed.events (reverse if you want newest on top)
// Step 2a — Head only (optional — cheap poll while panel is visible)
async function pollHead() {
await sdk.easy.read.getTradeFeed({
pair: '42-USDT',
scope: 'headOnly',
})
// Optional — tail refresh (Step 2b) is enough for most panels
}
// Step 2b — Tail refresh (when new trades may have arrived — e.g. every 10s)
async function refreshNewTrades() {
const after = lastFeedCursor(feed.events)
if (after === undefined) return
const tail = await sdk.easy.read.getTradeFeed({
pair: '42-USDT',
after,
})
feed = mergeFeedSeries(feed, tail)
}For Step 2b, mergeFeedSeries deduplicates by eventId and keeps ASC order. Step 2a is optional; most panels rely on tail refresh alone.
You still choose: poll intervals, errors, loading states, and list layout. Use headOnly for cheap edge polls when that fits your UI; use after for new events — not full bootstrap on every poll.
Choosing a read method
| You have… | Call |
|---|---|
Pair label (42-USDT) | easy.read.getTradeFeed |
venueId + trades only | telemetry.getTrades |
venueId + multiple kinds | telemetry.getVenueFeed |
Start with easy.read when you have pair labels. Use telemetry when you already resolved the venue ID.
Kernel helpers
Small pure functions (no network, no cache) from @nutrimatic/kernel, also exported by @nutrimatic/sdk:
import {
compareFeedCursors,
mergeFeedEvents,
mergeFeedSeries,
lastFeedCursor,
firstFeedCursor,
} from '@nutrimatic/sdk'FeedSeries vs raw events[]
| You have… | Use |
|---|---|
Two full API responses (let feed + const tail = await getTradeFeed(…)) | mergeFeedSeries(feed, tail) — usual case for tail refresh |
Only two event arrays (e.g. you store events separately) | mergeFeedEvents(base, incoming) — lower level; you update asOf yourself |
Default rule: if both sides came from getTradeFeed or getVenueFeed, keep the whole FeedSeries and call mergeFeedSeries.
Example — prepend older history:
const before = firstFeedCursor(feed.events)
if (before === undefined) return
const older = await sdk.easy.read.getTradeFeed({
pair: '42-USDT',
before,
limit: 50,
})
feed = {
...feed,
events: mergeFeedEvents(older.events, feed.events),
}Helper reference
| Function | Input | Output | Typical use |
|---|---|---|---|
lastFeedCursor(events) | FeedEventRef[] | FeedCursor | undefined | Value for after (tail refresh) |
firstFeedCursor(events) | FeedEventRef[] | FeedCursor | undefined | Value for before (page backward) |
compareFeedCursors(a, b) | two FeedCursor | number | Sort cursors, or check which of two events came first, without comparing time and eventId yourself |
mergeFeedEvents(base, incoming) | two event arrays | event array | Merge arrays only |
mergeFeedSeries(base, tail) | two FeedSeries | FeedSeries | Tail refresh after getTradeFeed — start here |
mergeFeedSeries throws if scopeId or scopeKind differ — run bootstrap again instead of merging.
Liquidity events
Kinds 'liquidity_add' and 'liquidity_remove' use the same FeedQuery and merge helpers as trades. Request them through getVenueFeed with the appropriate kinds filter. Event shape: LiquidityEventView.
After you submit liquidity.add or liquidity.remove, use the returned ExecutionHandle for your operation status, then refresh the venue feed (and pool view) to show the market event. Feeds do not replace execution tracking — see Write operations.
Live updates (default)
For a live activity panel, prefer sdk.live.watchPair — Nutrimatic streams trades (and related channels) over SSE. The window helpers on this page are for bootstrap and paging history, not for inventing poll timers.
Alert rules and a notification engine remain outside the SDK.
Related
- Chart windows — OHLCV incremental refresh (separate domain)
- Read operations — getTradeFeed ·
getVenueFeed·getTrades - Types — FeedQuery ·
FeedCursor·TradeView·FeedSeries - Integration guides — Scope