add web server to show data
parent
c62281b2f6
commit
0ecae09714
@ -0,0 +1,186 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Art101 Sales Statistics</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 5fr 2fr;
|
||||||
|
grid-gap: 2.5rem;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#container > div {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
#loading {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: "Open Sans";
|
||||||
|
color: black;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Art101 Sales Statistics</h1>
|
||||||
|
<p><a href="https://github.com/lalanza808/nft-sales-scraper" target="_blank">Source Code</a></p>
|
||||||
|
<div id="loading">
|
||||||
|
Loading, please wait...
|
||||||
|
</div>
|
||||||
|
<div id="container"></div>
|
||||||
|
<script>
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
|
||||||
|
fetch('/api/contracts').then(function(response) {
|
||||||
|
response.json().then(function(json) {
|
||||||
|
for (contract in json) {
|
||||||
|
console.log(`Found contract ${contract} (${json[contract]['contract_address']})`)
|
||||||
|
const contractName = contract;
|
||||||
|
const contractAddress = json[contract]['contract_address'];
|
||||||
|
const salesId = `sales-chart-${contractName}`;
|
||||||
|
const platformId = `platform-chart-${contractName}`;
|
||||||
|
newSalesChartDiv = document.createElement('div');
|
||||||
|
newSalesChart = document.createElement('canvas');
|
||||||
|
newSalesChart.setAttribute('id', salesId);
|
||||||
|
newSalesChartDiv.appendChild(newSalesChart);
|
||||||
|
newPlatformChartDiv = document.createElement('div');
|
||||||
|
newPlatformChart = document.createElement('canvas');
|
||||||
|
newPlatformChart.setAttribute('id', platformId);
|
||||||
|
newPlatformChartDiv.appendChild(newPlatformChart);
|
||||||
|
container.appendChild(newSalesChartDiv);
|
||||||
|
container.appendChild(newPlatformChartDiv);
|
||||||
|
let loaded = 0;
|
||||||
|
fetch(`/api/${contractAddress}/data`).then(function(response) {
|
||||||
|
response.json().then(function(json) {
|
||||||
|
loaded++;
|
||||||
|
if (loaded == 2) {
|
||||||
|
document.getElementById("container").style.visibility = "visible";
|
||||||
|
document.getElementById("loading").style.display = "none";
|
||||||
|
}
|
||||||
|
const labels = json.map(d => d.block);
|
||||||
|
const data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
label: 'Average price',
|
||||||
|
backgroundColor: 'rgb(99, 132, 255)',
|
||||||
|
borderColor: 'rgb(99, 132, 255)',
|
||||||
|
data: json.map(d => d.average_price),
|
||||||
|
yAxisID: 'y1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
label: 'Floor price',
|
||||||
|
backgroundColor: '#ccc',
|
||||||
|
borderColor: '#ccc',
|
||||||
|
data: json.map(d => d.floor_price),
|
||||||
|
yAxisID: 'y1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Volume',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: json.map(d => d.volume),
|
||||||
|
yAxisID: 'y',
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const config = {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: `Sales for ${contractName}`,
|
||||||
|
font: {
|
||||||
|
size: '18px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'left',
|
||||||
|
},
|
||||||
|
y1: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'right',
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const myChart = new Chart(
|
||||||
|
document.getElementById(salesId),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
fetch(`/api/${contractAddress}/platforms`).then(function(response) {
|
||||||
|
response.json().then(function(json) {
|
||||||
|
loaded++;
|
||||||
|
if (loaded == 2) {
|
||||||
|
document.getElementById("container").style.visibility = "visible";
|
||||||
|
document.getElementById("loading").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = json.map(d => d.marketplace);
|
||||||
|
const total = json.map(d => d.volume).reduce((prev,next) => prev+next, 0);
|
||||||
|
const data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Platform',
|
||||||
|
data: json.map(d => d.volume),
|
||||||
|
backgroundColor: ["#7463A8", "#80B7D8", "#AC39A0", "#B0C484", "#B75055", "#F12055"]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const config = {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: `Volume for ${contractName}: ${total.toFixed(2)}Ξ`,
|
||||||
|
font: {
|
||||||
|
size: '18px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const myChart = new Chart(
|
||||||
|
document.getElementById(platformId),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,104 @@
|
|||||||
|
const ALL_CONTRACTS = require('./contracts');
|
||||||
|
|
||||||
|
const Database = require('better-sqlite3');
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
const db = new Database('./state/sqlite.db', {readonly: true});
|
||||||
|
|
||||||
|
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.use('/', express.static('public'));
|
||||||
|
|
||||||
|
app.use('/app', express.static('public'));
|
||||||
|
|
||||||
|
app.get('/api/contracts', (req, res) => {
|
||||||
|
res.status(200).json(ALL_CONTRACTS)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/api/:contractAddress/events', (req, res) => {
|
||||||
|
const results = [];
|
||||||
|
const stmt = db.prepare(`select *
|
||||||
|
from events
|
||||||
|
where contract = '${req.params.contractAddress}'
|
||||||
|
collate nocase
|
||||||
|
order by block_number desc
|
||||||
|
limit 100
|
||||||
|
`);
|
||||||
|
for (const entry of stmt.iterate()) {
|
||||||
|
results.push(entry);
|
||||||
|
}
|
||||||
|
res.status(200).json(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/token/:contractAddress/:tokenId/history', (req, res) => {
|
||||||
|
const results = [];
|
||||||
|
const stmt = db.prepare(`select *
|
||||||
|
from events
|
||||||
|
where token_id = ${req.params.tokenId}
|
||||||
|
and contract = '${req.params.contractAddress}'
|
||||||
|
collate nocase
|
||||||
|
order by block_number desc
|
||||||
|
`);
|
||||||
|
for (const entry of stmt.iterate()) {
|
||||||
|
results.push(entry);
|
||||||
|
}
|
||||||
|
res.status(200).json(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/latest', (req, res) => {
|
||||||
|
const stmt = db.prepare(`select *
|
||||||
|
from events
|
||||||
|
order by block_number desc
|
||||||
|
limit 1
|
||||||
|
`);
|
||||||
|
res.status(200).json(stmt.get());
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/:contractAddress/data', (req, res) => {
|
||||||
|
const results = [];
|
||||||
|
const stmt = db.prepare(`select
|
||||||
|
block_number block,
|
||||||
|
sum(sale_price/1000000000000000000.0) volume,
|
||||||
|
avg(sale_price/1000000000000000000.0) average_price,
|
||||||
|
(select avg(sale_price/1000000000000000000.0) from (select * from events
|
||||||
|
where contract = '${req.params.contractAddress}'
|
||||||
|
collate nocase
|
||||||
|
order by sale_price
|
||||||
|
limit 10)) floor_price,
|
||||||
|
count(*) sales
|
||||||
|
from events ev
|
||||||
|
where contract = '${req.params.contractAddress}'
|
||||||
|
collate nocase
|
||||||
|
group by block
|
||||||
|
order by block
|
||||||
|
`);
|
||||||
|
for (const entry of stmt.iterate()) {
|
||||||
|
results.push(entry);
|
||||||
|
}
|
||||||
|
res.status(200).json(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/:contractAddress/platforms', (req, res) => {
|
||||||
|
const results = [];
|
||||||
|
const stmt = db.prepare(`select marketplace,
|
||||||
|
sum(sale_price/1000000000000000000.0) volume,
|
||||||
|
count(*) sales
|
||||||
|
from events
|
||||||
|
where contract = '${req.params.contractAddress}'
|
||||||
|
collate nocase
|
||||||
|
group by marketplace
|
||||||
|
order by sum(sale_price/1000000000000000000.0) desc
|
||||||
|
`);
|
||||||
|
for (const entry of stmt.iterate()) {
|
||||||
|
results.push(entry);
|
||||||
|
}
|
||||||
|
res.status(200).json(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Example app listening at http://localhost:${port}`);
|
||||||
|
});
|
Loading…
Reference in New Issue