A TypeScript SDK for receiving updates from the OpenSea Stream API - pushed over websockets. We currently support the following event types on a per-collection basis:
- item listed
- item sold
- item transferred
- item metadata updates
- item cancelled
- item received offer
- item received bid
This is a best effort delivery messaging system. Messages that are not received due to connection errors will not be re-sent. Messages may be delivered out of order. This SDK is offered as a beta experience as we work with developers in the ecosystem to make this a more robust and reliable system.
Documentation: https://docs.opensea.io/reference/stream-js
Please use Node.js version 16 or greater to make sure common crypto dependencies work.
- Install this package:
npm install @opensea/stream-js - Install types for phoenix:
npm install --save-dev @types/phoenix - NodeJS only: Install required libraries:
npm install ws node-localstorage
In order to make onboarding easy, we've integrated the OpenSea Stream API with our existing API key system. The API keys you have been using for the REST API should work here as well.
Get an API key instantly (no signup needed):
curl -s -X POST https://api.opensea.io/api/v2/auth/keys | jq -r '.api_key'Or get a full key at opensea.io/settings/developer for higher rate limits.
import { OpenSeaStreamClient } from '@opensea/stream-js';
const client = new OpenSeaStreamClient({
token: 'YOUR_OPENSEA_API_KEY'
});import { OpenSeaStreamClient } from '@opensea/stream-js';
import { WebSocket } from 'ws';
import { LocalStorage } from 'node-localstorage';
const client = new OpenSeaStreamClient({
token: 'YOUR_OPENSEA_API_KEY',
connectOptions: {
transport: WebSocket,
sessionStorage: LocalStorage
}
});You can also optionally pass in:
- a
networkparameter (defaults toNetwork.MAINNET) apiUrlif you would like to access another OpenSea Stream API endpoint. Not needed if you provide a network or use the default values.- an
onErrorcallback to handle errors. The default behavior is toconsole.errorthe error. - a
logLevelto set the log level. The default isLogLevel.INFO.
The OpenSea Stream API is available on mainnet:
wss://stream.openseabeta.com/socket
The client will automatically connect to the socket as soon as you subscribe to the first channel. If you would like to connect to the socket manually (before that), you can do so:
client.connect();After successfully connecting to our websocket it is time to listen to specific events you're interested in!
We will only send out metadata updates when we detect that the metadata provided in tokenURI has changed from what OpenSea has previously cached.
client.onItemMetadataUpdated('collection-slug', (event) => {
// handle event
});client.onItemListed('collection-slug', (event) => {
// handle event
});client.onItemSold('collection-slug', (event) => {
// handle event
});client.onItemTransferred('collection-slug', (event) => {
// handle event
});client.onItemReceivedBid('collection-slug', (event) => {
// handle event
});
client.onItemReceivedOffer('collection-slug', (event) => {
// handle event
});client.onEvents(
'collection-slug',
[EventType.ITEM_RECEIVED_OFFER, EventType.ITEM_TRANSFERRED],
(event) => {
// handle event
}
);client.onItemCancelled('collection-slug', (event) => {
// handle event
});If you'd like to listen to an event from all collections use wildcard * for the collectionSlug parameter.
Every stream event includes a numeric version field that is monotonically increasing per source entity. Use it to handle out-of-order event delivery: when two events arrive for the same entity, the one with the higher version is the newer state.
The backend emits whichever authoritative monotonic counter exists for the underlying entity, so the scale of version varies:
| Event types | version semantic |
|---|---|
item_listed, item_cancelled, item_received_offer, item_received_bid, collection_offer, trait_offer, order_invalidate, order_revalidate |
Order revision counter (small monotonic integer per order) |
item_transferred, item_sold, item_metadata_updated |
Epoch milliseconds of the event's source timestamp |
Both representations are monotonic and sufficient for resolving out-of-order delivery, but the two scales are not comparable to each other. Never compare version across different event families or across unrelated entities — only compare versions for the same entity within the same event family.
Scope your stored versions by event type (or by the source entity — order id for order-derived events, nft_id for item events):
const latestOrderVersion = new Map<string, number>();
client.onItemListed('*', (event) => {
const orderHash = event.payload.order_hash;
const prev = latestOrderVersion.get(orderHash) ?? 0;
if (event.version > prev) {
latestOrderVersion.set(orderHash, event.version);
// Process — this is newer state for this order
}
// Otherwise discard — we already have newer state for this order
});Multiple backend pods process events, DLQ replays can deliver older state, and normal Kafka processing can deliver events out of order. Comparing version within the same entity lets consumers always converge to the correct current state.
Types are included to make working with our event payload objects easier.
All subscription methods return a callback function that will unsubscribe from a stream when invoked.
const unsubscribe = client.onItemMetadataUpdated('collection-slug', () => {});
unsubscribe();client.disconnect();This repository is a read-only mirror synced from an internal monorepo. We can't merge pull requests directly, but we review every one — if your fix or idea is solid, we'll recreate it internally and it will ship in the next release.
Issues and bug reports are the best way to contribute. See CONTRIBUTING.md for details.
MIT Copyright 2022 Ozone Networks, Inc.
