diff --git a/src/api.ts b/src/api.ts index 820cf4bb9..e150c518e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,12 +1,7 @@ import "isomorphic-unfetch"; import _ from "lodash"; import * as QueryString from "query-string"; -import { - API_BASE_MAINNET, - API_BASE_TESTNET, - API_PATH, - ORDERBOOK_PATH, -} from "./constants"; +import { API_BASE_MAINNET, API_BASE_TESTNET, API_PATH } from "./constants"; import { BuildOfferResponse, FulfillmentDataResponse, @@ -272,22 +267,6 @@ export class OpenSeaAPI { return !!json.success; } - /** - * Get which version of Wyvern exchange to use to create orders - * Simply return null in case API doesn't give us a good response - */ - public async getOrderCreateWyvernExchangeAddress(): Promise { - try { - const result = await this.get(`${ORDERBOOK_PATH}/exchange/`); - return result as string; - } catch (error) { - this.logger( - "Couldn't retrieve Wyvern exchange address for order creation" - ); - return null; - } - } - /** * Fetch an asset from the API, throwing if none is found * @param tokenAddress Address of the asset's contract diff --git a/src/constants.ts b/src/constants.ts index 3bfbbcb4f..710b7ee85 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -39,47 +39,9 @@ export const API_BASE_MAINNET = "https://api.opensea.io"; export const API_BASE_TESTNET = "https://testnets-api.opensea.io"; export const RPC_URL_PATH = "jsonrpc/v1/"; export const MAINNET_PROVIDER_URL = `${API_BASE_MAINNET}/${RPC_URL_PATH}`; -export const ORDERBOOK_PATH = `/wyvern/v${ORDERBOOK_VERSION}`; export const API_PATH = `/api/v${ORDERBOOK_VERSION}`; export const MAX_UINT_256 = new BigNumber(2).pow(256).minus(1); -export const EIP_712_ORDER_TYPES = { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], - Order: [ - { name: "exchange", type: "address" }, - { name: "maker", type: "address" }, - { name: "taker", type: "address" }, - { name: "makerRelayerFee", type: "uint256" }, - { name: "takerRelayerFee", type: "uint256" }, - { name: "makerProtocolFee", type: "uint256" }, - { name: "takerProtocolFee", type: "uint256" }, - { name: "feeRecipient", type: "address" }, - { name: "feeMethod", type: "uint8" }, - { name: "side", type: "uint8" }, - { name: "saleKind", type: "uint8" }, - { name: "target", type: "address" }, - { name: "howToCall", type: "uint8" }, - { name: "calldata", type: "bytes" }, - { name: "replacementPattern", type: "bytes" }, - { name: "staticTarget", type: "address" }, - { name: "staticExtradata", type: "bytes" }, - { name: "paymentToken", type: "address" }, - { name: "basePrice", type: "uint256" }, - { name: "extra", type: "uint256" }, - { name: "listingTime", type: "uint256" }, - { name: "expirationTime", type: "uint256" }, - { name: "salt", type: "uint256" }, - { name: "nonce", type: "uint256" }, - ], -}; - -export const EIP_712_WYVERN_DOMAIN_NAME = "Wyvern Exchange Contract"; -export const EIP_712_WYVERN_DOMAIN_VERSION = "2.3"; export const MERKLE_VALIDATOR_MAINNET = "0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7"; export const MERKLE_VALIDATOR_RINKEBY = diff --git a/src/index.ts b/src/index.ts index bd3ae0d00..85479d8d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,9 @@ /* eslint-disable import/no-unused-modules */ import { OpenSeaAPI } from "./api"; -import { OpenSeaSDK, WyvernProtocol } from "./sdk"; +import { OpenSeaSDK } from "./sdk"; import { Network, EventData, EventType } from "./types"; export { orderToJSON, orderFromJSON } from "./utils/utils"; -export { - encodeCall, - encodeSell, - encodeAtomicizedBuy, - encodeAtomicizedSell, - encodeDefaultCall, - encodeReplacementPattern, -} from "./utils/schemas/schema"; -export { WyvernProtocol }; +export { encodeDefaultCall } from "./utils/schemas/schema"; /** * Example setup: diff --git a/src/sdk.ts b/src/sdk.ts index 90c837e8d..c0944201c 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -8,10 +8,8 @@ import { CreateInputItem, OrderComponents, } from "@opensea/seaport-js/lib/types"; -import { generateRandomSalt } from "@opensea/seaport-js/lib/utils/order"; import { BigNumber } from "bignumber.js"; import { Web3JsProvider } from "ethereum-types"; -import { isValidAddress } from "ethereumjs-util"; import { ethers, providers } from "ethers"; import { EventEmitter, EventSubscription } from "fbemitter"; import * as _ from "lodash"; @@ -23,9 +21,6 @@ import { CONDUIT_KEYS_TO_CONDUIT, DEFAULT_GAS_INCREASE_FACTOR, DEFAULT_WRAPPED_NFT_LIQUIDATION_UNISWAP_SLIPPAGE_IN_BASIS_POINTS, - EIP_712_ORDER_TYPES, - EIP_712_WYVERN_DOMAIN_NAME, - EIP_712_WYVERN_DOMAIN_VERSION, ENJIN_COIN_ADDRESS, INVERSE_BASIS_POINT, MANA_ADDRESS, @@ -72,7 +67,6 @@ import { WrappedNFTFactoryAbi } from "./typechain/contracts/WrappedNFTFactoryAbi import { Asset, ComputedFees, - ECSignature, EventData, EventType, FeeMethod, @@ -82,27 +76,19 @@ import { OpenSeaAsset, OpenSeaCollection, OpenSeaFungibleToken, - Order, OrderSide, PartialReadonlyContractAbi, - RawWyvernOrderJSON, - SaleKind, TokenStandardVersion, UnhashedOrder, - UnsignedOrder, WyvernAsset, WyvernFTAsset, WyvernNFTAsset, WyvernSchemaName, } from "./types"; import { - encodeAtomicizedBuy, - encodeAtomicizedSell, encodeAtomicizedTransfer, - encodeBuy, encodeCall, encodeProxyCall, - encodeSell, encodeTransferCall, schemas, Schema, @@ -118,11 +104,9 @@ import { getNonCompliantApprovalAddress, getWyvernAsset, makeBigNumber, - merkleValidatorByNetwork, onDeprecated, rawCall, sendRawTransaction, - signTypedDataAsync, validateAndFormatWalletAddress, getMaxOrderExpirationTimestamp, hasErrorCode, @@ -134,8 +118,6 @@ import { toBaseUnitAmount, } from "./utils/utils"; -export { WyvernProtocol }; - export class OpenSeaSDK { // Web3 instance to use public web3: Web3; @@ -2193,123 +2175,6 @@ export class OpenSeaSDK { return makeBigNumber(approved); } - public _makeMatchingOrder({ - order, - accountAddress, - recipientAddress, - }: { - order: UnsignedOrder; - accountAddress: string; - recipientAddress: string; - }): UnsignedOrder { - accountAddress = validateAndFormatWalletAddress(this.web3, accountAddress); - recipientAddress = validateAndFormatWalletAddress( - this.web3, - recipientAddress - ); - - const computeOrderParams = () => { - const shouldValidate = - order.target === merkleValidatorByNetwork[this._networkName]; - - if ("asset" in order.metadata) { - const schema = this._getSchema(order.metadata.schema); - return order.side == OrderSide.Buy - ? encodeSell( - schema, - order.metadata.asset, - recipientAddress, - shouldValidate ? order.target : undefined - ) - : encodeBuy( - schema, - order.metadata.asset, - recipientAddress, - shouldValidate ? order.target : undefined - ); - } else if ("bundle" in order.metadata) { - // We're matching a bundle order - const bundle = order.metadata.bundle; - const orderedSchemas = bundle.schemas - ? bundle.schemas.map((schemaName) => this._getSchema(schemaName)) - : // Backwards compat: - 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({ - expirationTimestamp: 0, - isMatchingOrder: true, - }); - // Compat for matching buy orders that have fee recipient still on them - const feeRecipient = - order.feeRecipient == NULL_ADDRESS - ? OPENSEA_LEGACY_FEE_RECIPIENT - : NULL_ADDRESS; - - const matchingOrder: UnhashedOrder = { - 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: new BigNumber(generateRandomSalt()), - metadata: order.metadata, - }; - - return matchingOrder; - } - // For creating email whitelists on order takers public async _createEmailWhitelistEntry({ order, @@ -2325,90 +2190,6 @@ export class OpenSeaSDK { await this.api.postAssetWhitelist(asset.address, asset.id, buyerEmail); } - // Throws - public async _sellOrderValidationAndApprovals({ - order, - accountAddress, - }: { - order: UnhashedOrder; - accountAddress: string; - }) { - 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, - }); - - // For fulfilling bids, - // need to approve access to fungible token because of the way fees are paid - // This can be done at a higher level to show UI - if (tokenAddress != NULL_ADDRESS) { - const minimumAmount = makeBigNumber(order.basePrice); - const tokenTransferProxyAddress = - this._wyvernConfigOverride?.wyvernTokenTransferProxyContractAddress || - WyvernProtocol.getTokenTransferProxyAddress(this._networkName); - await this.approveFungibleToken({ - accountAddress, - tokenAddress, - minimumAmount, - proxyAddress: tokenTransferProxyAddress, - }); - } - - // Check sell parameters - const sellValid = await this._wyvernProtocol.wyvernExchange - .validateOrderParameters_( - [ - 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 - ) - .callAsync({ from: accountAddress }); - if (!sellValid) { - console.error(order); - throw new Error( - `Failed to validate sell order parameters. Make sure you're on the right network!` - ); - } - } - /** * Instead of signing an off-chain order, you can approve an order * with on on-chain transaction using this method @@ -2439,45 +2220,6 @@ export class OpenSeaSDK { return transaction.hash; } - public async _validateOrder(order: Order): Promise { - const isValid = await this._wyvernProtocolReadOnly.wyvernExchange - .validateOrder_( - [ - 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 - ) - .callAsync(); - - return isValid; - } - public async _approveAll({ schemaNames, wyAssets, @@ -2835,7 +2577,7 @@ export class OpenSeaSDK { ); } - // Note: WyvernProtocol.toBaseUnitAmount(makeBigNumber(startAmount), token.decimals) + // Note: toBaseUnitAmount(makeBigNumber(startAmount), token.decimals) // will fail if too many decimal places, so special-case ether const basePrice = isEther ? makeBigNumber( @@ -2871,100 +2613,6 @@ export class OpenSeaSDK { return { basePrice, extra, paymentToken, reservePrice, endPrice }; } - private _getMetadata(order: Order, referrerAddress?: string) { - const referrer = referrerAddress || order.metadata.referrerAddress; - if (referrer && isValidAddress(referrer)) { - return referrer; - } - return undefined; - } - - /** - * Gets the current order nonce for an account - * @param accountAddress account to check the nonce for - * @returns nonce - */ - public getNonce(accountAddress: string) { - return this._wyvernProtocol.wyvernExchange - .nonces(accountAddress) - .callAsync(); - } - - /** - * Generate the signature for authorizing an order - * @param order Unsigned wyvern order - * @returns order signature in the form of v, r, s, also an optional nonce - */ - public async authorizeOrder( - order: UnsignedOrder - ): Promise<(ECSignature & { nonce?: number }) | null> { - const signerAddress = order.maker; - - this._dispatch(EventType.CreateOrder, { - order, - accountAddress: order.maker, - }); - - try { - // 2.3 Sign order flow using EIP-712 - const signerOrderNonce = await this.getNonce(signerAddress); - - // We need to manually specify each field because OS orders can contain unrelated data - const orderForSigning: RawWyvernOrderJSON = { - maker: order.maker, - exchange: order.exchange, - taker: order.taker, - makerRelayerFee: order.makerRelayerFee.toString(), - takerRelayerFee: order.takerRelayerFee.toString(), - makerProtocolFee: order.makerProtocolFee.toString(), - takerProtocolFee: order.takerProtocolFee.toString(), - feeRecipient: order.feeRecipient, - feeMethod: order.feeMethod, - side: order.side, - saleKind: order.saleKind, - target: order.target, - howToCall: order.howToCall, - calldata: order.calldata, - replacementPattern: order.replacementPattern, - staticTarget: order.staticTarget, - staticExtradata: order.staticExtradata, - paymentToken: order.paymentToken, - basePrice: order.basePrice.toString(), - extra: order.extra.toString(), - listingTime: order.listingTime.toString(), - expirationTime: order.expirationTime.toString(), - salt: order.salt.toString(), - }; - - // We don't JSON.stringify as certain wallet providers sanitize this data - // https://github.com/coinbase/coinbase-wallet-sdk/issues/60 - const message = { - types: EIP_712_ORDER_TYPES, - domain: { - name: EIP_712_WYVERN_DOMAIN_NAME, - version: EIP_712_WYVERN_DOMAIN_VERSION, - chainId: this._networkName == Network.Main ? 1 : 4, - verifyingContract: order.exchange, - }, - primaryType: "Order", - message: { ...orderForSigning, nonce: signerOrderNonce.toNumber() }, - }; - - const ecSignature = await signTypedDataAsync( - this.web3, - message, - signerAddress - ); - return { ...ecSignature, nonce: signerOrderNonce.toNumber() }; - } catch (error) { - this._dispatch(EventType.OrderDenied, { - order, - accountAddress: signerAddress, - }); - throw error; - } - } - private _getSchemaName(asset: Asset | OpenSeaAsset) { if (asset.schemaName) { return asset.schemaName; @@ -3002,19 +2650,16 @@ export class OpenSeaSDK { */ private _getClientsForRead({ retries }: { retries: number }): { web3: Web3; - wyvernProtocol: WyvernProtocol; } { if (retries > 0) { // Use injected provider by default return { web3: this.web3, - wyvernProtocol: this._wyvernProtocol, }; } else { // Use provided provider as fallback return { web3: this.web3ReadOnly, - wyvernProtocol: this._wyvernProtocolReadOnly, }; } } diff --git a/src/types.ts b/src/types.ts index 5318058d8..3bd6342f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -113,7 +113,7 @@ export type WyvernConfig = { }; /** - * Wyvern order side: buy or sell. + * Seaport order side: buy or sell. */ export enum OrderSide { Buy = 0, diff --git a/src/utils/schemas/schema.ts b/src/utils/schemas/schema.ts index 348793882..c7dcd40d0 100644 --- a/src/utils/schemas/schema.ts +++ b/src/utils/schemas/schema.ts @@ -1,7 +1,6 @@ import { BigNumber } from "bignumber.js"; import * as ethABI from "ethereumjs-abi"; import { WyvernProtocol } from "wyvern-js"; -import { WyvernAtomicizerContract } from "wyvern-js/lib/abi_gen/wyvern_atomicizer"; import { goerliSchemas } from "./goerli/index"; import { mainSchemas } from "./main/index"; import { rinkebySchemas } from "./rinkeby/index"; @@ -13,7 +12,6 @@ import { FunctionInputKind, HowToCall, Network, - OrderSide, WyvernAsset, } from "../../types"; @@ -112,22 +110,6 @@ export interface Schema { } /* eslint-enable @typescript-eslint/no-explicit-any */ -type ReplacementEncoder = ( - abi: AnnotatedFunctionABI, - kind?: FunctionInputKind, - encodeToBytes?: boolean -) => string; - -export const encodeReplacementPattern: ReplacementEncoder = - WyvernProtocol.encodeReplacementPattern; - -export type Encoder = ( - schema: Schema, - asset: WyvernAsset, - address: string, - validatorAddress?: string -) => CallSpec; - export const encodeCall = ( abi: AnnotatedFunctionABI, parameters: unknown[] @@ -142,150 +124,6 @@ export const encodeCall = ( ); }; -export const encodeSell: Encoder = ( - schema, - asset, - address, - validatorAddress?: string -) => { - const transfer = - validatorAddress && schema.functions.checkAndTransfer - ? schema.functions.checkAndTransfer(asset, validatorAddress) - : schema.functions.transfer(asset); - return { - target: transfer.target, - calldata: encodeDefaultCall(transfer, address), - replacementPattern: encodeReplacementPattern(transfer), - }; -}; - -export type AtomicizedSellEncoder = ( - schemas: Array>, - assets: WyvernAsset[], - address: string, - wyvernProtocol: WyvernProtocol, - networkName: Network -) => CallSpec; - -export const encodeAtomicizedSell: AtomicizedSellEncoder = ( - schemas, - assets, - address, - wyvernProtocol, - networkName -) => { - const atomicizer = wyvernProtocol.wyvernAtomicizer; - - const { atomicizedCalldata, atomicizedReplacementPattern } = - encodeAtomicizedCalldata( - atomicizer, - schemas, - assets, - address, - OrderSide.Sell - ); - - return { - calldata: atomicizedCalldata, - replacementPattern: atomicizedReplacementPattern, - target: WyvernProtocol.getAtomicizerContractAddress(networkName), - }; -}; - -export type AtomicizedBuyEncoder = ( - schemas: Array>, - assets: WyvernAsset[], - address: string, - wyvernProtocol: WyvernProtocol, - networkName: Network -) => CallSpec; - -export const encodeAtomicizedBuy: AtomicizedBuyEncoder = ( - schemas, - assets, - address, - wyvernProtocol, - networkName -) => { - const atomicizer = wyvernProtocol.wyvernAtomicizer; - - const { atomicizedCalldata, atomicizedReplacementPattern } = - encodeAtomicizedCalldata( - atomicizer, - schemas, - assets, - address, - OrderSide.Buy - ); - - return { - calldata: atomicizedCalldata, - replacementPattern: atomicizedReplacementPattern, - target: WyvernProtocol.getAtomicizerContractAddress(networkName), - }; -}; - -export const encodeBuy: Encoder = ( - schema, - asset, - address, - validatorAddress?: string -) => { - const transfer = - validatorAddress && schema.functions.checkAndTransfer - ? schema.functions.checkAndTransfer(asset, validatorAddress) - : schema.functions.transfer(asset); - const replaceables = transfer.inputs.filter( - (i) => i.kind === FunctionInputKind.Replaceable - ); - const ownerInputs = transfer.inputs.filter( - (i) => i.kind === FunctionInputKind.Owner - ); - - // Validate - if (replaceables.length !== 1) { - throw new Error( - "Only 1 input can match transfer destination, but instead " + - replaceables.length + - " did" - ); - } - - // Compute calldata - const parameters = transfer.inputs.map((input) => { - switch (input.kind) { - case FunctionInputKind.Replaceable: - return address; - case FunctionInputKind.Owner: - return WyvernProtocol.generateDefaultValue(input.type); - default: - try { - return input.value.toString(); - } catch (e) { - console.error(schema); - console.error(asset); - throw e; - } - } - }); - const calldata = encodeCall(transfer, parameters); - - // Compute replacement pattern - let replacementPattern = "0x"; - if (ownerInputs.length > 0) { - replacementPattern = encodeReplacementPattern( - transfer, - FunctionInputKind.Owner - ); - } - - return { - target: transfer.target, - calldata, - replacementPattern, - }; -}; - export type DefaultCallEncoder = ( abi: AnnotatedFunctionABI, address: string @@ -401,63 +239,6 @@ export function encodeProxyCall( ]); } -// Helpers for atomicizer - -function encodeAtomicizedCalldata( - atomicizer: WyvernAtomicizerContract, - schemas: Array>, - assets: WyvernAsset[], - address: string, - side: OrderSide -) { - const encoder = side === OrderSide.Sell ? encodeSell : encodeBuy; - - try { - const transactions = assets.map((asset, i) => { - const schema = schemas[i]; - const { target, calldata } = encoder(schema, asset, address); - return { - calldata, - abi: schema.functions.transfer(asset), - address: target, - value: new BigNumber(0), - }; - }); - - const atomicizedCalldata = atomicizer - .atomicize( - transactions.map((t) => t.address), - transactions.map((t) => t.value), - transactions.map((t) => new BigNumber((t.calldata.length - 2) / 2)), // subtract 2 for '0x', divide by 2 for hex - transactions.map((t) => t.calldata).reduce((x, y) => x + y.slice(2)) // cut off the '0x' - ) - .getABIEncodedTransactionData(); - - const kind = side === OrderSide.Buy ? FunctionInputKind.Owner : undefined; - - const atomicizedReplacementPattern = - WyvernProtocol.encodeAtomicizedReplacementPattern( - transactions.map((t) => t.abi), - kind - ); - - if (!atomicizedCalldata || !atomicizedReplacementPattern) { - throw new Error( - `Invalid calldata: ${atomicizedCalldata}, ${atomicizedReplacementPattern}` - ); - } - return { - atomicizedCalldata, - atomicizedReplacementPattern, - }; - } catch (error) { - console.error({ schemas, assets, address, side }); - throw new Error( - `Failed to construct your order: likely something strange about this type of item. OpenSea has been notified. Please contact us in Discord! Original error: ${error}` - ); - } -} - export const schemas = { goerli: goerliSchemas, rinkeby: rinkebySchemas,