Express Protocol Docs Back to Express Protocol
SDK Docs
Getting Started Overview

Express Protocol SDK

The SDK for agentic onchain commerce

v2 · agent-ready npm: pandora-express Open source · MIT

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.

Shared order book. Every marketplace built on Express shares one liquidity layer. An NFT listed in one app can be discovered and filled from any other app on the protocol — solving the fragmented, unbalanced liquidity that plagues siloed NFT markets.

What you can build

New in 2026 — x402 payments. Express now ships an optional module built on the HTTP 402 stablecoin standard so autonomous agents can price, authorize, and settle NFT & RWA purchases in USDC on their own. Read the x402 docs →
Getting Started Why Express?

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.

Audited foundation. Because the SDK is built on well-tested contracts, applications built with Express are hardened against the classes of threats and vulnerabilities that have historically plagued NFT dApps.

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.

Getting Started Installation

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.

terminal
$ 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.

sdk.tspandora-express
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
});
Every SDK method takes the active 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.

NetworkchainId
Polygon Mainnet137
Polygon Mumbai (testnet)80001
BSC Mainnet56
BSC Testnet97
Base · x402-ready8453
Arbitrum One · x402-ready42161
Modernized for 2026. Alongside the original Polygon and BSC deployments, Express now targets low-fee L2s — Base and Arbitrum — which are also the settlement chains for the x402 stablecoin module.
Getting Started Quickstart

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.

mint-and-sell.tspandora-express
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
);
That's a full mint-to-listing flow. Buyers on any Express marketplace can now fill the order via sdk.erc721.order.buyNFT(...). Continue to the ERC-721 reference for every function and its emitted events.
Getting Started Collections

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

create-collection.ts
// 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;
SDK · ERC-721 Overview

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.

sdk.erc721.nft.* sdk.erc721.order.* sdk.erc721.collection.*

Function surface

Public-collection minting/burning lives under nft, trading lives under order, and personal-collection actions live under collection.

erc721.d.ts
// 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()
SDK · ERC-721 Mint

Mint

sdk.erc721.nft.mint(web3, chainId, minterAddress, tokenURI, royalties)

Mints a single ERC-721 token into the public collection with creator royalties baked in. Returns the on-chain transaction receipt.

ParameterDescription
web3Web3 instance configured with the wallet provider.
chainIdNetwork id of the blockchain.
minterAddressAddress of the minter / creator.
tokenURIMetadata URI string (e.g. an ipfs:// link).
royaltiesNested array [[recipient, fraction], …] — at most 10 recipients.
mint.ts
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:

RoyaltiesSetForTokenId
SDK · ERC-721 Batch mint

Batch mint

sdk.erc721.nft.batchMint(web3, chainId, minterAddress, tokenURIs, royalties)

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.

batch-mint.ts
const result = await sdk.erc721.nft.batchMint(
  web3, chainId,
  minterAddress,
  ["ipfs://…/1.json", "ipfs://…/2.json"],  // tokenURIs
  [[[creator, 500]], [[creator, 500]]]       // per-token royalties
);
Batch minting mirrors mint() but accepts arrays. Pin all your assets to IPFS first (see IPFS & Pinata), then pass the resulting URIs.
SDK · ERC-721 Burn

Burn

sdk.erc721.nft.burn(web3, chainId, ownerAddress, tokenId)

Permanently destroys the token associated with tokenId.

burn.ts
const result = await sdk.erc721.nft.burn(
  web3, chainId,
  ownerAddress,   // token owner
  tokenId         // id of token to burn
);
SDK · ERC-721 Sell

Sell (fixed price)

sdk.erc721.order.sellNFT(web3, chainId, tokenId, tokenPrice, ownerAddress)

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.

sell.ts
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, … }
TokenMetaReturn
SDK · ERC-721 Buy

Buy

sdk.erc721.order.buyNFT(web3, chainId, saleId, buyerAddress, price)

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.

buy.ts
await sdk.erc721.order.buyNFT(
  web3, chainId,
  saleId,         // from the listing's TokenMetaReturn
  buyerAddress,
  price
);
SDK · ERC-721 Auction

Auction

sdk.erc721.order.sellNFTByBid(web3, chainId, tokenId, initialPrice, ownerAddress, auctionTime)

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.

auction.ts
const result = await sdk.erc721.order.sellNFTByBid(
  web3, chainId,
  tokenId,
  initialPrice,   // starting bid
  ownerAddress,
  120             // auctionTime — seconds (120 = 120s)
);
TokenMetaReturn
SDK · ERC-721 Bid

Bid

sdk.erc721.order.bid(web3, chainId, saleId, bidderAddress, bidPrice)

Places a bid on an item that is on auction. Emits BidOrderReturn describing the bid.

bid.ts
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 }
BidOrderReturn
SDK · ERC-721 Execute bid

Execute bid

sdk.erc721.order.acceptBid(web3, chainId, saleId, bidId, sellerAddress)

The seller accepts a specific bid before the auction ends. Emits BidExecuted, whose price is the amount at which the bid was accepted.

execute-bid.ts
const result = await sdk.erc721.order.acceptBid(
  web3, chainId,
  saleId,
  bidId,          // the bid the seller accepts
  sellerAddress
);

const soldFor = result.events.BidExecuted.returnValues.price;
BidExecuted
SDK · ERC-721 Withdraw bid

Withdraw bid

sdk.erc721.order.withdrawBid(web3, chainId, saleId, bidId, buyerAddress)

