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

  1. Build trade
  2. Sign trade
  3. 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

  1. Bid on an NFT

  2. View Bids on an NFT
  3. Accept a bid on an NFT

Bids

  1. Bid on an NFT

  2. View Bids on an NFT
  3. Accept a bid on an NFT

Bids

  1. Bid on an NFT

  2. View Bids on an NFT
  3. Accept a bid on an NFT

Bids

  1. Bid on an NFT

  2. View Bids on an NFT
  3. 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

  1. Bid on an NFT

  2. View Bids on an NFT
  3. Accept a bid on an NFT

Bids

  1. Bid on an NFT

  2. View Bids on an NFT
  3. 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

  1. Bid on an NFT

  2. View Bids on an NFT
  3. 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

  1. List an NFT for sale

  2. View all NFTs for Sale
  3. Buy an NFT for sale

Asks

  1. List an NFT for sale

  2. View NFT for sale order
  3. 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);
  1. List an NFT for sale

  2. View NFT for sale order
  3. Buy the NFT for sale

Asks

  1. List an NFT for sale

  2. View NFT for sale order
  3. Buy the NFT for sale

Asks

  1. List an NFT for sale

  2. View NFT for sale order
  3. 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

  1. List an NFT for sale

  2. View NFT for sale order
  3. Buy the NFT for sale

Asks

  1. List an NFT for sale

  2. View NFT for sale order
  3. 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;
  1. List an NFT for sale

  2. View NFT for sale order
  3. 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

ethdenver nft workshop

  • 1,264