Skip to main content

Display marketplace listings


💡This guide requires you to interact with assets on Immutable zkEVM
Learn how to deploy your own NFT collection here.

Marketplaces need to display listings so that users can discover and purchase NFTs. This can be done in 2 ways:

Immutable strongly recommends partners use webhook streams to keep track of orders and trades for the following reasons:

  1. Real-time Updates: Webhooks provide real-time updates. When an event occurs, the webhook immediately triggers an action in the receiving application. This is in contrast to polling, which involves repeatedly checking for updates at predefined intervals.

  2. Efficiency: Polling can be inefficient, especially when there are no updates. It requires regular requests to the API, even when there's no new data. In contrast, webhooks are event-driven, meaning they only send data when an event occurs, reducing unnecessary traffic and load on the server.

  3. Reduced Latency: Webhooks minimise latency. Since they send updates in real-time, you get the latest data as soon as it becomes available. With polling, you may experience delays depending on the polling frequency.

  4. Simplicity: Setting up a webhook involves configuring the source application to send data to a specified endpoint. Polling, on the other hand, requires writing and maintaining code to periodically query an API, making it more complex.

Overall, webhooks in Web3 gaming provide a mechanism for real-time communication and data synchronization, enabling a more dynamic and engaging gaming experience while also enhancing the operational efficiency of blockchain-based games.

Option 1: Webhooks

Webhook streams enable marketplaces and other interested parties to receive order updates in real time. The Orders event is your go-to source for achieving this use case.

To gain more insights into the assets being listed, partners can complement orders event monitoring with separate API calls or subscribe to additional webhook events.

Immutable's recommendation is for partners to develop their own backend database and subscribe to NFT and Metadata events. This approach allows them to maintain a local asset database, enhancing performance and facilitating more detailed analysis.

Identify what type of events your application would like to be notified of via a webhooks stream

Immutable's Blockchain Data API sends notifications for various events. Partners can receive multiple events groups through a single webhook connection.

The Activities event should be the primary event subscribed to for the purpose tracking inventory changes. Additional events can also be subscribed to for additional information regarding the asset, collection or transaction.

Event GroupEvent TypesDescription
Ordersorder_updatedThis event signals when an order is placed or updated on Immutable's Global Orderbook. All details regarding the listing are present in this event including price, order_id, token_id, seller address and listing marketplace. No NFT metadata is delivered with this endpoint.
NFTnft_updatedThis event signals when an NFT is created or modified. It includes token_id to metadata_id mappings, connecting metadata stacks to individual NFTs. When used in conjunction with the metadata_updated event, it provides a comprehensive view of NFTs and their associated metadata within Immutable's ecosystem.
Metadatametadata_updatedThis event provides details of a metadata stack's attributes, with each stack being defined by a unique metadata_id. This event is triggered when new metadata stacks are created, as well as when existing stacks get updated through the Metadata Refresh service.
Collectionscollection_updatedThis event offers insights into newly deployed collections and updates to existing collections, providing metadata attributes. It serves as a valuable source of additional information regarding the collection to which the asset represented by the order belongs.

Select filters for each event

Each event mentioned above can be customised with specific filters applied at the event level. This means that different events can have their own unique filters to refine the data stream as needed. For more information on each event's filters, select the links above to learn more about each event.

note

By default a webhook subscription will return all events from all collections on a specified chain

Request webhooks from your Partner Success representative

Contact your Immutable Partner Success representative to set up your custom webhooks connection.

Develop a webhook endpoint function

To set up your webhook, follow these steps:

  1. Create a webhook endpoint function to handle the events sent by Immutable via POST.
  2. Share your webhook's endpoint details with Immutable.
  3. Accept the subscription invitation sent by Immutable.
  4. Begin receiving and processing webhooks as they come in.

Examples

Below are some examples of order_updated events that represent activity on Immutable's Global Orderbook.

