Skip to content

Commit

Permalink
feat: fetch and calculate stats from NFTPort (#2085)
Browse files Browse the repository at this point in the history
* feat: fetch and calculate stats from NFTPort

* fix: mobile layout for lg down

* feat: update NFTPort stat calculations

* chore: adjust responsive font sizes

* feat: add eth conversion fallback and refactor into function

* feat: add missing nft values to total

* chore: update nftport stats calclations

* chore: update fallback NFTPort data

* chore: add minus sign to drops in activity

* chore: update stats text colors
  • Loading branch information
johnathonroach authored Sep 1, 2022
1 parent db094ca commit a3b6116
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 8 deletions.
7 changes: 6 additions & 1 deletion packages/website/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/00000
NEXT_PUBLIC_ENV=dev
NEXT_PUBLIC_API=http://localhost:8787
NEXT_PUBLIC_MAGIC=<PUBLISHABLE_API_KEY>
NEXT_PUBLIC_STATUS_PAGE_API=https://nftstorage-jroach.statuspage.io/api/v2
NEXT_PUBLIC_STATUS_PAGE_API=

# NFTPort API
NEXT_PUBLIC_NFT_PORT_ENDPOINT=https://api.nftport.xyz/v0/reports/uris
# Get API Key at https://dashboard.nftport.xyz/sign-up
NEXT_PUBLIC_NFT_PORT_API_KEY=
7 changes: 7 additions & 0 deletions packages/website/lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
let API = /** @type {string} **/ process.env.NEXT_PUBLIC_API || ''
let MAGIC_TOKEN = /** @type {string} **/ process.env.NEXT_PUBLIC_MAGIC || ''
export const NFT_PORT_ENDPOINT =
/** @type {string} **/ process.env.NEXT_PUBLIC_NFT_PORT_ENDPOINT ||
'https://api.nftport.xyz/v0/reports/uris'
export const NFT_PORT_API_KEY =
/** @type {string} **/ process.env.NEXT_PUBLIC_NFT_PORT_API_KEY || ''

if (globalThis.window) {
switch (location.host) {
Expand Down Expand Up @@ -33,4 +38,6 @@ export default {
AUTHENTICATED_ROUTES,
API: API,
MAGIC_TOKEN: MAGIC_TOKEN,
NFT_PORT_ENDPOINT: NFT_PORT_ENDPOINT,
NFT_PORT_API_KEY: NFT_PORT_API_KEY,
}
64 changes: 64 additions & 0 deletions packages/website/lib/statsUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ const getTotalUploads = (stats, keys) => {
const getPreviousTotal = (stats, totalUploads) =>
totalUploads - stats.uploads_past_7_total

const getRateFromEthTo = async (to = 'USD') => {
const ethRateAPI = `https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=${to}`
return await (await fetch(ethRateAPI, { method: 'GET' })).json().catch(() => {
return null
})
}

/**
* Format Bytes
* @param {any} stats
Expand All @@ -63,3 +70,60 @@ export function calculateStats(stats) {

return { ...stats, totalUploads, growthRate, dealsSizeGrowthRate }
}

/**
* Aggregate stats from NFTPort
* @param {any} stats
* @returns {Promise<any>}
*/
export async function calculateMarketStats(stats) {
const { USD: ethConversion } = await getRateFromEthTo('USD')
const statsToCalculate = ['image_uri_stats']
const calculated = Object.keys(stats)
.filter((stat) => statsToCalculate.includes(stat))
.reduce(
(a, c) => {
for (const prop in stats[c]) {
a.totalNfts += stats[c][prop].count_successful
a.totalNfts += stats[c][prop].count_failed
a.totalMissing += stats[c][prop].count_failed
a.missingMarketValue += stats[c][prop].estimated_price_failed
a.totalMarketValue += stats[c][prop].estimated_price_successful
a.totalMarketValue += stats[c][prop].estimated_price_failed
}

return a
},
{
totalNfts: 0,
totalMarketValue: 0.0,
totalMissing: 0,
missingMarketValue: 0.0,
missingPercentage: 0.0,
totalMarketValueUSD: 0,
missingMarketValueUSD: 0,
}
)
calculated.totalNfts = parseFloat(calculated.totalNfts.toFixed(0))
calculated.totalMarketValue = parseFloat(
calculated.totalMarketValue.toFixed(2)
)
calculated.missingMarketValue = parseFloat(
calculated.missingMarketValue.toFixed(2)
)
if (ethConversion) {
calculated.totalMarketValueUSD =
parseFloat(calculated.totalMarketValue.toFixed(2)) *
parseFloat(ethConversion.toFixed(2))
calculated.missingMarketValueUSD =
parseFloat(calculated.missingMarketValue.toFixed(2)) *
parseFloat(ethConversion.toFixed(2))
}
calculated.missingPercentage = (() => {
return parseFloat(
((calculated.totalMissing / calculated.totalNfts) * 100).toFixed(1)
)
})()

return calculated
}
126 changes: 119 additions & 7 deletions packages/website/pages/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { useEffect, useState } from 'react'
import { TrustedBy } from '../components/trustedByLogos'
import { NftUpCta } from '../components/nftUpCta'
import fs from 'fs'
import { calculateStats } from '../lib/statsUtils'
import { calculateStats, calculateMarketStats } from '../lib/statsUtils'
import Img from '../components/cloudflareImage'
import { API } from '../lib/api'
import Loading from '../components/loading'
import bytes from 'bytes'
import { NFT_PORT_ENDPOINT, NFT_PORT_API_KEY } from '../lib/constants'

/**
*
Expand Down Expand Up @@ -47,6 +48,8 @@ export function getStaticProps() {
export default function Stats({ logos }) {
/** @type [any, any] */
const [stats, setStats] = useState({})
/** @type [any, any] */
const [marketStats, setMarketStats] = useState({})
const [statsLoading, setStatsLoading] = useState(false)

useEffect(() => {
Expand All @@ -55,6 +58,29 @@ export default function Stats({ logos }) {

async function fetchStats() {
setStatsLoading(true)
try {
const nftPortStats = await fetch(NFT_PORT_ENDPOINT, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: NFT_PORT_API_KEY,
},
redirect: 'follow',
})
const data = await nftPortStats.json()
setMarketStats(await calculateMarketStats(data.report))
} catch (e) {
const fakeData = {
totalNfts: 91100000,
totalMarketValueUSD: 26800000000,
totalMarketValue: 0,
totalMissing: 1150000,
missingPercentage: 22.3,
missingMarketValueUSD: 874700000,
missingMarketValue: 0,
}
setMarketStats(fakeData)
}
try {
const stats = await fetch(`${API}/stats`, {
method: 'GET',
Expand Down Expand Up @@ -110,6 +136,20 @@ export default function Stats({ logos }) {
)
}

/**
* @param {Object} props
* @param {string} [props.title]
* @param {any} [props.children]
*/
const MarketStatCard = ({ title, children }) => {
return (
<div className="market-stats-card bg-white text-center border border-black h-full box-content flex flex-col justify-center p-4">
<div>{children}</div>
<div className="text-lg">{title}</div>
</div>
)
}

const StatCards = () => {
const figureClass = `chicagoflf text-[clamp(22px,_4.2rem,_6vw)] my-5`
const statImageClass = `w-full border-b border-black object-cover aspect-[5/2]`
Expand Down Expand Up @@ -140,9 +180,11 @@ export default function Stats({ logos }) {
</figure>
<p
className={`chicagoflf ${
stats.growthRate > 0
? `text-forest before:content-['+']`
: 'text-red'
stats.growthRate >= 0
? `text-forest ${
stats.growthRate > 0 ? "before:content-['+']" : ''
}`
: `text-red before:content-['-']`
}`}
>
{stats.growthRate || 0}%
Expand Down Expand Up @@ -172,9 +214,11 @@ export default function Stats({ logos }) {
</figure>
<p
className={`chicagoflf ${
stats.deals_total > 0
? `text-forest before:content-['+']`
: 'text-red'
stats.deals_total >= 0
? `text-forest ${
stats.deals_total > 0 ? "before:content-['+']" : ''
}`
: `text-red before:content-['-']`
}`}
>
{stats.dealsSizeGrowthRate || 0}%
Expand All @@ -189,10 +233,78 @@ export default function Stats({ logos }) {
)
}

const MarketStatCards = () => {
return (
<div className="max-w-7xl mx-auto py-4 px-6 sm:px-16">
<div className="mb-16 pl-8 grid gap-x-4 gap-y-[8vw] md:grid-cols-2 xl:grid-cols-4">
<MarketStatCard title="Total Count of NFTS">
<figure className="chicagoflf text-[clamp(2rem,2.6rem,3.3rem)] text-navy">
{statsLoading && <Loading />}
{new Intl.NumberFormat('en-GB', {
notation: 'compact',
compactDisplay: 'short',
maximumFractionDigits: 1,
}).format(marketStats.totalNfts || 0)}
</figure>
</MarketStatCard>
<MarketStatCard title="Total market value of NFTs">
<figure className="chicagoflf text-[clamp(2rem,2.6rem,3.3rem)] text-forest">
{statsLoading && <Loading />}
{marketStats.totalMarketValueUSD > 0 ? '$' : 'Ξ'}
{new Intl.NumberFormat('en-GB', {
notation: 'compact',
compactDisplay: 'short',
maximumFractionDigits: 1,
}).format(
marketStats.missingMarketValueUSD > 0
? marketStats.totalMarketValueUSD
: marketStats.totalMarketValue || 0
)}
</figure>
</MarketStatCard>
<MarketStatCard title="Market value of missing NFTs">
<figure className="chicagoflf text-[clamp(2rem,2.6rem,3.3rem)] text-red">
{statsLoading && <Loading />}
{marketStats.missingMarketValueUSD > 0 ? '$' : 'Ξ'}
{new Intl.NumberFormat('en-GB', {
notation: 'compact',
compactDisplay: 'short',
maximumFractionDigits: 1,
}).format(
marketStats.missingMarketValueUSD > 0
? marketStats.missingMarketValueUSD
: marketStats.missingMarketValue || 0
)}
</figure>
</MarketStatCard>
<MarketStatCard title="Percentage of NFTs deemed missing">
<figure className="chicagoflf text-[clamp(2rem,2.6rem,3.3rem)] text-yellow">
{statsLoading && <Loading />}
{marketStats.missingPercentage ?? 0}%
</figure>
</MarketStatCard>
</div>
</div>
)
}

return (
<main className="bg-nsgreen">
<Marquee />
<StatCards />
<div className="bg-nspeach">
<div className="relative w-screen flex items-center justify-center">
<div className="text-center">
<p className="chicagoflf p-4 m-0 mt-5 text-[clamp(14px,_2rem,_6vw)]">
NFT Market By the Numbers
</p>
<p className="chicagoflf p-4 m-0 mt-5 text-[clamp(12px,_1.6rem,_6vw)]">
The Price of Missing NFTS
</p>
</div>
</div>
<MarketStatCards />
</div>
<div className="bg-nsblue">
<div className="stats-trusted-wrapper max-w-7xl mx-auto py-4 px-6 sm:px-16">
<div>
Expand Down
14 changes: 14 additions & 0 deletions packages/website/styles/stats.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@
transform: translate3d(1rem, -1rem, 0);
}

.market-stats-card {
box-sizing: border-box;
z-index: 1;
position: relative;
padding: 2rem;
margin: 0 0.25rem;
box-shadow: black 8px 8px 0 0;
border-radius: 2px;
}

.market-stats-card:nth-child(even) {
margin-top: 1rem;
}

.stats-trusted-wrapper > div {
max-width: calc(100% - 2rem);
margin: 4rem 0;
Expand Down

0 comments on commit a3b6116

Please sign in to comment.