Find out how much ETH is required to move to a certain floor price.
yarn
./node_modules/.bin/ts-node floor-depth.ts --slug forgottenruneswizardscult
NOTE: OpenSea limits to 50 listings per page (:eyeroll:) so this takes an eternity (hours) to run
| import { BigNumber } from "@ethersproject/bignumber"; | |
| import * as chalk from "chalk"; | |
| import { formatEther } from "ethers/lib/utils"; | |
| import * as fs from "fs"; | |
| import { forEach, groupBy, last } from "lodash"; | |
| import * as fetch from "node-fetch"; | |
| import * as ora from "ora"; | |
| import * as yargs from "yargs"; | |
| const argv = yargs | |
| .usage("$0") | |
| .option("slug", { | |
| describe: "open-sea slug", | |
| string: true, | |
| required: true, | |
| default: "forgottenruneswizardscult" | |
| }) | |
| .option("rpc", { | |
| describe: "The URL to your provider", | |
| string: true, | |
| required: true, | |
| default: "https://cloudflare-eth.com/" | |
| }) | |
| .help("help").argv; | |
| const runes = ["Φ", "Ψ", "λ", "ϐ", "ҩ", "Ӝ", "⊙", "⚧", "⚭", "Ω"]; | |
| async function fetchOrderPage({ | |
| slug, | |
| spinner, | |
| page = 0 | |
| }: { | |
| slug: string; | |
| spinner: any; | |
| page: number; | |
| }) { | |
| spinner.text = `Fetching page ${chalk.blue("#" + page)}`; | |
| const url = `https://api.opensea.io/wyvern/v1/orders?collection_slug=${slug}&bundled=false&include_bundled=false&include_invalid=false&side=1&limit=50&offset=${page}&order_by=created_date&order_direction=desc`; | |
| const response = await ( | |
| await fetch(url, { | |
| method: "GET", | |
| headers: { Accept: "application/json" } | |
| }) | |
| ).json(); | |
| return response.orders; | |
| } | |
| const WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; | |
| const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; | |
| function preprocessOrders({ orders }) { | |
| let lowestOrders = []; | |
| const ordersByToken = groupBy(orders, (o) => o.asset.token_id); | |
| forEach(ordersByToken, (tokensOrders, tokenId) => { | |
| const ethOrders = tokensOrders.filter( | |
| (o) => o.payment_token === WETH || o.payment_token === ZERO_ADDRESS | |
| ); | |
| const sortedByPrice = ethOrders.sort((a, b) => | |
| BigNumber.from(a.base_price).gt(BigNumber.from(b.base_price)) ? 1 : -1 | |
| ); | |
| lowestOrders.push(sortedByPrice[0]); | |
| }); | |
| return lowestOrders.sort((a, b) => | |
| BigNumber.from(a.base_price).gt(BigNumber.from(b.base_price)) ? 1 : -1 | |
| ); | |
| } | |
| async function run(argv: any) { | |
| const spinner = ora({ | |
| text: "Downloading", | |
| spinner: { interval: 80, frames: runes }, | |
| prefixText: "🧙♀️" | |
| }).start(); | |
| let page = 0; | |
| let lastCount = 0; | |
| let orders = []; | |
| let maxPages = 5000; | |
| do { | |
| const newOrders = await fetchOrderPage({ slug: argv.slug, spinner, page }); | |
| lastCount = newOrders.length; | |
| orders = orders.concat(newOrders); | |
| page += 1; | |
| } while (lastCount > 0 && page <= maxPages); | |
| /** | |
| * the same user might have multiple open orders at different price points | |
| * because it costs gas to cancel an order so users often add a new order at a | |
| * lower price point. we're trying to calculate the lowest price that could | |
| * move the floor, so we want to ignore all but the lowest open sell order | |
| */ | |
| const processedOrders = preprocessOrders({ orders }); | |
| fs.writeFileSync("depth.csv", ""); | |
| let sum = BigNumber.from(0); | |
| processedOrders.map((o) => { | |
| const row = [ | |
| o.asset.token_id, | |
| o.base_price, | |
| sum.toString(), | |
| formatEther(sum) | |
| ]; | |
| console.log(...row); | |
| fs.appendFileSync("depth.csv", row.join(",") + "\n"); | |
| sum = sum.add(BigNumber.from(o.base_price)); | |
| }); | |
| spinner.color = "green"; | |
| spinner.text = "Complete"; | |
| spinner.succeed(); | |
| } | |
| run(argv); |
| { | |
| "name": "floor-depth", | |
| "version": "1.0.0", | |
| "description": "", | |
| "main": "index.js", | |
| "scripts": { | |
| "test": "echo \"Error: no test specified\" && exit 1" | |
| }, | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "dependencies": { | |
| "chalk": "^4.1.0", | |
| "ethers": "^5.0.26", | |
| "lodash": "^4.17.20", | |
| "ora": "^5.3.0", | |
| "ts-node": "^9.0.0", | |
| "typescript": "^4.0.5", | |
| "yargs": "^16.1.0" | |
| } | |
| } |