Seaport Hooks
Introduced in Seaport 1.6, Seaport Hooks are a powerful set of primitives that can be used to extend the native functionality of the protocol by allowing developer-defined, stateful contracts to "react" to Seaport order fulfillments. Those smart contracts can be NFTs involved in the fulfillment, but can also be external protocols.
Seaport Hooks can be used to build a variety of novel experiences that expand the utility and liquidity of NFTs. Hooks are still an emergent and experimental feature, but, if you are working on Seaport Hooks and have an idea you're excited about having integrated into the OpenSea application, please reach out to [email protected].
There are three key flavors of hooks: zone hooks, contract hooks, and item hooks. All three modalities are called by Seaport during the order fulfillment process, but they are each called at different times in the order flow and with different information supplied.
Zone Hooks
Zone hooks are a mechanic for extending the native functionality of a Seaport order. By using a restricted order and specifying a zone, the order will call out to that zone both before and after transferring tokens, delegating control flow to the zone. In particular, zone hooks are an efficient way for collection owners to enforce how their tokens are bought and sold on Seaport.
authorizeOrder & validateOrder
When processing restricted orders (OrderType
with FULL_RESTRICTED
or PARTIAL_RESTRICTED
), Seaport will call the zone specified by the order (unless the zone is the caller) twice: once before executing any token transfers ( authorizeOrder
) and again after executing the token transfers (validateOrder
).
While handling these calls, the zone can perform custom validation logic (modifying state, performing additional calls of its own, etc.) and determine whether or not the order in question should be allowed or rejected.
The fulfillment will revert if the call to the zone reverts, or if the magic value (function selector in question) is not returned. In cases where the call to authorizeOrder
reverts and the fulfillment method is fulfillAvailableOrders
or fulfillAvailableAdvancedOrders
, the order will be skipped.
Both authorizeOrder
and validateOrder
will receive the same ZoneParameters
struct with one key difference: the orderHashes
array will only contain orders that were supplied and processed prior to the current order. Example: when fulfilling 3 orders, and checking the 2nd order, the orderHashes
array will have a single element (the first order hash, or bytes32(0)
if the first order was skipped) when calling authorizeOrder
and 3 elements (each respective order hash, or bytes32(0)
for any skipped orders) when calling validateOrder
.
Note that the offer
and consideration
arrays provided to the zone will have any criteria items resolved and all current amounts derived from the original start and end amounts.
struct ZoneParameters {
bytes32 orderHash;
address fulfiller;
address offerer;
SpentItem[] offer;
ReceivedItem[] consideration;
bytes extraData;
bytes32[] orderHashes;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
}
// Before executing token transfers
function authorizeOrder(
ZoneParameters calldata zoneParameters
) external returns (bytes4 authorizeOrderMagicValue)
// After executing token transfers
function validateOrder(
ZoneParameters calldata zoneParameters
) external returns (bytes4 validateOrderMagicValue)
getSeaportMetadata
function getSeaportMetadata()
external
view
returns (
string memory name,
Schema[] memory schemas // map to Seaport Improvement Proposal IDs
// https://github.com/ProjectOpenSea/SIPs
)
Contract Hooks
Contract orders (OrderType
of CONTRACT
) enable contract offerers that implement the compliant interface to dynamically generate Seaport orders via generateOrder
and to perform any additional validation or processing after token transfers are complete via ratifyOrder
. Contract orders are particularly useful for protocols and other contracts that are interested in dynamically participating as an automated buyer or seller in Seaport-powered marketplaces.
Contract orders are not signed offchain like standard Seaport orders, but instead are constructed by the fulfiller to adhere to the requirements of the contract offerer. Contract offerers will generally implement a previewOrder
function that takes some subset of the full order and returns the missing components of that order. Example: an NFT pool contract that implements a bonding curve to buy and sell NFTs according to an algorithmic process would implement a previewOrder
function that takes some amount of tokens as the offer
and returns the full order including the NFTs that would be received back as the consideration
.
When fulfilling a contract order, the fulfiller provides an offer
representing the minimum number of items and amounts that need to be supplied by the contract offerer as well as a consideration
representing the maximum number of items and amounts that the contract offerer may require. The fulfiller may also provide extraData
which will be supplied to the contract offerer as additional context.
Seaport will supply the original offer
, consideration
, and extraData
arguments to the contract offerer. The contract offerer will process the request, modifying state or performing additional calls where relevant, and return a modified offer
and consideration
array where the offer
array contains at least as many items or amounts of those items and the consideration
array returns no more than the original items or amounts. If the contract offerer does not return compliant arrays, the fulfillment will revert. If the call to generateOrder
reverts, the fulfillment will revert unless the fulfillAvailableOrders
or fulfillAvailableAdvancedOrders
method is used, in which case the order will be skipped.
For more information on contract orders, see the relevant documentation in the Seaport repository.
ContractOffererInterface
interface ContractOffererInterface {
function generateOrder(
address fulfiller,
SpentItem[] calldata minimumReceived,
SpentItem[] calldata maximumSpent,
bytes calldata context
)
external
returns (SpentItem[] memory offer, ReceivedItem[] memory consideration);
function ratifyOrder(
SpentItem[] calldata offer,
ReceivedItem[] calldata consideration,
bytes calldata context,
bytes32[] calldata orderHashes,
uint256 contractNonce
) external returns (bytes4 ratifyOrderMagicValue);
function previewOrder(
address caller,
address fulfiller,
SpentItem[] calldata minimumReceived,
SpentItem[] calldata maximumSpent,
bytes calldata context
)
external
view
returns (SpentItem[] memory offer, ReceivedItem[] memory consideration);
function getSeaportMetadata()
external
view
returns (
string memory name,
Schema[] memory schemas // map to Seaport Improvement Proposal IDs
// https://github.com/ProjectOpenSea/SIPs
);
}
Item Hooks
While zone and contract hooks are triggered before and after executing token transfers, item hooks are triggered mid-execution, typically triggered from calls to safeTransferFrom
. They are not a formal construct within Seaport, but can still be utilized for accomplishing more complex stateful operations requiring additional logic. A key limitation of item hooks is that the amount of data that can be supplied is inherently limited. Furthermore, they are more opaque than zone and contract hooks.
These take 3 different flavors:
- Synthetic tokens: supplying an item with a token that does not adhere to formal semantics but instead performs custom logic, the order can trigger an external hook
- Receive fallback: selecting a recipient of a native token transfer with a receive function that performs additional logic, it can trigger an external hook (though it can't provide any additional data)
- ERC-1155 onReceived fallback: functions similarly to the native token receive fallback but allows for passing through additional data
Before reaching for item hooks, it is strongly encouraged to explore zone or contract hooks as a workable alternative due to their increased flexibility, interoperability, and clarity of purpose.
Seaport Improvement Proposals
To facilitate discovery and interaction with hooks, a set of standards called Seaport Improvement Proposals (SIPs) are maintained at https://github.com/ProjectOpenSea/SIPs.
Relevant SIPs for hook authors to consider include:
- SIP-5: how to signal which SIPs are implemented by the Seaport hook via
getSeaportMetadata
- SIP-6: how to encode
extraData
as to support multiple concurrent SIPs
Authors of hooks should review existing SIPs to determine if there is an existing standard they can implement for their particular use case. If one does not exist, the authors are encouraged to propose a new SIP via pull request so that OpenSea and other interested parties can integrate with Seaport orders using the hook.
Hook authors are encouraged to join the Seaport Working Group Discord to engage in discussion related to their hooks and corresponding SIPs.
Updated 9 months ago