Skip to content
CTRL K
    Activity feeds

    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:

    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 to
    • txHash — 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

    PatternWhen you need itWhat to call
    BootstrapUser opens the activity panel, or changes pair / venuegetTradeFeed({ pair, limit: 50 })
    Tail refreshNew events may have arrived since last loadgetTradeFeed({ pair, after: … })
    Head onlyPanel is open; cheap poll for the live edgegetTradeFeed({ pair, scope: 'headOnly' })
    TickPool summary only — spot, TVL, volume, not individual eventsgetPoolView({ 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.

    SituationShould you pass limit?Example
    First panel load (bootstrap)Yeslimit: 50
    Tail refresh (you already passed after)Usually noafter already limits to new events
    User scrolls up for older eventsYes, with beforebefore: { time, eventId }, limit: 50
    Pool summary without eventsNot usedgetPoolView

    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.

    ValueUse
    '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

    Valueevents in responseWhen to use
    'default' or omitEvents matching window paramsBootstrap, 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:

    FieldWhat it isWhat you do with it
    scopeIdID of the venue (pool) this feed belongs toCompare against your panel’s current venue so you don’t render a stale feed after a pair or venue switch
    scopeKindAlways 'venue' for feed readsConfirms the scope type; no action needed unless your code branches on it
    eventsArray of events, oldest → newestRender list; reverse for “newest on top”
    asOfUnix timestamp in seconds for when Nutrimatic built this snapshotShow a “data as of …” label, or use it to detect a stale feed
    orderingAlways '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 onlytelemetry.getTrades
    venueId + multiple kindstelemetry.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

    FunctionInputOutputTypical use
    lastFeedCursor(events)FeedEventRef[]FeedCursor | undefinedValue for after (tail refresh)
    firstFeedCursor(events)FeedEventRef[]FeedCursor | undefinedValue for before (page backward)
    compareFeedCursors(a, b)two FeedCursornumberSort cursors, or check which of two events came first, without comparing time and eventId yourself
    mergeFeedEvents(base, incoming)two event arraysevent arrayMerge arrays only
    mergeFeedSeries(base, tail)two FeedSeriesFeedSeriesTail refresh after getTradeFeedstart 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