Skip to content

Setup & Connection

This guide covers how to connect to Warp streams, authenticate, handle events, and implement best practices for reliable real-time streaming.

Authentication

Warp streams require API key authentication via the x-api-key header.

Note: The native browser EventSource API doesn't support custom headers. Use fetch with ReadableStream for browser applications, or the eventsource npm package for Node.js.

Using Fetch (Browser & Node.js)

javascript
const response = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
  headers: { 'x-api-key': apiKey }
})

Using eventsource Package (Node.js)

bash
npm install eventsource
javascript
const EventSource = require('eventsource')

const eventSource = new EventSource(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
  headers: { 'x-api-key': apiKey }
})

Connection Handling

Basic Connection

javascript
const streamId = 'your-stream-id'
const apiKey = 'your-api-key'

// AbortController is required for graceful shutdown
const abort = new AbortController()
process.on('SIGINT', () => abort.abort())

const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
  headers: { 'x-api-key': apiKey },
  signal: abort.signal
})

let buffer = ''
for await (const chunk of res.body) {
  buffer += new TextDecoder().decode(chunk)
  const parts = buffer.split('\n\n')
  buffer = parts.pop() // Keep incomplete part
  for (const part of parts) {
    const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
    if (dataLine) console.log(JSON.parse(dataLine.slice(5)))
  }
}

Closing the Connection

javascript
// Clean up when done
abort.abort()

Event Types

Warp uses named SSE events. Each event type has a specific structure.

connected - Connection Established

Sent immediately when the connection is established:

json
{
  "connId": "conn_abc123",
  "subscriptionId": "sub_xyz789",
  "subscriptionType": "event",
  "heartbeatIntervalMs": 5000,
  "eventType": "0x2::coin::Transfer"
}

event - Blockchain Event

Sent when a matching blockchain event occurs:

json
{
  "activityType": "package_event",
  "txDigest": "5KmR...",
  "eventSequence": 0,
  "checkpoint": 12345678,
  "timestamp": 1704729600000,
  "type": "0x2::coin::Transfer",
  "sender": "0xabc...",
  "data": {
    "amount": "1000000000",
    "recipient": "0xdef..."
  }
}

heartbeat - Keep-Alive

Sent every 5 seconds to maintain the connection:

json
{
  "ts": 1704729630000,
  "r": 9500
}
  • ts - Timestamp in milliseconds
  • r - Remaining compute units

quota_exceeded - CU Limit Reached

Sent when your compute unit quota is exhausted:

json
{
  "remaining": 0,
  "eventCount": 150,
  "heartbeatCount": 10
}

Error Handling

Connection Errors

Wrap your fetch in try/catch and handle AbortError separately:

javascript
try {
  const res = await fetch(url, { headers: { 'x-api-key': apiKey }, signal: abort.signal })
  // ... process stream
} catch (err) {
  if (err.name === 'AbortError') return // Intentional shutdown
  console.error('Connection error:', err)
  // Implement reconnection logic
}

Manual Reconnection

For custom reconnection logic with exponential backoff:

javascript
let abort
let reconnectAttempts = 0

async function connect() {
  abort = new AbortController()
  try {
    const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
      headers: { 'x-api-key': apiKey },
      signal: abort.signal
    })
    reconnectAttempts = 0
    let buffer = ''
    for await (const chunk of res.body) {
      buffer += new TextDecoder().decode(chunk)
      const parts = buffer.split('\n\n')
      buffer = parts.pop()
      for (const part of parts) {
        const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
        if (dataLine) console.log(JSON.parse(dataLine.slice(5)))
      }
    }
  } catch (err) {
    if (err.name === 'AbortError') return
    if (reconnectAttempts++ < 10) {
      const delay = Math.min(1000 * 2 ** reconnectAttempts, 30000)
      setTimeout(connect, delay)
    }
  }
}

connect()

Node.js Usage

The native EventSource API is browser-only. For Node.js, use the eventsource package:

bash
npm install eventsource
javascript
const EventSource = require('eventsource')

const eventSource = new EventSource(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
  headers: {
    'x-api-key': apiKey
  }
})

