Commit 503fc37a authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

fix: Can list and relist to all marketplaces (#5250)

* all listing working

* working when connected locally

* update LR and cleanup

* add comment about LR strategy

* use ms 60s
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 1788c9f3
...@@ -127,7 +127,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => { ...@@ -127,7 +127,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
return ( return (
total + total +
(maxListing.price ?? 0) - (maxListing.price ?? 0) -
(maxListing.price ?? 0) * (maxListing.marketplace.fee / 100 + asset.basisPoints) (maxListing.price ?? 0) * ((maxListing.marketplace.fee + asset.basisPoints / 100) / 100)
) )
} }
return total return total
......
import { OPENSEA_BASE_API_PATH } from 'nft/queries/openSea' import ms from 'ms.macro'
export async function PostOpenSeaSellOrder<T>( export async function PostOpenSeaSellOrder(payload?: Record<string, unknown>): Promise<boolean> {
apiPath: string, const body = payload ? JSON.stringify(payload) : undefined
body?: Record<string, unknown>, const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/postOpenSeaSellOrderWithApiKey`
opts: RequestInit = {} const ac = new AbortController()
): Promise<T> { const req = new Request(url, {
const fetchOpts = {
method: 'POST', method: 'POST',
body: body ? JSON.stringify(body) : undefined,
headers: { headers: {
Accept: 'application/json', 'Content-Type': 'application/json; charset=utf-8',
'Content-Type': 'application/json',
'X-API-KEY': process.env.REACT_APP_OPENSEA_API_KEY ?? '',
}, },
...opts, body,
} signal: ac.signal,
})
const response = await _fetch(apiPath, fetchOpts) const timeout = setTimeout(() => ac.abort(), ms`60s`)
return response.json()
}
async function _fetch(apiPath: string, opts: RequestInit = {}) {
const apiBase = OPENSEA_BASE_API_PATH
const finalUrl = apiBase + apiPath
const finalOpts = {
...opts,
headers: {
...(opts.headers || {}),
},
}
return fetch(finalUrl, finalOpts).then(async (res) => _handleApiResponse(res))
}
async function _handleApiResponse(response: Response) {
if (response.ok) {
return response
}
let result
let errorMessage
try { try {
result = await response.text() const res = await fetch(req)
result = JSON.parse(result) const data = await res.json()
} catch {
// Result will be undefined or text return data.code === 200
} catch (e) {
return false
} finally {
clearTimeout(timeout)
} }
switch (response.status) {
case 400:
errorMessage = result && result.errors ? result.errors.join(', ') : `Invalid request: ${JSON.stringify(result)}`
break
case 401:
case 403:
errorMessage = `Unauthorized. Full message was '${JSON.stringify(result)}'`
break
case 404:
errorMessage = `Not found. Full message was '${JSON.stringify(result)}'`
break
case 500:
errorMessage = `Internal server error. OpenSea has been alerted, but if the problem persists please contact us via Discord: https://discord.gg/ga8EJbv - full message was ${JSON.stringify(
result
)}`
break
case 503:
errorMessage = `Service unavailable. Please try again in a few minutes. If the problem persists please contact us via Discord: https://discord.gg/ga8EJbv - full message was ${JSON.stringify(
result
)}`
break
default:
errorMessage = `Message: ${JSON.stringify(result)}`
break
}
throw new Error(`API Error ${response.status}: ${errorMessage}`)
} }
...@@ -25,3 +25,15 @@ export const newX2Y2Order = async (payload: OrderPayload): Promise<boolean> => { ...@@ -25,3 +25,15 @@ export const newX2Y2Order = async (payload: OrderPayload): Promise<boolean> => {
clearTimeout(timeout) clearTimeout(timeout)
} }
} }
export const getOrderId = async (collectionAddress: string, tokenId: string): Promise<number | undefined> => {
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/getX2Y2OrderId?collectionAddress=${collectionAddress}&tokenId=${tokenId}`
const r = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
const data = await r.json()
return data?.data?.data?.[0]?.id
}
...@@ -11,11 +11,16 @@ import { ...@@ -11,11 +11,16 @@ import {
OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY, OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY,
OPENSEA_DEFAULT_ZONE, OPENSEA_DEFAULT_ZONE,
OPENSEA_KEY_TO_CONDUIT, OPENSEA_KEY_TO_CONDUIT,
OPENSEA_LISTINGS_API_PATH,
} from 'nft/queries/openSea' } from 'nft/queries/openSea'
import ERC721 from '../../abis/erc721.json' import ERC721 from '../../abis/erc721.json'
import { createLooksRareOrder, LOOKSRARE_MARKETPLACE_CONTRACT, newX2Y2Order, PostOpenSeaSellOrder } from '../queries' import {
createLooksRareOrder,
getOrderId,
LOOKSRARE_MARKETPLACE_CONTRACT,
newX2Y2Order,
PostOpenSeaSellOrder,
} from '../queries'
import { INVERSE_BASIS_POINTS, OPENSEA_DEFAULT_FEE, OPENSEA_FEE_ADDRESS } from '../queries/openSea' import { INVERSE_BASIS_POINTS, OPENSEA_DEFAULT_FEE, OPENSEA_FEE_ADDRESS } from '../queries/openSea'
import { ListingMarket, ListingStatus, WalletAsset } from '../types' import { ListingMarket, ListingStatus, WalletAsset } from '../types'
import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } from './x2y2' import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } from './x2y2'
...@@ -23,7 +28,7 @@ import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } ...@@ -23,7 +28,7 @@ import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData }
export const ListingMarkets: ListingMarket[] = [ export const ListingMarkets: ListingMarket[] = [
{ {
name: 'LooksRare', name: 'LooksRare',
fee: 2.0, fee: 1.5,
icon: '/nft/svgs/marketplaces/looksrare.svg', icon: '/nft/svgs/marketplaces/looksrare.svg',
}, },
{ {
...@@ -55,7 +60,7 @@ const getConsiderationItems = ( ...@@ -55,7 +60,7 @@ const getConsiderationItems = (
creatorFee?: ConsiderationInputItem creatorFee?: ConsiderationInputItem
} => { } => {
const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS
const creatorFeeBasisPoints = asset.basisPoints * INVERSE_BASIS_POINTS const creatorFeeBasisPoints = asset.basisPoints
const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints
const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString() const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
...@@ -151,9 +156,9 @@ export async function signListing( ...@@ -151,9 +156,9 @@ export async function signListing(
) )
const order = await executeAllActions() const order = await executeAllActions()
const res = await PostOpenSeaSellOrder(OPENSEA_LISTINGS_API_PATH, order) const res = await PostOpenSeaSellOrder(order)
if (res) setStatus(ListingStatus.APPROVED) if (res) setStatus(ListingStatus.APPROVED)
return true return res
} catch (error) { } catch (error) {
if (error.code === 4001) setStatus(ListingStatus.REJECTED) if (error.code === 4001) setStatus(ListingStatus.REJECTED)
else setStatus(ListingStatus.FAILED) else setStatus(ListingStatus.FAILED)
...@@ -186,8 +191,9 @@ export async function signListing( ...@@ -186,8 +191,9 @@ export async function signListing(
// endTime timestamp in seconds // endTime timestamp in seconds
endTime: BigNumber.from(asset.expirationTime), endTime: BigNumber.from(asset.expirationTime),
// minimum ratio to be received by the user (per 10000) // minimum ratio to be received by the user (per 10000)
// As of 11.10.22 LooksRare charges 1.5% + 0.5% if there's creator royalties set https://docs.looksrare.org/blog/looksrare-offers-zero-royalty-trading-shares-protocol-fees-with-creators-instead
minPercentageToAsk: BigNumber.from(10000) minPercentageToAsk: BigNumber.from(10000)
.sub(BigNumber.from(200).add(BigNumber.from(asset.basisPoints * 10000))) .sub(BigNumber.from(150 + (asset.basisPoints ? 50 : 0)))
.toNumber(), .toNumber(),
// params (e.g., price, target account for private sale) // params (e.g., price, target account for private sale)
params: [], params: [],
...@@ -214,7 +220,7 @@ export async function signListing( ...@@ -214,7 +220,7 @@ export async function signListing(
price: parseEther(listingPrice.toString()).toString(), price: parseEther(listingPrice.toString()).toString(),
startTime: currentTime, startTime: currentTime,
endTime: asset.expirationTime, endTime: asset.expirationTime,
minPercentageToAsk: 10000 - (200 + asset.basisPoints * 10000), minPercentageToAsk: 10000 - (150 + (asset.basisPoints ? 50 : 0)),
params: [], params: [],
} }
const res = await createLooksRareOrder(payload) const res = await createLooksRareOrder(payload)
...@@ -238,14 +244,15 @@ export async function signListing( ...@@ -238,14 +244,15 @@ export async function signListing(
} }
const order = createSellOrder(signerAddress, asset.expirationTime, [orderItem]) const order = createSellOrder(signerAddress, asset.expirationTime, [orderItem])
try { try {
const prevOrderId = await getOrderId(asset.asset_contract.address, asset.tokenId)
await signOrderData(provider, order) await signOrderData(provider, order)
const payload: OrderPayload = { const payload: OrderPayload = {
order: encodeOrder(order), order: encodeOrder(order),
isBundle: false, isBundle: false,
bundleName: '', bundleName: '',
bundleDesc: '', bundleDesc: '',
orderIds: [], orderIds: prevOrderId ? [prevOrderId] : [],
changePrice: false, changePrice: Boolean(prevOrderId),
isCollection: false, isCollection: false,
} }
setStatus(ListingStatus.PENDING) setStatus(ListingStatus.PENDING)
......
...@@ -2638,10 +2638,10 @@ ...@@ -2638,10 +2638,10 @@
"@babel/runtime" "^7.11.2" "@babel/runtime" "^7.11.2"
"@lingui/core" "^3.14.0" "@lingui/core" "^3.14.0"
"@looksrare/sdk@^0.7.1": "@looksrare/sdk@^0.10.2":
version "0.7.4" version "0.10.2"
resolved "https://registry.yarnpkg.com/@looksrare/sdk/-/sdk-0.7.4.tgz#f7b3d5d5a0d94bc5b12e8a29d380cb923f72ce9c" resolved "https://registry.yarnpkg.com/@looksrare/sdk/-/sdk-0.10.2.tgz#08b14cb6d3a5499ad0417c817dbb2da924477f4f"
integrity sha512-zmn0dM0Twe01CGriCNeAY2DV6K8xGZ2VcfSbDYjxbRvL74M0Ec0wgtqTt/6F6m7OwetcYzljhovF4TuZcvvhwA== integrity sha512-K3VVq7rpS0hD5IwfhAcAPJZO5IjL0VbL89Zl04x8WnLsFOwXHzsUkxUpRrL8ko2/bgZdgTqWgUWCvbSoVr8hzw==
"@metamask/detect-provider@^1.2.0": "@metamask/detect-provider@^1.2.0":
version "1.2.0" version "1.2.0"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment