Bazaar Developer Guide
Learn how to integrate with Bazaar. This guide covers smart contract interactions, order creation, querying, and execution.
How Bazaar Works
Core Architecture
Bazaar uses a three-layer architecture:
- Bazaar Contracts: Handle order submission and validation
- Seaport Protocol: Handles order execution and matching
- Zone Contracts: Validate orders during execution and track execution data
Two-Phase Order Lifecycle
Bazaar separates order submission from execution:
Submission Phase (Bazaar contracts):
- User creates Seaport order
- Bazaar validates order structure and recipients
- Order stored as Net message for onchain indexing
Execution Phase (Seaport + Zones):
- Buyer fulfills order via Seaport
- Seaport validates signature, expiration, and cancellation status
- Seaport executes transfers (NFT/ERC20 and payment)
- Zone's
validateOrder()is called during execution - Zone stores execution data in NetStorage and sends Net message for indexing
Key Point: Bazaar stores orders but doesn't execute them. Seaport executes orders. Zones track executions.
Submission Structure
All Bazaar contracts use the same Submission struct:
struct Submission {
OrderParameters parameters;
uint256 counter;
bytes signature;
}
Net Message Structure
When Bazaar stores an order, it creates a Net message with:
- Topic: NFT/ERC20 contract address (hex string)
- Text: Human-readable format (e.g., "List 0x... #123\nPrice: 0.00200000 eth\nExpiration Date: 1724033053")
- Data: ABI-encoded Submission struct
Core Contracts
BazaarV2 (NFT Listings)
Purpose: Submit NFT listings for sale
Function:
function submit(Submission calldata submission) external
Validation Rules:
- Exactly 1 offer item (the NFT)
- Exactly 2 consideration items (payment to seller + fee)
- First consideration recipient must be
msg.sender(seller) - Second consideration recipient must be
FEE_ADDRESS
Net Message:
- Topic: NFT contract address (from offer item)
- Text: "List <address> #<tokenId>\nPrice: <price>\nExpiration Date: <timestamp>"
- Data: ABI-encoded Submission
Events:
Submitted(address indexed tokenAddress, uint256 indexed tokenId)
Errors:
OfferItemsMustContainOneItem()ConsiderationItemsMustContainTwoItems()ConsiderationItemsMustIncludeMsgSender()ConsiderationItemsMustIncludeFeeAddress()InvalidFee()
BazaarV2ERC20 (ERC20 Listings)
Purpose: Submit ERC20 token listings for sale
Function:
function submit(Submission calldata submission) external
Validation Rules:
- Same structure as BazaarV2
Net Message:
- Same format as BazaarV2, but for ERC20 tokens
BazaarV2CollectionOffers (Collection Offers)
Purpose: Submit collection offers (buyers making offers on any NFT in a collection)
Function:
function submit(Submission calldata submission) external
Validation Rules:
- Same validation as BazaarV2
- Uses criteria items (ERC721_WITH_CRITERIA or ERC1155_WITH_CRITERIA) for collection-wide offers
Net Message:
- Topic: NFT contract address (from consideration item, not offer)
- Text: "Order <address> #<tokenId>\nPrice: <price>\nExpiration Date: <timestamp>"
- Data: ABI-encoded Submission
Note: Collection offers have reversed structure - buyer offers payment (consideration), receives NFT (offer)
BazaarV2ERC20Offers (ERC20 Offers)
Purpose: Submit ERC20 offers (buyers making offers to buy ERC20 tokens)
Function:
function submit(Submission calldata submission) external
Validation Rules:
- Exactly 1 offer item (payment token, typically WETH)
- Exactly 2 consideration items (ERC20 being bought + fee)
- First consideration recipient must be
msg.sender(maker) - Second consideration recipient must be
FEE_ADDRESS
Net Message:
- Topic: ERC20 token address (from consideration item)
- Text: "Offer <address>\nPrice: <price>\nExpiration Date: <timestamp>"
- Data: ABI-encoded Submission
Events:
Submitted(address indexed tokenAddress)
Zone Contracts
What Zones Do
Zones are Seaport contracts that validate orders during execution. When someone fulfills an order through Seaport, Seaport calls the zone's validateOrder() function. Zones serve two purposes:
- Order Validation: Can enforce custom rules (e.g., private orders)
- Execution Tracking: Store execution data in NetStorage and send Net messages for indexing
NetSeaportZone
Address: 0x000000007F8c58fbf215bF91Bda7421A806cf3ae (same across all chains)
Purpose: Main zone for public orders
Functions:
function authorizeOrder(ZoneParameters calldata zoneParameters) external override returns (bytes4)
Always returns selector (allows all orders).
function validateOrder(ZoneParameters calldata zoneParameters) external override returns (bytes4)
Called by Seaport during order execution. Stores execution data in NetStorage and sends Net message.
Storage Details:
- Key:
orderHash(bytes32) - unique identifier for the order - Operator: Zone contract address (NetSeaportZone)
- Data:
abi.encode(timestamp, netTotalMessageLength, netTotalMessageForAppTopicLength, zoneParameters)block.timestamp: When order was executednetTotalMessageLength: Total Net messages at execution timenetTotalMessageForAppTopicLength: Total messages for this app-topic at execution timezoneParameters: Complete Seaport execution parameters (offerer, fulfiller, items, etc.)
Net Message:
- Topic: NFT contract address (from offer[0].token)
- Data:
abi.encode(orderHash)
NetSeaportPrivateOrderZone
Address: 0x000000bC63761cbb05305632212e2f3AE2BE7a9B (same across all chains)
Purpose: Zone for private orders (restricted to specific buyers)
Function:
function validateOrder(ZoneParameters calldata zoneParameters) external override returns (bytes4)
Validation:
- Validates
zoneHash == keccak256(fulfiller)to restrict order to specific buyer - If validation passes, delegates to
NetSeaportZone.validateOrder()for storage
Error:
ZoneHashNotMatchFulfillerHash()- thrown if fulfiller doesn't match zoneHash
Usage: For private orders where only a specific address can fulfill
NetSeaportCollectionOfferZone
Address: 0x000000B799ec6D7aCC1B578f62bFc324c25DFC5A (same across all chains)
Purpose: Zone for collection offers
Function:
function validateOrder(ZoneParameters calldata zoneParameters) external override returns (bytes4)
Behavior:
- Swaps offer/consideration items because collection offers have reversed structure (buyer offers payment, receives NFT)
- Creates swapped ZoneParameters and delegates to
NetSeaportZone.validateOrder()
Usage: For collection offers (buyer makes offer for any NFT in collection)
Creating Orders
To create an order in Bazaar, you create a Seaport order and submit it to a Bazaar contract. Bazaar validates the order structure and stores it as a Net message, making it discoverable onchain.
Complete Flow
- Create Seaport order: Use Seaport SDK to create an order with offer/consideration items
- Set zone: Specify which zone contract to use (NetSeaportZone for public, NetSeaportPrivateOrderZone for private)
- Sign order: Sign the order with your wallet
- Submit to Bazaar: Call the appropriate Bazaar contract's
submit()function - Stored in Net: Bazaar stores the order as a Net message with topic = NFT/ERC20 address
Bazaar contracts validate the order structure (item counts, recipient addresses) before storing. The order is then discoverable via Net Protocol queries.
Example: Listing an NFT
import { Seaport } from "@opensea/seaport-js";
import { createWalletClient } from "viem";
const seaport = new Seaport(provider);
const walletClient = createWalletClient({ ... });
// 1. Create Seaport order
const order = await seaport.createOrder({
offer: [{
itemType: ItemType.ERC721,
token: nftAddress,
identifierOrCriteria: tokenId,
}],
consideration: [
{
itemType: ItemType.NATIVE,
recipient: sellerAddress,
startAmount: price,
endAmount: price,
},
{
itemType: ItemType.NATIVE,
recipient: FEE_ADDRESS,
startAmount: feeAmount,
endAmount: feeAmount,
},
],
zone: NET_SEAPORT_ZONE_CONTRACT.address, // Use NetSeaportPrivateOrderZone for private orders
// ... other parameters (startTime, endTime, etc.)
});
// 2. Sign order
const signature = await walletClient.signTypedData({ ... });
// 3. Submit to Bazaar
const submission = {
parameters: order.parameters,
counter: order.counter,
signature,
};
await bazaarContract.write.submit([submission]);
// Order is now stored in Net and discoverable via Net Protocol queries
What happens: Bazaar validates the order structure (exactly 1 offer item, exactly 2 consideration items, correct recipients), then stores it as a Net message. The message topic is the NFT contract address, making it queryable by collection.
Private Orders
For orders restricted to a specific buyer, use NetSeaportPrivateOrderZone and set the zoneHash to keccak256(fulfillerAddress). The zone will validate that only the specified address can fulfill the order.
// For private orders
const zoneHash = keccak256(abi.encodePacked(fulfillerAddress));
const order = await seaport.createOrder({
// ... order parameters
zone: NET_SEAPORT_PRIVATE_ORDER_ZONE_CONTRACT.address,
zoneHash: zoneHash,
// ... other parameters
});
Other Order Types
Collection Offers: Use BazaarV2CollectionOffers with NetSeaportCollectionOfferZone. The order structure is reversed (buyer offers payment, receives NFT).
ERC20 Listings: Use BazaarV2ERC20 with the same structure as NFT listings but for ERC20 tokens.
ERC20 Offers: Use BazaarV2ERC20Offers where the offer is payment (WETH) and consideration is the ERC20 token being bought.
Querying Orders
Bazaar uses Net Protocol for onchain order discovery. When you submit an order to Bazaar, it's stored as a Net message with the NFT/ERC20 contract address as the topic. This enables querying all orders for a collection directly from the blockchain.
Querying Listings
To find all listings for an NFT collection, query Net messages by topic (the NFT contract address):
import { WILLIE_NET_CONTRACT } from "@/app/constants";
import { decodeAbiParameters } from "viem";
import { BAZAAR_SUBMISSION_ABI } from "./constants";
// 1. Query Net messages for the NFT collection
const messages = await publicClient.readContract({
address: WILLIE_NET_CONTRACT.address,
abi: WILLIE_NET_CONTRACT.abi,
functionName: "getMessagesForAppTopic",
args: [bazaarAddress, nftAddress], // App = Bazaar contract, Topic = NFT address
});
// 2. Parse each message to get the Seaport order
const listings = messages.map((message) => {
// Decode Submission struct from message data
const [submission] = decodeAbiParameters(BAZAAR_SUBMISSION_ABI, message.data);
// Reconstruct Seaport order from submission
const order = {
parameters: submission.parameters,
signature: submission.signature,
counter: submission.counter,
// Note: You may need to convert identifierOrCriteria to string for Seaport SDK compatibility
};
return {
order,
message,
};
});
// 3. Check order status (optional)
for (const listing of listings) {
const status = await seaport.getOrderStatus(listing.order.parameters);
// Returns: CANCELLED, EXPIRED, OPEN, or FILLED
}
What this does: Queries Net Protocol for all messages from the Bazaar contract with the NFT address as the topic. Each message contains a complete Seaport order that can be fulfilled.
Querying Execution Data
To track order executions (sales), query NetStorage using order hashes from zone Net messages:
// 1. Query zone Net messages by topic to get order hashes
const zoneMessages = await publicClient.readContract({
address: WILLIE_NET_CONTRACT.address,
abi: WILLIE_NET_CONTRACT.abi,
functionName: "getMessagesForAppTopic",
args: [zoneAddress, nftAddress], // App = Zone contract, Topic = NFT address
});
// 2. Extract order hashes from messages
const orderHashes = zoneMessages.map((msg) => msg.data);
// 3. Query NetStorage for execution details
for (const orderHash of orderHashes) {
const executionData = await storage.get(orderHash, zoneAddress);
const [timestamp, netTotalMessageLength, netTotalMessageForAppTopicLength, zoneParameters] =
decodeAbiParameters([...], executionData);
// zoneParameters contains: offerer, fulfiller, offer items, consideration items, etc.
}
This enables building sales charts, execution history, and analytics using entirely onchain data.
Executing Orders
When someone wants to fulfill an order, they retrieve it from Net Protocol, then execute it via Seaport. During execution, the zone contract tracks the sale in NetStorage, completing the onchain record.
Complete Flow
- Get order from Net: Query Net messages to find the order (see Querying Orders)
- Reconstruct Seaport order: Decode the Submission struct and rebuild the Seaport order
- Fulfill via Seaport: Use Seaport SDK to execute the order
- Zone tracks execution: Seaport calls the zone's
validateOrder()during execution - Execution stored: Zone stores execution data in NetStorage and sends a Net message for indexing
This creates a complete onchain record: order submission (stored by Bazaar) + order execution (tracked by Zone).
Example: Fulfilling a Listing
import { Seaport } from "@opensea/seaport-js";
import { decodeAbiParameters } from "viem";
import { BAZAAR_SUBMISSION_ABI } from "./constants";
// 1. Get order from Net message (from Querying Orders section)
const message = /* ... from Net query ... */;
// 2. Decode Submission struct and reconstruct Seaport order
const [submission] = decodeAbiParameters(BAZAAR_SUBMISSION_ABI, message.data);
const order = {
parameters: submission.parameters,
signature: submission.signature,
counter: submission.counter,
};
// 3. Fulfill order via Seaport
const seaport = new Seaport(provider);
const transaction = await seaport.fulfillOrder({
order,
accountAddress: buyerAddress,
});
// 4. Wait for transaction
await transaction.wait();
// The zone automatically:
// - Validates the order during execution
// - Stores execution data in NetStorage (key = orderHash, operator = zone address)
// - Sends Net message with orderHash for indexing
What happens: Seaport executes the order (transfers NFT and payment), then calls the zone's validateOrder() function. The zone stores execution details (timestamp, offerer, fulfiller, items, etc.) in NetStorage and sends a Net message for indexing. This enables querying sales history entirely onchain.
Execution Tracking
The zone stores execution data in NetStorage with:
- Key:
orderHash(bytes32) - unique identifier for the order - Operator: Zone contract address
- Data:
abi.encode(timestamp, netTotalMessageLength, netTotalMessageForAppTopicLength, zoneParameters)
The zone also sends a Net message with:
- Topic: NFT contract address
- Data:
abi.encode(orderHash)
This enables querying all sales for a collection by querying zone Net messages, then fetching execution details from NetStorage (see Querying Execution Data in the Querying Orders section).
Contract Addresses
Bazaar Contracts
Base:
- BazaarV2:
0x000000009edd5e2c525ac15f303208ccb8582af4 - BazaarV2ERC20:
0x0000001C6d9772D597D6282621D3818A17446Dcd - BazaarV2CollectionOffers:
0x00000000b98f7b8e8cc4833868f0c819e2459fc5 - BazaarV2ERC20Offers:
0x000000d61719d248da1f3f7692ee1d36dc2ab6d1
Other Chains (default addresses):
- BazaarV2:
0x00000000E3dA5fC031282A39759bDDA78ae7fAE5 - BazaarV2ERC20:
0x00000000a2d173a4610c85c7471a25b6bc216a70 - BazaarV2CollectionOffers:
0x0000000D43423E0A12CecB307a74591999b32B32 - BazaarV2ERC20Offers:
0x0000000000000000000000000000000000000000(not deployed on most chains)
Zone Contracts
Same addresses across all chains:
- NetSeaportZone:
0x000000007F8c58fbf215bF91Bda7421A806cf3ae - NetSeaportPrivateOrderZone:
0x000000bC63761cbb05305632212e2f3AE2BE7a9B - NetSeaportCollectionOfferZone:
0x000000B799ec6D7aCC1B578f62bFc324c25DFC5A