// Use addEventListener for named event types
eventSource.addEventListener('event', (e) => {
  const data = JSON.parse(e.data)
  console.log('Event:', data)
})

eventSource.addEventListener('heartbeat', (e) => {
  const data = JSON.parse(e.data)
  console.log('Heartbeat:', data.eventCount, 'events received')
})

React Integration

Example React hook for Warp streams:

jsx
import { useEffect, useState } from 'react'

function useWarpStream(streamId, apiKey) {
  const [events, setEvents] = useState([])
  const [error, setError] = useState(null)

  useEffect(() => {
    const abort = new AbortController()
    ;(async () => {
      try {
        const res = await fetch(`https://api.inodra.com/v1/warp/${streamId}/stream`, {
          headers: { 'x-api-key': apiKey },
          signal: abort.signal
        })
        let buffer = ''
        for await (const chunk of res.body) {
          buffer += new TextDecoder().decode(chunk)
          const parts = buffer.split('\n\n')
          buffer = parts.pop()
          for (const part of parts) {
            const dataLine = part.split('\n').find((l) => l.startsWith('data:'))
            if (dataLine) setEvents((prev) => [...prev, JSON.parse(dataLine.slice(5))].slice(-100))
          }
        }
      } catch (err) {
        if (err.name !== 'AbortError') setError('Connection lost')
      }
    })()
    return () => abort.abort()
  }, [streamId, apiKey])

  return { events, error }
}

// Usage
function LiveEvents({ streamId }) {
  const { events, error } = useWarpStream(streamId, process.env.NEXT_PUBLIC_INODRA_API_KEY)
  return (
    <div>
      {error && <div>Error: {error}</div>}
      <ul>
        {events.map((event, i) => (
          <li key={i}>
            {event.type} - {event.txDigest}
          </li>
        ))}
      </ul>
    </div>
  )
}

Best Practices

1. Handle Reconnections Gracefully

Your application should:

  • Implement exponential backoff for repeated failures
  • Buffer important events during reconnection
  • Use AbortController for clean shutdown

2. Implement Deduplication

Use txDigest to deduplicate events:

javascript
const seen = new Set()
function handleEvent(data) {
  if (seen.has(data.txDigest)) return
  seen.add(data.txDigest)
  // Process event...
  if (seen.size > 10000) seen.clear() // Prevent memory leak
}

3. Monitor Connection Health

Heartbeats arrive every 5 seconds. If none received for 60s, reconnect:

javascript
let lastHeartbeat = Date.now()
// In your event loop, update lastHeartbeat when you see heartbeat events
// Check periodically and call abort.abort() + reconnect if stale

Stream Limits by Plan

Each stream allows 1 concurrent connection. The maximum number of Warp streams you can create depends on your plan:

PlanMax Warp StreamsLog Retention
Free1None
Team37 days
Professional1530 days
Business2590 days
EnterpriseCustomCustom

Reconnection & Event Replay

When you reconnect to a stream, events from your disconnection period can be replayed using the Last-Event-ID header:

  • Retention period: 1 hour
  • Max buffered events: 10,000 per stream
  • Event ID format: Checkpoint number (automatically sent with each event)

Note: The Event ID format (checkpoint number) will change soon to a more granular identifier for better deduplication support. This will allow more precise event tracking and replay.

When using fetch, you can pass the Last-Event-ID header on reconnect to resume from where you left off. Events older than the retention period or exceeding the buffer limit will not be available for replay.

Compute Unit Usage

Warp streams are billed based on compute units:

ActionCompute Units
Event received100 CU
Heartbeat4 CU

Heartbeats are sent every 5 seconds (~50 CU/minute for keepalive).

Troubleshooting

Connection Refused

  • Verify your API key is valid
  • Check that the stream ID exists and is active
  • Ensure you haven't exceeded connection limits

Events Not Arriving

  • Verify the stream is active in the dashboard
  • Check that your filter criteria match on-chain activity
  • Review the stream type and configuration

Frequent Disconnections

  • Check your network stability
  • Implement proper reconnection logic
  • Consider using a more stable hosting environment

Next Steps

Released under the MIT License.