import "isomorphic-unfetch" ; import * as QueryString from "query-string" ; import { API_BASE_MAINNET , API_BASE_RINKEBY , API_PATH , ORDERBOOK_PATH , ORDERBOOK_VERSION , SITE_HOST_MAINNET , SITE_HOST_RINKEBY , } from "./constants" ; import { Network , OpenSeaAPIConfig , OpenSeaAsset , OpenSeaAssetBundle , OpenSeaAssetBundleQuery , OpenSeaAssetQuery , OpenSeaFungibleToken , OpenSeaFungibleTokenQuery , Order , OrderbookResponse , OrderJSON , OrderQuery , } from "./types" ; import { assetBundleFromJSON , assetFromJSON , delay , orderFromJSON , tokenFromJSON , } from "./utils/utils" ; export class OpenSeaAPI { /**
     * Host url for OpenSea
     */ public readonly hostUrl : string ; /**
     * Base url for the API
     */ public readonly apiBaseUrl : string ; /**
     * Page size to use for fetching orders
     */ public pageSize = 20 ; /**
     * Logger function to use when debugging
     */ public logger : ( arg : string ) => void ; private apiKey : string | undefined ; param config param logger constructor ( config , logger ? ) { this . apiKey = config . apiKey ; switch ( config . networkName ) { case Network . Rinkeby : this . apiBaseUrl = config . apiBaseUrl || API_BASE_RINKEBY ; this . hostUrl = SITE_HOST_RINKEBY ; break ; case Network . Main : default : this . apiBaseUrl = config . apiBaseUrl || API_BASE_MAINNET ; this . hostUrl = SITE_HOST_MAINNET ; break ; } this . logger = logger || ( ( arg ) => arg ) ; } param order param retries public async postOrder ( order , retries = 2 ) { let json ; try { json = ( await this . post ( `${ ORDERBOOK_PATH }/orders/post/` , order ) ) as OrderJSON ; } catch ( error ) { _throwOrContinue ( error , retries ) ; await delay ( 3000 ) ; return this . postOrder ( order , retries - 1 ) ; } return orderFromJSON ( json ) ; } param tokenAddress param tokenId param email public async postAssetWhitelist ( tokenAddress , tokenId , email ) { const json = await this . post < { success : boolean ; } > ( `${ API_PATH }/asset/${ tokenAddress }/${ tokenId }/whitelist/` , { email , } ) ; return ! ! json . success ; } param query public async getOrder ( query ) { const result = await this . get ( `${ ORDERBOOK_PATH }/orders/` , { limit : 1 , ... query , } ) ; let orderJSON ; if ( ORDERBOOK_VERSION == 0 ) { const json = result as OrderJSON [ ] ; orderJSON = json [ 0 ] ; } else { const json = result as OrderbookResponse ; orderJSON = json . orders [ 0 ] ; } if ( ! orderJSON ) { throw new Error ( `Not found: no matching order found` ) ; } return orderFromJSON ( orderJSON ) ; } param query param page public async getOrders ( query = { } , page = 1 ) { const result = await this . get ( `${ ORDERBOOK_PATH }/orders/` , { limit : this . pageSize , offset : ( page - 1 ) * this . pageSize , ... query , } ) ; if ( ORDERBOOK_VERSION == 0 ) { const json = result as OrderJSON [ ] ; return { orders : json . map ( ( j ) => orderFromJSON ( j ) ) , count : json . length , } ; } else { const json = result as OrderbookResponse ; return { orders : json . orders . map ( ( j ) => orderFromJSON ( j ) ) , count : json . count , } ; } } param tokenAddress param tokenId param retries public async getAsset ( { tokenAddress , tokenId , } , retries = 1 ) { let json ; try { json = await this . get ( `${ API_PATH }/asset/${ tokenAddress }/${ tokenId || 0 }/` ) ; } catch ( error ) { _throwOrContinue ( error , retries ) ; await delay ( 1000 ) ; return this . getAsset ( { tokenAddress , tokenId } , retries - 1 ) ; } return assetFromJSON ( json ) ; } param query param page public async getAssets ( query = { } , page = 1 ) { const json = await this . get < { estimated_count : number ; assets : unknown [ ] ; } > ( `${ API_PATH }/assets/` , { limit : this . pageSize , offset : ( page - 1 ) * this . pageSize , ... query , } ) ; return { assets : json . assets . map ( ( j ) => assetFromJSON ( j ) ) , estimatedCount : json . estimated_count , } ; } param query param page param retries public async getPaymentTokens ( query = { } , page = 1 , retries = 1 ) { let json ; try { json = await this . get < unknown [ ] > ( `${ API_PATH }/tokens/` , { ... query , limit : this . pageSize , offset : ( page - 1 ) * this . pageSize , } ) ; } catch ( error ) { _throwOrContinue ( error , retries ) ; await delay ( 1000 ) ; return this . getPaymentTokens ( query , page , retries - 1 ) ; } return { tokens : json . map ( ( t ) => tokenFromJSON ( t ) ) , } ; } param slug public async getBundle ( { slug , } ) { const json = await this . get ( `${ API_PATH }/bundle/${ slug }/` ) ; return json ? assetBundleFromJSON ( json ) : null ; } param query param page public async getBundles ( query = { } , page = 1 ) { const json = await this . get < { estimated_count : number ; bundles : unknown [ ] ; } > ( `${ API_PATH }/bundles/` , { ... query , limit : this . pageSize , offset : ( page - 1 ) * this . pageSize , } ) ; return { bundles : json . bundles . map ( ( j ) => assetBundleFromJSON ( j ) ) , estimatedCount : json . estimated_count , } ; } param apiPath param query public async get < T > ( apiPath , query = { } ) { const qs = QueryString . stringify ( query ) ; const url = `${ apiPath }?${ qs }` ; const response = await this . _fetch ( url ) ; return response . json ( ) ; } param apiPath param body param opts public async post < T > ( apiPath , body ? , opts = { } ) { const fetchOpts = { method : "POST" , body : body ? JSON . stringify ( body ) : undefined , headers : { Accept : "application/json" , "Content-Type" : "application/json" , } , ... opts , } ; const response = await this . _fetch ( apiPath , fetchOpts ) ; return response . json ( ) ; } param apiPath param body param opts public async put ( apiPath , body , opts = { } ) { return this . post ( apiPath , body , { method : "PUT" , ... opts , } ) ; } param apiPath param opts private async _fetch ( apiPath , opts = { } ) { const apiBase = this . apiBaseUrl ; const apiKey = this . apiKey ; const finalUrl = apiBase + apiPath ; const finalOpts = { ... opts , headers : { ... ( apiKey ? { "X-API-KEY" : apiKey } : { } ) , ... ( opts . headers || { } ) , } , } ; this . logger ( `Sending request: ${ finalUrl } ${ JSON . stringify ( finalOpts ) . substr ( 0 , 100 ) }...` ) ; return fetch ( finalUrl , finalOpts ) . then ( async ( res ) => this . _handleApiResponse ( res ) ) ; } private async _handleApiResponse ( response ) { if ( response . ok ) { this . logger ( `Got success: ${ response . status }` ) ; return response ; } let result ; let errorMessage ; try { result = await response . text ( ) ; result = JSON . parse ( result ) ; } catch { } this . logger ( `Got error ${ response . status }: ${ JSON . stringify ( result ) }` ) ; switch ( response . status ) { case 400 : errorMessage = result && result . errors ? result . errors . join ( ", " ) : `Invalid request: ${ JSON . stringify ( result ) }` ; break ; case 401 : case 403 : errorMessage = `Unauthorized. Full message was '${ JSON . stringify ( result ) }'` ; break ; case 404 : errorMessage = `Not found. Full message was '${ JSON . stringify ( result ) }'` ; break ; case 500 : errorMessage = `Internal server error. OpenSea has been alerted, but if the problem persists please contact us via Discord: https://discord.gg/ga8EJbv - full message was ${ JSON . stringify ( result ) }` ; break ; case 503 : errorMessage = `Service unavailable. Please try again in a few minutes. If the problem persists please contact us via Discord: https://discord.gg/ga8EJbv - full message was ${ JSON . stringify ( result ) }` ; break ; default : errorMessage = `Message: ${ JSON . stringify ( result ) }` ; break ; } throw new Error ( `API Error ${ response . status }: ${ errorMessage }` ) ; } } function _throwOrContinue ( error , retries ) { const isUnavailable = error instanceof Error && ! ! error . message && ( error . message . includes ( "503" ) || error . message . includes ( "429" ) ) ; if ( retries <= 0 || ! isUnavailable ) { throw error ; } }