A bidder withdraws their bid from an auction. On success the bid is removed and the money is transferred back to the bidder.

withdraw-bid.ts
await sdk.erc721.order.withdrawBid(
  web3, chainId,
  saleId,
  bidId,
  buyerAddress
);
SDK · ERC-721 Cancel sale

Cancel sale

sdk.erc721.order.cancelSale(web3, chainId, sellerAddress, saleId)

Removes an item that was on direct sale or auction from sale.

cancel-sale.ts
await sdk.erc721.order.cancelSale(
  web3, chainId,
  sellerAddress,
  saleId
);
SDK · ERC-721 Personal collections

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

sdk.erc721.collection.mint(web3, chainId, collectionAddress, tokenURI, minterAddress, royalties)
collection-mint.ts
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

sdk.erc721.collection.sellNFTByBid(web3, chainId, collectionAddress, tokenId, initialPrice, ownerAddress, auctionTime)
collection-auction.ts
// 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);
The rest of the lifecycle — buyNFT(), acceptBid(), withdrawBid(), cancelSale() — is identical to the public-collection order.* functions, just namespaced under collection.
SDK · ERC-721 Token URI

Fetch token URI

sdk.erc721.nft.fetchTokenURI(web3, chainId, tokenId)

Reads the metadata URI for a token — resolve it (e.g. through an IPFS gateway) to load the asset's JSON metadata and media.

token-uri.ts
const uri = await sdk.erc721.nft.fetchTokenURI(
  web3, chainId,
  tokenId
);
SDK · ERC-1155 Overview

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.

sdk.erc1155.nft.* sdk.erc1155.order.* sdk.erc1155.collection.*

Function surface

erc1155.d.ts
// 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()
SDK · ERC-1155 Mint

Mint (with royalties)

sdk.erc1155.nft.mint(web3, chainId, minterAddress, tokenAmount, tokenURI, royalties)

Mints tokenAmount copies of an ERC-1155 token. Returns the receipt; two events are emitted — TransferSingle (the mint) and RoyaltiesSetForTokenId (the royalty config).

ParameterDescription
tokenAmountNumber of copies to mint.
tokenURIMetadata URI string.
royaltiesNested array [[recipient, fraction], …] — at most 10 recipients.
erc1155-mint.ts
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, … }
TransferSingleRoyaltiesSetForTokenId
Royalties shape. [[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.
SDK · ERC-1155 Sell · Buy · Auction

Sell, buy & auction

Identical to ERC-721 trading, with a token amount on every call since editions are semi-fungible.

Sell

sdk.erc1155.order.sellNFT(web3, chainId, tokenId, tokenPrice, ownerAddress, tokenAmount)
erc1155-sell.ts
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, … }
TokenMetaReturn

Buy

sdk.erc1155.order.buyNFT(web3, chainId, saleId, buyerAddress, price, amount)

Auction

sdk.erc1155.order.sellNFTByBid(web3, chainId, tokenId, initialPrice, ownerAddress, amount)
erc1155-buy-auction.ts
// 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);
SDK · ERC-1155 Bid & execute

Bid & execute bid

sdk.erc1155.order.bid(web3, chainId, saleId, buyerAddress, bidPrice, amount)

Bid on an ERC-1155 auction (with the token amount). Emits BidOrderReturn. The seller later calls acceptBid to settle.

erc1155-bid.ts
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);
BidOrderReturnBidExecuted
SDK · ERC-1155 Personal collections

ERC-1155 personal collections

Deploy your own ERC-1155 collection with createCollection(), then mint and trade inside it via sdk.erc1155.collection.*.

Create collection

sdk.erc1155.collection.createCollection(web3, chainId, ownerAddress, uri, description, collectionRoyalties)

Mint into collection

sdk.erc1155.collection.mint(web3, collectionAddress, tokenId, tokenAmount, tokenURI, minterAddress, royalties)
erc1155-collection.ts
// 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;
ERC1155DeployedRoyaltiesSetForTokenId
x402 Payments What is x402

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.

USDC stablecoin Base & Arbitrum HTTP 402 native opt-in module
x402 revives the long-dormant HTTP 402 "Payment Required" status code as a real settlement rail. Instead of a human clicking through a checkout, a protected endpoint answers a request with 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.

1 · Agent requests an asset. It hits a protected endpoint and receives an HTTP 402 response carrying the price.
2 · x402 signs a USDC payment. The module attaches a stablecoin payment header — no seat at a checkout page, no stored card.
3 · Protocol settles & transfers. Funds clear on Base or Arbitrum and the NFT/RWA lands in the agent's wallet.
x402 Payments Enabling

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.

sdk-x402.tspandora-express/x402
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
});
Because it's a module, x402 is entirely optional — omit it and the SDK behaves exactly as before. Add it and you unlock the paymentMethod: 'x402' flag on purchases.
x402 Payments Agent checkout

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.

agent-checkout.ts@pandora-express/x402
// 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",
});
Autonomous end to end. No human checkout, no card on file, no API key rotation — the agent's wallet signs a USDC payment, funds settle on Base or Arbitrum, and the asset transfers in a single call.
Guides & Storage IPFS & Pinata

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().

pin-and-mint.ts
// 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);
Express supports presigned Pinata uploads, so large media can be pushed straight to IPFS from the client without proxying bytes through your backend.
Guides & Storage Build guides

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.

The complete, runnable guide sources live in the docs repo: Pandora-Finance/express-protocol-docs. Clone the SDK repo to get started.