Create orders - listings
This page shows you could create a listing, so that you can list an NFT for sale on the Orderbook.
Immutable provides two distinct types of orders:
- Listings: These are orders initiated by an NFT owner who intends to sell their asset on a marketplace. Listings are considered sell orders.
- 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.
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.
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:
- ERC721 listing creation
- ERC1155 listing creation
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.
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.
// prepare ERC721 listing
const prepareERC721Listing =
async (): Promise<orderbook.PrepareListingResponse> => {
// build the sell item
const sell: ERC721Item = {
contractAddress: sellItemContractAddress,
tokenId: sellItemTokenID,
type: "ERC721",
};
// build the buy item
const buy =
buyItemType === "Native"
? ({
amount: buyItemAmount,
type: "NATIVE",
} as NativeItem)
: ({
amount: buyItemAmount,
type: "ERC20",
contractAddress: buyItemContractAddress,
} as ERC20Item);
// build the prepare listing parameters
const prepareListingParams: PrepareListingParams = {
makerAddress: accountsState[0],
buy,
sell,
};
// invoke the orderbook SDK to prepare the listing
return await orderbookSDK.prepareListing(prepareListingParams);
};
If preparing a listing containing ERC1155 tokens, the amount
of tokens offered for sale should be specified in the amount
field in the sell
token section. The amount of tokens in the buy
section should be a multiple of the sell token amount.
For example, if the user is listing 5 tokens for sale i.e amount
in sell token section is 5
then the amount
in the buy token section should be a multiple of 5
so 5, 10, 15, etc.
Orders containing ERC1155 tokens should be setup the order_type as PARTIAL_RESTRICTED
to allow for partial fulfillment. 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.
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.
// prepare ERC1155 listing
const prepareERC1155Listing =
async (): Promise<orderbook.PrepareListingResponse> => {
// build the sell item
const sell: ERC1155Item = {
contractAddress: sellItemContractAddress,
tokenId: sellItemTokenID,
amount: sellItemQty,
type: "ERC1155",
};
// build the buy item
const buy =
buyItemType === "Native"
? ({
amount: buyItemAmount,
type: "NATIVE",
} as NativeItem)
: ({
amount: buyItemAmount,
type: "ERC20",
contractAddress: buyItemContractAddress,
} as ERC20Item);
// build the prepare listing parameters
const prepareListingParams: PrepareListingParams = {
makerAddress: accountsState[0],
buy,
sell,
};
// invoke the orderbook SDK to prepare the listing
return await orderbookSDK.prepareListing(prepareListingParams);
};
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.
export const signAndSubmitApproval = async (
provider: Web3Provider,
listing: orderbook.PrepareListingResponse,
): Promise<void> => {
// get your user's Web3 wallet, e.g. MetaMask, Passport, etc
const signer = provider.getSigner();
// 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
const approvalActions = listing.actions.filter(
(action): action is orderbook.TransactionAction =>
action.type === orderbook.ActionType.TRANSACTION,
);
for (const approvalAction of approvalActions) {
const unsignedTx = await approvalAction.buildTransaction();
const receipt = await signer.sendTransaction(unsignedTx);
await receipt.wait();
}
return;
};
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.
export const signListing = async (
provider: Web3Provider,
listing: orderbook.PrepareListingResponse,
): Promise<string> => {
// get your user's Web3 wallet, e.g. MetaMask, Passport, etc
const signer = provider.getSigner();
// 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.
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,
);
return signature;
};
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.
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:
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.
export const createListing = async (
client: orderbook.Orderbook,
preparedListing: orderbook.PrepareListingResponse,
orderSignature: string,
makerEcosystemFee?: {
recipientAddress: string;
amount: string;
},
): Promise<string> => {
const order = await client.createListing({
orderComponents: preparedListing.orderComponents,
orderHash: preparedListing.orderHash,
orderSignature,
// Optional maker marketplace fee
makerFees: makerEcosystemFee ? [
{
recipientAddress: makerEcosystemFee.recipientAddress, // Replace address with your own marketplace address
amount: makerEcosystemFee.amount,
},
] : [],
});
return order.result.id;
};