Auctionhouse Integration Guide
This guide explains how to integrate Manifold Creator Core contracts with the auctionhouse marketplace, covering lazy minting, dynamic pricing, and standard NFT sales.
Table of Contents
- Overview
- Integration Patterns
- Lazy Minting Setup
- Dynamic Pricing Setup
- Complete Examples
- Best Practices
Overview
The auctionhouse marketplace supports three main integration patterns:
- Standard NFT Sales: Mint tokens upfront, transfer to marketplace
- Lazy Minting: Mint tokens on-demand when purchased via marketplace
- Dynamic Pricing: Price changes based on sales progress or time
Key Interfaces
- ILazyDelivery: Required for lazy minting (minting tokens at purchase time)
- IPriceEngine: Required for dynamic pricing (price changes based on sales/time)
Integration Patterns
Pattern 1: Standard NFT Sales
Best for: Edition contracts, fixed-price sales, auctions
Flow:
1. Mint tokens using Creator Core contract
2. Transfer tokens to marketplace contract
3. Create listing with transfered tokens
4. Marketplace handles sales
Example:
// 1. Mint 100 edition tokens
IERC721CreatorCore creatorCore = IERC721CreatorCore(creatorContract);
uint256[] memory tokenIds = creatorCore.mintBaseBatch(artist, 100);
// 2. Transfer first token to marketplace
IERC721(creatorContract).transferFrom(artist, marketplace, tokenIds[0]);
// 3. Create FIXED_PRICE listing
ListingDetails memory details = ListingDetails({
initialAmount: 0.05 ether,
type_: ListingType.FIXED_PRICE,
totalAvailable: 100,
totalPerSale: 1,
// ... other fields
});
Pattern 2: Lazy Minting
Best for: On-demand minting, dynamic pricing, reducing upfront gas costs
Flow:
1. Deploy ILazyDelivery adapter
2. Register adapter as extension on Creator Core
3. Authorize marketplace on adapter
4. Create listing with lazy=true
5. Marketplace calls adapter.deliver() when purchased
Components:
ERC721CreatorLazyDelivery: Adapter for ERC721 Creator Core contractsERC1155CreatorLazyDelivery: Adapter for ERC1155 Creator Core contracts
Pattern 3: Dynamic Pricing
Best for: Bonding curves, Dutch auctions, tiered pricing
Flow:
1. Deploy IPriceEngine contract
2. Create listing with DYNAMIC_PRICE type
3. Set token address to price engine contract
4. Price engine calculates price based on sales/time
Components:
LinearBondingCurvePriceEngine: Linear price increaseExponentialBondingCurvePriceEngine: Exponential price increaseDutchAuctionPriceEngine: Time-based decreasing priceStepPricingEngine: Tiered pricing at quantity thresholds
Lazy Minting Setup
Step 1: Deploy the Adapter
// Deploy ERC721 adapter
ERC721CreatorLazyDelivery adapter = new ERC721CreatorLazyDelivery(creatorContractAddress);
Step 2: Register Adapter as Extension
On your Creator Core contract, register the adapter as an extension:
ICreatorCore(creatorContract).registerExtension(address(adapter), "");
Step 3: Authorize Marketplace
Authorize the marketplace contract to call the adapter:
adapter.setAuthorizedMarketplace(marketplaceAddress, true);
Note: The setAuthorizedMarketplace function currently allows any caller. In production, you should add proper access control (e.g., only creator contract owner can authorize).
Step 4: Create Lazy Listing
When creating a listing, set lazy: true and use the adapter address as the token address:
TokenDetails memory token = TokenDetails({
id: 1, // Asset ID (used for tracking)
address_: address(adapter), // Adapter contract address
spec: TokenLib.Spec.ERC721,
lazy: true // Enable lazy minting
});
How It Works
- Buyer calls
marketplace.purchase(listingId) - Marketplace calls
adapter.deliver(...) - Adapter calls
creatorCore.mintExtension(to, tokenData) - Token is minted directly to buyer
- Payment is settled
Dynamic Pricing Setup
Linear Bonding Curve
Price increases linearly: price = basePrice + (alreadyMinted * increment)
// Deploy: base price 0.01 ETH, increment 0.001 ETH per token
LinearBondingCurvePriceEngine priceEngine = new LinearBondingCurvePriceEngine(
0.01 ether, // basePrice
0.001 ether // increment
);
// Create DYNAMIC_PRICE listing
ListingDetails memory details = ListingDetails({
initialAmount: 0, // Must be 0 for dynamic price
type_: ListingType.DYNAMIC_PRICE,
totalAvailable: 1000,
totalPerSale: 1,
// ... other fields
});
TokenDetails memory token = TokenDetails({
id: 1,
address_: address(priceEngine), // Price engine address
spec: TokenLib.Spec.ERC721,
lazy: true // Required for dynamic price
});
Exponential Bonding Curve
Price increases exponentially: price = basePrice * (multiplier ^ alreadyMinted)
// Deploy: base price 0.01 ETH, 5% increase per token (1.05e18)
ExponentialBondingCurvePriceEngine priceEngine = new ExponentialBondingCurvePriceEngine(
0.01 ether, // basePrice
1.05e18 // multiplier (5% = 1.05 with 18 decimals)
);
Dutch Auction
Price decreases over time from start price to reserve price:
// Deploy: starts at 1 ETH, decreases to 0.1 ETH over 7 days
DutchAuctionPriceEngine priceEngine = new DutchAuctionPriceEngine(
1 ether, // startPrice
0.1 ether, // reservePrice
block.timestamp, // startTime
7 days // duration
);
Step Pricing
Tiered pricing at quantity thresholds:
// Define pricing steps
StepPricingEngine.PricingStep[] memory steps = new StepPricingEngine.PricingStep[](3);
steps[0] = StepPricingEngine.PricingStep({quantity: 10, price: 0.1 ether}); // First 10: 0.1 ETH
steps[1] = StepPricingEngine.PricingStep({quantity: 50, price: 0.2 ether}); // Next 40: 0.2 ETH
steps[2] = StepPricingEngine.PricingStep({quantity: 100, price: 0.3 ether}); // Rest: 0.3 ETH
StepPricingEngine priceEngine = new StepPricingEngine(steps);
Important: For DYNAMIC_PRICE listings, the token contract must implement both ILazyDelivery and IPriceEngine. You can deploy the price engine separately and use it as the token address, but you'll also need a lazy delivery adapter.
Complete Examples
Example 1: Lazy Minting with Fixed Price
// 1. Deploy adapter
ERC721CreatorLazyDelivery adapter = new ERC721CreatorLazyDelivery(creatorContract);
// 2. Register adapter
ICreatorCore(creatorContract).registerExtension(address(adapter), "");
// 3. Authorize marketplace
adapter.setAuthorizedMarketplace(marketplaceAddress, true);
// 4. Create listing
ListingDetails memory details = ListingDetails({
initialAmount: 0.1 ether,
type_: ListingType.FIXED_PRICE,
totalAvailable: 100,
totalPerSale: 1,
extensionInterval: 0,
minIncrementBPS: 0,
erc20: address(0),
identityVerifier: address(0),
startTime: block.timestamp,
endTime: block.timestamp + 30 days
});
TokenDetails memory token = TokenDetails({
id: 1,
address_: address(adapter),
spec: TokenLib.Spec.ERC721,
lazy: true
});
uint40 listingId = marketplace.createListing(
details,
token,
DeliveryFees({deliverBPS: 0, deliverFixed: 0}),
new ListingReceiver[](0),
false,
false,
""
);
Example 2: Dynamic Pricing with Lazy Minting
// 1. Deploy price engine
LinearBondingCurvePriceEngine priceEngine = new LinearBondingCurvePriceEngine(
0.01 ether,
0.001 ether
);
// 2. Deploy adapter (must also implement IPriceEngine or use separate contracts)
// For this example, we'll use a combined approach
// In practice, you may need a wrapper contract that implements both interfaces
// 3. Create DYNAMIC_PRICE listing
ListingDetails memory details = ListingDetails({
initialAmount: 0, // Must be 0
type_: ListingType.DYNAMIC_PRICE,
totalAvailable: 1000,
totalPerSale: 1,
extensionInterval: 0,
minIncrementBPS: 0,
erc20: address(0),
identityVerifier: address(0),
startTime: block.timestamp,
endTime: block.timestamp + 30 days
});
TokenDetails memory token = TokenDetails({
id: 1,
address_: address(priceEngine), // Price engine address
spec: TokenLib.Spec.ERC721,
lazy: true
});
uint40 listingId = marketplace.createListing(
details,
token,
DeliveryFees({deliverBPS: 0, deliverFixed: 0}),
new ListingReceiver[](0),
false,
false,
""
);
Note: For DYNAMIC_PRICE listings, the contract at token.address_ must implement both ILazyDelivery and IPriceEngine. You may need to create a wrapper contract that combines both, or modify the adapter to also implement IPriceEngine.
Best Practices
Security
-
Access Control: Implement proper access control for
setAuthorizedMarketplace(). Only the creator contract owner should be able to authorize marketplaces. -
EOA Check: The adapters include an EOA check to prevent contract addresses from receiving lazy-minted tokens. This prevents exploit patterns where contracts revert if they don't receive desired attributes.
-
Token Data: Use the
assetIdandlistingIdparameters to encode meaningful data in the token minting process, but be careful not to expose sensitive information.
Gas Optimization
-
Batch Minting: For standard sales, use
mintBaseBatch()to mint multiple tokens at once. -
Lazy Minting: Lazy minting reduces upfront gas costs for sellers since tokens aren't minted until purchased.
-
Price Engine: Keep price calculation logic simple and gas-efficient. Complex calculations in
price()can become expensive.
Integration Tips
-
Testing: Always test adapters with your specific Creator Core contract before deploying to mainnet.
-
Extension Registration: Ensure the adapter is properly registered as an extension before creating listings.
-
Marketplace Authorization: Double-check that the marketplace address is authorized before creating listings.
-
Asset IDs: Use meaningful asset IDs to track different collections or series within the same Creator Core contract.
-
Price Engine Selection: Choose the appropriate price engine based on your use case:
- Linear: Simple, predictable price increases
- Exponential: Faster price growth, good for limited editions
- Dutch Auction: Time-based urgency, good for launch sales
- Step Pricing: Clear tiers, good for milestone-based pricing
Troubleshooting
"Unauthorized marketplace" Error
- Ensure
setAuthorizedMarketplace()was called withtrue - Verify the marketplace address is correct
- Check that the adapter contract is correctly deployed
"Invalid creator contract" Error
- Verify the Creator Core contract implements
IERC721CreatorCoreorIERC1155CreatorCore - Check that the contract address is correct
"Cannot deliver to contract" Error
- This is intentional - lazy minting only works with EOA addresses
- If you need to deliver to contracts, modify the
onlyEOAmodifier (not recommended for security)
Price Engine Not Working
- For DYNAMIC_PRICE listings, ensure
initialAmountis 0 - Verify the price engine contract implements
IPriceEngine - Check that the price engine address is correctly set in
TokenDetails