All order_updated events contain all properties of the order, but for brevity and clarity, immutable fields on the order have been trimmed from the below examples on the subsequent events.

note

Please be advised that all fees and quantities within our system are denoted in the smallest unit of the respective currency, and decimal representations are not supported.

For instance, IMX, which has 18 decimal places, will have a fee of 0.000000000000000001 IMX represented as "1".

Similarly, 1 IMX is represented as "1000000000000000000" in our system.

New order being placed

In this example, an order is posted on Immutable's Global Orderbook. Orders are validated asynchronously, and a number of status changes can occur before the order becomes "ACTIVE". Orders are non-executable until custody and approvals are confirmed, at which point the status is updated to "ACTIVE."

Initial pending order

"PENDING" orders have not yet been validated by the orderbook.

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
"buy": [
{
"item_type": "NATIVE",
"end_amount": "1000000",
"start_amount": "1000000",
"contract_address": ""
}
],
"hash": "0xdd62c8ff1e410075251673fdca918a1b8a26a4aaff3f558ddfe9fe88af4b30cd",
"salt": "0x52c2669364b63b85",
"sell": [
{
"token_id": "0",
"item_type": "ERC721",
"end_amount": "1",
"start_amount": "1",
"contract_address": "0xfdee4926d1a7b01cc1b8d086faf0b2194c99acc8"
}
],
"status":
{
"name": "PENDING",
"evaluated": false,
"started": false
},
"chain_id": "eip155:13473",
"end_time": 1697528363,
"signature": "0xb2dd0a83659d419355a6ad77ccc83c23a0d6dea6f080ecee55eb8e11cfd42848049b2649c787fa27a2caf4d340661f77d278e953fa8569d992a65883febe43ec1c",
"buyer_fees": [
{
"amount": "1",
"fee_type": "MAKER_ECOSYSTEM",
"recipient": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d"
},
{
"amount": "20000",
"fee_type": "PROTOCOL",
"recipient": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d"
},
{
"amount": "10000",
"fee_type": "ROYALTY",
"recipient": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d"
}
],
"created_at": 1697498374,
"start_time": 1697498363,
"updated_at": 1697498374,
"protocol_data": {
"counter": "0",
"order_type": "FULL_RESTRICTED",
"zone_address": "0x63441cbfbd15dd8f5e92598e535661ac5acb808e",
"seaport_address": "0x7fb7da6de152597830ed16361633e362a2f59410",
"seaport_version": "1.5"
},
"account_address": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d"
}
}
Inactive order

If the user has only just approved the settlement contract, the order might temporarily be "INACTIVE" until those on-chain events are received by the orderbook.

In a number of scenarios, an order will transtion from "PENDING" straight to "ACTIVE".

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "INACTIVE",
"sufficient_balances": false,
"sufficient_approvals": false,
},
"updated_at": 1697498374,
}
}
Live order
{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "ACTIVE"
},
"updated_at": 1697498374,
}
}
tip

If custody of the asset referenced in the listing cannot be confirmed by Immutable, we will attempt to move the order to a "CANCELLED" state.

Order is executed

In this example, an order is executed on Immutable's Global Orderbook. The previous status of the order was "ACTIVE".

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "FILLED"
},
"updated_at": 1697498374,
}
}

Order is cancelled

In this example, an order is cancelled. The previous status of the order was "ACTIVE".

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "CANCELLED",
"pending": false,
"cancellation_type": "ON_CHAIN"
},
"updated_at": 1697498374,
}
}

Order expires

In this example, an order that was placed previously on the orderbook expires as it does not execute in the allocated time window. The previous status of the order was "ACTIVE".

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "EXPIRED",
},
"updated_at": 1697498374,
}
}

Order becomes cancelled due to an asset transfer

In this example, an order placed previously on the orderbook becomes "CANCELLED" because the seller no longer has custody of the asset. This could be for a variety of reasons including the asset being consumed in a crafting activity or because the seller has transferred the asset to another wallet. The previous status of the order was "ACTIVE".

