add web server to show data

master
lza_menace 4 weeks ago
parent c62281b2f6
commit 0ecae09714

Binary file not shown.

@ -1,12 +1,13 @@
{
"dependencies": {
"alchemy-sdk": "^3.5.0",
"better-sqlite3": "^11.7.0",
"ethers": "^6.13.4",
"express": "^4.21.2",
"sqlite3": "^5.1.7"
},
"name": "alchemy-nft-scraper",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},

@ -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…
Cancel
Save