Skip to main content

Create orders - listings

This page shows you could create a listing, so that you can list an NFT for sale on the Orderbook.


💡Listing vs Order

Immutable provides two distinct types of orders:

  1. Listings: These are orders initiated by an NFT owner who intends to sell their asset on a marketplace. Listings are considered sell orders.
  2. Bids: Representing a prospective buyer's intention to acquire an asset, bids allow users to express their interest in purchasing a specific asset. Users can place a bid on the order book, anticipating a match with a seller. Bids are considered buy orders.
It is important to note that while bids are not currently available on the zkEVM platform. However, their integration is a key part of Immutable's future development roadmap.

Prepare the listing

The call to prepareListing returns actions, order components and the order hash. The order components, order hash and a signature of from the signable action are required for creating the order in later steps. Actions for preparing a listing include both transaction and signable action types. The details of these actions are as follows:

  • APPROVAL transaction - An approval transaction is required when the user has not yet approved the seaport contract for the collection they are creating the listing for.
  • CREATE_LISTING signable message - This signable message is used to create the order on the Immutable order book.

Listing creation enforces royalties requirements based on the ERC2981 interface - prepare listing in the SDK will query the royalty information automatically.

The maker below is any ethers compatible Signer or Wallet instance for the user creating the 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.

Select the tabs below to learn about the differences between preparing listings for ERC721 and ERC1155 tokens:

If preparing a listing containing ERC721 tokens, the amount of tokens offered for sale is always 1 and therefore the amount field in the sell token section is not required.

Orders containing ERC721 tokens should be setup with the order_type as FULL_RESTRICTED because ERC721 orders can only be fully filled. If you are using the SDK, the order type will be automatically set based on the token type. If using the API directly, please ensure you are setting the order type correctly in the protocol data section.

note

All new listings created starting May 2024 should use Signed Zone version 2 contract address as specified in the list of deployed contracts.

The legacy zone contract is deprecated as of May 2024 and will be sunset in May 2025. All existing listings that were created using the legacy zone contract will continue to be supported until May 2025.

If you are using the SDK, the contract address is automatically updated for you. If you are using the API directly, please ensure you are using the correct zone contract address in the protocol data section.

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

const prepareERC721Listing = 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',
// },
// ERC721 sell token
sell: {
contractAddress: '0x300516606a8100342f6d45b24b8af8c4191cb195',
tokenId: '1',
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 }
};

Sign and submit the approval transaction

If there is an approval transaction required for the listing, it needs to be signed and submitted to the zkEVM.

import { Web3Provider } from '@ethersproject/providers';
import { orderbook } from '@imtbl/sdk';

async function example(provider: Web3Provider, listing: orderbook.PrepareListingResponse) {
// get your user's Web3 wallet, e.g. MetaMask, Passport, etc
const signer = provider.getSigner();

const approvalAction = listing.actions.find(
(action): action is orderbook.TransactionAction =>
action.type === orderbook.ActionType.TRANSACTION
);

if (approvalAction) {
const unsignedTx = await approvalAction.buildTransaction();
const receipt = await signer.sendTransaction(unsignedTx);
await receipt.wait();
}
}

Sign the typed order data

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.

import { Web3Provider } from '@ethersproject/providers';
import { orderbook } from '@imtbl/sdk';
import { Wallet } from 'ethers';

async function example(provider: Web3Provider, listing: orderbook.PrepareListingResponse) {
// get your user's Web3 wallet, e.g. MetaMask, Passport, etc
const signer = provider.getSigner();

const signableAction = listing.actions.find(
(action): action is orderbook.SignableAction =>
action.type === orderbook.ActionType.SIGNABLE
)!;

const signature = await signer._signTypedData(
signableAction.message.domain,
signableAction.message.types,
signableAction.message.value
);
}

Create the listing

This last step is sending the locally signed order to the Immutable orderbook where validation will be performed for the order. If the order contains malformed data, an incorrect signature or incorrect buy / sell amounts (in case of ERC1155 listings) the server will return an invalid response, otherwise it will be server side signed and ready to be fulfilled.

When a marketplace submits a locally signed order to the Immutable orderbook, they should include a makerFees field as demonstrated in the code block below. 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.

note

If creating a listing for ERC1155 tokens, the fee amount should be a multiple of the sell token amount. For example, if the user is selling 5 tokens, the maker fee amount should be 5, 10, 15, etc.

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

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.

makerFees: [{
amount: '500000000000000000', // 0.5 IMX
}]

For additional details on fees that ecosystems like marketplaces can incorporate into orders for the services they offer, please refer to our fee guide.

Orders created will initially be in PENDING status. Upon further validating blockchain approval events (if necessary) and balance checks (i.e. listing owner indeed owns NFT), it will become ACTIVE. You can read more about order statuses here.

💡Status polling
You can poll the Get Listing endpoint to check on status updates - in the near future we also plan on introducing push based (webhook) integration for order events.
import { orderbook } from '@imtbl/sdk';

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
}],
});
};