import BigNumber from "bignumber.js" ; import * as ethUtil from "ethereumjs-util" ; import * as _ from "lodash" ; import * as Web3 from "web3" ; import { WyvernProtocol } from "wyvern-js" ; import { AnnotatedFunctionABI , FunctionInputKind , FunctionOutputKind , Schema , StateMutability , } from "wyvern-schemas/dist/types" ; import { ENJIN_ADDRESS , ENJIN_COIN_ADDRESS , INVERSE_BASIS_POINT , NULL_ADDRESS , NULL_BLOCK_HASH , } from "../constants" ; import { ERC1155 } from "../contracts" ; import { Asset , AssetEvent , ECSignature , OpenSeaAccount , OpenSeaAsset , OpenSeaAssetBundle , OpenSeaAssetContract , OpenSeaCollection , OpenSeaFungibleToken , OpenSeaTraitStats , OpenSeaUser , Order , OrderJSON , OrderSide , SaleKind , Transaction , TxnCallback , UnhashedOrder , UnsignedOrder , Web3Callback , WyvernAsset , WyvernBundle , WyvernFTAsset , WyvernNFTAsset , WyvernSchemaName , } from "../types" ; export { WyvernProtocol } ; export const annotateERC721TransferABI = ( asset ) : AnnotatedFunctionABI => ( { constant : false , inputs : [ { name : "_to" , type : "address" , kind : FunctionInputKind . Replaceable , } , { name : "_tokenId" , type : "uint256" , kind : FunctionInputKind . Asset , value : asset . id , } , ] , target : asset . address , name : "transfer" , outputs : [ ] , payable : false , stateMutability : StateMutability . Nonpayable , type : Web3 . AbiType . Function , } ) ; export const annotateERC20TransferABI = ( asset ) : AnnotatedFunctionABI => ( { constant : false , inputs : [ { name : "_to" , type : "address" , kind : FunctionInputKind . Replaceable , } , { name : "_amount" , type : "uint256" , kind : FunctionInputKind . Count , value : asset . quantity , } , ] , target : asset . address , name : "transfer" , outputs : [ { name : "success" , type : "bool" , kind : FunctionOutputKind . Other , } , ] , payable : false , stateMutability : StateMutability . Nonpayable , type : Web3 . AbiType . Function , } ) ; const txCallbacks = { } ; param inner async function promisify < T > ( inner ) { return new Promise < T > ( ( resolve , reject ) => inner ( ( err , res ) => { if ( err ) { reject ( err ) ; } resolve ( res ) ; } ) ) ; } param callback param onError export async function promisifyCall < T > ( callback , onError ? ) { try { const result = await promisify < T > ( callback ) ; if ( typeof result === "string" && result == "0x" ) { return undefined ; } return result as T ; } catch ( error ) { if ( onError ) { onError ( error ) ; } else { console . error ( error ) ; } return undefined ; } } const track = ( web3 , txHash , onFinalized ) => { if ( txCallbacks [ txHash ] ) { txCallbacks [ txHash ] . push ( onFinalized ) ; } else { txCallbacks [ txHash ] = [ onFinalized ] ; const poll = async ( ) => { const tx = await promisify < Web3 . Transaction > ( ( c ) => web3 . eth . getTransaction ( txHash , c ) ) ; if ( tx && tx . blockHash && tx . blockHash !== NULL_BLOCK_HASH ) { const receipt = await promisify < Web3 . TransactionReceipt | null > ( ( c ) => web3 . eth . getTransactionReceipt ( txHash , c ) ) ; if ( ! receipt ) { console . warn ( "No receipt found for " , txHash ) ; } const status = receipt ? parseInt ( ( receipt . status || "0" ) . toString ( ) ) == 1 : true ; txCallbacks [ txHash ] . map ( ( f ) => f ( status ) ) ; delete txCallbacks [ txHash ] ; } else { setTimeout ( poll , 1000 ) ; } } ; poll ( ) . catch ( ) ; } } ; export const confirmTransaction = async ( web3 , txHash ) => { return new Promise ( ( resolve , reject ) => { track ( web3 , txHash , ( didSucceed ) => { if ( didSucceed ) { resolve ( "Transaction complete!" ) ; } else { reject ( new Error ( `Transaction failed :( You might have already completed this action. See more on the mainnet at etherscan.io/tx/${ txHash }` ) ) ; } } ) ; } ) ; } ; export const assetFromJSON = ( asset ) : OpenSeaAsset => { const isAnimated = asset . image_url && asset . image_url . endsWith ( ".gif" ) ; const isSvg = asset . image_url && asset . image_url . endsWith ( ".svg" ) ; const fromJSON = { tokenId : asset . token_id . toString ( ) , tokenAddress : asset . asset_contract . address , name : asset . name , description : asset . description , owner : asset . owner , assetContract : assetContractFromJSON ( asset . asset_contract ) , collection : collectionFromJSON ( asset . collection ) , orders : asset . orders ? asset . orders . map ( orderFromJSON ) : null , sellOrders : asset . sell_orders ? asset . sell_orders . map ( orderFromJSON ) : null , buyOrders : asset . buy_orders ? asset . buy_orders . map ( orderFromJSON ) : null , isPresale : asset . is_presale , imageUrl : isAnimated || isSvg ? asset . image_url : asset . image_preview_url || asset . image_url , imagePreviewUrl : asset . image_preview_url , imageUrlOriginal : asset . image_original_url , imageUrlThumbnail : asset . image_thumbnail_url , externalLink : asset . external_link , openseaLink : asset . permalink , traits : asset . traits , numSales : asset . num_sales , lastSale : asset . last_sale ? assetEventFromJSON ( asset . last_sale ) : null , backgroundColor : asset . background_color ? `#${ asset . background_color }` : null , transferFee : asset . transfer_fee ? makeBigNumber ( asset . transfer_fee ) : null , transferFeePaymentToken : asset . transfer_fee_payment_token ? tokenFromJSON ( asset . transfer_fee_payment_token ) : null , } ; if ( fromJSON . orders && ! fromJSON . sellOrders ) { fromJSON . sellOrders = fromJSON . orders . filter ( ( o ) => o . side == OrderSide . Sell ) ; } if ( fromJSON . orders && ! fromJSON . buyOrders ) { fromJSON . buyOrders = fromJSON . orders . filter ( ( o ) => o . side == OrderSide . Buy ) ; } return fromJSON ; } ; export const assetEventFromJSON = ( assetEvent ) : AssetEvent => { return { eventType : assetEvent . event_type , eventTimestamp : assetEvent . event_timestamp , auctionType : assetEvent . auction_type , totalPrice : assetEvent . total_price , transaction : assetEvent . transaction ? transactionFromJSON ( assetEvent . transaction ) : null , paymentToken : assetEvent . payment_token ? tokenFromJSON ( assetEvent . payment_token ) : null , } ; } ; export const transactionFromJSON = ( transaction ) : Transaction => { return { fromAccount : accountFromJSON ( transaction . from_account ) , toAccount : accountFromJSON ( transaction . to_account ) , createdDate : new Date ( `${ transaction . created_date }Z` ) , modifiedDate : new Date ( `${ transaction . modified_date }Z` ) , transactionHash : transaction . transaction_hash , transactionIndex : transaction . transaction_index , blockNumber : transaction . block_number , blockHash : transaction . block_hash , timestamp : new Date ( `${ transaction . timestamp }Z` ) , } ; } ; export const accountFromJSON = ( account ) : OpenSeaAccount => { return { address : account . address , config : account . config , profileImgUrl : account . profile_img_url , user : account . user ? userFromJSON ( account . user ) : null , } ; } ; export const userFromJSON = ( user ) : OpenSeaUser => { return { username : user . username , } ; } ; export const assetBundleFromJSON = ( asset_bundle ) : OpenSeaAssetBundle => { const fromJSON = { maker : asset_bundle . maker , assets : asset_bundle . assets . map ( assetFromJSON ) , assetContract : asset_bundle . asset_contract ? assetContractFromJSON ( asset_bundle . asset_contract ) : undefined , name : asset_bundle . name , slug : asset_bundle . slug , description : asset_bundle . description , externalLink : asset_bundle . external_link , permalink : asset_bundle . permalink , sellOrders : asset_bundle . sell_orders ? asset_bundle . sell_orders . map ( orderFromJSON ) : null , } ; return fromJSON ; } ; export const assetContractFromJSON = ( asset_contract ) : OpenSeaAssetContract => { return { name : asset_contract . name , description : asset_contract . description , type : asset_contract . asset_contract_type , schemaName : asset_contract . schema_name , address : asset_contract . address , tokenSymbol : asset_contract . symbol , buyerFeeBasisPoints : + asset_contract . buyer_fee_basis_points , sellerFeeBasisPoints : + asset_contract . seller_fee_basis_points , openseaBuyerFeeBasisPoints : + asset_contract . opensea_buyer_fee_basis_points , openseaSellerFeeBasisPoints : + asset_contract . opensea_seller_fee_basis_points , devBuyerFeeBasisPoints : + asset_contract . dev_buyer_fee_basis_points , devSellerFeeBasisPoints : + asset_contract . dev_seller_fee_basis_points , imageUrl : asset_contract . image_url , externalLink : asset_contract . external_link , wikiLink : asset_contract . wiki_link , } ; } ; export const collectionFromJSON = ( collection ) : OpenSeaCollection => { const createdDate = new Date ( `${ collection . created_date }Z` ) ; return { createdDate , name : collection . name , description : collection . description , slug : collection . slug , editors : collection . editors , hidden : collection . hidden , featured : collection . featured , featuredImageUrl : collection . featured_image_url , displayData : collection . display_data , paymentTokens : ( collection . payment_tokens || [ ] ) . map ( tokenFromJSON ) , openseaBuyerFeeBasisPoints : + collection . opensea_buyer_fee_basis_points , openseaSellerFeeBasisPoints : + collection . opensea_seller_fee_basis_points , devBuyerFeeBasisPoints : + collection . dev_buyer_fee_basis_points , devSellerFeeBasisPoints : + collection . dev_seller_fee_basis_points , payoutAddress : collection . payout_address , imageUrl : collection . image_url , largeImageUrl : collection . large_image_url , stats : collection . stats , traitStats : collection . traits as OpenSeaTraitStats , externalLink : collection . external_url , wikiLink : collection . wiki_url , } ; } ; export const tokenFromJSON = ( token ) : OpenSeaFungibleToken => { const fromJSON = { name : token . name , symbol : token . symbol , decimals : token . decimals , address : token . address , imageUrl : token . image_url , ethPrice : token . eth_price , usdPrice : token . usd_price , } ; return fromJSON ; } ; export const orderFromJSON = ( order ) : Order => { const createdDate = new Date ( `${ order . created_date }Z` ) ; const fromJSON = { hash : order . order_hash || order . hash , cancelledOrFinalized : order . cancelled || order . finalized , markedInvalid : order . marked_invalid , metadata : order . metadata , quantity : new BigNumber ( order . quantity || 1 ) , exchange : order . exchange , makerAccount : order . maker , takerAccount : order . taker , maker : order . maker . address , taker : order . taker . address , makerRelayerFee : new BigNumber ( order . maker_relayer_fee ) , takerRelayerFee : new BigNumber ( order . taker_relayer_fee ) , makerProtocolFee : new BigNumber ( order . maker_protocol_fee ) , takerProtocolFee : new BigNumber ( order . taker_protocol_fee ) , makerReferrerFee : new BigNumber ( order . maker_referrer_fee || 0 ) , waitingForBestCounterOrder : order . fee_recipient . address == NULL_ADDRESS , feeMethod : order . fee_method , feeRecipientAccount : order . fee_recipient , feeRecipient : order . fee_recipient . address , side : order . side , saleKind : order . sale_kind , target : order . target , howToCall : order . how_to_call , calldata : order . calldata , replacementPattern : order . replacement_pattern , staticTarget : order . static_target , staticExtradata : order . static_extradata , paymentToken : order . payment_token , basePrice : new BigNumber ( order . base_price ) , extra : new BigNumber ( order . extra ) , currentBounty : new BigNumber ( order . current_bounty || 0 ) , currentPrice : new BigNumber ( order . current_price || 0 ) , createdTime : new BigNumber ( Math . round ( createdDate . getTime ( ) / 1000 ) ) , listingTime : new BigNumber ( order . listing_time ) , expirationTime : new BigNumber ( order . expiration_time ) , salt : new BigNumber ( order . salt ) , v : parseInt ( order . v ) , r : order . r , s : order . s , paymentTokenContract : order . payment_token_contract ? tokenFromJSON ( order . payment_token_contract ) : undefined , asset : order . asset ? assetFromJSON ( order . asset ) : undefined , assetBundle : order . asset_bundle ? assetBundleFromJSON ( order . asset_bundle ) : undefined , } ; fromJSON . currentPrice = estimateCurrentPrice ( fromJSON ) ; return fromJSON ; } ; param order export const orderToJSON = ( order ) : OrderJSON => { const asJSON = { exchange : order . exchange . toLowerCase ( ) , maker : order . maker . toLowerCase ( ) , taker : order . taker . toLowerCase ( ) , makerRelayerFee : order . makerRelayerFee . toString ( ) , takerRelayerFee : order . takerRelayerFee . toString ( ) , makerProtocolFee : order . makerProtocolFee . toString ( ) , takerProtocolFee : order . takerProtocolFee . toString ( ) , makerReferrerFee : order . makerReferrerFee . toString ( ) , feeMethod : order . feeMethod , feeRecipient : order . feeRecipient . toLowerCase ( ) , side : order . side , saleKind : order . saleKind , target : order . target . toLowerCase ( ) , howToCall : order . howToCall , calldata : order . calldata , replacementPattern : order . replacementPattern , staticTarget : order . staticTarget . toLowerCase ( ) , staticExtradata : order . staticExtradata , paymentToken : order . paymentToken . toLowerCase ( ) , quantity : order . quantity . toString ( ) , basePrice : order . basePrice . toString ( ) , englishAuctionReservePrice : order . englishAuctionReservePrice ? order . englishAuctionReservePrice . toString ( ) : undefined , extra : order . extra . toString ( ) , createdTime : order . createdTime ? order . createdTime . toString ( ) : undefined , listingTime : order . listingTime . toString ( ) , expirationTime : order . expirationTime . toString ( ) , salt : order . salt . toString ( ) , metadata : order . metadata , v : order . v , r : order . r , s : order . s , hash : order . hash , } ; return asJSON ; } ; param web3 param message param signerAddress returns export async function personalSignAsync ( web3 , message , signerAddress ) { const signature = await promisify < Web3 . JSONRPCResponsePayload > ( ( c ) => web3 . currentProvider . sendAsync ( { method : "personal_sign" , params : [ message , signerAddress ] , from : signerAddress , id : new Date ( ) . getTime ( ) , } as any , c ) ) ; const error = ( signature as any ) . error ; if ( error ) { throw new Error ( error ) ; } return parseSignatureHex ( signature . result ) ; } param web3 param address export async function isContractAddress ( web3 , address ) { const code = await promisify < string > ( ( c ) => web3 . eth . getCode ( address , c ) ) ; return code !== "0x" ; } param arg export function makeBigNumber ( arg ) { if ( arg === "0x" ) { arg = 0 ; } arg = arg . toString ( ) ; return new BigNumber ( arg ) ; } param web3 param param0 param from param to param data param gasPrice param value param onError export async function sendRawTransaction ( web3 , { from , to , data , gasPrice , value = 0 , gas } , onError ) { if ( gas == null ) { gas = await estimateGas ( web3 , { from , to , data , value } ) ; } try { const txHashRes = await promisify < string > ( ( c ) => web3 . eth . sendTransaction ( { from , to , value , data , gas , gasPrice , } , c ) ) ; return txHashRes . toString ( ) ; } catch ( error ) { onError ( error ) ; throw error ; } } param web3 param param0 param from param to param data param onError export async function rawCall ( web3 , { from , to , data } , onError ? ) { try { const result = await promisify < string > ( ( c ) => web3 . eth . call ( { from , to , data , } , c ) ) ; return result ; } catch ( error ) { if ( onError ) { onError ( error ) ; } return "0x" ; } } param web3 param from param to param data param value export async function estimateGas ( web3 , { from , to , data , value = 0 } ) { const amount = await promisify < number > ( ( c ) => web3 . eth . estimateGas ( { from , to , value , data , } , c ) ) ; return amount ; } param web3 export async function getCurrentGasPrice ( web3 ) { const meanGas = await promisify < BigNumber > ( ( c ) => web3 . eth . getGasPrice ( c ) ) ; return meanGas ; } param web3 param asset export async function getTransferFeeSettings ( web3 , { asset , accountAddress , } ) { let transferFee ; let transferFeeTokenAddress ; if ( asset . tokenAddress . toLowerCase ( ) == ENJIN_ADDRESS . toLowerCase ( ) ) { const feeContract = web3 . eth . contract ( ERC1155 as Web3 . AbiDefinition [ ] ) . at ( asset . tokenAddress ) ; const params = await promisifyCall < any [ ] > ( ( c ) => feeContract . transferSettings ( asset . tokenId , { from : accountAddress } , c ) ) ; if ( params ) { transferFee = makeBigNumber ( params [ 3 ] ) ; if ( params [ 2 ] == 0 ) { transferFeeTokenAddress = ENJIN_COIN_ADDRESS ; } } } return { transferFee , transferFeeTokenAddress } ; } function parseSignatureHex ( signature ) { const validVParamValues = [ 27 , 28 ] ; const ecSignatureRSV = _parseSignatureHexAsRSV ( signature ) ; if ( _ . includes ( validVParamValues , ecSignatureRSV . v ) ) { return ecSignatureRSV ; } const ecSignatureVRS = _parseSignatureHexAsVRS ( signature ) ; if ( _ . includes ( validVParamValues , ecSignatureVRS . v ) ) { return ecSignatureVRS ; } throw new Error ( "Invalid signature" ) ; function _parseSignatureHexAsVRS ( signatureHex ) { const signatureBuffer = ethUtil . toBuffer ( signatureHex ) ; let v = signatureBuffer [ 0 ] ; if ( v < 27 ) { v += 27 ; } const r = signatureBuffer . slice ( 1 , 33 ) ; const s = signatureBuffer . slice ( 33 , 65 ) ; const ecSignature = { v , r : ethUtil . bufferToHex ( r ) , s : ethUtil . bufferToHex ( s ) , } ; return ecSignature ; } function _parseSignatureHexAsRSV ( signatureHex ) { const { v , r , s } = ethUtil . fromRpcSig ( signatureHex ) ; const ecSignature = { v , r : ethUtil . bufferToHex ( r ) , s : ethUtil . bufferToHex ( s ) , } ; return ecSignature ; } } param order param secondsToBacktrack param shouldRoundUp export function estimateCurrentPrice ( order , secondsToBacktrack = 30 , shouldRoundUp = true ) { let { basePrice , listingTime , expirationTime , extra } = order ; const { side , takerRelayerFee , saleKind } = order ; const now = new BigNumber ( Math . round ( Date . now ( ) / 1000 ) ) . minus ( secondsToBacktrack ) ; basePrice = new BigNumber ( basePrice ) ; listingTime = new BigNumber ( listingTime ) ; expirationTime = new BigNumber ( expirationTime ) ; extra = new BigNumber ( extra ) ; let exactPrice = basePrice ; if ( saleKind === SaleKind . FixedPrice ) { } else if ( saleKind === SaleKind . DutchAuction ) { const diff = extra . times ( now . minus ( listingTime ) ) . dividedBy ( expirationTime . minus ( listingTime ) ) ; exactPrice = side == OrderSide . Sell ? basePrice . minus ( diff ) : basePrice . plus ( diff ) ; } if ( side === OrderSide . Sell && ! order . waitingForBestCounterOrder ) { exactPrice = exactPrice . times ( + takerRelayerFee / INVERSE_BASIS_POINT + 1 ) ; } return shouldRoundUp ? exactPrice . ceil ( ) : exactPrice ; } param schema param asset param quantity export function getWyvernAsset ( schema , asset , quantity = new BigNumber ( 1 ) ) { const tokenId = asset . tokenId != null ? asset . tokenId . toString ( ) : undefined ; return schema . assetFromFields ( { ID : tokenId , Quantity : quantity . toString ( ) , Address : asset . tokenAddress . toLowerCase ( ) , Name : asset . name , } ) ; } param assets param schemas param quantities export function getWyvernBundle ( assets , schemas , quantities ) { if ( assets . length != quantities . length ) { throw new Error ( "Bundle must have a quantity for every asset" ) ; } if ( assets . length != schemas . length ) { throw new Error ( "Bundle must have a schema for every asset" ) ; } const wyAssets = assets . map ( ( asset , i ) => getWyvernAsset ( schemas [ i ] , asset , quantities [ i ] ) ) ; const sorters = [ ( assetAndSchema ) => assetAndSchema . asset . address , ( assetAndSchema ) => assetAndSchema . asset . id || 0 , ] ; const wyAssetsAndSchemas = wyAssets . map ( ( asset , i ) => ( { asset , schema : schemas [ i ] . name as WyvernSchemaName , } ) ) ; const uniqueAssets = _ . uniqBy ( wyAssetsAndSchemas , ( group ) => `${ sorters [ 0 ] ( group ) }-${ sorters [ 1 ] ( group ) }` ) ; if ( uniqueAssets . length != wyAssetsAndSchemas . length ) { throw new Error ( "Bundle can't contain duplicate assets" ) ; } const sortedWyAssetsAndSchemas = _ . sortBy ( wyAssetsAndSchemas , sorters ) ; return { assets : sortedWyAssetsAndSchemas . map ( ( group ) => group . asset ) , schemas : sortedWyAssetsAndSchemas . map ( ( group ) => group . schema ) , } ; } param order export function getOrderHash ( order ) { const orderWithStringTypes = { ... order , maker : order . maker . toLowerCase ( ) , taker : order . taker . toLowerCase ( ) , feeRecipient : order . feeRecipient . toLowerCase ( ) , side : order . side . toString ( ) , saleKind : order . saleKind . toString ( ) , howToCall : order . howToCall . toString ( ) , feeMethod : order . feeMethod . toString ( ) , } ; return WyvernProtocol . getOrderHashHex ( orderWithStringTypes as any ) ; } param order param matchingOrder export function assignOrdersToSides ( order , matchingOrder ) { const isSellOrder = order . side == OrderSide . Sell ; let buy ; let sell ; if ( ! isSellOrder ) { buy = order ; sell = { ... matchingOrder , v : buy . v , r : buy . r , s : buy . s , } ; } else { sell = order ; buy = { ... matchingOrder , v : sell . v , r : sell . r , s : sell . s , } ; } return { buy , sell } ; } param ms export async function delay ( ms ) { return new Promise ( ( res ) => setTimeout ( res , ms ) ) ; } param address export function validateAndFormatWalletAddress ( web3 , address ) { if ( ! address ) { throw new Error ( "No wallet address found" ) ; } if ( ! web3 . isAddress ( address ) ) { throw new Error ( "Invalid wallet address" ) ; } if ( address == NULL_ADDRESS ) { throw new Error ( "Wallet cannot be the null address" ) ; } return address . toLowerCase ( ) ; } param msg export function onDeprecated ( msg ) { console . warn ( `DEPRECATION NOTICE: ${ msg }` ) ; } param erc721Contract export async function getNonCompliantApprovalAddress ( erc721Contract , tokenId , _accountAddress ) { const results = await Promise . all ( [ promisifyCall < string > ( ( c ) => erc721Contract . kittyIndexToApproved . call ( tokenId , c ) ) , promisifyCall < string > ( ( c ) => erc721Contract . partIndexToApproved . call ( tokenId , c ) ) , ] ) ; return _ . compact ( results ) [ 0 ] ; }