|
|
@ -1,18 +1,15 @@
|
|
|
|
const ALL_CONTRACTS = require('./contracts');
|
|
|
|
const ALL_CONTRACTS = require('./contracts');
|
|
|
|
|
|
|
|
|
|
|
|
const { Alchemy, Network } = require("alchemy-sdk");
|
|
|
|
import Moralis from 'moralis';
|
|
|
|
const { Database } = require('sqlite3');
|
|
|
|
const { Database } = require('sqlite3');
|
|
|
|
const fs = require('fs');
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const db = new Database('./state/sqlite.db');
|
|
|
|
const db = new Database('./state/sqlite.db');
|
|
|
|
const config = {
|
|
|
|
const config = {
|
|
|
|
apiKey: process.env.ALCHEMY_KEY,
|
|
|
|
apiKey: process.env.MORALIS_KEY
|
|
|
|
network: Network.ETH_MAINNET,
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const alchemy = new Alchemy(config);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function sleep(sec) {
|
|
|
|
async function sleep(sec) {
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, Number(sec) * 1000));
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, Number(sec) * 1000));
|
|
|
@ -34,7 +31,7 @@ class Scrape {
|
|
|
|
this.lastFile = `./state/${this.contractName}.txt`;
|
|
|
|
this.lastFile = `./state/${this.contractName}.txt`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getpageKey() {
|
|
|
|
getCursor() {
|
|
|
|
if (fs.existsSync(this.lastFile)) {
|
|
|
|
if (fs.existsSync(this.lastFile)) {
|
|
|
|
return fs.readFileSync(this.lastFile).toString();
|
|
|
|
return fs.readFileSync(this.lastFile).toString();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -44,57 +41,56 @@ class Scrape {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async scrape() {
|
|
|
|
async scrape() {
|
|
|
|
const pageKey = this.getpageKey()
|
|
|
|
const cursor = this.getCursor()
|
|
|
|
if (pageKey === '') {
|
|
|
|
if (cursor === '') {
|
|
|
|
console.log('no page key')
|
|
|
|
console.log(`no cursor for ${this.contractName}. skipping`)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`[+] Scraping ${this.contractName} with pageKey ${pageKey}`)
|
|
|
|
console.log(`[+] Scraping ${this.contractName}`);
|
|
|
|
const response = await alchemy.nft.getNftSales({
|
|
|
|
const response = await Moralis.EvmApi.nft.getNFTTrades({
|
|
|
|
|
|
|
|
chain: '0x1',
|
|
|
|
|
|
|
|
marketplace: 'opensea',
|
|
|
|
fromBlock: this.startBlock,
|
|
|
|
fromBlock: this.startBlock,
|
|
|
|
contractAddress: this.contractAddress,
|
|
|
|
address: this.contractAddress,
|
|
|
|
limit: process.env.LIMIT,
|
|
|
|
limit: process.env.LIMIT,
|
|
|
|
order: 'asc',
|
|
|
|
cursor: cursor
|
|
|
|
pageKey: pageKey
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync(this.lastFile, response.pageKey || '')
|
|
|
|
fs.writeFileSync(this.lastFile, response.json.cursor || '')
|
|
|
|
|
|
|
|
|
|
|
|
response.nftSales.map(async (sale) => {
|
|
|
|
response.json.result.map(async (sale) => {
|
|
|
|
const rowExists = await new Promise((resolve) => {
|
|
|
|
sale.token_ids.map(async (tokenId) => {
|
|
|
|
db.get('SELECT * FROM events WHERE tx_hash = ? AND log_index = ?', [sale.transactionHash, sale.logIndex], (err, row) => {
|
|
|
|
const rowExists = await new Promise((resolve) => {
|
|
|
|
if (err) { resolve(false); }
|
|
|
|
db.get('SELECT * FROM events WHERE tx_hash = ? AND token_id = ?', [sale.transaction_hash, tokenId], (err, row) => {
|
|
|
|
resolve(row !== undefined);
|
|
|
|
if (err) { resolve(false); }
|
|
|
|
|
|
|
|
resolve(row !== undefined);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if (!rowExists) {
|
|
|
|
if (!rowExists) {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
db.run(`
|
|
|
|
db.run(`
|
|
|
|
INSERT INTO events VALUES (
|
|
|
|
INSERT INTO events VALUES (
|
|
|
|
"${this.contractAddress}",
|
|
|
|
"${this.contractAddress}",
|
|
|
|
"${sale.buyer_address}",
|
|
|
|
"${sale.buyerAddress}",
|
|
|
|
"${sale.seller_address}",
|
|
|
|
"${sale.sellerAddress}",
|
|
|
|
"${tokenId}",
|
|
|
|
"${sale.taker}",
|
|
|
|
"${sale.price}",
|
|
|
|
"${sale.tokenId}",
|
|
|
|
"",
|
|
|
|
"${sale.sellerFee.amount}",
|
|
|
|
"${sale.transaction_hash}",
|
|
|
|
"${sale.protocolFee.amount}",
|
|
|
|
"${sale.block_number}",
|
|
|
|
"${sale.royaltyFee.amount}",
|
|
|
|
"opensea",
|
|
|
|
"",
|
|
|
|
"${cursor}",
|
|
|
|
"${sale.transactionHash}",
|
|
|
|
0, 0
|
|
|
|
"${sale.blockNumber}",
|
|
|
|
)`);
|
|
|
|
"${sale.logIndex}",
|
|
|
|
console.log(` ::: Inserted sale of ${this.contractName} #${tokenId} in block ${sale.block_number} for ${sale.price} wei.`)
|
|
|
|
"${sale.bundleIndex}",
|
|
|
|
} catch(err) {
|
|
|
|
"${sale.marketplace}",
|
|
|
|
console.log(`Error when writing to database: ${err}`);
|
|
|
|
"${pageKey}",
|
|
|
|
return false;
|
|
|
|
0, 0
|
|
|
|
}
|
|
|
|
)`);
|
|
|
|
|
|
|
|
console.log(` ::: Inserted sale of ${this.contractName} #${sale.tokenId} in block ${sale.blockNumber} for ${sale.sellerFee.amount} wei.`)
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
|
|
|
|
console.log(`Error when writing to database: ${err}`);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await sleep(1);
|
|
|
|
await sleep(1);
|
|
|
@ -119,25 +115,21 @@ class Scrape {
|
|
|
|
contract text,
|
|
|
|
contract text,
|
|
|
|
buyer text,
|
|
|
|
buyer text,
|
|
|
|
seller text,
|
|
|
|
seller text,
|
|
|
|
taker text,
|
|
|
|
|
|
|
|
token_id number,
|
|
|
|
token_id number,
|
|
|
|
sale_price text,
|
|
|
|
sale_price text,
|
|
|
|
protocol_fee text,
|
|
|
|
|
|
|
|
royalty_fee text,
|
|
|
|
|
|
|
|
tx_date text,
|
|
|
|
tx_date text,
|
|
|
|
tx_hash text,
|
|
|
|
tx_hash text,
|
|
|
|
block_number number,
|
|
|
|
block_number number,
|
|
|
|
log_index number,
|
|
|
|
|
|
|
|
bundle_index number,
|
|
|
|
|
|
|
|
marketplace text,
|
|
|
|
marketplace text,
|
|
|
|
page_key text,
|
|
|
|
cursor text,
|
|
|
|
discord_sent number,
|
|
|
|
discord_sent number,
|
|
|
|
twitter_sent number,
|
|
|
|
twitter_sent number,
|
|
|
|
UNIQUE(tx_hash, log_index, bundle_index)
|
|
|
|
UNIQUE(tx_hash, token_id)
|
|
|
|
);`,
|
|
|
|
);`,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await Moralis.start(config);
|
|
|
|
while(true) {
|
|
|
|
while(true) {
|
|
|
|
for(const contract in ALL_CONTRACTS) {
|
|
|
|
for(const contract in ALL_CONTRACTS) {
|
|
|
|
if (process.env.ONLY && process.env.ONLY != contract) continue
|
|
|
|
if (process.env.ONLY && process.env.ONLY != contract) continue
|
|
|
|