{
"event_name": "order_updated",
"event_id": "018b3ac9-a2df-d8d3-1a5a-2e2ac158ae59",
"chain": "imtbl-zkevm-testnet",
"data": {
"id": "018b3ac9-a2c3-c153-8c18-01902b663abb",
...
"status": {
"name": "CANCELLED",
"pending": false,
"cancellation_type": "UNDERFUNDED",
},
"updated_at": 1697498374,
}
}

Option 2: Polling

Follow the below guide to use polling as a means to track orders on Immutable's Global Orderbook.

Setup

Prerequisites

Node Version 20 or later
Immutable's Typescript SDK requires **Node v20** (Active LTS version) or **higher**. Node v20 can be installed via `nvm`.

To install nvm follow these instructions. Once installed, run:

nvm install --lts
  • (Optional) To enable Code Splitting (importing only the SDK modules you need) there are additional prerequisites.

Install the Immutable SDK

Run the following command in your project root directory.

npm install -D @imtbl/sdk
# if necessary, install dependencies
npm install -D typescript ts-node
Troubleshooting

The Immutable SDK is still in early development. If experiencing complications, use the following commands to ensure the most recent release of the SDK is correctly installed:

rm -Rf node_modules
npm cache clean --force
npm i

Initialize modules

First, we'll need to set up our SDK clients. This tutorial will use the Orderbook and BlockchainData modules:

import { config, blockchainData, orderbook } from '@imtbl/sdk';

import { ethers } from 'ethers'; // ethers v5

const PUBLISHABLE_KEY = 'YOUR_PUBLISHABLE_KEY'; // Replace with your Publishable Key from the Immutable Hub

const blockchainDataClient = new blockchainData.BlockchainData({
baseConfig: {
environment: config.Environment.SANDBOX,
publishableKey: PUBLISHABLE_KEY,
},
});

const orderbookClient = new orderbook.Orderbook({
baseConfig: {
environment: config.Environment.SANDBOX,
publishableKey: PUBLISHABLE_KEY,
},
overrides: {
// @ts-ignore FIXME
provider: new ethers.providers.JsonRpcProvider(
'https://rpc.testnet.immutable.com/'
),
},
});

Listing collections

Most marketplaces start by allowing users to see all collections on a chain. Using filters, marketplaces can provide users with a user-friendly interface from which to browse and select items from their favourite collections.

In this section we will utilize the BlockchainData module to retrieve this information.

listCollections.tsView on GitHub
import { blockchainData } from "@imtbl/sdk";
import { client } from "../lib";

export async function listCollections(
chainName: string
): Promise<blockchainData.Types.ListCollectionsResult> {
return await client.listCollections({
chainName,
});
}

Example response:

{
"base_uri": "https://webhook.site/5ce48076-ccdf-45d7-ab2a-a315833ba60a",
"chain": {
"id": "eip155:13392",
"name": "imtbl-zkevm-testnet"
},
"contract_address": "0x3109fe9344e3d299ab6a2b42ecacee0610f391a4",
"contract_type": "ERC721",
"contract_uri": "https://webhook.site/10c86fab-d5a7-4283-93c3-398fe269497b",
"description": "OpenSea Creatures are adorable aquatic beings primarily for demonstrating what can be done using the OpenSea platform. Adopt one today to try out all the OpenSea buying, selling, and bidding feature set.",
"external_link": "external-link-url",
"image": "external-link-url/image.png",
"indexed_at": "2023-07-06T01:18:40.098064Z",
"last_metadata_synced_at": "2023-07-06T01:18:43.363814Z",
"name": "OpenSea Creatures",
"symbol": "TC",
"updated_at": "2023-07-06T01:18:43.363814Z"
}

This JSON represents the metadata of a collection provided by the indexer API. It includes information such as the base URI, chain details, contract address and type (ERC721), contract URI, description, external link, image URL, timestamps, name, and symbol. The metadata provides important details about the collection, such as image, name and description which can be used in the marketplace collection display.

