Express Protocol SDK
The SDK for agentic onchain commerce
Express Protocol is a decentralized protocol built on top of the blockchain layer that lets any Web2 developer mint, trade, auction, and manage NFTs and real-world assets onchain — without writing a single line of Solidity.
The protocol wraps a suite of audited smart contracts and libraries in one typed SDK. It handles every interaction at the blockchain level, so you design a frontend, plug in a wallet, and call an SDK function to ship a fully-operating NFT or RWA application. Building an NFT marketplace used to take days or weeks — with Express it can be done in a few hours.
What you can build
Mint anything
Single or batch mint ERC-721 & ERC-1155 with creator royalties baked in.
Full marketplace
Fixed-price sales, buys, timed auctions and bidding on a shared order book.
Agent payments
Let AI agents pay & settle purchases autonomously with the x402 module.
Decentralized storage
Pin metadata & media to IPFS out of the box with the Pinata integration.
Why Express?
Through Express Protocol's SDK you can build anything from a simple NFT-minting dApp to a complex, multi-standard, multichain marketplace — with auctions, collections, royalties and more — without becoming a blockchain engineer first.
Challenges with building NFT dApps
- Nativity. Developers normally need deep web3 familiarity and must be proficient at writing smart contracts.
- Ease of use. Shipping a dApp means writing efficient functions, adding modifiers and access control, and a great deal more.
- Time consumption. Writing and testing those contracts from scratch takes a serious amount of time.
- Security. Moving assets and funds through dApps introduces vulnerabilities that have caused real exploits and total application failures in the past.
How Express Protocol solves them
The SDK lets developers create NFT dApps without being intimidated by blockchain and smart-contract complexity. It takes care of interacting at the blockchain level — you just design a frontend, set up a wallet, and call SDK functions to get a working dApp.
You never write contracts from scratch, so your energy goes into building innovative products instead of re-inventing the wheel. Express Protocol's codebase is open source and its smart contracts have been through multiple phases of auditing, so dApps built on top inherit that battle-tested foundation.
Liquidating assets
The centre of attention is liquidity. Today, the major problem with NFT marketplaces is a lack of — or unbalanced — liquidity across markets. An NFT can be listed on one marketplace while the potential bidders and buyers sit on another, unaware of the listing, so the order stays unfilled.
Express Protocol liquidates assets through a shared order book. Every marketplace built with the SDK shares all listings and placed orders, balancing liquidity across markets. In practice, an NFT listed on one marketplace can be filled through every other marketplace built on the SDK.
Installation & setup
Add the package, create an SDK instance with a signer, and you are ready to mint and trade onchain.
Install the package
The SDK ships on npm as pandora-express.
$ npm i pandora-express
# or
$ pnpm add pandora-express
Initialize the SDK
Create a single SDK instance and reuse it across your app. Pass a signer (a viem / web3 provider connected to the user's wallet) and the chain you want to operate on.
import { createPandoraExpressSDK } from "pandora-express";
// one instance, reused everywhere
export const sdk = createPandoraExpressSDK({
signer, // viem / web3 signer from the connected wallet
chainId: 137, // Polygon — see supported networks below
});
chainId. A token minted on a given network can be listed, sold or auctioned on that same network. Under the hood the SDK forwards a configured web3 instance and chainId to the protocol contracts.Supported networks
The protocol contracts are deployed across multiple EVM chains. Pass the network id as chainId when you create the SDK.
| Network | chainId |
|---|---|
| Polygon Mainnet | 137 |
| Polygon Mumbai (testnet) | 80001 |
| BSC Mainnet | 56 |
| BSC Testnet | 97 |
| Base · x402-ready | 8453 |
| Arbitrum One · x402-ready | 42161 |
Quickstart: mint & sell
From npm install to onchain in one file — mint an ERC-721 into Pandora's public collection, then list it on the shared order book.
import { createPandoraExpressSDK } from "pandora-express";
const sdk = createPandoraExpressSDK({ signer, chainId: 137 });
// 1 · mint an ERC-721 into the public collection
const receipt = await sdk.erc721.nft.mint(
web3, chainId,
minterAddress,
"ipfs://…/asset.json", // tokenURI
[[creator, 500]] // royalties: 5% (500 bps) to creator
);
const tokenId = receipt.events.RoyaltiesSetForTokenId.returnValues.tokenId;
// 2 · list it for sale on the shared order book
await sdk.erc721.order.sellNFT(
web3, chainId,
tokenId,
"25", // price
minterAddress
);
sdk.erc721.order.buyNFT(...). Continue to the ERC-721 reference for every function and its emitted events.Public vs. personal collections
Every token in Express lives in a collection. You can mint into Pandora's shared public collection to go live instantly, or deploy your own personal collection contract for a fully-branded storefront.
The API surface mirrors across both. Public-collection actions hang off nft (mint/burn) and order (trade); personal-collection actions hang off collection, which additionally exposes createCollection(). ERC-1155 follows the exact same shape under sdk.erc1155.*.
Create a personal collection
// ERC-1155 shown; ERC-721 exposes the same createCollection()
const result = await sdk.erc1155.collection.createCollection(
web3, chainId,
ownerAddress, // collection creator
uri, // token URL
description, // collection description
collectionRoyalties // royalties received by the owner
);
// the new contract address is emitted in ERC1155Deployed
const collectionAddress = result.events.ERC1155Deployed.returnValues._tokenAddress;
ERC-721 functions
The classic non-fungible standard — one-of-one collectibles, art and tickets. The full mint → trade → auction lifecycle is available through the same typed SDK, in both public and personal collections.
Function surface
Public-collection minting/burning lives under nft, trading lives under order, and personal-collection actions live under collection.
// public collection — mint / burn / read
sdk.erc721.nft.mint()
sdk.erc721.nft.burn()
sdk.erc721.nft.fetchTokenURI()
// trading — shared order book
sdk.erc721.order.sellNFT() // fixed-price listing
sdk.erc721.order.buyNFT()
sdk.erc721.order.sellNFTByBid() // auction
sdk.erc721.order.bid()
sdk.erc721.order.acceptBid() // execute a bid
sdk.erc721.order.withdrawBid()
sdk.erc721.order.cancelSale()
// personal collection — same lifecycle, your own contract
sdk.erc721.collection.createCollection()
sdk.erc721.collection.mint() / sellNFTByBid() / bid() / buyNFT() …
Mint
Mints a single ERC-721 token into the public collection with creator royalties baked in. Returns the on-chain transaction receipt.
| Parameter | Description |
|---|---|
| web3 | Web3 instance configured with the wallet provider. |
| chainId | Network id of the blockchain. |
| minterAddress | Address of the minter / creator. |
| tokenURI | Metadata URI string (e.g. an ipfs:// link). |
| royalties | Nested array [[recipient, fraction], …] — at most 10 recipients. |
const result = await sdk.erc721.nft.mint(
web3, chainId,
minterAddress,
tokenURI,
royalties // [[addr, 500]] → 5%
);
// tokenId is emitted in RoyaltiesSetForTokenId
const tokenId = result.events.RoyaltiesSetForTokenId.returnValues.tokenId;
Emitted events:
Batch mint
Mint many tokens in a single transaction — ideal for NFT drops. Pass an array of token URIs and per-token royalties. Each minted token emits its own RoyaltiesSetForTokenId so you can collect every new tokenId from the receipt.
const result = await sdk.erc721.nft.batchMint(
web3, chainId,
minterAddress,
["ipfs://…/1.json", "ipfs://…/2.json"], // tokenURIs
[[[creator, 500]], [[creator, 500]]] // per-token royalties
);
mint() but accepts arrays. Pin all your assets to IPFS first (see IPFS & Pinata), then pass the resulting URIs.Burn
Permanently destroys the token associated with tokenId.
const result = await sdk.erc721.nft.burn(
web3, chainId,
ownerAddress, // token owner
tokenId // id of token to burn
);
Sell (fixed price)
Lists a token for a fixed price on the shared order book. Emits TokenMetaReturn, from which you can read the saleId and full listing metadata.
const result = await sdk.erc721.order.sellNFT(
web3, chainId,
tokenId,
tokenPrice, // selling price
ownerAddress
);
// listing metadata (incl. saleId) is on TokenMetaReturn
const meta = result.events.TokenMetaReturn.returnValues.data;
// meta → { saleId, price, currentOwner, directSale, collectionAddress, … }
Buy
Fills a fixed-price listing by its saleId. On success, token ownership and the price money are transferred between the new and previous owner respectively.
await sdk.erc721.order.buyNFT(
web3, chainId,
saleId, // from the listing's TokenMetaReturn
buyerAddress,
price
);
Auction
Puts a token up for a timed auction. Emits the same TokenMetaReturn event as sellNFT; items on auction can then be bid on by others using the saleId.
const result = await sdk.erc721.order.sellNFTByBid(
web3, chainId,
tokenId,
initialPrice, // starting bid
ownerAddress,
120 // auctionTime — seconds (120 = 120s)
);
Bid
Places a bid on an item that is on auction. Emits BidOrderReturn describing the bid.
const result = await sdk.erc721.order.bid(
web3, chainId,
saleId, // item on auction
bidderAddress,
bidPrice
);
const bid = result.events.BidOrderReturn.returnValues.bid;
// bid → { saleId, price, buyerAddress, sellerAddress, withdrawn }
Execute bid
The seller accepts a specific bid before the auction ends. Emits BidExecuted, whose price is the amount at which the bid was accepted.
const result = await sdk.erc721.order.acceptBid(
web3, chainId,
saleId,
bidId, // the bid the seller accepts
sellerAddress
);
const soldFor = result.events.BidExecuted.returnValues.price;
Withdraw bid
A bidder withdraws their bid from an auction. On success the bid is removed and the money is transferred back to the bidder.
await sdk.erc721.order.withdrawBid(
web3, chainId,
saleId,
bidId,
buyerAddress
);
Cancel sale
Removes an item that was on direct sale or auction from sale.
await sdk.erc721.order.cancelSale(
web3, chainId,
sellerAddress,
saleId
);
Personal collections
Deploy your own ERC-721 collection contract and run the full lifecycle inside it via sdk.erc721.collection.*. The methods mirror the public-collection ones, with the collection address threaded through where needed.
Mint into a collection
const result = await sdk.erc721.collection.mint(
web3, chainId,
collectionAddress, // where the item is minted
tokenURI,
minterAddress,
royalties
);
const tokenId = result.events.RoyaltiesSetForTokenId.returnValues.tokenId;
Auction & bid in a collection
// auction inside your collection…
const a = await sdk.erc721.collection.sellNFTByBid(
web3, chainId, collectionAddress,
tokenId, initialPrice, ownerAddress, 120
);
const saleId = a.events.TokenMetaReturn.returnValues.data;
// …then bid on it
await sdk.erc721.collection.bid(web3, chainId, saleId, bidderAddress, bidPrice);
buyNFT(), acceptBid(), withdrawBid(), cancelSale() — is identical to the public-collection order.* functions, just namespaced under collection.Fetch token URI
Reads the metadata URI for a token — resolve it (e.g. through an IPFS gateway) to load the asset's JSON metadata and media.
const uri = await sdk.erc721.nft.fetchTokenURI(
web3, chainId,
tokenId
);
ERC-1155 functions
Fungible and non-fungible editions from a single contract — ideal for game items, passes and semi-fungible drops. The full mint-to-auction lifecycle is available with the same API surface as ERC-721, plus a token amount on every call.
Function surface
// public collection
sdk.erc1155.nft.mint() / burn()
sdk.erc1155.order.sellNFT() / buyNFT()
sdk.erc1155.order.sellNFTByBid() // auction
sdk.erc1155.order.bid() / acceptBid() / withdrawBid()
sdk.erc1155.order.cancelSale()
// personal collection
sdk.erc1155.collection.createCollection()
sdk.erc1155.collection.mint() / sellNFT() / bid() …
Mint (with royalties)
Mints tokenAmount copies of an ERC-1155 token. Returns the receipt; two events are emitted — TransferSingle (the mint) and RoyaltiesSetForTokenId (the royalty config).
| Parameter | Description |
|---|---|
| tokenAmount | Number of copies to mint. |
| tokenURI | Metadata URI string. |
| royalties | Nested array [[recipient, fraction], …] — at most 10 recipients. |
const result = await sdk.erc1155.nft.mint(
web3, chainId,
minterAddress,
5, // tokenAmount
tokenURI,
[[creator, "100"]] // royalties (max 10 recipients)
);
const transfer = result.events.TransferSingle.returnValues;
// transfer → { to, tokenId, value, … }
[[recipient1, fraction1], …, [recipientN, fractionN]] where N ≤ 10. A token minted on a given network can be listed for sale or auction on that same network.Sell, buy & auction
Identical to ERC-721 trading, with a token amount on every call since editions are semi-fungible.
Sell
const result = await sdk.erc1155.order.sellNFT(
web3, chainId,
tokenId, tokenPrice, ownerAddress,
10 // tokenAmount to sell
);
const meta = result.events.TokenMetaReturn.returnValues.data;
// meta → { saleId, price, numberOfTokens, collectionAddress, … }
Buy
Auction
// fill part of a listing
await sdk.erc1155.order.buyNFT(web3, chainId, saleId, buyerAddress, price, 3);
// auction an amount of the edition
await sdk.erc1155.order.sellNFTByBid(web3, chainId, tokenId, initialPrice, ownerAddress, 10);
Bid & execute bid
Bid on an ERC-1155 auction (with the token amount). Emits BidOrderReturn. The seller later calls acceptBid to settle.
const result = await sdk.erc1155.order.bid(
web3, chainId,
saleId, buyerAddress, bidPrice,
10 // amount of token
);
const bid = result.events.BidOrderReturn.returnValues.bid;
// seller accepts before auction end → BidExecuted
await sdk.erc1155.order.acceptBid(web3, chainId, saleId, bidId, sellerAddress);
ERC-1155 personal collections
Deploy your own ERC-1155 collection with createCollection(), then mint and trade inside it via sdk.erc1155.collection.*.
Create collection
Mint into collection
// 1 · deploy the collection
const c = await sdk.erc1155.collection.createCollection(
web3, chainId, ownerAddress, uri, description, collectionRoyalties
);
const addr = c.events.ERC1155Deployed.returnValues._tokenAddress;
// 2 · mint into it
const m = await sdk.erc1155.collection.mint(
web3, addr, tokenId, 10, tokenURI, minterAddress, royalties
);
const newId = m.events.RoyaltiesSetForTokenId.returnValues.tokenId;
New in 2026
x402 — payments for AI agents
Express Protocol now ships an optional x402 module built on the HTTP 402 stablecoin standard, so autonomous agents can price, authorize and settle NFT & RWA purchases on their own — paying in USDC over the wire, with no human checkout and no API keys to babysit.
402 and a price, and the caller attaches a signed stablecoin payment to retry — a natural fit for machine-to-machine commerce.How it fits Express
The module plugs into the same SDK you already use. When an agent (or any client) tries to buy a listing, the module can settle the purchase inline by attaching a USDC payment, then the protocol clears funds and transfers the asset into the buyer's wallet. Nothing about your minting, listing or order-book code changes — payment is just another method on buy.
Enabling the x402 module
x402 is opt-in. Import it and register it in the modules array when you create the SDK. Point the SDK at an x402-ready chain — Base (8453) or Arbitrum One (42161) — and choose the settlement asset.
import { createPandoraExpressSDK } from "pandora-express";
import { x402 } from "pandora-express/x402";
const sdk = createPandoraExpressSDK({
signer: agentWallet,
chainId: 8453, // Base
modules: [x402({ asset: "USDC" })], // settlement asset
});
paymentMethod: 'x402' flag on purchases.Agent checkout flow
With the module registered, an agent buys exactly like a human would — except payment is handled inline. Pass paymentMethod: 'x402' and the module prices, authorizes and settles the purchase in USDC.
// the agent buys — the x402 module settles payment inline
await sdk.erc721.order.buyNFT({
saleId,
buyerAddress: agentWallet.address,
paymentMethod: "x402", // HTTP 402 · USDC on Base
});
// works for RWA & ERC-1155 listings too
await sdk.erc1155.order.buyNFT({
saleId, amount: 1,
buyerAddress: agentWallet.address,
paymentMethod: "x402",
});
IPFS & Pinata
NFT metadata and media belong on decentralized storage. Express ships with a Pinata integration so you can pin assets to IPFS and get back an ipfs:// URI to use as your tokenURI.
Pin, then mint
The typical flow: upload your media, upload a metadata JSON that references it, then pass the resulting metadata URI into mint() or batchMint().
// 1 · pin media + metadata to IPFS via Pinata
const { uri } = await sdk.pinata.upload({
name: "Asset #1",
image: file, // media to pin
attributes: […],
});
// uri → "ipfs://…/metadata.json"
// 2 · mint with the pinned metadata URI
await sdk.erc721.nft.mint(web3, chainId, minterAddress, uri, royalties);
Build guides
End-to-end walkthroughs that build a working product on the SDK. Each pulls together minting, the shared order book, auctions and IPFS.