ERC1155Orders
ERC1155<->ERC20/ETH Limit Order Functionality
This feature implements the ERC1155<->ERC20/ETH limit order type. The source code for ERC1155OrdersFeature.sol can be found here.
Here are some highlights:
- Limit orders in the 0x protocol are off-chain messages that define a fixed-rate trade signed by a "maker" and filled on-chain by a "taker."
- The protocol is non-custodial in that neither party needs to deposit assets into the protocol in advance. Instead, both parties must first grant the protocol an ERC20/ERC115 allowance for the assets they intend to sell, and the protocol will spend this allowance during settlement.
- Due to the non-custodial nature of the protocol, the raw native asset (e.g., ETH) is only supported as a taker or fee token. Makers must use a wrapped variant (e.g., WETH) if trying to sell the network's native asset.
- Unlike ERC721 limit orders, ERC1155 limit orders have a quantity of the ERC1155 token being traded. These orders can be fully filled once or partially filled repeatedly until all quantities in the order have been exhausted.
- Once a limit order is signed, anyone with the order and signature details can fill the order (unless the order restricts the
taker
address). There is no way to un-sign an order so cancellations must be performed on-chain before a taker attempts to fill it.
struct ERC1155Order {
TradeDirection direction;
address maker;
address taker;
uint256 expiry;
uint256 nonce;
address erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
address erc1155Token;
uint256 erc1155TokenId;
Property[] erc1155TokenProperties;
uint128 erc1155TokenAmount;
}
direction
: Whether this order is selling an NFT or buying an NFT (see ERC1155 Sell Orders or undefined).maker
: The maker/signer of this limit order.taker
: Who can fill this order. May be NULL (0x000...
) to allow anyone.expiry
: The block timestamp at which this trade is no longer valid.nonce
: Usually a random nonce the maker sets to ensure the hash of this order is unique and to identify it during fills and cancellations. Carefully choosing this value can be used to reduce gas usage during cancellations (see Smart Nonces).erc20Token
: The ERC20 token (or native asset, if allowed) to exchange for the NFT. For eligible trades, the native asset may be specified as0xeee...
.erc20TokenAmount
: The amount oferc20Token
to exchange for the NFT.fees
: Optional fees, denominated inerc20Token
, that are paid out to designated recipients when the order is filled. Fees are collected in addition to theerc20TokenAmount
.erc1155Token
: The NFT's ERC1155 token contract address being exchanged.erc1155TokenId
: The ID of the ERC1155 token insideerc1155Token
being exchanged. This will be ignored for buy orders with validerc1155TokenProperties
.erc1155TokenProperties
: A series of contracts that will be called to validate an unknown NFT token ID being bought at settlement.erc1155TokenAmount
: The maximum total quantity oferc1155TokenId
being traded.
Where
TradeDirection
is:enum TradeDirection {
SELL_NFT,
BUY_NFT
}
Fee
is:struct Fee {
address recipient;
uint256 amount;
bytes feeData;
}
Property
is:struct Property {
address propertyValidator;
bytes propertyData;
}
The protocol utilizes a canonical, EIP712 hash of the limit order struct to verify the authenticity of the order. In typical usage, this is the hash that the maker signs to make the order valid and fillable.
Computing the hash of a limit order in solidity is given by:
bytes32[] memory feesStructHashArray = new bytes32[](order.fees.length);
for (uint256 i = 0; i < order.fees.length; ++i) {
feesStructHashArray[i] = keccak256(abi.encode(
bytes32(0xe68c29f1b4e8cce0bbcac76eb1334bdc1dc1f293a517c90e9e532340e1e94115),
order.fees[i].recipient,
order.fees[i].amount,
keccak256(order.fees[i].feeData)
));
}
bytes32 memory feesHash = keccak256(abi.encodePacked(
feesStructHashArray
));
bytes32[] memory propertiesStructHashArray = new bytes32[](order.erc11155TokenProperties.length);
for (uint256 i = 0; i < order.erc1155TokenProperties.length; ++i) {
propertiesStructHashArray[i] = keccak256(abi.encode(
bytes32(0x6292cf854241cb36887e639065eca63b3af9f7f70270cebeda4c29b6d3bc65e8),
order.erc1155TokenProperties[i].propertyValidator,
keccak256(order.erc1155TokenProperties[i].propertyData)
));
}
bytes32 memory propertiesHash = keccak256(abi.encodePacked(
propertiesStructHashArray
));
keccak256(abi.encode(
bytes32(0x930490b1bcedd2e5139e22c761fafd52e533960197c2283f3922c7fd8c880be9),
order.direction,
order.maker,
order.taker,
order.expiry,
order.nonce,
order.erc20Token,
order.erc20TokenAmount,
kecca256(abi,
order.erc1155Token,
order.erc1155TokenId,
propertiesHash,
order.erc1155TokenAmount
));
Another way to get the hash of an order is to simply call
getERC1155OrderHash()
on the Exchange Proxy.To fill an order, the taker must (usually, see below) also submit an ECDSA signature generated for that order, typically signed by the maker. For information on the format an how to generate these signatures, see Signatures.
Smart contracts do not have a private key associated with them so they cannot generate a valid ECDSA signature. Yet they still can operate as the
maker
of an order. To do this, they must call preSignERC1155Order()
, which will allow the order to be filled using the PRESIGNED
signatureType
.Another feature of the
preSignERC1155Order()
function is that it will emit an event ERC1155OrderPreSigned
(see Events) listing all the details of the order. Anyone can listen for these events and potentially fill the order without relying on off-chain sharing mechanisms.The
nonce
field in the order is a number the maker chooses to ensure uniqueness of the order, but it also one other function:- To identify the order (in addition to the caller/maker) in cancellation functions (see Maker Functions). This can make cancellations more efficient.
The protocol marks ERC1155 orders as cancelled by setting a bit in a 256-bit word (cancellation bit vector) indexed by the order maker and upper 248 bits of the order's nonce. The lower 8 bits of the nonce determine which bit in the cancellation bit vector corresponds to that order.

This allows for much cheaper cancellation for up to 256 simultaneous orders. To take advantage of this gas optimization, makers should reuse the upper 248 bits of their nonce across up to 256 different orders, varying the value of the lower 8 bits between them.
Sell orders are orders where
direction
is set to TradeDirection.SELL_NFT
, which indicates that a maker wishes to sell an ERC1155 token that they possess.For these orders, the maker must set the
erc1155Token
and erc1155TokenId
fields to the ERC1155 token contract address and the ID of the ERC1155 token they're trying to sell, respectively. The erc20Token
and erc20TokenAmount
fields will be set to the ERC20 token and the ERC20 amount they wish to receive for their NFT.Buy orders are where
direction
is set to TradeDirection.BUY_NFT
, which indicates that a maker wishes to buy an ERC1155 token that they do not possess.A buy order can be created for a known token ID or an unknown token ID.
This is when the maker knows exactly which NFT they want, down to the token ID. These are fairly straight-forward and just require the maker setting the
erc1155Token
and erc1155TokenId
fields to the ERC1155 token contract address and the ID of the token they want to buy, respectively. The The erc20Token
and erc20TokenAmount
fields will be set to the ERC20 token and the ERC20 amount they are willing to pay for that particular NFT. The erc1155TokenProperties
field should be set to the empty array.This is when the maker will accept any token ID from an ERC1155 contract. In this scenario, the
erc1155TokenProperties
order field is used to signal and validate terms of this kind of order.If a maker wishes to accept any token ID, regardless of any other properties of that NFT, they should set the
erc1155TokenProperties
field to an array with exactly one item in it:[
Property({
propertyValidator: address(0),
propertyData: ""
})
]
If a maker wishes to accept any token ID, but only if that token also satisfies some other properties (e.g., cryptokitty with green eyes), they should set
erc1155TokenProperties
to an array of callable property validator contracts. Each propertyValidator
contract must implement the IPropertyValidator
interface, which is defined as:interface IPropertyValidator {
function validateProperty(
address tokenAddress,
uint256 tokenId,
bytes calldata propertyData
)
external
view;
}
Each entry in
erc1155TokenProperties
with a non-null propertyValidator
address will be called using this interface, passing in the tokenAddress
and tokenId
of the ERC1155 the taker is trying to fill the order with. If the validator fails to validate the requested properties of the NFT, it should revert.The
propertyData
field of each property entry is supplied by the maker and is also passed in. For example, if a property validator contract was designed to check that a cryptokitty had a specific eye color, the propertyData
could simply be some encoding of "green"
, and the validator contract would parse this data and perform the necessary checks on the token to ensure it has green eyes.Some fill functions accept a
callbackData
bytes
parameter. If this parameter is non-empty (length > 0
), a call will be issued against the caller using callbackData
as the full call data. This call happens after the transfer of the maker asset and before the transfer of the taker asset. This allows a smart contract taker to leverage the maker's asset to fulfill their end of the trade. For example, if a taker finds a compatible, complementary order on another protocol they could fulfill that order with the maker's asset, supply the Exchange Proxy with the necessary opposing asset with the proceeds, and pocket the difference.validateERC1155OrderSignature(ERC1155Order order, Signature signature)
- Check if
signature
is valid fororder
. Reverts if not.
validateERC1155OrderProperties(ERC1155Order order, uint256 erc1155TokenId)
- Validates that a given
erc1155TokenId
satisfies a buy order. - For orders with a known token ID, verifies that they match.
- For orders with an unknown token ID, checks that all property validators succeed for the token.
- Reverts if
erc1155TokenId
does not satisfy the buy order.
getERC1155OrderInfo(ERC1155Order order) returns (OrderInfo orderInfo)
getERC1155OrderHash
(ERC1155Order orde
) returns (bytes32 orderHash)
preSignERC1155Order(ERC1155Order order)
- If called by
order.maker
, marks the order as presigned, which means takers can simply pass in an empty signature withsignatureType = PRESIGNED
. - Must be called by the
order.maker
. - This can also be used to publish/broadcast orders on-chain, as it will emit an
ERC1155OrderPreSigned
event.
cancelERC1155Order(uint256 orderNonce)
- Cancels any order with
nonce == orderNonce
and where themaker
is the caller.
batchCancelERC1155Orders(uint256[] orderNonces)
- Batch version of
cancelERC1155Order()
.
sellERC1155(ERC1155Order buyOrder, Signature signature, uint256 erc1155TokenId, uint256 erc1155SellAmount,bool unwrapNativeToken, bytes callbackData)
- Sell
erc1155SellAmount
amount of ERC1155 tokenerc1155TokenId
to abuyOrder
. - The
signature
must be a valid signature forbuyOrder
generated bybuyOrder.maker
. - If
unwrapNativeToken
istrue
and theerc20Token
of the order is a wrapped version of the native token (e.g., WETH), this will also unwrap and transfer the native token to the taker. - If
callbackData
is non-empty, the taker (msg.sender
) will be called using thiscallbackData
as call data. This happens aftererc20Token
is transferred to the taker, but beforeerc1155SellAmount
oferc1155TokenId
is transferred to the maker.
buyERC1155(ERC1155Order sellOrder, uint128 erc1155BuyAmount, Signature signature, bytes callbackData) payable
- Buy
erc1155BuyAmount
quantity of an ERC1155 token being sold bysellOrder
. - The
signature
must be a valid signature forsellOrder
generated bysellOrder.maker
. - If the order's
erc20Token
is the the native token (0xeee...
), the native token (ETH on Ethereum) must be attached to the function call. - If
callbackData
is non-empty, the taker (msg.sender
) will be called using thiscallbackData
as call data. This happens aftererc1155BuyAmount
of ERC1155 token is transferred to the taker, but beforeerc20Token
is transferred to the maker.
batchBuyERC1155s(ERC1155Order[] sellOrders, uint128[] ercc1155BuyAmounts, Signature[] signatures, bytes[] callbackData, bool revertIfIncomplete) payable returns (bool[] successes)
- Batch version of
buyERC1155()
. - If
revertIfIncomplete
is true, reverts if any of the individual buys fail. - If
revertIfIncomplete
isfalse
, returns an array of which respective fill succeeded.
getERC1155OrderInfo()
returns a structure defined as:struct OrderInfo {
bytes32 orderHash;
OrderStatus status;
uint128 orderAmount;
uint128 remainingAmount;
}
Each field
status
: One ofINVALID: 0
,FILLABLE: 1
,UNFILLABLE: 2
,EXPIRED: 3
.orderAmount
: Equal toorder.erc1155TokenAmount
.remainingAmount
: The portion oforderAmount
that has yet to be filled.
/// @dev Emitted whenever an `ERC1155Order` is filled.
/// @param direction Whether the order is selling or
/// buying the ERC1155 token.
/// @param maker The maker of the order.
/// @param taker The taker of the order.
/// @param nonce The unique maker nonce in the order.
/// @param erc20Token The address of the ERC20 token.
/// @param erc20FillAmount The amount of ERC20 token filled.
/// @param erc1155Token The address of the ERC1155 token.
/// @param erc1155TokenId The ID of the ERC1155 asset.
/// @param erc1155FillAmount The amount of ERC1155 asset filled.
/// @param matcher Currently unused.
event ERC1155OrderFilled(
LibNFTOrder.TradeDirection direction,
address maker,
address taker,
uint256 nonce,
IERC20TokenV06 erc20Token,
uint256 erc20FillAmount,
IERC1155Token erc1155Token,
uint256 erc1155TokenId,
uint128 erc1155FillAmount,
address matcher
);
/// @dev Emitted whenever an `ERC1155Order` is cancelled.
/// @param maker The maker of the order.
/// @param nonce The nonce of the order that was cancelled.
event ERC1155OrderCancelled(
address maker,
uint256 nonce
);
/// @dev Emitted when an `ERC1155Order` is pre-signed.
/// Contains all the fields of the order.
event ERC1155OrderPreSigned(
LibNFTOrder.TradeDirection direction,
address maker,
address taker,
uint256 expiry,
uint256 nonce,
IERC20TokenV06 erc20Token,
uint256 erc20TokenAmount,
LibNFTOrder.Fee[] fees,
IERC1155Token erc1155Token,
uint256 erc1155TokenId,
LibNFTOrder.Property[] erc1155TokenProperties,
uint128 erc1155TokenAmount
);
Last modified 9mo ago