💡If you want more extensive search capabilities...
[Let us know on Discord](https://discord.gg/TkVumkJ9D6) so we can prioritize building one!

Listing orders

Once a user has selected the collection that they want to browse the assets of, we can display all active fillable orders that are available. In this section we will use the Orderbook module in the Immutable SDK and utilize the collection filter.

For more information on this endpoint, see: Get orders.

import { orderbook } from '@imtbl/sdk';

const listListings = async (client: orderbook.Orderbook, contractAddress: string) => {
const listOfListings = await client.listListings({
sellItemContractAddress: contractAddress,
status: orderbook.OrderStatusName.ACTIVE,
pageSize: 50,
});
};
{
"account_address": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d",
"buy": [
{
"item_type": "NATIVE",
"start_amount": "1000000"
}
],
"chain": {
"id": "eip155:13403",
"name": "imtbl-zkevm-testnet"
},
"create_time": "2023-07-05T01:48:23.696122Z",
"end_time": "2026-07-05T10:08:04Z",
"fees": [
{
"amount": "10000",
"fee_type": "ROYALTY",
"recipient": "0xc606830d8341bc9f5f5dd7615e9313d2655b505d"
}
],
"id": "018923bc-8109-c775-2829-27dd3700e6e0",
"protocol_data": {
"counter": "0",
"operator_signature": "0x1aef76023aa9040f7c1e8aa58fcbadcefb39a401c17835b80762a14bb99433ed711d34ffa2075c5b35c42d569166342761ffbe3a2f42c455843225c28819acdc1c",
"order_type": "FULL_RESTRICTED",
"seaport_address": "0x41388404Efb7a68Fd31d75CEf71dF91e2BDBa2fb",
"seaport_version": "1.4",
"zone_address": "0xcb5063b0c1dcf4f7fed8e7eaa79faf9859792767"
},
"salt": "0xe3deca95738ccfc4",
"sell": [
{
"contract_address": "0xeee54f55c805b11bd0ebfaf800ccaeecb4a57bc0",
"item_type": "ERC721",
"token_id": "0"
}
],
"signature": "0x0230042e33b9fb2f73a81286eeb08c45d1ecf4b571b67a7552a73b44aac5e57421ab5c3bc0cc170a75112a8d080e568375fa469a2d44c429f57680983a4699bd1c",
"start_time": "2023-07-05T01:48:06Z",
"status": {
"name": "EXPIRED"
},
"update_time": "2023-07-05T10:09:03.732996Z"
}

This JSON represents a sell order in the NFT marketplace. It includes information such as the account address of the seller, the buy item (with its type and starting amount), the chain details, creation and update timestamps, fees information, and more. The sell item is an ERC721 NFT with a contract address and token ID. The order status is marked as "ACTIVE", indicating that the order is active.

Displaying a specific order and its asset details

To display a particular order listing to users, it is a much more user-friendly experience to provide more information about that order, ie. the collection it is from, its name, token ID, images. This data is typically stored in an asset's metadata.

💡No bulk retrieval yet
Please note that at the moment, there is no bulk endpoint available to retrieve NFT metadata based on IDs. However, keep an eye out for future updates on this feature.
import { blockchainData, orderbook } from '@imtbl/sdk';

const getOrderAndNFT = async (
orderbook: orderbook.Orderbook,
blockchainData: blockchainData.BlockchainData,
orderId: string
) => {
const order = await orderbook.getListing(orderId); // orderId is the UUID of the order (see listing.id)
const nft = await blockchainData.getNFT({
chainName: '',
contractAddress: '', // The address of the deployed collection contract
tokenId: '', // The ID of the minted token
});
};
🚧Webhook-based integration coming soon
We are currently developing a webhook-based integration which will allow developers to receive and process individual events without polling our SDK functions or APIs