Skip to main content

Create, cancel and fill listings


Immutable's Orderbook APIs and SDKs make it simple to create and settle orders.

💡One settlement contract
On Immutable zkEVM, there is currently only one settlement contract for NFT trades. Only orders with royalties that adhere to ERC2981 standard will be approved by the Immutable operator and accepted by the settlement contract. More information on this is available in Introduction.

Setup

Prerequisites

Node Version >18
Immutable's Typescript SDK requires **Node v18** (Active LTS version) or **higher**. Node v18 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. See the Code Splitting section below.

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

In order to get started, we'll first need to set up an Orderbook client:

import { config, 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 client = new orderbook.Orderbook({
baseConfig: {
environment: config.Environment.SANDBOX,
publishableKey: PUBLISHABLE_KEY,
},
});

Create listing

The Orderbook is responsible for managing the status of NFT orders. It does this by monitoring on-chain events and updating order statuses.

See the table below for a list of the order statuses:

StatusDescriptionTerminal (status cannot be reversed)
PENDINGThis status is attached when an order has just been created and has not yet been validated. Additionally, another reason is that the listing time has not yet begun.No
ACTIVEThe order has passed all validation requirements and can be filled (purchased) by a user.No
INACTIVEThe order does not have the required approval or the owner's balance was not sufficient to successfully list the order. Note that INACTIVE orders can become ACTIVE if conditions are metNo
CANCELLEDThe order has been cancelled and can no longer by filled.Yes
FILLEDThe order has been filled (purchased)Yes
EXPIREDThe order has expired and can no longer be filled.Yes

To create a listing, you can use the prepareListing function provided by the Orderbook client, followed by createListing function. It's important to note that listing creation does not incur any gas fees but approval does require small amount of gas. The order will need to go through various validations before it becomes fulfillable and transitions to the ACTIVE state.

By adhering to royalty requirements and following the necessary validations, your listings will be ready for trading on the marketplace. Below is an example of how to create a listing:

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.

Prepare

First we need to prepare the listing. This will return an unsigned approval transaction if the user has not approved the settlement contract for the collection yet. The user will also need to sign the EIP712 order details.

import { orderbook } from '@imtbl/sdk';
import { Wallet } from 'ethers'; // ethers v5

const prepareListing = async (
client: orderbook.Orderbook,
signer: Wallet,
): Promise<{preparedListing: orderbook.PrepareListingResponse, orderSignature: string}> => {
const offerer = await signer.getAddress();

const preparedListing = await client.prepareListing({
makerAddress: offerer,
// native payment token
buy: {
amount: '1000000',
type: 'NATIVE',
},
// ERC20 payment token
// buy: {
// amount: '1000000',
// type: 'ERC20',
// contractAddress: '0x5b0516606a8100342f6d45b24b8af8c4191cb172',
// },
sell: {
contractAddress: '0x300516606a8100342f6d45b24b8af8c4191cb195',
tokenId: '0',
type: 'ERC721',
},
});

let orderSignature = ''
for (const action of preparedListing.actions) {
// If the user hasn't yet approved the Immutable Seaport contract to transfer assets from this
// collection on their behalf they'll need to do so before they create an order
if (action.type === orderbook.ActionType.TRANSACTION) {
const builtTx = await action.buildTransaction()
console.log(`Submitting ${action.purpose} transaction`)
await signer.sendTransaction(builtTx);
}

// For an order to be created (and subsequently filled), Immutable needs a valid signature for the order data.
// This signature is stored off-chain and is later provided to any user wishing to fulfil the open order.
// The signature only allows the order to be fulfilled if it meets the conditions specified by the user that created the listing.
if (action.type === orderbook.ActionType.SIGNABLE) {
orderSignature = await signer._signTypedData(
action.message.domain,
action.message.types,
action.message.value,
)
}
}

return { preparedListing, orderSignature }
};

Create

To create the listing, the EIP712 signature needs to be sent to the orderbook.

import { orderbook } from '@imtbl/sdk';
import { Wallet, providers } from 'ethers'; // ethers v5

const createListing = async (
client: orderbook.Orderbook,
preparedListing: orderbook.PrepareListingResponse,
orderSignature: string
): Promise<void> => {
const order = await client.createListing({
orderComponents: preparedListing.orderComponents,
orderHash: preparedListing.orderHash,
orderSignature,
// Optional maker marketplace fee
makerFees: [{
amount: '100',
recipientAddress: '0xFooBar', // Replace address with your own marketplace address
}],
});
};

Fill listing

The fullfilment function in the orderbook SDK returns two unsigned transactions. One for the approval and the other for the fulfillment of the listing. These need to be signed and submitted by the user's signer.

Approval transaction only need to be submitted for ERC20 tokens and where the current approved amount is not sufficient. Note also that filling an order will incur a gas fee.

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.

import { orderbook } from '@imtbl/sdk';
import { Signer } from 'ethers'; // ethers v5

const fulfillListing = async (
client: orderbook.Orderbook,
signer: Signer,
listingId: string
): Promise<void> => {
const fulfiller = await signer.getAddress();

const { actions, expiration, order } = await client.fulfillOrder(
listingId,
fulfiller,
[{
amount: '1000000', // Insert taker ecosystem/marketplace fee here
recipientAddress: '0xFooBar', // Replace address with your own marketplace address
}]
);

console.log(`Fulfilling listing ${order}, transaction expiry ${expiration}`);

for (const action of actions) {
if (action.type === orderbook.ActionType.TRANSACTION) {
const builtTx = await action.buildTransaction();
console.log(`Submitting ${action.purpose} transaction`);
await signer.sendTransaction(builtTx);
}
}
};

After this transaction has been confirmed, the status of this order will change to FILLED. You can poll Get orders to monitor this transition.

info

Stay tuned for bulk orders fulfillment capability within the orderbook SDK.

Cancel listing

Cancellation will transition any order state into CANCELLED. This will render the order unfulfillable for the remainder of its time. Note also that order cancellations will incur a gas fee.

import { orderbook } from '@imtbl/sdk';
import { Signer } from 'ethers'; // ethers v5
import { TransactionResponse } from "@ethersproject/providers"; // ethers v5

const cancelListingOnChain = async (
client: orderbook.Orderbook,
signer: Signer,
listingId: string
): Promise<TransactionResponse> => {
const offerer = await signer.getAddress();
const { cancellationAction } = await client.cancelOrdersOnChain(
[ listingId ],
offerer
);

const unsignedCancelOrderTransaction = await cancellationAction.buildTransaction();
const receipt = await signer.sendTransaction(unsignedCancelOrderTransaction);
return receipt;
};

After this transaction has been confirmed, the status of this order will change to CANCELLED. You can poll Get orders to monitor this transition.

Marketplace fees

One of the most attractive incentives that is unique to Immutable is Marketplace fees. Through our zkEVM Orderbook, any marketplace can attach fees to an order they facilitate. When these orders are filled on any marketplace on the protocol, the originating marketplace will receive the fees specified on the original listing.

There will be two types of marketplace fees on Immutable's Global Order Book:

  • Maker: A maker is a user who places an order on the marketplace that does not get executed immediately but instead rests on the order book, waiting for another user to take the other side of the trade. Makers add liquidity to the market by creating orders that can be executed in the future. The marketplace facilitating the placement of the original sell order would collect the makerFees

  • Taker: A taker is a user who places an order that instantly matches with an existing order on the order book, effectively "taking" liquidity from the market. The marketplace facilitating the placement of the original sell order would collect the takerFees

These two fees incentivise marketplaces to share liquidity with each other through Immutable's Global Order Book. If the marketplace creating the listing (maker) is also the marketplace that finds the buyer (taker); they wiill collect both fees if specified.

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.

Adding maker fees to new listings

To collect maker fees the marketplace must add a makerFees when sending the locally signed order to the Immutable orderbook. This is done through the createListing function call in Immutable's SDK. This fee should be represented as the net amount that the marketplace wishes to receive for the services provided, and it should be quoted in the same ERC20 token in which the order is listed. See the makerFees section of the code example above in the Create Listing tutorial above.

For example, if the NFT is selling for 50 IMX, and a maker fee of 1% is applied, it should be represented like this:

makerFees: [{
amount: '500000000000000000', // 0.5 IMX
recipient: '0xFooBar', // Replace address with your own marketplace address
}]

Adding taker fees to trades

To collect taker fees the marketplace must add a taker fee when filling a listing on Immutable orderbook. This is done through the fulfillOrder function call in Immutable's SDK. This fee should be represented as the net amount that the marketplace wishes to receive for the services provided, and it should be quoted in the same ERC20 token in which the order is listed. Refer to the comment below for the code section where a marketplace should input their taker fee amount when executing a listing. To fill an order see the example code block above in the Fill listing tutorial above.

For example, if the NFT is selling for 50 IMX, and a taker fee of 2% is applied, it should be represented like this:

  const { actions, expiration, order } = await client.fulfillOrder(
listingId,
fulfiller,
[{
amount: '1000000000000000000', // Insert taker ecosystem/marketplace fee here
recipient: '0xFooBar', // Replace address with your own marketplace address
}]
);

Please also visit our Fees section for more information on Orderbook fees.