OpenSea.js
A JavaScript library for crypto-native ecommerce: buying, selling, and bidding on any cryptogood. With OpenSea.js, you can easily build your own native marketplace for your non-fungible tokens, or NFTs. These can be ERC-721 or ERC-1155 (semi-fungible) items. You don't have to deploy your own smart contracts or backend orderbooks.
Published on GitHub and npm
Synopsis
This is the JavaScript SDK for OpenSea, the largest marketplace for NFTs.
It allows developers to access the official orderbook, filter it, create buy orders (offers), create sell orders (auctions), and complete trades programmatically.
Get started by requesting an API key and instantiating your own OpenSea SDK instance. Then you can create orders off-chain or fulfill orders on-chain, and listen to events (like ApproveAllAssets
or WrapEth
) in the process.
Happy seafaring! ⛵️
Installation
Switching to Node.js version 16 is required for SDK Version 3.0+ and to make sure common crypto dependencies work. Execute nvm use
, if you have Node Version Manager.
Then, in your project, run:
npm install --save opensea-js
Warning
Due to the use of git-url dependencies, versions of npm
below 8.5.2 are incompatible with this package due to broken integrity checksum validation.
Above version 8.5.2, npm
will no longer validate integrity checksums for git-url dependencies.
Install web3 too if you haven't already.
If you run into an error while building the dependencies and you're on a Mac, run this:
xcode-select --install
sudo xcode-select --switch /Library/Developer/CommandLineTools
sudo npm explore npm -g -- npm install node-gyp@latest
Getting Started
To get started, first request an API key here. Note the terms of use for using API data.
Then, create a new OpenSeaJS client, called an OpenSeaSDK 🚢, using your Web3 provider:
import * as Web3 from 'web3'
import { OpenSeaSDK, Network } from 'opensea-js'
const provider = new Web3.providers.HttpProvider('https://mainnet.infura.io')
const openseaSDK = new OpenSeaSDK(provider, {
networkName: Network.Main,
apiKey: YOUR_API_KEY
})
NOTE: for testnet, please use Network.Goerli
as the networkName
- Rinkeby was deprecated in 2022.
NOTE: Using the sample Infura provider above won't let you authorize transactions, which are needed when approving and trading assets and currency. To make transactions, you need a provider with a private key or mnemonic set.
In a browser with web3 or an extension like MetaMask or Coinbase Wallet, you can use window.ethereum
to access the native provider.
Fetching Assets
Assets are items on OpenSea. They can be non-fungible (conforming to standards like ERC721), semi-fungible (like ERC1155 assets), and even fungible (ERC20).
Assets are represented by the Asset
type, defined in TypeScript:
export interface Asset {
tokenId: string | null,
tokenAddress: string,
schemaName?: WyvernSchemaName,
name?: string,
decimals?: number
}
The Asset
type is the minimal type you need for most marketplace actions. WyvernSchemaName
is optional. If omitted, most actions will assume you're referring to a non-fungible, ERC721 asset. Other options include 'ERC20' and 'ERC1155'. You can import import { WyvernSchemaName } from "opensea-js/lib/types"
to get the full range of schemas supported.
You can fetch an asset using the OpenSeaAPI
, which will return an OpenSeaAsset
for you (OpenSeaAsset
extends Asset
):
const asset: OpenSeaAsset = await openseaSDK.api.getAsset({
tokenAddress,
tokenId,
})
Note that fungible ERC20 assets have null
as their token id.
Checking Balances and Ownerships
The nice thing about the Asset
type is that it unifies logic between fungibles, non-fungibles, and semi-fungibles.
Once you have an Asset
, you can see how many any account owns, regardless of whether it's an ERC-20 token or a non-fungible good:
const asset = {
tokenAddress: "0x06012c8cf97bead5deae237070f9587f8e7a266d",
tokenId: "1",
}
const balance = await openseaSDK.getAssetBalance({
accountAddress,
asset,
})
const ownsKitty = balance.greaterThan(0)
You can use this same method for fungible ERC-20 tokens like wrapped ETH (WETH). As a convenience, you can use this fungible wrapper for checking fungible balances:
const balanceOfWETH = await openseaSDK.getTokenBalance({
accountAddress,
tokenAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
})
Making Offers
Once you have your asset, you can do this to make an offer on it:
const { tokenId, tokenAddress } = YOUR_ASSET
const accountAddress = "0x1234..."
const offer = await openseaSDK.createBuyOrder({
asset: {
tokenId,
tokenAddress,
schemaName
},
accountAddress,
startAmount: 1.2,
})
When you make an offer on an item owned by an OpenSea user, that user will automatically get an email notifying them with the offer amount, if it's above their desired threshold.
Bidding on ENS Short Name Auctions
The Ethereum Name Service (ENS) is auctioning short (3-6 character) names that can be used for labeling wallet addresses and more. Learn more on the ENS FAQ.
To bid, you must use the ENS Short Name schema:
const {
tokenId,
tokenAddress,
name
} = ENS_ASSET
const offer = await openseaSDK.createBuyOrder({
asset: {
tokenId,
tokenAddress,
name,
schemaName: "ENSShortNameAuction"
},
accountAddress: "0x1234..."
startAmount: 1.2,
})
Offer Limits
Note: The total value of buy orders must not exceed 1000 x wallet balance.
Making Listings / Selling Items
To sell an asset, call createSellOrder
. You can do a fixed-price listing, where startAmount
is equal to endAmount
, or a declining Dutch auction, where endAmount
is lower and the price declines until expirationTime
is hit:
const expirationTime = Math.round(Date.now() / 1000 + 60 * 60 * 24)
const listing = await openseaSDK.createSellOrder({
asset: {
tokenId,
tokenAddress,
},
accountAddress,
startAmount: 3,
endAmount: 0.1,
expirationTime
})
The units for startAmount
and endAmount
are Ether, ETH. If you want to specify another ERC-20 token to use, see Using ERC-20 Tokens Instead of Ether.
See Listening to Events to respond to the setup transactions that occur the first time a user sells an item.
Creating English Auctions
English Auctions are auctions that start at a small amount (we recommend even doing 0!) and increase with every bid. At expiration time, the item sells to the highest bidder.
To create an English Auction, create a listing that waits for the highest bid by setting waitForHighestBid
to true
:
const paymentTokenAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
const startAmount = 0
const auction = await openseaSDK.createSellOrder({
asset: {
tokenId,
tokenAddress,
},
accountAddress,
startAmount,
expirationTime,
paymentTokenAddress,
waitForHighestBid: true
})
Note that auctions aren't supported with Ether directly due to limitations in Ethereum, so you have to use an ERC20 token, like Wrapped Ether (WETH), a stablecoin like DAI, etc. See Using ERC-20 Tokens Instead of Ether for more info.
Fetching Orders
To retrieve a list of offers and auctions on an asset, you can use an instance of the OpenSeaAPI
exposed on the client. Parameters passed into API filter objects are camel-cased and serialized before being sent as OpenSea API parameters:
const { orders, count } = await openseaSDK.api.getOrders({
assetContractAddress: tokenAddress,
tokenId,
side: "bid"
})
const { orders, count } = await openseaSDK.api.getOrders({
assetContractAddress: tokenAddress,
tokenId,
side: "ask"
}, 2)
Note that the listing price of an asset is equal to the currentPrice
of the lowest valid sell order on the asset. Users can lower their listing price without invalidating previous sell orders, so all get shipped down until they're canceled, or one is fulfilled.
To learn more about signatures, makers, takers, listingTime vs createdTime and other kinds of order terminology, please read the Terminology Section of the API Docs.
The available API filters for the orders endpoint is documented in the OrdersQueryOptions
interface below, but see the main API Docs for a playground, along with more up-to-date and detailed explanations.
side: "bid" | "ask",
protocol?: "seaport";
maker?: string,
taker?: string,
owner?: string,
sale_kind?: SaleKind,
assetContractAddress?: string,
paymentTokenAddress?: string;
tokenId?: number | string,
tokenIds?: Array<number | string>,
listedAfter?: number | string,
listedBefore?: number | string,
orderBy?: "created_date" | "eth_price",
orderDirection?: "asc" | "desc",
onlyEnglish?: boolean,
limit?: number,
offset?: number,
Buying Items
To buy an item, you need to fulfill a sell order. To do that, it's just one call:
const order = await openseaSDK.api.getOrder({ side: "ask", ... })
const accountAddress = "0x..."
const transactionHash = await openseaSDK.fulfillOrder({ order, accountAddress })
Note that the fulfillOrder
promise resolves when the transaction has been confirmed and mined to the blockchain. To get the transaction hash before this happens, add an event listener (see Listening to Events) for the TransactionCreated
event.
If the order is a sell order (order.side === "ask"
), the taker is the buyer and this will prompt the buyer to pay for the item(s).
Accepting Offers
Similar to fulfilling sell orders above, you need to fulfill a buy order on an item you own to receive the tokens in the offer.
const order = await openseaSDK.api.getOrder({ side: "bid", ... })
const accountAddress = "0x..."
await openseaSDK.fulfillOrder({ order, accountAddress })
If the order is a buy order (order.side === "bid"
), then the taker is the owner and this will prompt the owner to exchange their item(s) for whatever is being offered in return. See Listening to Events below to respond to the setup transactions that occur the first time a user accepts a bid.
Transferring Items or Coins (Gifting)
A handy feature in OpenSea.js is the ability to transfer any supported asset (fungible or non-fungible tokens) in one line of JavaScript.
To transfer an ERC-721 asset or an ERC-1155 asset, it's just one call:
const transactionHash = await openseaSDK.transfer({
asset: { tokenId, tokenAddress },
fromAddress,
toAddress
})
For fungible ERC-1155 assets, you can set schemaName
to "ERC1155" and pass a quantity
in to transfer multiple at once:
const transactionHash = await openseaSDK.transfer({
asset: {
tokenId,
tokenAddress,
schemaName: "ERC1155"
},
fromAddress,
toAddress,
quantity: 2,
})
To transfer fungible assets without token IDs, like ERC20 tokens, you can pass in an OpenSeaFungibleToken
as the asset
, set schemaName
to "ERC20", and include quantity
in base units (e.g. wei) to indicate how many.
Example for transferring 2 DAI ($2) to another address:
const paymentToken = (await openseaSDK.api.getPaymentTokens({ symbol: 'DAI'})).tokens[0]
const quantity = new BigNumber(Math.pow(10, paymentToken.decimals)).times(2)
const transactionHash = await openseaSDK.transfer({
asset: {
tokenId: null,
tokenAddress: paymentToken.address,
schemaName: "ERC20"
},
fromAddress,
toAddress,
quantity
})
For more information, check out the documentation for WyvernSchemas on https://projectopensea.github.io/opensea-js/.
Advanced
Interested in purchasing for users server-side or with a bot, scheduling future orders, or making bids in different ERC-20 tokens? OpenSea.js can help with that.
Scheduling Future Listings
You can create sell orders that aren't fulfillable until a future date. Just pass in a listingTime
(a UTC timestamp in seconds) to your SDK instance:
const auction = await openseaSDK.createSellOrder({
tokenAddress,
tokenId,
accountAddress,
startAmount: 1,
listingTime: Math.round(Date.now() / 1000 + 60 * 60 * 24)
})
Purchasing Items for Other Users
You can buy and transfer an item to someone else in one step! Just pass the recipientAddress
parameter:
const order = await openseaSDK.api.getOrder({ side: "ask", ... })
await openseaSDK.fulfillOrder({
order,
accountAddress,
recipientAddress
})
If the order is a sell order (order.side === "ask"
), the taker is the buyer and this will prompt the buyer to pay for the item(s) but send them to the recipientAddress
. If the order is a buy order ( "bid"
), the taker is the seller but the bid amount be sent to the recipientAddress
.
Bulk Transfers
A handy feature in OpenSea.js is the ability to transfer multiple items at once in a single transaction. This works by grouping together as many transferFrom
calls as the Ethereum gas limit allows, which is usually under 30 items, for most item contracts.
To make a bulk transfer, it's just one call:
const assets: Array<{tokenId: string; tokenAddress: string}> = [...]
const transactionHash = await openseaSDK.transferAll({
assets,
fromAddress,
toAddress
})
This will automatically approve the assets for trading and confirm the transaction for sending them.
Using ERC-20 Tokens Instead of Ether
Here's an example of listing the Genesis CryptoKitty for $100! No more needing to worry about the exchange rate:
const paymentTokenAddress = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"
const auction = await openseaSDK.createSellOrder({
tokenAddress: "0x06012c8cf97bead5deae237070f9587f8e7a266d",
tokenId: "1",
accountAddress: OWNERS_WALLET_ADDRESS,
startAmount: 100,
paymentTokenAddress
})
You can use getPaymentTokens
to search for tokens by symbol name. And you can even list all orders for a specific ERC-20 token by querying the API:
const token = (await openseaSDK.api.getPaymentTokens({ symbol: 'MANA'})).tokens[0]
const order = await openseaSDK.api.getOrders({
side: "ask",
paymentTokenAddress: token.address
})
Fun note: soon, all ERC-20 tokens will be allowed! This will mean you can create crazy offers on crypto collectibles using your own ERC-20 token. However, opensea.io will only display offers and auctions in ERC-20 tokens that it knows about, optimizing the user experience of order takers. Orders made with the following tokens will be shown on OpenSea:
Private Auctions
Now you can make auctions and listings that can only be fulfilled by an address or email of your choosing. This allows you to negotiate a price in some channel and sell for your chosen price on OpenSea, without having to trust that the counterparty will abide by your terms!
Here's an example of listing a Decentraland parcel for 10 ETH with a specific buyer address allowed to take it. No more needing to worry about whether they'll give you enough back!
const buyerAddress = "0x123..."
const listing = await openseaSDK.createSellOrder({
tokenAddress: "0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d",
tokenId: "115792089237316195423570985008687907832853042650384256231655107562007036952461",
accountAddress: OWNERS_WALLET_ADDRESS,
startAmount: 10,
buyerAddress
})
Listening to Events
Events are fired whenever transactions or orders are being created, and when transactions return receipts from recently mined blocks on the Ethereum blockchain.
Our recommendation is that you "forward" OpenSea events to your own store or state management system. Here's an example of doing that with a Redux action:
import { EventType } from 'opensea-js'
import * as ActionTypes from './index'
import { openSeaSDK } from '../globalSingletons'
handleSDKEvents() {
return async function(dispatch, getState) {
openSeaSDK.addListener(EventType.TransactionCreated, ({ transactionHash, event }) => {
console.info({ transactionHash, event })
dispatch({ type: ActionTypes.SET_PENDING_TRANSACTION_HASH, hash: transactionHash })
})
openSeaSDK.addListener(EventType.TransactionConfirmed, ({ transactionHash, event }) => {
console.info({ transactionHash, event })
if (event == EventType.MatchOrders || event == EventType.CancelOrder) {
dispatch({ type: ActionTypes.RESET_EXCHANGE })
}
})
openSeaSDK.addListener(EventType.TransactionDenied, ({ transactionHash, event }) => {
console.info({ transactionHash, event })
dispatch({ type: ActionTypes.RESET_EXCHANGE })
})
openSeaSDK.addListener(EventType.TransactionFailed, ({ transactionHash, event }) => {
console.info({ transactionHash, event })
dispatch({ type: ActionTypes.RESET_EXCHANGE })
})
openSeaSDK.addListener(EventType.InitializeAccount, ({ accountAddress }) => {
console.info({ accountAddress })
dispatch({ type: ActionTypes.INITIALIZE_PROXY })
})
openSeaSDK.addListener(EventType.WrapEth, ({ accountAddress, amount }) => {
console.info({ accountAddress, amount })
dispatch({ type: ActionTypes.WRAP_ETH })
})
openSeaSDK.addListener(EventType.UnwrapWeth, ({ accountAddress, amount }) => {
console.info({ accountAddress, amount })
dispatch({ type: ActionTypes.UNWRAP_WETH })
})
openSeaSDK.addListener(EventType.ApproveCurrency, ({ accountAddress, tokenAddress }) => {
console.info({ accountAddress, tokenAddress })
dispatch({ type: ActionTypes.APPROVE_WETH })
})
openSeaSDK.addListener(EventType.ApproveAllAssets, ({ accountAddress, proxyAddress, tokenAddress }) => {
console.info({ accountAddress, proxyAddress, tokenAddress })
dispatch({ type: ActionTypes.APPROVE_ALL_ASSETS })
})
openSeaSDK.addListener(EventType.ApproveAsset, ({ accountAddress, proxyAddress, tokenAddress, tokenId }) => {
console.info({ accountAddress, proxyAddress, tokenAddress, tokenId })
dispatch({ type: ActionTypes.APPROVE_ASSET })
})
openSeaSDK.addListener(EventType.CreateOrder, ({ order, accountAddress }) => {
console.info({ order, accountAddress })
dispatch({ type: ActionTypes.CREATE_ORDER })
})
openSeaSDK.addListener(EventType.OrderDenied, ({ order, accountAddress }) => {
console.info({ order, accountAddress })
dispatch({ type: ActionTypes.RESET_EXCHANGE })
})
openSeaSDK.addListener(EventType.MatchOrders, ({ buy, sell, accountAddress }) => {
console.info({ buy, sell, accountAddress })
dispatch({ type: ActionTypes.FULFILL_ORDER })
})
openSeaSDK.addListener(EventType.CancelOrder, ({ order, accountAddress }) => {
console.info({ order, accountAddress })
dispatch({ type: ActionTypes.CANCEL_ORDER })
})
}
}
To remove all listeners and start over, just call openseaSDK.removeAllListeners()
.
Learning More
Auto-generated documentation for each export is available here.
Migrating to version 1.0
See the Changelog.
Development Information
Setup
Before any development, install the required NPM dependencies:
npm install
And install TypeScript if you haven't already:
npm install -g tslint typescript
Build
Then, lint and build the library into the lib
directory:
npm run build
Or run the tests:
npm test
Note that the tests require access to both Infura and the OpenSea API. The timeout is adjustable via the test
script in package.json
.
Generate Documentation
Generate html docs, also available for browsing here:
yarn docs-build
Contributing
Contributions welcome! Please use GitHub issues for suggestions/concerns - if you prefer to express your intentions in code, feel free to submit a pull request.
Diagnosing Common Issues
-
Is the expirationTime
in the future? If not, change it to a time in the future.
-
Are the input addresses all strings? If not, convert them to strings.
-
Is your computer's internal clock accurate? If not, try enabling automatic clock adjustment locally or following this tutorial to update an Amazon EC2 instance.
Testing your branch locally
yarn link
yarn link opensea-js