Building NFT Exchange in Dapps
With NFT Swap SDK
@johnjohnson
matcha.xyz
0x Project
trader.xyz
NFTs in Dapps
Where are we today?
View only
social media (twitter)
View only
nft communities
View only
nft communities (cont'd)
View only
wallets
NFT swapping functionality
unlocks value in your dapp
Read + = web3
Write
multichain
But building exchange
from the ground up is hard
order persistance
protocol development
orderbook
searching
and filtering
security
nft indexing
gas costs
optimization
frontend contract integration
adding protocol
features
order management
Wouldn't it be cool to
easily add support for swapping NFTs?
Why 0x?
- Orders created off-chain (free!)
- Fees and royalties
- Collections and property-based orders
- Cheapest and most gas-efficient
- Continuous feature updates
- Multichain
Why 0x?
The cheapest way to swap NFTs on Ethereum
Why Swap SDK?
- Easy-to-use JavaScript SDK
- Hosted orderbooks
- Focus on your application
- No fees, no gimmicks
Integrating
Install NFT Swap
yarn add @traderxyz/nft-swap-sdk
Configure SDK
import { Web3Provider } from '@ethersproject/providers';
import { NftSwapSdkV4 } from '@traderxyz/nft-swap-sdk';
const provider = new Web3Provider(window.ethereum);
const signer = provider.getSigner();
const swapSdk = new NftSwapSdkV4(provider, signer);
Getting Started
ERC20 <> ERC721 Swap
What's in a trade?
sounds good!
send me the offer and i'll accept
I'd like to buy your CryptoCoven for $5,000
What's in a trade?
sounds good! send me the offer and i'll accept
I'd like to buy your CryptoCoven for $5,000
5000 USDC
Coven #9757
What's in a trade?
sounds good! send me the offer and i'll accept
I'd like to buy your CryptoCoven for $5,000
5000 USDC
Coven #9757
What's in a trade?
sounds good! send me the offer and i'll accept
I'd like to buy your CryptoCoven for $5,000
5000 USDC
Coven #9757
What's in a trade?
sounds good! send me the offer and i'll accept
I'd like to buy your CryptoCoven for $5,000
5000 USDC
Coven #9757
Create a trade
5000 USDC
Coven #9757
Create a trade
5000 USDC
Coven #9757
const order = {
nftToken: '0x5180',
nftTokenId: '9757',
erc20Token: '0xa0b8',
erc20TokenAmount: '5000000000',
maker: '0x1a3b...',
direction: '0',
nonce: '0x4206...',
};
Sign a trade
5000 USDC
Coven #9757
const signedOrder = {
nftToken: '0x5180',
nftTokenId: '9757',
erc20Token: '0xa0b8',
erc20TokenAmount: '5000000000',
maker: '0x1a3b...',
direction: '0',
nonce: '0x4206...',
signature: '0x12345678',
};
Once signed, this order can be filled by the other party
Fill a trade
5000 USDC
Coven #9757
const signedOrder = {
nftToken: '0x5180',
nftTokenId: '9757',
erc20Token: '0xa0b8',
erc20TokenAmount: '5000000000',
maker: '0x1a3b...',
direction: '0',
nonce: '0x4206...',
signature: '0x12345678',
};
nftSwap.fillSignedOrder(signedOrder)
Fill a trade
5000 USDC
Coven #9757
const signedOrder = {
nftToken: '0x5180',
nftTokenId: '9757',
erc20Token: '0xa0b8',
erc20TokenAmount: '5000000000',
maker: '0x1a3b...',
direction: '0',
nonce: '0x4206...',
signature: '0x12345678',
};
nftSwap.fillSignedOrder(signedOrder)
Trade Lifecycle
5000 USDC
- Build trade
- Sign trade
- Fill trade
Coven #9757
Using the Swap SDK
Bids and Asks
Bids and Asks
Bids
Bids and Asks
Bids
Asks
Bids and Asks
Asks
Bids
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
// Create the 0x order
const order = nftSdk.buildOrder(
// I will offering an ERC20 (5,000 of USDC)
{
type: "ERC20",
tokenAddress: "0x31f42841c2db5173425b5223809cf3a38fede360",
amount: "500000000000000", // 5000 USDC (5000 * 6 decimals)
},
// I am receiving an NFT (CryptoCoven #9757)
{
type: "ERC721",
tokenAddress: "0x5180db8f5c931aae63c74266b211f580155ecac8",
tokenId: "9757",
},
// My wallet address
"0xabc23F70Df4F45dD3Df4EC6DA6827CB05853eC9b"
);
// User signs order with their wallet
const signedOrder = await nftSdk.signOrder(order);
// Share order to the orderbook so it can be discovered and used in your app
const postedOrder = await nftSdk.postOrder(signedOrder, CHAIN_ID);
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
// Search the orderbook for all offers to buy this NFT (CryptoCoven #9757)
const orders = await nftSwap.getOrders({
nftToken: "0x5180db8f5c931aae63c74266b211f580155ecac8",
nftTokenId: "9757",
sellOrBuyNft: "buy", // Only show bids (offers to buy) for this NFT
});
// Or search for a specific order by nonce
const orders = await nftSwap.getOrders({
nonce: "0x31f42841c2db5173425b5223809cf3a38fede360",
});
const foundOrder = orders[0];
Bids
-
Bid on an NFT
- View Bids on an NFT
- Accept a bid on an NFT
// Search the orderbook for all offers to buy this NFT (CryptoCoven #9757)
const orders = await nftSwap.getOrders({
nftToken: "0x5180db8f5c931aae63c74266b211f580155ecac8",
nftTokenId: "9757",
sellOrBuyNft: "buy", // Only show bids (offers to buy) for this NFT
});
// Or search for a specific order by nonce
const orders = await nftSwap.getOrders({
nonce: "0x31f42841c2db5173425b5223809cf3a38fede360",
});
const foundOrder = orders[0];
// NFT owner can fill the order to complete the swap
const fillTx = await nftSwap.fillSignedOrder(foundOrder);
const txReceipt = await fillTx.wait();
const txHash = txReceipt.transactionHash;
Asks
-
List an NFT for sale
- View all NFTs for Sale
- Buy an NFT for sale
Asks
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
Asks
const order = nftSdk.buildOrder(
// I am offering an NFT (CryptoCoven #9757)
{
type: "ERC721",
tokenAddress: "0x5180db8f5c931aae63c74266b211f580155ecac8",
tokenId: "9757",
},
// I will receive an ERC20 (5,000 of USDC)
{
type: "ERC20",
tokenAddress: "0x31f42841c2db5173425b5223809cf3a38fede360",
amount: "500000000000000", // 5000 USDC (5000 * 6 decimals)
},
// My wallet address
"0xabc23F70Df4F45dD3Df4EC6DA6827CB05853eC9b"
);
const signedOrder = await nftSdk.signOrder(order);
const postedOrder = await nftSdk.postOrder(signedOrder, CHAIN_ID);
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
Asks
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
Asks
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
// Search the orderbook for all offers to sell this NFT (CryptoCoven #9757)
const orders = await nftSwap.getOrders({
nftToken: "0x5180db8f5c931aae63c74266b211f580155ecac8",
nftTokenId: "9757",
sellOrBuyNft: "sell", // Only show asks (sells) for this NFT (excludes asks)
});
// Or search by unique nonce
const orders = await nftSwap.getOrders({
nonce: "0x31f42841c2db5173425b5223809cf3a38fede360",
});
const foundOrder = orders[0];
Asks
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
Asks
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
// Search the orderbook for all offers to sell this NFT (CryptoCoven #9757)
const orders = await nftSwap.getOrders({
nftToken: "0x5180db8f5c931aae63c74266b211f580155ecac8",
nftTokenId: "9757",
sellOrBuyNft: "sell", // Only show asks (sells) for this NFT (excludes asks)
});
// Or search by unique nonce
const orders = await nftSwap.getOrders({
nonce: "0x31f42841c2db5173425b5223809cf3a38fede360",
});
const foundOrder = orders[0];
Asks
// Search the orderbook for all offers to sell this NFT (CryptoCoven #9757)
const orders = await nftSwap.getOrders({
nftToken: "0x5180db8f5c931aae63c74266b211f580155ecac8",
nftTokenId: "9757",
sellOrBuyNft: "sell", // Only show asks (sells) for this NFT (excludes asks)
});
// Or search by unique nonce
const orders = await nftSwap.getOrders({
nonce: "0x31f42841c2db5173425b5223809cf3a38fede360",
});
const foundOrder = orders[0];
// Anyone can buy the NFT as long as they have enough ERC20
const fillTx = await nftSwap.fillSignedOrder(foundOrder);
const txReceipt = await fillTx.wait();
const txHash = txReceipt.transactionHash;
-
List an NFT for sale
- View NFT for sale order
- Buy the NFT for sale
Trade Lifecycle Overview
Order Management
// Cancel an order (requires on-chain transaction)
await nftSwap.cancelOrder(signedOrder);
// Check order status
const status = await nftSwap.getOrderStatus(signedOrder);
if (status === 1) {
console.log('order is still open!');
}
Easily manage orders via the SDK
Approvals
const makerAsset = {
type: "ERC721",
tokenAddress: "0x5180db8f5c931aae63c74266b211f580155ecac8",
tokenId: "9757",
};
// Get approval status for wallet
const approvalStatusForMaker = await nftSwap.loadApprovalStatus(
makerAsset,
makerWalletAddress
);
// Check if wallet needs to approve 0x v4
if (!approvalStatusForMaker.contractApproved) {
const approvalTx = await nftSwapSdk.approveTokenOrNftByAsset(
makerAsset,
makerWalletAddress
);
const approvalTxReceipt = await approvalTx.wait();
console.log(
`Approved ${makerAsset.tokenAddress} contract
to swap with 0x (txHash: ${approvalTxReceipt.transactionHash})`
);
}
// Now the user can trade this asset with 0x
Advanced Features
Using ETH
const orderWithEth = nftSwap.buildOrder(
// NFT is for sale for
{ type: 'ERC721', tokenAddress: '0xa0b8...', tokenId: '401' },
// In exchange for 1 ETH
{ type: 'ERC20', tokenAddress: '0xeeee...', amount: '1e18' },
MAKER_WALLET_ADDRESS,
);
0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
To require filling order in ETH, hardcode the tokenAddress to:
Fees
const orderWithOneFee = nftSwap.buildOrder(
// NFT is for sale for
{ type: 'ERC721', tokenAddress: '0xa0b8...', tokenId: '401' },
// In exchange for 5000 USDC
{ type: 'ERC20', tokenAddress: '0xa0b8...', amount: '5000000000' },
MAKER_WALLET_ADDRESS,
{
fees: [
{
// 6.9 USDC fee
amount: "6900000",
recipient: "0xaaa1388cD71e88Ae3D8432f16bed3c603a58aD34",
},
],
}
);
The buyer of the NFT will pay the fee recipient
This is in addition to the erc20TokenAmount that the buyer is paying for the NFT itself.
Collection-based Orders
const collectionBasedOrder = nftSwap.buildCollectionBasedOrder(
// Offering 5000 USDC
{ type: 'ERC20', tokenAddress: '0xa0b8...', amount: '5000000000' },
// Order is valid to buy any NFT in this collection
{ type: 'ERC721', tokenAddress: '0x5180...' },
MAKER_WALLET_ADDRESS,
);
This trade can be filled by anyone holding an NFT in the specified collection
- Behaves just like a normal order
- Can be filled as usual via fillSignedOrder()
"I want to buy any CryptoCoven for 3 ETH"
Just bring your UI
Let NFT Swap SDK do all the work
Resources
trader.xyz
swapsdk.xyz
github.com/trader-xyz/nft-swap-sdk
api.trader.xyz
@usetrader on twitter
#dev-help on our discord
App Ideas
- Real-time p2p trading
- Mobile NFT swap
- Shopify NFT store generator (meta-marketplace)
- Add NFT marketplace to an existing game
- Chrome extension for twitter where you can bid on NFTs directly in-app
App Ideas
Thank you
Questions?
ethdenver nft workshop
By John Johnson