import { BigNumber } from "bignumber.js" ; import { isValidAddress } from "ethereumjs-util" ; import { EventEmitter , EventSubscription } from "fbemitter" ; import * as _ from "lodash" ; import Web3 from "web3" ; import { WyvernProtocol } from "wyvern-js" ; import * as WyvernSchemas from "wyvern-schemas" ; import { Schema } from "wyvern-schemas/dist/types" ; import { OpenSeaAPI } from "./api" ; import { CHEEZE_WIZARDS_BASIC_TOURNAMENT_ADDRESS , CHEEZE_WIZARDS_BASIC_TOURNAMENT_RINKEBY_ADDRESS , CHEEZE_WIZARDS_GUILD_ADDRESS , CHEEZE_WIZARDS_GUILD_RINKEBY_ADDRESS , CK_ADDRESS , CK_RINKEBY_ADDRESS , DECENTRALAND_ESTATE_ADDRESS , DEFAULT_BUYER_FEE_BASIS_POINTS , DEFAULT_GAS_INCREASE_FACTOR , DEFAULT_MAX_BOUNTY , DEFAULT_SELLER_FEE_BASIS_POINTS , DEFAULT_WRAPPED_NFT_LIQUIDATION_UNISWAP_SLIPPAGE_IN_BASIS_POINTS , ENJIN_COIN_ADDRESS , INVERSE_BASIS_POINT , MANA_ADDRESS , MIN_EXPIRATION_SECONDS , NULL_ADDRESS , NULL_BLOCK_HASH , OPENSEA_FEE_RECIPIENT , OPENSEA_SELLER_BOUNTY_BASIS_POINTS , ORDER_MATCHING_LATENCY_SECONDS , RPC_URL_PATH , SELL_ORDER_BATCH_SIZE , STATIC_CALL_CHEEZE_WIZARDS_ADDRESS , STATIC_CALL_CHEEZE_WIZARDS_RINKEBY_ADDRESS , STATIC_CALL_DECENTRALAND_ESTATES_ADDRESS , STATIC_CALL_TX_ORIGIN_ADDRESS , STATIC_CALL_TX_ORIGIN_RINKEBY_ADDRESS , UNISWAP_FACTORY_ADDRESS_MAINNET , UNISWAP_FACTORY_ADDRESS_RINKEBY , WRAPPED_NFT_FACTORY_ADDRESS_MAINNET , WRAPPED_NFT_FACTORY_ADDRESS_RINKEBY , WRAPPED_NFT_LIQUIDATION_PROXY_ADDRESS_MAINNET , WRAPPED_NFT_LIQUIDATION_PROXY_ADDRESS_RINKEBY , } from "./constants" ; import { CanonicalWETH , CheezeWizardsBasicTournament , DecentralandEstates , ERC20 , ERC721 , getMethod , StaticCheckCheezeWizards , StaticCheckDecentralandEstates , StaticCheckTxOrigin , UniswapExchange , UniswapFactory , WrappedNFT , WrappedNFTFactory , WrappedNFTLiquidationProxy , } from "./contracts" ; import { MAX_ERROR_LENGTH , requireOrderCalldataCanMatch , requireOrdersCanMatch , } from "./debugging" ; import { Asset , ComputedFees , ECSignature , EventData , EventType , FeeMethod , HowToCall , Network , OpenSeaAPIConfig , OpenSeaAsset , OpenSeaFungibleToken , Order , OrderSide , PartialReadonlyContractAbi , SaleKind , TokenStandardVersion , UnhashedOrder , UnsignedOrder , WyvernAsset , WyvernAtomicMatchParameters , WyvernFTAsset , WyvernNFTAsset , WyvernSchemaName , } from "./types" ; import { encodeAtomicizedBuy , encodeAtomicizedSell , encodeAtomicizedTransfer , encodeBuy , encodeCall , encodeProxyCall , encodeSell , encodeTransferCall , } from "./utils/schema" ; import { annotateERC20TransferABI , annotateERC721TransferABI , assignOrdersToSides , confirmTransaction , delay , estimateCurrentPrice , estimateGas , getCurrentGasPrice , getNonCompliantApprovalAddress , getOrderHash , getTransferFeeSettings , getWyvernAsset , getWyvernBundle , isContractAddress , makeBigNumber , onDeprecated , orderToJSON , personalSignAsync , promisifyCall , rawCall , sendRawTransaction , validateAndFormatWalletAddress , } from "./utils/utils" ; export class OpenSeaPort { public web3 : Web3 ; public web3ReadOnly : Web3 ; public logger : ( arg : string ) => void ; public readonly api : OpenSeaAPI ; public gasPriceAddition = new BigNumber ( 3 ) ; public gasIncreaseFactor = DEFAULT_GAS_INCREASE_FACTOR ; private _networkName : Network ; private _wyvernProtocol : WyvernProtocol ; private _wyvernProtocolReadOnly : WyvernProtocol ; private _emitter : EventEmitter ; private _wrappedNFTFactoryAddress : string ; private _wrappedNFTLiquidationProxyAddress : string ; private _uniswapFactoryAddress : string ; param provider param apiConfig param logger constructor ( provider , apiConfig = { } , logger ? ) { apiConfig . networkName = apiConfig . networkName || Network . Main ; this . api = new OpenSeaAPI ( apiConfig ) ; this . _networkName = apiConfig . networkName ; const readonlyProvider = new Web3 . providers . HttpProvider ( `${ this . api . apiBaseUrl }/${ RPC_URL_PATH }` ) ; const useReadOnlyProvider = apiConfig . useReadOnlyProvider ?? true ; this . web3 = new Web3 ( provider ) ; this . web3ReadOnly = useReadOnlyProvider ? new Web3 ( readonlyProvider ) : this . web3 ; this . _wyvernProtocol = new WyvernProtocol ( provider , { network : this . _networkName , } ) ; this . _wyvernProtocolReadOnly = useReadOnlyProvider ? new WyvernProtocol ( readonlyProvider , { network : this . _networkName , } ) : this . _wyvernProtocol ; this . _wrappedNFTFactoryAddress = this . _networkName == Network . Main ? WRAPPED_NFT_FACTORY_ADDRESS_MAINNET : WRAPPED_NFT_FACTORY_ADDRESS_RINKEBY ; this . _wrappedNFTLiquidationProxyAddress = this . _networkName == Network . Main ? WRAPPED_NFT_LIQUIDATION_PROXY_ADDRESS_MAINNET : WRAPPED_NFT_LIQUIDATION_PROXY_ADDRESS_RINKEBY ; this . _uniswapFactoryAddress = this . _networkName == Network . Main ? UNISWAP_FACTORY_ADDRESS_MAINNET : UNISWAP_FACTORY_ADDRESS_RINKEBY ; this . _emitter = new EventEmitter ( ) ; this . logger = logger || ( ( arg ) => arg ) ; } param event param listener param once public addListener ( event , listener , once = false ) { const subscription = once ? this . _emitter . once ( event , listener ) : this . _emitter . addListener ( event , listener ) ; return subscription ; } param subscription public removeListener ( subscription ) { subscription . remove ( ) ; } param event public removeAllListeners ( event ? ) { this . _emitter . removeAllListeners ( event ) ; } param param0 param assets param accountAddress public async wrapAssets ( { assets , accountAddress , } ) { const schema = this . _getSchema ( WyvernSchemaName . ERC721 ) ; const wyAssets = assets . map ( ( a ) => getWyvernAsset ( schema , a ) ) ; const tokenIds = wyAssets . map ( ( a ) => a . id ) ; const tokenAddresses = wyAssets . map ( ( a ) => a . address ) ; const isMixedBatchOfAssets = ! tokenAddresses . every ( ( val , _i , arr ) => val === arr [ 0 ] ) ; this . _dispatch ( EventType . WrapAssets , { assets : wyAssets , accountAddress } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : this . _wrappedNFTLiquidationProxyAddress , value : 0 , data : encodeCall ( getMethod ( WrappedNFTLiquidationProxy , "wrapNFTs" ) , [ tokenIds , tokenAddresses , isMixedBatchOfAssets , ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . WrapAssets , "Wrapping Assets" ) ; } param param0 param assets param destinationAddresses param accountAddress public async unwrapAssets ( { assets , destinationAddresses , accountAddress , } ) { if ( ! assets || ! destinationAddresses || assets . length != destinationAddresses . length ) { throw new Error ( "The 'assets' and 'destinationAddresses' arrays must exist and have the same length." ) ; } const schema = this . _getSchema ( WyvernSchemaName . ERC721 ) ; const wyAssets = assets . map ( ( a ) => getWyvernAsset ( schema , a ) ) ; const tokenIds = wyAssets . map ( ( a ) => a . id ) ; const tokenAddresses = wyAssets . map ( ( a ) => a . address ) ; const isMixedBatchOfAssets = ! tokenAddresses . every ( ( val , _i , arr ) => val === arr [ 0 ] ) ; this . _dispatch ( EventType . UnwrapAssets , { assets : wyAssets , accountAddress , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : this . _wrappedNFTLiquidationProxyAddress , value : 0 , data : encodeCall ( getMethod ( WrappedNFTLiquidationProxy , "unwrapNFTs" ) , [ tokenIds , tokenAddresses , destinationAddresses , isMixedBatchOfAssets , ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . UnwrapAssets , "Unwrapping Assets" ) ; } param param0 param assets param accountAddress param uniswapSlippageAllowedInBasisPoints public async liquidateAssets ( { assets , accountAddress , uniswapSlippageAllowedInBasisPoints , } ) { const uniswapSlippage = uniswapSlippageAllowedInBasisPoints === 0 ? DEFAULT_WRAPPED_NFT_LIQUIDATION_UNISWAP_SLIPPAGE_IN_BASIS_POINTS : uniswapSlippageAllowedInBasisPoints ; const schema = this . _getSchema ( WyvernSchemaName . ERC721 ) ; const wyAssets = assets . map ( ( a ) => getWyvernAsset ( schema , a ) ) ; const tokenIds = wyAssets . map ( ( a ) => a . id ) ; const tokenAddresses = wyAssets . map ( ( a ) => a . address ) ; const isMixedBatchOfAssets = ! tokenAddresses . every ( ( val , _i , arr ) => val === arr [ 0 ] ) ; this . _dispatch ( EventType . LiquidateAssets , { assets : wyAssets , accountAddress , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : this . _wrappedNFTLiquidationProxyAddress , value : 0 , data : encodeCall ( getMethod ( WrappedNFTLiquidationProxy , "liquidateNFTs" ) , [ tokenIds , tokenAddresses , isMixedBatchOfAssets , uniswapSlippage ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . LiquidateAssets , "Liquidating Assets" ) ; } param param0 param numTokensToBuy param amount param contractAddress param accountAddress public async purchaseAssets ( { numTokensToBuy , amount , contractAddress , accountAddress , } ) { this . _dispatch ( EventType . PurchaseAssets , { amount , contractAddress , accountAddress , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : this . _wrappedNFTLiquidationProxyAddress , value : amount , data : encodeCall ( getMethod ( WrappedNFTLiquidationProxy , "purchaseNFTs" ) , [ numTokensToBuy , contractAddress ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . PurchaseAssets , "Purchasing Assets" ) ; } param param0 param numTokens param isBuying param contractAddress public async getQuoteFromUniswap ( { numTokens , isBuying , contractAddress , } ) { const wrappedNFTFactoryContract = this . web3 . eth . contract ( WrappedNFTFactory as Web3 . AbiDefinition [ ] ) ; const wrappedNFTFactory = await wrappedNFTFactoryContract . at ( this . _wrappedNFTFactoryAddress ) ; const wrappedNFTAddress = await wrappedNFTFactory . nftContractToWrapperContract ( contractAddress ) ; const wrappedNFTContract = this . web3 . eth . contract ( WrappedNFT as Web3 . AbiDefinition [ ] ) ; const wrappedNFT = await wrappedNFTContract . at ( wrappedNFTAddress ) ; const uniswapFactoryContract = this . web3 . eth . contract ( UniswapFactory as Web3 . AbiDefinition [ ] ) ; const uniswapFactory = await uniswapFactoryContract . at ( this . _uniswapFactoryAddress ) ; const uniswapExchangeAddress = await uniswapFactory . getExchange ( wrappedNFTAddress ) ; const uniswapExchangeContract = this . web3 . eth . contract ( UniswapExchange as Web3 . AbiDefinition [ ] ) ; const uniswapExchange = await uniswapExchangeContract . at ( uniswapExchangeAddress ) ; const amount = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( numTokens ) , wrappedNFT . decimals ( ) ) ; if ( isBuying ) { return parseInt ( await uniswapExchange . getEthToTokenOutputPrice ( amount ) ) ; } else { return parseInt ( await uniswapExchange . getTokenToEthInputPrice ( amount ) ) ; } } param param0 param amountInEth param accountAddress public async wrapEth ( { amountInEth , accountAddress , } ) { const token = WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther ; const amount = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( amountInEth ) , token . decimals ) ; this . _dispatch ( EventType . WrapEth , { accountAddress , amount } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : token . address , value : amount , data : encodeCall ( getMethod ( CanonicalWETH , "deposit" ) , [ ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . WrapEth , "Wrapping ETH" ) ; } param param0 param amountInEth param accountAddress public async unwrapWeth ( { amountInEth , accountAddress , } ) { const token = WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther ; const amount = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( amountInEth ) , token . decimals ) ; this . _dispatch ( EventType . UnwrapWeth , { accountAddress , amount } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : token . address , value : 0 , data : encodeCall ( getMethod ( CanonicalWETH , "withdraw" ) , [ amount . toString ( ) , ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . UnwrapWeth , "Unwrapping W-ETH" ) ; } param param0 param assets param collection param quantities param accountAddress param startAmount param expirationTime param paymentTokenAddress param sellOrder param referrerAddress public async createBundleBuyOrder ( { assets , collection , quantities , accountAddress , startAmount , expirationTime = 0 , paymentTokenAddress , sellOrder , referrerAddress , } ) { quantities = quantities || assets . map ( ( _a ) => 1 ) ; paymentTokenAddress = paymentTokenAddress || WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther . address ; const order = await this . _makeBundleBuyOrder ( { assets , collection , quantities , accountAddress , startAmount , expirationTime , paymentTokenAddress , extraBountyBasisPoints : 0 , sellOrder , referrerAddress , } ) ; await this . _buyOrderValidationAndApprovals ( { order , accountAddress } ) ; const hashedOrder = { ... order , hash : getOrderHash ( order ) , } ; let signature ; try { signature = await this . _authorizeOrder ( hashedOrder ) ; } catch ( error ) { console . error ( error ) ; throw new Error ( "You declined to authorize your offer" ) ; } const orderWithSignature = { ... hashedOrder , ... signature , } ; return this . validateAndPostOrder ( orderWithSignature ) ; } param param0 param asset param accountAddress param startAmount param quantity param expirationTime param paymentTokenAddress param sellOrder param referrerAddress public async createBuyOrder ( { asset , accountAddress , startAmount , quantity = 1 , expirationTime = 0 , paymentTokenAddress , sellOrder , referrerAddress , } ) { paymentTokenAddress = paymentTokenAddress || WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther . address ; const order = await this . _makeBuyOrder ( { asset , quantity , accountAddress , startAmount , expirationTime , paymentTokenAddress , extraBountyBasisPoints : 0 , sellOrder , referrerAddress , } ) ; await this . _buyOrderValidationAndApprovals ( { order , accountAddress } ) ; const hashedOrder = { ... order , hash : getOrderHash ( order ) , } ; let signature ; try { signature = await this . _authorizeOrder ( hashedOrder ) ; } catch ( error ) { console . error ( error ) ; throw new Error ( "You declined to authorize your offer" ) ; } const orderWithSignature = { ... hashedOrder , ... signature , } ; return this . validateAndPostOrder ( orderWithSignature ) ; } param param0 param tokenId param tokenAddress param asset param accountAddress param startAmount param endAmount param quantity param listingTime param expirationTime param waitForHighestBid param englishAuctionReservePrice param paymentTokenAddress param extraBountyBasisPoints param buyerAddress param buyerEmail public async createSellOrder ( { asset , accountAddress , startAmount , endAmount , quantity = 1 , listingTime , expirationTime = 0 , waitForHighestBid = false , englishAuctionReservePrice , paymentTokenAddress , extraBountyBasisPoints = 0 , buyerAddress , buyerEmail , } ) { const order = await this . _makeSellOrder ( { asset , quantity , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , englishAuctionReservePrice , paymentTokenAddress : paymentTokenAddress || NULL_ADDRESS , extraBountyBasisPoints , buyerAddress : buyerAddress || NULL_ADDRESS , } ) ; await this . _sellOrderValidationAndApprovals ( { order , accountAddress } ) ; if ( buyerEmail ) { await this . _createEmailWhitelistEntry ( { order , buyerEmail } ) ; } const hashedOrder = { ... order , hash : getOrderHash ( order ) , } ; let signature ; try { signature = await this . _authorizeOrder ( hashedOrder ) ; } catch ( error ) { console . error ( error ) ; throw new Error ( "You declined to authorize your auction" ) ; } const orderWithSignature = { ... hashedOrder , ... signature , } ; return this . validateAndPostOrder ( orderWithSignature ) ; } param param0 param assets param accountAddress param startAmount param endAmount param quantity param listingTime param expirationTime param waitForHighestBid param paymentTokenAddress param extraBountyBasisPoints param buyerAddress param buyerEmail param numberOfOrders returns public async createFactorySellOrders ( { assets , accountAddress , startAmount , endAmount , quantity = 1 , listingTime , expirationTime = 0 , waitForHighestBid = false , paymentTokenAddress , extraBountyBasisPoints = 0 , buyerAddress , buyerEmail , numberOfOrders = 1 , } ) { if ( numberOfOrders < 1 ) { throw new Error ( "Need to make at least one sell order" ) ; } if ( ! assets || ! assets . length ) { throw new Error ( "Need at least one asset to create orders for" ) ; } if ( _ . uniqBy ( assets , ( a ) => a . tokenAddress ) . length !== 1 ) { throw new Error ( "All assets must be on the same factory contract address" ) ; } const dummyOrder = await this . _makeSellOrder ( { asset : assets [ 0 ] , quantity , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , paymentTokenAddress : paymentTokenAddress || NULL_ADDRESS , extraBountyBasisPoints , buyerAddress : buyerAddress || NULL_ADDRESS , } ) ; await this . _sellOrderValidationAndApprovals ( { order : dummyOrder , accountAddress , } ) ; const _makeAndPostOneSellOrder = async ( asset ) => { const order = await this . _makeSellOrder ( { asset , quantity , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , paymentTokenAddress : paymentTokenAddress || NULL_ADDRESS , extraBountyBasisPoints , buyerAddress : buyerAddress || NULL_ADDRESS , } ) ; if ( buyerEmail ) { await this . _createEmailWhitelistEntry ( { order , buyerEmail } ) ; } const hashedOrder = { ... order , hash : getOrderHash ( order ) , } ; let signature ; try { signature = await this . _authorizeOrder ( hashedOrder ) ; } catch ( error ) { console . error ( error ) ; throw new Error ( "You declined to authorize your auction, or your web3 provider can't sign using personal_sign. Try 'web3-provider-engine' and make sure a mnemonic is set. Just a reminder: there's no gas needed anymore to mint tokens!" ) ; } const orderWithSignature = { ... hashedOrder , ... signature , } ; return this . validateAndPostOrder ( orderWithSignature ) ; } ; const range = _ . range ( numberOfOrders * assets . length ) ; const batches = _ . chunk ( range , SELL_ORDER_BATCH_SIZE ) ; let numOrdersCreated = 0 ; for ( const subRange of batches ) { const batchOrdersCreated = await Promise . all ( subRange . map ( async ( assetOrderIndex ) => { const assetIndex = Math . floor ( assetOrderIndex / numberOfOrders ) ; return _makeAndPostOneSellOrder ( assets [ assetIndex ] ) ; } ) ) ; this . logger ( `Created and posted a batch of ${ batchOrdersCreated . length } orders in parallel.` ) ; numOrdersCreated += batchOrdersCreated . length ; await delay ( 500 ) ; } return numOrdersCreated ; } param param0 param bundleName param bundleDescription param bundleExternalLink param assets param collection param quantities param accountAddress param startAmount param endAmount param listingTime param expirationTime param waitForHighestBid param englishAuctionReservePrice param paymentTokenAddress param extraBountyBasisPoints param buyerAddress public async createBundleSellOrder ( { bundleName , bundleDescription , bundleExternalLink , assets , collection , quantities , accountAddress , startAmount , endAmount , expirationTime = 0 , listingTime , waitForHighestBid = false , englishAuctionReservePrice , paymentTokenAddress , extraBountyBasisPoints = 0 , buyerAddress , } ) { quantities = quantities || assets . map ( ( _a ) => 1 ) ; const order = await this . _makeBundleSellOrder ( { bundleName , bundleDescription , bundleExternalLink , assets , collection , quantities , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , englishAuctionReservePrice , paymentTokenAddress : paymentTokenAddress || NULL_ADDRESS , extraBountyBasisPoints , buyerAddress : buyerAddress || NULL_ADDRESS , } ) ; await this . _sellOrderValidationAndApprovals ( { order , accountAddress } ) ; const hashedOrder = { ... order , hash : getOrderHash ( order ) , } ; let signature ; try { signature = await this . _authorizeOrder ( hashedOrder ) ; } catch ( error ) { console . error ( error ) ; throw new Error ( "You declined to authorize your auction" ) ; } const orderWithSignature = { ... hashedOrder , ... signature , } ; return this . validateAndPostOrder ( orderWithSignature ) ; } param param0 param order param accountAddress param recipientAddress param referrerAddress returns public async fulfillOrder ( { order , accountAddress , recipientAddress , referrerAddress , } ) { const matchingOrder = this . _makeMatchingOrder ( { order , accountAddress , recipientAddress : recipientAddress || accountAddress , } ) ; const { buy , sell } = assignOrdersToSides ( order , matchingOrder ) ; const metadata = this . _getMetadata ( order , referrerAddress ) ; const transactionHash = await this . _atomicMatch ( { buy , sell , accountAddress , metadata , } ) ; await this . _confirmTransaction ( transactionHash , EventType . MatchOrders , "Fulfilling order" , async ( ) => { const isOpen = await this . _validateOrder ( order ) ; return ! isOpen ; } ) ; return transactionHash ; } param param0 param order param accountAddress public async cancelOrder ( { order , accountAddress , } ) { this . _dispatch ( EventType . CancelOrder , { order , accountAddress } ) ; const transactionHash = await this . _wyvernProtocol . wyvernExchange . cancelOrder_ . sendTransactionAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata , order . v || 0 , order . r || NULL_BLOCK_HASH , order . s || NULL_BLOCK_HASH , { from : accountAddress } ) ; await this . _confirmTransaction ( transactionHash . toString ( ) , EventType . CancelOrder , "Cancelling order" , async ( ) => { const isOpen = await this . _validateOrder ( order ) ; return ! isOpen ; } ) ; } param param0 param tokenId param tokenAddress param accountAddress param proxyAddress param tokenAbi param skipApproveAllIfTokenAddressIn param schemaName returns public async approveSemiOrNonFungibleToken ( { tokenId , tokenAddress , accountAddress , proxyAddress , tokenAbi = ERC721 , skipApproveAllIfTokenAddressIn = new Set ( ) , schemaName = WyvernSchemaName . ERC721 , } ) { const schema = this . _getSchema ( schemaName ) ; const tokenContract = this . web3 . eth . contract ( tokenAbi as Web3 . AbiDefinition [ ] ) ; const contract = await tokenContract . at ( tokenAddress ) ; if ( ! proxyAddress ) { proxyAddress = ( await this . _getProxy ( accountAddress ) ) || undefined ; if ( ! proxyAddress ) { throw new Error ( "Uninitialized account" ) ; } } const approvalAllCheck = async ( ) => { const isApprovedForAllRaw = await rawCall ( this . web3ReadOnly , { from : accountAddress , to : contract . address , data : contract . isApprovedForAll . getData ( accountAddress , proxyAddress ) , } ) ; return parseInt ( isApprovedForAllRaw ) ; } ; const isApprovedForAll = await approvalAllCheck ( ) ; if ( isApprovedForAll == 1 ) { this . logger ( "Already approved proxy for all tokens" ) ; return null ; } if ( isApprovedForAll == 0 ) { if ( skipApproveAllIfTokenAddressIn . has ( tokenAddress ) ) { this . logger ( "Already approving proxy for all tokens in another transaction" ) ; return null ; } skipApproveAllIfTokenAddressIn . add ( tokenAddress ) ; try { this . _dispatch ( EventType . ApproveAllAssets , { accountAddress , proxyAddress , contractAddress : tokenAddress , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : contract . address , data : contract . setApprovalForAll . getData ( proxyAddress , true ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress , } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . ApproveAllAssets , "Approving all tokens of this type for trading" , async ( ) => { const result = await approvalAllCheck ( ) ; return result == 1 ; } ) ; return txHash ; } catch ( error ) { console . error ( error ) ; throw new Error ( "Couldn't get permission to approve these tokens for trading. Their contract might not be implemented correctly. Please contact the developer!" ) ; } } this . logger ( "Contract does not support Approve All" ) ; const approvalOneCheck = async ( ) => { let approvedAddr = await promisifyCall < string > ( ( c ) => contract . getApproved . call ( tokenId , c ) ) ; if ( approvedAddr == proxyAddress ) { this . logger ( "Already approved proxy for this token" ) ; return true ; } this . logger ( `Approve response: ${ approvedAddr }` ) ; if ( ! approvedAddr ) { approvedAddr = await getNonCompliantApprovalAddress ( contract , tokenId , accountAddress ) ; if ( approvedAddr == proxyAddress ) { this . logger ( "Already approved proxy for this item" ) ; return true ; } this . logger ( `Special-case approve response: ${ approvedAddr }` ) ; } return false ; } ; const isApprovedForOne = await approvalOneCheck ( ) ; if ( isApprovedForOne ) { return null ; } try { this . _dispatch ( EventType . ApproveAsset , { accountAddress , proxyAddress , asset : getWyvernAsset ( schema , { tokenId , tokenAddress } ) , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : contract . address , data : contract . approve . getData ( proxyAddress , tokenId ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress , } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . ApproveAsset , "Approving single token for trading" , approvalOneCheck ) ; return txHash ; } catch ( error ) { console . error ( error ) ; throw new Error ( "Couldn't get permission to approve this token for trading. Its contract might not be implemented correctly. Please contact the developer!" ) ; } } param param0 param accountAddress param tokenAddress param proxyAddress param minimumAmount returns public async approveFungibleToken ( { accountAddress , tokenAddress , proxyAddress , minimumAmount = WyvernProtocol . MAX_UINT_256 , } ) { proxyAddress = proxyAddress || WyvernProtocol . getTokenTransferProxyAddress ( this . _networkName ) ; const approvedAmount = await this . _getApprovedTokenCount ( { accountAddress , tokenAddress , proxyAddress , } ) ; if ( approvedAmount . greaterThanOrEqualTo ( minimumAmount ) ) { this . logger ( "Already approved enough currency for trading" ) ; return null ; } this . logger ( `Not enough token approved for trade: ${ approvedAmount } approved to transfer ${ tokenAddress }` ) ; this . _dispatch ( EventType . ApproveCurrency , { accountAddress , contractAddress : tokenAddress , proxyAddress , } ) ; const hasOldApproveMethod = [ ENJIN_COIN_ADDRESS , MANA_ADDRESS ] . includes ( tokenAddress . toLowerCase ( ) ) ; if ( minimumAmount . greaterThan ( 0 ) && hasOldApproveMethod ) { await this . unapproveFungibleToken ( { accountAddress , tokenAddress , proxyAddress , } ) ; } const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : tokenAddress , data : encodeCall ( getMethod ( ERC20 , "approve" ) , [ proxyAddress , WyvernProtocol . MAX_UINT_256 . toString ( ) ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . ApproveCurrency , "Approving currency for trading" , async ( ) => { const newlyApprovedAmount = await this . _getApprovedTokenCount ( { accountAddress , tokenAddress , proxyAddress , } ) ; return newlyApprovedAmount . greaterThanOrEqualTo ( minimumAmount ) ; } ) ; return txHash ; } param param0 param accountAddress param tokenAddress param proxyAddress returns public async unapproveFungibleToken ( { accountAddress , tokenAddress , proxyAddress , } ) { proxyAddress = proxyAddress || WyvernProtocol . getTokenTransferProxyAddress ( this . _networkName ) ; const txHash = await sendRawTransaction ( this . web3 , { from : accountAddress , to : tokenAddress , data : encodeCall ( getMethod ( ERC20 , "approve" ) , [ proxyAddress , 0 ] ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . UnapproveCurrency , "Resetting Currency Approval" , async ( ) => { const newlyApprovedAmount = await this . _getApprovedTokenCount ( { accountAddress , tokenAddress , proxyAddress , } ) ; return newlyApprovedAmount . isZero ( ) ; } ) ; return txHash ; } param order public async getCurrentPrice ( order ) { const currentPrice = await this . _wyvernProtocolReadOnly . wyvernExchange . calculateCurrentPrice_ . callAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata ) ; return currentPrice ; } param param0 param order param accountAddress param recipientAddress param referrerAddress public async isOrderFulfillable ( { order , accountAddress , recipientAddress , referrerAddress , } ) { const matchingOrder = this . _makeMatchingOrder ( { order , accountAddress , recipientAddress : recipientAddress || accountAddress , } ) ; const { buy , sell } = assignOrdersToSides ( order , matchingOrder ) ; const metadata = this . _getMetadata ( order , referrerAddress ) ; const gas = await this . _estimateGasForMatch ( { buy , sell , accountAddress , metadata , } ) ; this . logger ( `Gas estimate for ${ order . side == OrderSide . Sell ? "sell" : "buy" } order: ${ gas }` ) ; return gas != null && gas > 0 ; } param param0 param tokenId param tokenAddress param asset param fromAddress param toAddress param quantity param useProxy param retries public async isAssetTransferrable ( { asset , fromAddress , toAddress , quantity , useProxy = false , } , retries = 1 ) { const schema = this . _getSchema ( asset . schemaName ) ; const quantityBN = quantity ? WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , asset . decimals || 0 ) : makeBigNumber ( 1 ) ; const wyAsset = getWyvernAsset ( schema , asset , quantityBN ) ; const abi = schema . functions . transfer ( wyAsset ) ; let from = fromAddress ; if ( useProxy ) { const proxyAddress = await this . _getProxy ( fromAddress ) ; if ( ! proxyAddress ) { console . error ( `This asset's owner (${ fromAddress }) does not have a proxy!` ) ; return false ; } from = proxyAddress ; } const data = encodeTransferCall ( abi , fromAddress , toAddress ) ; try { const gas = await estimateGas ( this . _getClientsForRead ( retries ) . web3 , { from , to : abi . target , data , } ) ; return gas > 0 ; } catch ( error ) { if ( retries <= 0 ) { console . error ( error ) ; console . error ( from , abi . target , data ) ; return false ; } await delay ( 500 ) ; return await this . isAssetTransferrable ( { asset , fromAddress , toAddress , quantity , useProxy } , retries - 1 ) ; } } param param0 param fromAddress param toAddress param asset param quantity returns public async transfer ( { fromAddress , toAddress , asset , quantity = 1 , } ) { const schema = this . _getSchema ( asset . schemaName ) ; const quantityBN = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , asset . decimals || 0 ) ; const wyAsset = getWyvernAsset ( schema , asset , quantityBN ) ; const isCryptoKitties = [ CK_ADDRESS , CK_RINKEBY_ADDRESS ] . includes ( wyAsset . address ) ; const isOldNFT = isCryptoKitties || ( ! ! asset . version && [ TokenStandardVersion . ERC721v1 , TokenStandardVersion . ERC721v2 ] . includes ( asset . version ) ) ; const abi = asset . schemaName === WyvernSchemaName . ERC20 ? annotateERC20TransferABI ( wyAsset as WyvernFTAsset ) : isOldNFT ? annotateERC721TransferABI ( wyAsset as WyvernNFTAsset ) : schema . functions . transfer ( wyAsset ) ; this . _dispatch ( EventType . TransferOne , { accountAddress : fromAddress , toAddress , asset : wyAsset , } ) ; const data = encodeTransferCall ( abi , fromAddress , toAddress ) ; const txHash = await sendRawTransaction ( this . web3 , { from : fromAddress , to : abi . target , data , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress : fromAddress , } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . TransferOne , `Transferring asset` ) ; return txHash ; } param param0 param assets param fromAddress param toAddress param schemaName returns public async transferAll ( { assets , fromAddress , toAddress , schemaName = WyvernSchemaName . ERC721 , } ) { toAddress = validateAndFormatWalletAddress ( this . web3 , toAddress ) ; const schemaNames = assets . map ( ( asset ) => asset . schemaName || schemaName ) ; const wyAssets = assets . map ( ( asset ) => getWyvernAsset ( this . _getSchema ( asset . schemaName ) , asset ) ) ; const { calldata , target } = encodeAtomicizedTransfer ( schemaNames . map ( ( name ) => this . _getSchema ( name ) ) , wyAssets , fromAddress , toAddress , this . _wyvernProtocol , this . _networkName ) ; let proxyAddress = await this . _getProxy ( fromAddress ) ; if ( ! proxyAddress ) { proxyAddress = await this . _initializeProxy ( fromAddress ) ; } await this . _approveAll ( { schemaNames , wyAssets , accountAddress : fromAddress , proxyAddress , } ) ; this . _dispatch ( EventType . TransferAll , { accountAddress : fromAddress , toAddress , assets : wyAssets , } ) ; const txHash = await sendRawTransaction ( this . web3 , { from : fromAddress , to : proxyAddress , data : encodeProxyCall ( target , HowToCall . DelegateCall , calldata ) , } , ( error ) => { this . _dispatch ( EventType . TransactionDenied , { error , accountAddress : fromAddress , } ) ; } ) ; await this . _confirmTransaction ( txHash , EventType . TransferAll , `Transferring ${ assets . length } asset${ assets . length == 1 ? "" : "s" }` ) ; return txHash ; } param param0 param symbol param address param name public async getFungibleTokens ( { symbol , address , name , } = { } ) { onDeprecated ( "Use `api.getPaymentTokens` instead" ) ; const tokenSettings = WyvernSchemas . tokens [ this . _networkName ] ; const { tokens } = await this . api . getPaymentTokens ( { symbol , address , name , } ) ; const offlineTokens = [ tokenSettings . canonicalWrappedEther , ... tokenSettings . otherTokens , ] . filter ( ( t ) => { if ( symbol != null && t . symbol . toLowerCase ( ) != symbol . toLowerCase ( ) ) { return false ; } if ( address != null && t . address . toLowerCase ( ) != address . toLowerCase ( ) ) { return false ; } if ( name != null && t . name != name ) { return false ; } return true ; } ) ; return [ ... offlineTokens , ... tokens ] ; } param param0 param accountAddress param asset param retries public async getAssetBalance ( { accountAddress , asset } , retries = 1 ) { const schema = this . _getSchema ( asset . schemaName ) ; const wyAsset = getWyvernAsset ( schema , asset ) ; if ( schema . functions . countOf ) { const abi = schema . functions . countOf ( wyAsset ) ; const contract = this . _getClientsForRead ( retries ) . web3 . eth . contract ( [ abi as Web3 . FunctionAbi ] ) . at ( abi . target ) ; const inputValues = abi . inputs . filter ( ( x ) => x . value !== undefined ) . map ( ( x ) => x . value ) ; const count = await promisifyCall < BigNumber > ( ( c ) => contract [ abi . name ] . call ( accountAddress , ... inputValues , c ) ) ; if ( count !== undefined ) { return count ; } } else if ( schema . functions . ownerOf ) { const abi = schema . functions . ownerOf ( wyAsset ) ; const contract = this . _getClientsForRead ( retries ) . web3 . eth . contract ( [ abi as Web3 . FunctionAbi ] ) . at ( abi . target ) ; if ( abi . inputs . filter ( ( x ) => x . value === undefined ) [ 0 ] ) { throw new Error ( "Missing an argument for finding the owner of this asset" ) ; } const inputValues = abi . inputs . map ( ( i ) => i . value . toString ( ) ) ; const owner = await promisifyCall < string > ( ( c ) => contract [ abi . name ] . call ( ... inputValues , c ) ) ; if ( owner ) { return owner . toLowerCase ( ) == accountAddress . toLowerCase ( ) ? new BigNumber ( 1 ) : new BigNumber ( 0 ) ; } } else { throw new Error ( "Missing ownership schema for this asset type" ) ; } if ( retries <= 0 ) { throw new Error ( "Unable to get current owner from smart contract" ) ; } else { await delay ( 500 ) ; return await this . getAssetBalance ( { accountAddress , asset } , retries - 1 ) ; } } param param0 param accountAddress param tokenAddress param schemaName param retries public async getTokenBalance ( { accountAddress , tokenAddress , schemaName = WyvernSchemaName . ERC20 , } , retries = 1 ) { const asset = { tokenId : null , tokenAddress , schemaName , } ; return this . getAssetBalance ( { accountAddress , asset } , retries ) ; } param param0 param asset param side param accountAddress param extraBountyBasisPoints public async computeFees ( { asset , side , accountAddress , extraBountyBasisPoints = 0 , } ) { let openseaBuyerFeeBasisPoints = DEFAULT_BUYER_FEE_BASIS_POINTS ; let openseaSellerFeeBasisPoints = DEFAULT_SELLER_FEE_BASIS_POINTS ; let devBuyerFeeBasisPoints = 0 ; let devSellerFeeBasisPoints = 0 ; let transferFee = makeBigNumber ( 0 ) ; let transferFeeTokenAddress = null ; let maxTotalBountyBPS = DEFAULT_MAX_BOUNTY ; if ( asset ) { openseaBuyerFeeBasisPoints = + asset . collection . openseaBuyerFeeBasisPoints ; openseaSellerFeeBasisPoints = + asset . collection . openseaSellerFeeBasisPoints ; devBuyerFeeBasisPoints = + asset . collection . devBuyerFeeBasisPoints ; devSellerFeeBasisPoints = + asset . collection . devSellerFeeBasisPoints ; maxTotalBountyBPS = openseaSellerFeeBasisPoints ; } if ( side == OrderSide . Sell && asset ) { transferFee = asset . transferFee ? makeBigNumber ( asset . transferFee ) : transferFee ; transferFeeTokenAddress = asset . transferFeePaymentToken ? asset . transferFeePaymentToken . address : transferFeeTokenAddress ; try { const result = await getTransferFeeSettings ( this . web3 , { asset , accountAddress , } ) ; transferFee = result . transferFee != null ? result . transferFee : transferFee ; transferFeeTokenAddress = result . transferFeeTokenAddress || transferFeeTokenAddress ; } catch ( error ) { console . error ( error ) ; } } const sellerBountyBasisPoints = side == OrderSide . Sell ? extraBountyBasisPoints : 0 ; const bountyTooLarge = sellerBountyBasisPoints + OPENSEA_SELLER_BOUNTY_BASIS_POINTS > maxTotalBountyBPS ; if ( sellerBountyBasisPoints > 0 && bountyTooLarge ) { let errorMessage = `Total bounty exceeds the maximum for this asset type (${ maxTotalBountyBPS / 100 }%).` ; if ( maxTotalBountyBPS >= OPENSEA_SELLER_BOUNTY_BASIS_POINTS ) { errorMessage += ` Remember that OpenSea will add ${ OPENSEA_SELLER_BOUNTY_BASIS_POINTS / 100 }% for referrers with OpenSea accounts!` ; } throw new Error ( errorMessage ) ; } return { totalBuyerFeeBasisPoints : openseaBuyerFeeBasisPoints + devBuyerFeeBasisPoints , totalSellerFeeBasisPoints : openseaSellerFeeBasisPoints + devSellerFeeBasisPoints , openseaBuyerFeeBasisPoints , openseaSellerFeeBasisPoints , devBuyerFeeBasisPoints , devSellerFeeBasisPoints , sellerBountyBasisPoints , transferFee , transferFeeTokenAddress , } ; } param order returns public async validateAndPostOrder ( order ) { const hash = await this . _wyvernProtocolReadOnly . wyvernExchange . hashOrder_ . callAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata ) ; if ( hash !== order . hash ) { console . error ( order ) ; throw new Error ( `Order couldn't be validated by the exchange due to a hash mismatch. Make sure your wallet is on the right network!` ) ; } this . logger ( "Order hashes match" ) ; const confirmedOrder = await this . api . postOrder ( orderToJSON ( order ) ) ; return confirmedOrder ; } /**
     * DEPRECATED: ERC-1559
     * https://eips.ethereum.org/EIPS/eip-1559
     * Compute the gas price for sending a txn, in wei
     * Will be slightly above the mean to make it faster
     */ public async _computeGasPrice ( ) { const meanGas = await getCurrentGasPrice ( this . web3 ) ; const weiToAdd = this . web3 . toWei ( this . gasPriceAddition , "gwei" ) ; return meanGas . plus ( weiToAdd ) ; } param estimation public _correctGasAmount ( estimation ) { return Math . ceil ( estimation * this . gasIncreaseFactor ) ; } param param0 param buy param sell param accountAddress param metadata param retries public async _estimateGasForMatch ( { buy , sell , accountAddress , metadata = NULL_BLOCK_HASH , } , retries = 1 ) { let value ; if ( buy . maker . toLowerCase ( ) == accountAddress . toLowerCase ( ) && buy . paymentToken == NULL_ADDRESS ) { value = await this . _getRequiredAmountForTakingSellOrder ( sell ) ; } try { return await this . _getClientsForRead ( retries ) . wyvernProtocol . wyvernExchange . atomicMatch_ . estimateGasAsync ( [ buy . exchange , buy . maker , buy . taker , buy . feeRecipient , buy . target , buy . staticTarget , buy . paymentToken , sell . exchange , sell . maker , sell . taker , sell . feeRecipient , sell . target , sell . staticTarget , sell . paymentToken , ] , [ buy . makerRelayerFee , buy . takerRelayerFee , buy . makerProtocolFee , buy . takerProtocolFee , buy . basePrice , buy . extra , buy . listingTime , buy . expirationTime , buy . salt , sell . makerRelayerFee , sell . takerRelayerFee , sell . makerProtocolFee , sell . takerProtocolFee , sell . basePrice , sell . extra , sell . listingTime , sell . expirationTime , sell . salt , ] , [ buy . feeMethod , buy . side , buy . saleKind , buy . howToCall , sell . feeMethod , sell . side , sell . saleKind , sell . howToCall , ] , buy . calldata , sell . calldata , buy . replacementPattern , sell . replacementPattern , buy . staticExtradata , sell . staticExtradata , [ buy . v || 0 , sell . v || 0 ] , [ buy . r || NULL_BLOCK_HASH , buy . s || NULL_BLOCK_HASH , sell . r || NULL_BLOCK_HASH , sell . s || NULL_BLOCK_HASH , metadata , ] , { from : accountAddress , value } as any ) ; } catch ( error ) { if ( retries <= 0 ) { console . error ( error ) ; return undefined ; } await delay ( 200 ) ; return await this . _estimateGasForMatch ( { buy , sell , accountAddress , metadata } , retries - 1 ) ; } } param param0 param assets param fromAddress param toAddress param schemaName public async _estimateGasForTransfer ( { assets , fromAddress , toAddress , schemaName = WyvernSchemaName . ERC721 , } ) { const schemaNames = assets . map ( ( asset ) => asset . schemaName || schemaName ) ; const wyAssets = assets . map ( ( asset ) => getWyvernAsset ( this . _getSchema ( asset . schemaName ) , asset ) ) ; const proxyAddress = await this . _getProxy ( fromAddress ) ; if ( ! proxyAddress ) { throw new Error ( "Uninitialized proxy address" ) ; } await this . _approveAll ( { schemaNames , wyAssets , accountAddress : fromAddress , proxyAddress , } ) ; const { calldata , target } = encodeAtomicizedTransfer ( schemaNames . map ( ( name ) => this . _getSchema ( name ) ) , wyAssets , fromAddress , toAddress , this . _wyvernProtocol , this . _networkName ) ; return estimateGas ( this . web3 , { from : fromAddress , to : proxyAddress , data : encodeProxyCall ( target , HowToCall . DelegateCall , calldata ) , } ) ; } param accountAddress param retries public async _getProxy ( accountAddress , retries = 0 ) { let proxyAddress = await this . _wyvernProtocolReadOnly . wyvernProxyRegistry . proxies . callAsync ( accountAddress ) ; if ( proxyAddress == "0x" ) { throw new Error ( "Couldn't retrieve your account from the blockchain - make sure you're on the correct Ethereum network!" ) ; } if ( ! proxyAddress || proxyAddress == NULL_ADDRESS ) { if ( retries > 0 ) { await delay ( 1000 ) ; return await this . _getProxy ( accountAddress , retries - 1 ) ; } proxyAddress = null ; } return proxyAddress ; } param accountAddress public async _initializeProxy ( accountAddress ) { this . _dispatch ( EventType . InitializeAccount , { accountAddress } ) ; this . logger ( `Initializing proxy for account: ${ accountAddress }` ) ; const txnData = { from : accountAddress } ; const gasEstimate = await this . _wyvernProtocolReadOnly . wyvernProxyRegistry . registerProxy . estimateGasAsync ( txnData ) ; const transactionHash = await this . _wyvernProtocol . wyvernProxyRegistry . registerProxy . sendTransactionAsync ( { ... txnData , gas : this . _correctGasAmount ( gasEstimate ) , } ) ; await this . _confirmTransaction ( transactionHash , EventType . InitializeAccount , "Initializing proxy for account" , async ( ) => { const polledProxy = await this . _getProxy ( accountAddress ) ; return ! ! polledProxy ; } ) ; const proxyAddress = await this . _getProxy ( accountAddress , 10 ) ; if ( ! proxyAddress ) { throw new Error ( "Failed to initialize your account :( Please restart your wallet/browser and try again!" ) ; } return proxyAddress ; } param param0 param accountAddress param tokenAddress param proxyAddress public async _getApprovedTokenCount ( { accountAddress , tokenAddress , proxyAddress , } ) { if ( ! tokenAddress ) { tokenAddress = WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther . address ; } const addressToApprove = proxyAddress || WyvernProtocol . getTokenTransferProxyAddress ( this . _networkName ) ; const approved = await rawCall ( this . web3 , { from : accountAddress , to : tokenAddress , data : encodeCall ( getMethod ( ERC20 , "allowance" ) , [ accountAddress , addressToApprove , ] ) , } ) ; return makeBigNumber ( approved ) ; } public async _makeBuyOrder ( { asset , quantity , accountAddress , startAmount , expirationTime = 0 , paymentTokenAddress , extraBountyBasisPoints = 0 , sellOrder , referrerAddress , } ) { accountAddress = validateAndFormatWalletAddress ( this . web3 , accountAddress ) ; const schema = this . _getSchema ( asset . schemaName ) ; const quantityBN = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , asset . decimals || 0 ) ; const wyAsset = getWyvernAsset ( schema , asset , quantityBN ) ; const openSeaAsset = await this . api . getAsset ( asset ) ; const taker = sellOrder ? sellOrder . maker : NULL_ADDRESS ; const { totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints } = await this . computeFees ( { asset : openSeaAsset , extraBountyBasisPoints , side : OrderSide . Buy , } ) ; const { makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , feeRecipient , feeMethod , } = this . _getBuyFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , sellOrder ) ; const { target , calldata , replacementPattern } = encodeBuy ( schema , wyAsset , accountAddress ) ; const { basePrice , extra , paymentToken } = await this . _getPriceParameters ( OrderSide . Buy , paymentTokenAddress , expirationTime , startAmount ) ; const times = this . _getTimeParameters ( expirationTime ) ; const { staticTarget , staticExtradata } = await this . _getStaticCallTargetAndExtraData ( { asset : openSeaAsset , useTxnOriginStaticCall : false , } ) ; return { exchange : WyvernProtocol . getExchangeContractAddress ( this . _networkName ) , maker : accountAddress , taker , quantity : quantityBN , makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , waitingForBestCounterOrder : false , feeMethod , feeRecipient , side : OrderSide . Buy , saleKind : SaleKind . FixedPrice , target , howToCall : HowToCall . Call , calldata , replacementPattern , staticTarget , staticExtradata , paymentToken , basePrice , extra , listingTime : times . listingTime , expirationTime : times . expirationTime , salt : WyvernProtocol . generatePseudoRandomSalt ( ) , metadata : { asset : wyAsset , schema : schema . name as WyvernSchemaName , referrerAddress , } , } ; } public async _makeSellOrder ( { asset , quantity , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , englishAuctionReservePrice = 0 , paymentTokenAddress , extraBountyBasisPoints , buyerAddress , } ) { accountAddress = validateAndFormatWalletAddress ( this . web3 , accountAddress ) ; const schema = this . _getSchema ( asset . schemaName ) ; const quantityBN = WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , asset . decimals || 0 ) ; const wyAsset = getWyvernAsset ( schema , asset , quantityBN ) ; const openSeaAsset = await this . api . getAsset ( asset ) ; const { totalSellerFeeBasisPoints , totalBuyerFeeBasisPoints , sellerBountyBasisPoints , } = await this . computeFees ( { asset : openSeaAsset , side : OrderSide . Sell , extraBountyBasisPoints , } ) ; const { target , calldata , replacementPattern } = encodeSell ( schema , wyAsset , accountAddress ) ; const orderSaleKind = endAmount != null && endAmount !== startAmount ? SaleKind . DutchAuction : SaleKind . FixedPrice ; const { basePrice , extra , paymentToken , reservePrice } = await this . _getPriceParameters ( OrderSide . Sell , paymentTokenAddress , expirationTime , startAmount , endAmount , waitForHighestBid , englishAuctionReservePrice ) ; const times = this . _getTimeParameters ( expirationTime , listingTime , waitForHighestBid ) ; const { makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , feeRecipient , feeMethod , } = this . _getSellFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , waitForHighestBid , sellerBountyBasisPoints ) ; const { staticTarget , staticExtradata } = await this . _getStaticCallTargetAndExtraData ( { asset : openSeaAsset , useTxnOriginStaticCall : waitForHighestBid , } ) ; return { exchange : WyvernProtocol . getExchangeContractAddress ( this . _networkName ) , maker : accountAddress , taker : buyerAddress , quantity : quantityBN , makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , waitingForBestCounterOrder : waitForHighestBid , englishAuctionReservePrice : reservePrice ? makeBigNumber ( reservePrice ) : undefined , feeMethod , feeRecipient , side : OrderSide . Sell , saleKind : orderSaleKind , target , howToCall : HowToCall . Call , calldata , replacementPattern , staticTarget , staticExtradata , paymentToken , basePrice , extra , listingTime : times . listingTime , expirationTime : times . expirationTime , salt : WyvernProtocol . generatePseudoRandomSalt ( ) , metadata : { asset : wyAsset , schema : schema . name as WyvernSchemaName , } , } ; } public async _getStaticCallTargetAndExtraData ( { asset , useTxnOriginStaticCall , } ) { const isCheezeWizards = [ CHEEZE_WIZARDS_GUILD_ADDRESS . toLowerCase ( ) , CHEEZE_WIZARDS_GUILD_RINKEBY_ADDRESS . toLowerCase ( ) , ] . includes ( asset . tokenAddress . toLowerCase ( ) ) ; const isDecentralandEstate = asset . tokenAddress . toLowerCase ( ) == DECENTRALAND_ESTATE_ADDRESS . toLowerCase ( ) ; const isMainnet = this . _networkName == Network . Main ; if ( isMainnet && ! useTxnOriginStaticCall ) { return { staticTarget : NULL_ADDRESS , staticExtradata : "0x" , } ; } if ( isCheezeWizards ) { const cheezeWizardsBasicTournamentAddress = isMainnet ? CHEEZE_WIZARDS_BASIC_TOURNAMENT_ADDRESS : CHEEZE_WIZARDS_BASIC_TOURNAMENT_RINKEBY_ADDRESS ; const cheezeWizardsBasicTournamentABI = this . web3 . eth . contract ( CheezeWizardsBasicTournament as Web3 . AbiDefinition [ ] ) ; const cheezeWizardsBasicTournmentInstance = await cheezeWizardsBasicTournamentABI . at ( cheezeWizardsBasicTournamentAddress ) ; const wizardFingerprint = await rawCall ( this . web3 , { to : cheezeWizardsBasicTournmentInstance . address , data : cheezeWizardsBasicTournmentInstance . wizardFingerprint . getData ( asset . tokenId ) , } ) ; return { staticTarget : isMainnet ? STATIC_CALL_CHEEZE_WIZARDS_ADDRESS : STATIC_CALL_CHEEZE_WIZARDS_RINKEBY_ADDRESS , staticExtradata : encodeCall ( getMethod ( StaticCheckCheezeWizards , "succeedIfCurrentWizardFingerprintMatchesProvidedWizardFingerprint" ) , [ asset . tokenId , wizardFingerprint , useTxnOriginStaticCall ] ) , } ; } else if ( isDecentralandEstate && isMainnet ) { const decentralandEstateAddress = DECENTRALAND_ESTATE_ADDRESS ; const decentralandEstateABI = this . web3 . eth . contract ( DecentralandEstates as Web3 . AbiDefinition [ ] ) ; const decentralandEstateInstance = await decentralandEstateABI . at ( decentralandEstateAddress ) ; const estateFingerprint = await rawCall ( this . web3 , { to : decentralandEstateInstance . address , data : decentralandEstateInstance . getFingerprint . getData ( asset . tokenId ) , } ) ; return { staticTarget : STATIC_CALL_DECENTRALAND_ESTATES_ADDRESS , staticExtradata : encodeCall ( getMethod ( StaticCheckDecentralandEstates , "succeedIfCurrentEstateFingerprintMatchesProvidedEstateFingerprint" ) , [ asset . tokenId , estateFingerprint , useTxnOriginStaticCall ] ) , } ; } else if ( useTxnOriginStaticCall ) { return { staticTarget : isMainnet ? STATIC_CALL_TX_ORIGIN_ADDRESS : STATIC_CALL_TX_ORIGIN_RINKEBY_ADDRESS , staticExtradata : encodeCall ( getMethod ( StaticCheckTxOrigin , "succeedIfTxOriginMatchesHardcodedAddress" ) , [ ] ) , } ; } else { return { staticTarget : NULL_ADDRESS , staticExtradata : "0x" , } ; } } public async _makeBundleBuyOrder ( { assets , collection , quantities , accountAddress , startAmount , expirationTime = 0 , paymentTokenAddress , extraBountyBasisPoints = 0 , sellOrder , referrerAddress , } ) { accountAddress = validateAndFormatWalletAddress ( this . web3 , accountAddress ) ; const quantityBNs = quantities . map ( ( quantity , i ) => WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , assets [ i ] . decimals || 0 ) ) ; const bundle = getWyvernBundle ( assets , assets . map ( ( a ) => this . _getSchema ( a . schemaName ) ) , quantityBNs ) ; const orderedSchemas = bundle . schemas . map ( ( name ) => this . _getSchema ( name ) ) ; const taker = sellOrder ? sellOrder . maker : NULL_ADDRESS ; const asset = collection ? await this . api . getAsset ( assets [ 0 ] ) : undefined ; const { totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints } = await this . computeFees ( { asset , extraBountyBasisPoints , side : OrderSide . Buy , } ) ; const { makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , feeRecipient , feeMethod , } = this . _getBuyFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , sellOrder ) ; const { calldata , replacementPattern } = encodeAtomicizedBuy ( orderedSchemas , bundle . assets , accountAddress , this . _wyvernProtocol , this . _networkName ) ; const { basePrice , extra , paymentToken } = await this . _getPriceParameters ( OrderSide . Buy , paymentTokenAddress , expirationTime , startAmount ) ; const times = this . _getTimeParameters ( expirationTime ) ; return { exchange : WyvernProtocol . getExchangeContractAddress ( this . _networkName ) , maker : accountAddress , taker , quantity : makeBigNumber ( 1 ) , makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , waitingForBestCounterOrder : false , feeMethod , feeRecipient , side : OrderSide . Buy , saleKind : SaleKind . FixedPrice , target : WyvernProtocol . getAtomicizerContractAddress ( this . _networkName ) , howToCall : HowToCall . DelegateCall , calldata , replacementPattern , staticTarget : NULL_ADDRESS , staticExtradata : "0x" , paymentToken , basePrice , extra , listingTime : times . listingTime , expirationTime : times . expirationTime , salt : WyvernProtocol . generatePseudoRandomSalt ( ) , metadata : { bundle , referrerAddress , } , } ; } public async _makeBundleSellOrder ( { bundleName , bundleDescription , bundleExternalLink , assets , collection , quantities , accountAddress , startAmount , endAmount , listingTime , expirationTime , waitForHighestBid , englishAuctionReservePrice = 0 , paymentTokenAddress , extraBountyBasisPoints , buyerAddress , } ) { accountAddress = validateAndFormatWalletAddress ( this . web3 , accountAddress ) ; const quantityBNs = quantities . map ( ( quantity , i ) => WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( quantity ) , assets [ i ] . decimals || 0 ) ) ; const bundle = getWyvernBundle ( assets , assets . map ( ( a ) => this . _getSchema ( a . schemaName ) ) , quantityBNs ) ; const orderedSchemas = bundle . schemas . map ( ( name ) => this . _getSchema ( name ) ) ; bundle . name = bundleName ; bundle . description = bundleDescription ; bundle . external_link = bundleExternalLink ; const asset = collection ? await this . api . getAsset ( assets [ 0 ] ) : undefined ; const { totalSellerFeeBasisPoints , totalBuyerFeeBasisPoints , sellerBountyBasisPoints , } = await this . computeFees ( { asset , side : OrderSide . Sell , extraBountyBasisPoints , } ) ; const { calldata , replacementPattern } = encodeAtomicizedSell ( orderedSchemas , bundle . assets , accountAddress , this . _wyvernProtocol , this . _networkName ) ; const { basePrice , extra , paymentToken , reservePrice } = await this . _getPriceParameters ( OrderSide . Sell , paymentTokenAddress , expirationTime , startAmount , endAmount , waitForHighestBid , englishAuctionReservePrice ) ; const times = this . _getTimeParameters ( expirationTime , listingTime , waitForHighestBid ) ; const orderSaleKind = endAmount != null && endAmount !== startAmount ? SaleKind . DutchAuction : SaleKind . FixedPrice ; const { makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , feeRecipient , } = this . _getSellFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , waitForHighestBid , sellerBountyBasisPoints ) ; return { exchange : WyvernProtocol . getExchangeContractAddress ( this . _networkName ) , maker : accountAddress , taker : buyerAddress , quantity : makeBigNumber ( 1 ) , makerRelayerFee , takerRelayerFee , makerProtocolFee , takerProtocolFee , makerReferrerFee , waitingForBestCounterOrder : waitForHighestBid , englishAuctionReservePrice : reservePrice ? makeBigNumber ( reservePrice ) : undefined , feeMethod : FeeMethod . SplitFee , feeRecipient , side : OrderSide . Sell , saleKind : orderSaleKind , target : WyvernProtocol . getAtomicizerContractAddress ( this . _networkName ) , howToCall : HowToCall . DelegateCall , calldata , replacementPattern , staticTarget : NULL_ADDRESS , staticExtradata : "0x" , paymentToken , basePrice , extra , listingTime : times . listingTime , expirationTime : times . expirationTime , salt : WyvernProtocol . generatePseudoRandomSalt ( ) , metadata : { bundle , } , } ; } public _makeMatchingOrder ( { order , accountAddress , recipientAddress , } ) { accountAddress = validateAndFormatWalletAddress ( this . web3 , accountAddress ) ; recipientAddress = validateAndFormatWalletAddress ( this . web3 , recipientAddress ) ; const computeOrderParams = ( ) => { if ( "asset" in order . metadata ) { const schema = this . _getSchema ( order . metadata . schema ) ; return order . side == OrderSide . Buy ? encodeSell ( schema , order . metadata . asset , recipientAddress ) : encodeBuy ( schema , order . metadata . asset , recipientAddress ) ; } else if ( "bundle" in order . metadata ) { const bundle = order . metadata . bundle ; const orderedSchemas = bundle . schemas ? bundle . schemas . map ( ( schemaName ) => this . _getSchema ( schemaName ) ) : bundle . assets . map ( ( ) => this . _getSchema ( "schema" in order . metadata ? order . metadata . schema : undefined ) ) ; const atomicized = order . side == OrderSide . Buy ? encodeAtomicizedSell ( orderedSchemas , order . metadata . bundle . assets , recipientAddress , this . _wyvernProtocol , this . _networkName ) : encodeAtomicizedBuy ( orderedSchemas , order . metadata . bundle . assets , recipientAddress , this . _wyvernProtocol , this . _networkName ) ; return { target : WyvernProtocol . getAtomicizerContractAddress ( this . _networkName ) , calldata : atomicized . calldata , replacementPattern : atomicized . replacementPattern , } ; } else { throw new Error ( "Invalid order metadata" ) ; } } ; const { target , calldata , replacementPattern } = computeOrderParams ( ) ; const times = this . _getTimeParameters ( 0 ) ; const feeRecipient = order . feeRecipient == NULL_ADDRESS ? OPENSEA_FEE_RECIPIENT : NULL_ADDRESS ; const matchingOrder = { exchange : order . exchange , maker : accountAddress , taker : order . maker , quantity : order . quantity , makerRelayerFee : order . makerRelayerFee , takerRelayerFee : order . takerRelayerFee , makerProtocolFee : order . makerProtocolFee , takerProtocolFee : order . takerProtocolFee , makerReferrerFee : order . makerReferrerFee , waitingForBestCounterOrder : false , feeMethod : order . feeMethod , feeRecipient , side : ( order . side + 1 ) % 2 , saleKind : SaleKind . FixedPrice , target , howToCall : order . howToCall , calldata , replacementPattern , staticTarget : NULL_ADDRESS , staticExtradata : "0x" , paymentToken : order . paymentToken , basePrice : order . basePrice , extra : makeBigNumber ( 0 ) , listingTime : times . listingTime , expirationTime : times . expirationTime , salt : WyvernProtocol . generatePseudoRandomSalt ( ) , metadata : order . metadata , } ; return { ... matchingOrder , hash : getOrderHash ( matchingOrder ) , } ; } param param0 param buy param sell param accountAddress param shouldValidateBuy param shouldValidateSell param retries public async _validateMatch ( { buy , sell , accountAddress , shouldValidateBuy = false , shouldValidateSell = false , } , retries = 1 ) { try { if ( shouldValidateBuy ) { const buyValid = await this . _validateOrder ( buy ) ; this . logger ( `Buy order is valid: ${ buyValid }` ) ; if ( ! buyValid ) { throw new Error ( "Invalid buy order. It may have recently been removed. Please refresh the page and try again!" ) ; } } if ( shouldValidateSell ) { const sellValid = await this . _validateOrder ( sell ) ; this . logger ( `Sell order is valid: ${ sellValid }` ) ; if ( ! sellValid ) { throw new Error ( "Invalid sell order. It may have recently been removed. Please refresh the page and try again!" ) ; } } const canMatch = await requireOrdersCanMatch ( this . _getClientsForRead ( retries ) . wyvernProtocol , { buy , sell , accountAddress } ) ; this . logger ( `Orders matching: ${ canMatch }` ) ; const calldataCanMatch = await requireOrderCalldataCanMatch ( this . _getClientsForRead ( retries ) . wyvernProtocol , { buy , sell } ) ; this . logger ( `Order calldata matching: ${ calldataCanMatch }` ) ; return true ; } catch ( error ) { if ( retries <= 0 ) { throw new Error ( `Error matching this listing: ${ error instanceof Error ? error . message : "" }. Please contact the maker or try again later!` ) ; } await delay ( 500 ) ; return await this . _validateMatch ( { buy , sell , accountAddress , shouldValidateBuy , shouldValidateSell } , retries - 1 ) ; } } public async _createEmailWhitelistEntry ( { order , buyerEmail , } ) { const asset = "asset" in order . metadata ? order . metadata . asset : undefined ; if ( ! asset || ! asset . id ) { throw new Error ( "Whitelisting only available for non-fungible assets." ) ; } await this . api . postAssetWhitelist ( asset . address , asset . id , buyerEmail ) ; } public async _sellOrderValidationAndApprovals ( { order , accountAddress , } ) { const wyAssets = "bundle" in order . metadata ? order . metadata . bundle . assets : order . metadata . asset ? [ order . metadata . asset ] : [ ] ; const schemaNames = "bundle" in order . metadata && "schemas" in order . metadata . bundle ? order . metadata . bundle . schemas : "schema" in order . metadata ? [ order . metadata . schema ] : [ ] ; const tokenAddress = order . paymentToken ; await this . _approveAll ( { schemaNames , wyAssets , accountAddress } ) ; if ( tokenAddress != NULL_ADDRESS ) { const minimumAmount = makeBigNumber ( order . basePrice ) ; await this . approveFungibleToken ( { accountAddress , tokenAddress , minimumAmount , } ) ; } const sellValid = await this . _wyvernProtocolReadOnly . wyvernExchange . validateOrderParameters_ . callAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata , { from : accountAddress } ) ; if ( ! sellValid ) { console . error ( order ) ; throw new Error ( `Failed to validate sell order parameters. Make sure you're on the right network!` ) ; } } param order returns public async _approveOrder ( order ) { const accountAddress = order . maker ; const includeInOrderBook = true ; this . _dispatch ( EventType . ApproveOrder , { order , accountAddress } ) ; const transactionHash = await this . _wyvernProtocol . wyvernExchange . approveOrder_ . sendTransactionAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata , includeInOrderBook , { from : accountAddress } ) ; await this . _confirmTransaction ( transactionHash . toString ( ) , EventType . ApproveOrder , "Approving order" , async ( ) => { const isApproved = await this . _validateOrder ( order ) ; return isApproved ; } ) ; return transactionHash ; } public async _validateOrder ( order ) { const isValid = await this . _wyvernProtocolReadOnly . wyvernExchange . validateOrder_ . callAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata , order . v || 0 , order . r || NULL_BLOCK_HASH , order . s || NULL_BLOCK_HASH ) ; return isValid ; } public async _approveAll ( { schemaNames , wyAssets , accountAddress , proxyAddress , } ) { proxyAddress = proxyAddress || ( await this . _getProxy ( accountAddress ) ) || undefined ; if ( ! proxyAddress ) { proxyAddress = await this . _initializeProxy ( accountAddress ) ; } const contractsWithApproveAll = new Set ( ) ; return Promise . all ( wyAssets . map ( async ( wyAsset , i ) => { const schemaName = schemaNames [ i ] ; let isOwner ; try { isOwner = await this . _ownsAssetOnChain ( { accountAddress , proxyAddress , wyAsset , schemaName , } ) ; } catch ( error ) { isOwner = true ; } if ( ! isOwner ) { const minAmount = "quantity" in wyAsset ? wyAsset . quantity : 1 ; console . error ( `Failed on-chain ownership check: ${ accountAddress } on ${ schemaName }:` , wyAsset ) ; throw new Error ( `You don't own enough to do that (${ minAmount } base units of ${ wyAsset . address }${ wyAsset . id ? " token " + wyAsset . id : "" })` ) ; } switch ( schemaName ) { case WyvernSchemaName . ERC721 : case WyvernSchemaName . ERC721v3 : case WyvernSchemaName . ERC1155 : case WyvernSchemaName . LegacyEnjin : case WyvernSchemaName . ENSShortNameAuction : const wyNFTAsset = wyAsset as WyvernNFTAsset ; return await this . approveSemiOrNonFungibleToken ( { tokenId : wyNFTAsset . id . toString ( ) , tokenAddress : wyNFTAsset . address , accountAddress , proxyAddress , schemaName , skipApproveAllIfTokenAddressIn : contractsWithApproveAll , } ) ; case WyvernSchemaName . ERC20 : const wyFTAsset = wyAsset as WyvernFTAsset ; if ( contractsWithApproveAll . has ( wyFTAsset . address ) ) { return null ; } contractsWithApproveAll . add ( wyFTAsset . address ) ; return await this . approveFungibleToken ( { tokenAddress : wyFTAsset . address , accountAddress , proxyAddress , } ) ; } } ) ) ; } public async _buyOrderValidationAndApprovals ( { order , counterOrder , accountAddress , } ) { const tokenAddress = order . paymentToken ; if ( tokenAddress != NULL_ADDRESS ) { const balance = await this . getTokenBalance ( { accountAddress , tokenAddress , } ) ; let minimumAmount = makeBigNumber ( order . basePrice ) ; if ( counterOrder ) { minimumAmount = await this . _getRequiredAmountForTakingSellOrder ( counterOrder ) ; } if ( balance . toNumber ( ) < minimumAmount . toNumber ( ) ) { if ( tokenAddress == WyvernSchemas . tokens [ this . _networkName ] . canonicalWrappedEther . address ) { throw new Error ( "Insufficient balance. You may need to wrap Ether." ) ; } else { throw new Error ( "Insufficient balance." ) ; } } await this . approveFungibleToken ( { accountAddress , tokenAddress , minimumAmount , } ) ; } const buyValid = await this . _wyvernProtocolReadOnly . wyvernExchange . validateOrderParameters_ . callAsync ( [ order . exchange , order . maker , order . taker , order . feeRecipient , order . target , order . staticTarget , order . paymentToken , ] , [ order . makerRelayerFee , order . takerRelayerFee , order . makerProtocolFee , order . takerProtocolFee , order . basePrice , order . extra , order . listingTime , order . expirationTime , order . salt , ] , order . feeMethod , order . side , order . saleKind , order . howToCall , order . calldata , order . replacementPattern , order . staticExtradata , { from : accountAddress } ) ; if ( ! buyValid ) { console . error ( order ) ; throw new Error ( `Failed to validate buy order parameters. Make sure you're on the right network!` ) ; } } param accountAddress param proxyAddress param wyAsset param schemaName public async _ownsAssetOnChain ( { accountAddress , proxyAddress , wyAsset , schemaName , } ) { const asset = { tokenId : wyAsset . id || null , tokenAddress : wyAsset . address , schemaName , } ; const minAmount = new BigNumber ( "quantity" in wyAsset ? wyAsset . quantity : 1 ) ; const accountBalance = await this . getAssetBalance ( { accountAddress , asset , } ) ; if ( accountBalance . greaterThanOrEqualTo ( minAmount ) ) { return true ; } proxyAddress = proxyAddress || ( await this . _getProxy ( accountAddress ) ) ; if ( proxyAddress ) { const proxyBalance = await this . getAssetBalance ( { accountAddress : proxyAddress , asset , } ) ; if ( proxyBalance . greaterThanOrEqualTo ( minAmount ) ) { return true ; } } return false ; } public _getBuyFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , sellOrder ? ) { this . _validateFees ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints ) ; let makerRelayerFee ; let takerRelayerFee ; if ( sellOrder ) { makerRelayerFee = sellOrder . waitingForBestCounterOrder ? makeBigNumber ( sellOrder . makerRelayerFee ) : makeBigNumber ( sellOrder . takerRelayerFee ) ; takerRelayerFee = sellOrder . waitingForBestCounterOrder ? makeBigNumber ( sellOrder . takerRelayerFee ) : makeBigNumber ( sellOrder . makerRelayerFee ) ; } else { makerRelayerFee = makeBigNumber ( totalBuyerFeeBasisPoints ) ; takerRelayerFee = makeBigNumber ( totalSellerFeeBasisPoints ) ; } return { makerRelayerFee , takerRelayerFee , makerProtocolFee : makeBigNumber ( 0 ) , takerProtocolFee : makeBigNumber ( 0 ) , makerReferrerFee : makeBigNumber ( 0 ) , feeRecipient : OPENSEA_FEE_RECIPIENT , feeMethod : FeeMethod . SplitFee , } ; } public _getSellFeeParameters ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints , waitForHighestBid , sellerBountyBasisPoints = 0 ) { this . _validateFees ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints ) ; const feeRecipient = waitForHighestBid ? NULL_ADDRESS : OPENSEA_FEE_RECIPIENT ; const makerRelayerFee = waitForHighestBid ? makeBigNumber ( totalBuyerFeeBasisPoints ) : makeBigNumber ( totalSellerFeeBasisPoints ) ; const takerRelayerFee = waitForHighestBid ? makeBigNumber ( totalSellerFeeBasisPoints ) : makeBigNumber ( totalBuyerFeeBasisPoints ) ; return { makerRelayerFee , takerRelayerFee , makerProtocolFee : makeBigNumber ( 0 ) , takerProtocolFee : makeBigNumber ( 0 ) , makerReferrerFee : makeBigNumber ( sellerBountyBasisPoints ) , feeRecipient , feeMethod : FeeMethod . SplitFee , } ; } param totalBuyerFeeBasisPoints param totalSellerFeeBasisPoints private _validateFees ( totalBuyerFeeBasisPoints , totalSellerFeeBasisPoints ) { const maxFeePercent = INVERSE_BASIS_POINT / 100 ; if ( totalBuyerFeeBasisPoints > INVERSE_BASIS_POINT || totalSellerFeeBasisPoints > INVERSE_BASIS_POINT ) { throw new Error ( `Invalid buyer/seller fees: must be less than ${ maxFeePercent }%` ) ; } if ( totalBuyerFeeBasisPoints < 0 || totalSellerFeeBasisPoints < 0 ) { throw new Error ( `Invalid buyer/seller fees: must be at least 0%` ) ; } } param expirationTimestamp param listingTimestamp param waitingForBestCounterOrder private _getTimeParameters ( expirationTimestamp , listingTimestamp ? , waitingForBestCounterOrder = false ) { const minExpirationTimestamp = Math . round ( Date . now ( ) / 1000 + MIN_EXPIRATION_SECONDS ) ; const minListingTimestamp = Math . round ( Date . now ( ) / 1000 ) ; if ( expirationTimestamp != 0 && expirationTimestamp < minExpirationTimestamp ) { throw new Error ( `Expiration time must be at least ${ MIN_EXPIRATION_SECONDS } seconds from now, or zero (non-expiring).` ) ; } if ( listingTimestamp && listingTimestamp < minListingTimestamp ) { throw new Error ( "Listing time cannot be in the past." ) ; } if ( listingTimestamp && expirationTimestamp != 0 && listingTimestamp >= expirationTimestamp ) { throw new Error ( "Listing time must be before the expiration time." ) ; } if ( waitingForBestCounterOrder && expirationTimestamp == 0 ) { throw new Error ( "English auctions must have an expiration time." ) ; } if ( waitingForBestCounterOrder && listingTimestamp ) { throw new Error ( `Cannot schedule an English auction for the future.` ) ; } if ( parseInt ( expirationTimestamp . toString ( ) ) != expirationTimestamp ) { throw new Error ( `Expiration timestamp must be a whole number of seconds` ) ; } if ( waitingForBestCounterOrder ) { listingTimestamp = expirationTimestamp ; expirationTimestamp = expirationTimestamp + ORDER_MATCHING_LATENCY_SECONDS ; } else { listingTimestamp = listingTimestamp || Math . round ( Date . now ( ) / 1000 - 100 ) ; } return { listingTime : makeBigNumber ( listingTimestamp ) , expirationTime : makeBigNumber ( expirationTimestamp ) , } ; } param tokenAddress param expirationTime param startAmount param endAmount private async _getPriceParameters ( orderSide , tokenAddress , expirationTime , startAmount , endAmount ? , waitingForBestCounterOrder = false , englishAuctionReservePrice ? ) { const priceDiff = endAmount != null ? startAmount - endAmount : 0 ; const paymentToken = tokenAddress . toLowerCase ( ) ; const isEther = tokenAddress == NULL_ADDRESS ; const { tokens } = await this . api . getPaymentTokens ( { address : paymentToken , } ) ; const token = tokens [ 0 ] ; if ( isNaN ( startAmount ) || startAmount == null || startAmount < 0 ) { throw new Error ( `Starting price must be a number >= 0` ) ; } if ( ! isEther && ! token ) { throw new Error ( `No ERC-20 token found for '${ paymentToken }'` ) ; } if ( isEther && waitingForBestCounterOrder ) { throw new Error ( `English auctions must use wrapped ETH or an ERC-20 token.` ) ; } if ( isEther && orderSide === OrderSide . Buy ) { throw new Error ( `Offers must use wrapped ETH or an ERC-20 token.` ) ; } if ( priceDiff < 0 ) { throw new Error ( "End price must be less than or equal to the start price." ) ; } if ( priceDiff > 0 && expirationTime == 0 ) { throw new Error ( "Expiration time must be set if order will change in price." ) ; } if ( englishAuctionReservePrice && ! waitingForBestCounterOrder ) { throw new Error ( "Reserve prices may only be set on English auctions." ) ; } if ( englishAuctionReservePrice && englishAuctionReservePrice < startAmount ) { throw new Error ( "Reserve price must be greater than or equal to the start amount." ) ; } const basePrice = isEther ? makeBigNumber ( this . web3 . toWei ( startAmount , "ether" ) ) . round ( ) : WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( startAmount ) , token . decimals ) ; const extra = isEther ? makeBigNumber ( this . web3 . toWei ( priceDiff , "ether" ) ) . round ( ) : WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( priceDiff ) , token . decimals ) ; const reservePrice = englishAuctionReservePrice ? isEther ? makeBigNumber ( this . web3 . toWei ( englishAuctionReservePrice , "ether" ) ) . round ( ) : WyvernProtocol . toBaseUnitAmount ( makeBigNumber ( englishAuctionReservePrice ) , token . decimals ) : undefined ; return { basePrice , extra , paymentToken , reservePrice } ; } private _getMetadata ( order , referrerAddress ? ) { const referrer = referrerAddress || order . metadata . referrerAddress ; if ( referrer && isValidAddress ( referrer ) ) { return referrer ; } return undefined ; } private async _atomicMatch ( { buy , sell , accountAddress , metadata = NULL_BLOCK_HASH , } ) { let value ; let shouldValidateBuy = true ; let shouldValidateSell = true ; if ( sell . maker . toLowerCase ( ) == accountAddress . toLowerCase ( ) ) { await this . _sellOrderValidationAndApprovals ( { order : sell , accountAddress , } ) ; shouldValidateSell = false ; } else if ( buy . maker . toLowerCase ( ) == accountAddress . toLowerCase ( ) ) { await this . _buyOrderValidationAndApprovals ( { order : buy , counterOrder : sell , accountAddress , } ) ; shouldValidateBuy = false ; if ( buy . paymentToken == NULL_ADDRESS ) { value = await this . _getRequiredAmountForTakingSellOrder ( sell ) ; } } else { } await this . _validateMatch ( { buy , sell , accountAddress , shouldValidateBuy , shouldValidateSell , } ) ; this . _dispatch ( EventType . MatchOrders , { buy , sell , accountAddress , matchMetadata : metadata , } ) ; let txHash ; const txnData = { from : accountAddress , value } ; const args = [ [ buy . exchange , buy . maker , buy . taker , buy . feeRecipient , buy . target , buy . staticTarget , buy . paymentToken , sell . exchange , sell . maker , sell . taker , sell . feeRecipient , sell . target , sell . staticTarget , sell . paymentToken , ] , [ buy . makerRelayerFee , buy . takerRelayerFee , buy . makerProtocolFee , buy . takerProtocolFee , buy . basePrice , buy . extra , buy . listingTime , buy . expirationTime , buy . salt , sell . makerRelayerFee , sell . takerRelayerFee , sell . makerProtocolFee , sell . takerProtocolFee , sell . basePrice , sell . extra , sell . listingTime , sell . expirationTime , sell . salt , ] , [ buy . feeMethod , buy . side , buy . saleKind , buy . howToCall , sell . feeMethod , sell . side , sell . saleKind , sell . howToCall , ] , buy . calldata , sell . calldata , buy . replacementPattern , sell . replacementPattern , buy . staticExtradata , sell . staticExtradata , [ buy . v || 0 , sell . v || 0 ] , [ buy . r || NULL_BLOCK_HASH , buy . s || NULL_BLOCK_HASH , sell . r || NULL_BLOCK_HASH , sell . s || NULL_BLOCK_HASH , metadata , ] , ] ; try { const gasEstimate = await this . _wyvernProtocolReadOnly . wyvernExchange . atomicMatch_ . estimateGasAsync ( args [ 0 ] , args [ 1 ] , args [ 2 ] , args [ 3 ] , args [ 4 ] , args [ 5 ] , args [ 6 ] , args [ 7 ] , args [ 8 ] , args [ 9 ] , args [ 10 ] , txnData ) ; txnData . gas = this . _correctGasAmount ( gasEstimate ) ; } catch ( error ) { console . error ( `Failed atomic match with args: ` , args , error ) ; throw new Error ( `Oops, the Ethereum network rejected this transaction :( The OpenSea devs have been alerted, but this problem is typically due an item being locked or untransferrable. The exact error was "${ error instanceof Error ? error . message . substr ( 0 , MAX_ERROR_LENGTH ) : "unknown" }..."` ) ; } try { this . logger ( `Fulfilling order with gas set to ${ txnData . gas }` ) ; txHash = await this . _wyvernProtocol . wyvernExchange . atomicMatch_ . sendTransactionAsync ( args [ 0 ] , args [ 1 ] , args [ 2 ] , args [ 3 ] , args [ 4 ] , args [ 5 ] , args [ 6 ] , args [ 7 ] , args [ 8 ] , args [ 9 ] , args [ 10 ] , txnData ) ; } catch ( error ) { console . error ( error ) ; this . _dispatch ( EventType . TransactionDenied , { error , buy , sell , accountAddress , matchMetadata : metadata , } ) ; throw new Error ( `Failed to authorize transaction: "${ error instanceof Error && error . message ? error . message : "user denied" }..."` ) ; } return txHash ; } private async _getRequiredAmountForTakingSellOrder ( sell ) { const currentPrice = await this . getCurrentPrice ( sell ) ; const estimatedPrice = estimateCurrentPrice ( sell ) ; const maxPrice = BigNumber . max ( currentPrice , estimatedPrice ) ; sell . takerRelayerFee = makeBigNumber ( sell . takerRelayerFee ) ; const feePercentage = sell . takerRelayerFee . div ( INVERSE_BASIS_POINT ) ; const fee = feePercentage . times ( maxPrice ) ; return fee . plus ( maxPrice ) . ceil ( ) ; } private async _authorizeOrder ( order ) { const message = order . hash ; const signerAddress = order . maker ; this . _dispatch ( EventType . CreateOrder , { order , accountAddress : order . maker , } ) ; const makerIsSmartContract = await isContractAddress ( this . web3 , signerAddress ) ; try { if ( makerIsSmartContract ) { await this . _approveOrder ( order ) ; return null ; } else { return await personalSignAsync ( this . web3 , message , signerAddress ) ; } } catch ( error ) { this . _dispatch ( EventType . OrderDenied , { order , accountAddress : signerAddress , } ) ; throw error ; } } private _getSchema ( schemaName ? ) { const schemaName_ = schemaName || WyvernSchemaName . ERC721 ; const schema = WyvernSchemas . schemas [ this . _networkName ] . filter ( ( s ) => s . name == schemaName_ ) [ 0 ] ; if ( ! schema ) { throw new Error ( `Trading for this asset (${ schemaName_ }) is not yet supported. Please contact us or check back later!` ) ; } return schema ; } private _dispatch ( event , data ) { this . _emitter . emit ( event , data ) ; } param retries private _getClientsForRead ( retries = 1 ) { if ( retries > 0 ) { return { web3 : this . web3 , wyvernProtocol : this . _wyvernProtocol , } ; } else { return { web3 : this . web3ReadOnly , wyvernProtocol : this . _wyvernProtocolReadOnly , } ; } } private async _confirmTransaction ( transactionHash , event , description , testForSuccess ? ) { const transactionEventData = { transactionHash , event } ; this . logger ( `Transaction started: ${ description }` ) ; if ( transactionHash == NULL_BLOCK_HASH ) { this . _dispatch ( EventType . TransactionCreated , { event } ) ; if ( ! testForSuccess ) { this . logger ( `Unknown action, waiting 1 minute: ${ description }` ) ; await delay ( 60 * 1000 ) ; return ; } return await this . _pollCallbackForConfirmation ( event , description , testForSuccess ) ; } try { this . _dispatch ( EventType . TransactionCreated , transactionEventData ) ; await confirmTransaction ( this . web3 , transactionHash ) ; this . logger ( `Transaction succeeded: ${ description }` ) ; this . _dispatch ( EventType . TransactionConfirmed , transactionEventData ) ; } catch ( error ) { this . logger ( `Transaction failed: ${ description }` ) ; this . _dispatch ( EventType . TransactionFailed , { ... transactionEventData , error , } ) ; throw error ; } } private async _pollCallbackForConfirmation ( event , description , testForSuccess ) { return new Promise < void > ( ( resolve , reject ) => { const initialRetries = 60 ; const testResolve = async ( retries ) => { const wasSuccessful = await testForSuccess ( ) ; if ( wasSuccessful ) { this . logger ( `Transaction succeeded: ${ description }` ) ; this . _dispatch ( EventType . TransactionConfirmed , { event } ) ; return resolve ( ) ; } else if ( retries <= 0 ) { return reject ( ) ; } if ( retries % 10 == 0 ) { this . logger ( `Tested transaction ${ initialRetries - retries + 1 } times: ${ description }` ) ; } await delay ( 5000 ) ; return testResolve ( retries - 1 ) ; } ; return testResolve ( initialRetries ) ; } ) ; } }