diff --git a/bun.lockb b/bun.lockb
index 337d096..177ff5b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 2aaa4fe..dbc28f6 100644
--- a/package.json
+++ b/package.json
@@ -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"
},
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..bfd7349
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,186 @@
+
+
+ Art101 Sales Statistics
+
+
+
+
+
+
+
+ Art101 Sales Statistics
+ Source Code
+
+ Loading, please wait...
+
+
+
+
+
diff --git a/src/server.js b/src/server.js
new file mode 100644
index 0000000..9a33171
--- /dev/null
+++ b/src/server.js
@@ -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}`);
+});
\ No newline at end of file