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[]) => {
return (
total +
(maxListing.price ?? 0) -
(maxListing.price ?? 0) * (maxListing.marketplace.fee / 100 + asset.basisPoints)
(maxListing.price ?? 0) * ((maxListing.marketplace.fee + asset.basisPoints / 100) / 100)
)
}
return total
......
import { OPENSEA_BASE_API_PATH } from 'nft/queries/openSea'
import ms from 'ms.macro'
export async function PostOpenSeaSellOrder<T>(
apiPath: string,
body?: Record<string, unknown>,
opts: RequestInit = {}
): Promise<T> {
const fetchOpts = {
export async function PostOpenSeaSellOrder(payload?: Record<string, unknown>): Promise<boolean> {
const body = payload ? JSON.stringify(payload) : undefined
const url = `${process.env.REACT_APP_GENIE_V3_API_URL}/postOpenSeaSellOrderWithApiKey`
const ac = new AbortController()
const req = new Request(url, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-API-KEY': process.env.REACT_APP_OPENSEA_API_KEY ?? '',
'Content-Type': 'application/json; charset=utf-8',
},
...opts,
}
const response = await _fetch(apiPath, fetchOpts)
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
body,
signal: ac.signal,
})
const timeout = setTimeout(() => ac.abort(), ms`60s`)
try {
result = await response.text()
result = JSON.parse(result)
} catch {
// Result will be undefined or text
const res = await fetch(req)
const data = await res.json()
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> => {
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 {
OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY,
OPENSEA_DEFAULT_ZONE,
OPENSEA_KEY_TO_CONDUIT,
OPENSEA_LISTINGS_API_PATH,
} from 'nft/queries/openSea'
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 { ListingMarket, ListingStatus, WalletAsset } from '../types'
import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } from './x2y2'
......@@ -23,7 +28,7 @@ import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData }
export const ListingMarkets: ListingMarket[] = [
{
name: 'LooksRare',
fee: 2.0,
fee: 1.5,
icon: '/nft/svgs/marketplaces/looksrare.svg',
},
{
......@@ -55,7 +60,7 @@ const getConsiderationItems = (
creatorFee?: ConsiderationInputItem
} => {
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 openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
......@@ -151,9 +156,9 @@ export async function signListing(
)
const order = await executeAllActions()
const res = await PostOpenSeaSellOrder(OPENSEA_LISTINGS_API_PATH, order)
const res = await PostOpenSeaSellOrder(order)
if (res) setStatus(ListingStatus.APPROVED)
return true
return res
} catch (error) {
if (error.code === 4001) setStatus(ListingStatus.REJECTED)
else setStatus(ListingStatus.FAILED)
......@@ -186,8 +191,9 @@ export async function signListing(
// endTime timestamp in seconds
endTime: BigNumber.from(asset.expirationTime),
// 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)
.sub(BigNumber.from(200).add(BigNumber.from(asset.basisPoints * 10000)))
.sub(BigNumber.from(150 + (asset.basisPoints ? 50 : 0)))
.toNumber(),
// params (e.g., price, target account for private sale)
params: [],
......@@ -214,7 +220,7 @@ export async function signListing(
price: parseEther(listingPrice.toString()).toString(),
startTime: currentTime,
endTime: asset.expirationTime,
minPercentageToAsk: 10000 - (200 + asset.basisPoints * 10000),
minPercentageToAsk: 10000 - (150 + (asset.basisPoints ? 50 : 0)),
params: [],
}
const res = await createLooksRareOrder(payload)
......@@ -238,14 +244,15 @@ export async function signListing(
}
const order = createSellOrder(signerAddress, asset.expirationTime, [orderItem])
try {
const prevOrderId = await getOrderId(asset.asset_contract.address, asset.tokenId)
await signOrderData(provider, order)
const payload: OrderPayload = {
order: encodeOrder(order),
isBundle: false,
bundleName: '',
bundleDesc: '',
orderIds: [],
changePrice: false,
orderIds: prevOrderId ? [prevOrderId] : [],
changePrice: Boolean(prevOrderId),
isCollection: false,
}
setStatus(ListingStatus.PENDING)
......
......@@ -2638,10 +2638,10 @@
"@babel/runtime" "^7.11.2"
"@lingui/core" "^3.14.0"
"@looksrare/sdk@^0.7.1":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@looksrare/sdk/-/sdk-0.7.4.tgz#f7b3d5d5a0d94bc5b12e8a29d380cb923f72ce9c"
integrity sha512-zmn0dM0Twe01CGriCNeAY2DV6K8xGZ2VcfSbDYjxbRvL74M0Ec0wgtqTt/6F6m7OwetcYzljhovF4TuZcvvhwA==
"@looksrare/sdk@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@looksrare/sdk/-/sdk-0.10.2.tgz#08b14cb6d3a5499ad0417c817dbb2da924477f4f"
integrity sha512-K3VVq7rpS0hD5IwfhAcAPJZO5IjL0VbL89Zl04x8WnLsFOwXHzsUkxUpRrL8ko2/bgZdgTqWgUWCvbSoVr8hzw==
"@metamask/detect-provider@^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