WENBlocks
  • Getting Started
    • Overview
      • Technical Architecture
    • Key Features
  • Staking
    • Overview
    • Creating a Stake
    • Withdrawing the Stake
    • Claiming Rewards
  • Liquidity Program
    • How It Works
  • WNT
    • WNT — The Engine of the WEN Ecosystem
  • Airdrops
    • Airdrop Schedule
    • Server Management
    • Penalty Structure
    • How It Works
    • Example
  • Smart Contracts
  • Security
    • Overview
    • Internal Audits
    • External Audits
    • AI Audits
      • Grok
  • CODE
    • WENBlocksManager.sol
    • wnmContract.sol
    • wblkToken.sol
    • wuniContract.sol
    • wntToken.sol
    • syncnode.py
    • airdropGenerator.js
    • dayTrigger.js
  • Conclusion
    • Conclusion
  • Socials
    • Links
Powered by GitBook
On this page
  1. CODE

airdropGenerator.js

Processes XENBlocks blockchain data to generate weekly Merkle trees, calculate token allocations, and automatically submit airdrop creation transactions.

const sqlite3 = require("sqlite3").verbose();
const fs = require("fs");
const readline = require("readline");
const { MerkleTree } = require("merkletreejs");
const { ethers, keccak256, solidityPackedKeccak256, isAddress, getAddress } = require("ethers");

// File to store the last processed block ID
const SNAPSHOT_FILE = "/root/lastSnapshot.txt";
const DB_PATH = "/root/xenminer/blockchain.db";
const OUTPUT_DIR = "/root/snapshots/";
const MERKLE_DIR = "/root/merkleTrees/";
const BATCH_SIZE = 10000;

// Ensure output directories exist
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
if (!fs.existsSync(MERKLE_DIR)) fs.mkdirSync(MERKLE_DIR, { recursive: true });

let totalRegularBlocks = 0;
let totalSuperBlocks = 0;
let totalXuniBlocks = 0;
let totalRegularBlocksBeforeHalving = 0;

// Function to get the last processed block from file
function getLastBlockId() {
  if (fs.existsSync(SNAPSHOT_FILE)) {
    const lastId = parseInt(fs.readFileSync(SNAPSHOT_FILE, "utf8"));
    if (!isNaN(lastId) && lastId > 0) {
      return lastId;
    }
  }
  return null;
}

// Function to get the latest block from the database
function getLatestBlockId() {
  return new Promise((resolve, reject) => {
    const db = new sqlite3.Database(DB_PATH);
    db.get("SELECT MAX(id) as max_id FROM blockchain;", (err, row) => {
      db.close();
      if (err) {
        reject(err);
      } else {
        resolve(row?.max_id || 0);
      }
    });
  });
}

// Main execution function
(async function () {
  let START_BLOCK = getLastBlockId();
  let END_BLOCK = await getLatestBlockId();
  const snapshotTimestamp = Date.now();

  console.log(`\nšŸ’¾ Last processed block: ${START_BLOCK || "None (First run)"}`);
  console.log(`šŸ“¢ Latest blockchain block at execution: ${END_BLOCK}`);

  if (!START_BLOCK) {
    console.log("āš ļø No previous snapshot found. Starting from block 1.");
    START_BLOCK = 1;
  }

  if (START_BLOCK >= END_BLOCK) {
    console.log("āœ… No new blocks to process. Exiting.");
    return;
  }

  console.log(`āœ… Processing records from Block ${START_BLOCK} to ${END_BLOCK}`);

  const db = new sqlite3.Database(DB_PATH);
  const outputFileName = `${OUTPUT_DIR}snapshot_${snapshotTimestamp}.csv`;
  const ws = fs.createWriteStream(outputFileName);
  ws.write("account,regularblocks,superblocks,xuniblocks\n");

  const query = `
    SELECT blockchain.id, 
           json_extract(value, '$.account') AS account, 
           json_extract(value, '$.hash_to_verify') AS hash_to_verify,
           json_extract(value, '$.block_id') AS block_id,
           json_extract(value, '$.xuni_id') AS xuni_id,
           json_extract(value, '$.date') AS date
    FROM blockchain, json_each(blockchain.records_json)
    WHERE blockchain.id >= ? 
      AND blockchain.id <= ? 
    ORDER BY blockchain.id ASC 
    LIMIT ?;
  `;

  let processedRecords = 0;
  const accountStats = {};
  let lastProcessedBlock = START_BLOCK;

  function processBatch(startId) {
    db.all(query, [startId, END_BLOCK, BATCH_SIZE], (err, rows) => {
      if (err) {
        console.error("āŒ SQL ERROR:", err);
        return;
      }

      if (rows.length === 0) {
        console.log("āœ… No more records. Writing final CSV...");

        for (const [account, data] of Object.entries(accountStats)) {
          ws.write(`${account},${data.regularblocks},${data.superblocks},${data.xuniblocks}\n`);
        }

        ws.end(() => {
          fs.writeFileSync(SNAPSHOT_FILE, lastProcessedBlock.toString());
        
          console.log(`āœ… Snapshot completed! CSV file saved as ${outputFileName}`);
          console.log(`šŸ“Œ Last processed block ID saved: ${lastProcessedBlock}`);
        
          db.close();
        
          // āœ… Now the file is fully written — safe to generate Merkle Tree
          generateMerkleTree(outputFileName, {
            regularblocks: totalRegularBlocks,
            superblocks: totalSuperBlocks,
            xuniblocks: totalXuniBlocks,
            regularblocksbeforehalving: totalRegularBlocksBeforeHalving
          }, snapshotTimestamp);
        });

        return;
      }

      rows.forEach((row) => {
        if (!row.account) return;
        const isXuni = !!row.xuni_id;
        const hasBlock = !!row.block_id;

        if (!accountStats[row.account]) {
          accountStats[row.account] = { regularblocks: 0, superblocks: 0, xuniblocks: 0 };
        }

        if (isXuni) {
          accountStats[row.account].xuniblocks += 1;
          totalXuniBlocks += 1;
        } else if (hasBlock && row.hash_to_verify) {
          const realHashPart = row.hash_to_verify.split("$").pop() || "";
          const uppercaseCount = (realHashPart.match(/[A-Z]/g) || []).length;
          //const dateThreshold = new Date("2024-09-16T20:59:55Z");
          //const recordDate = new Date(row.date);
          const bonus = row.block_id <= 29818420 ? 10 : 5;
          accountStats[row.account].regularblocks += bonus;
          totalRegularBlocks += bonus;

          if (row.block_id <= 29818420) {
            totalRegularBlocksBeforeHalving += bonus;
          }
          if (uppercaseCount >= 50) {
            accountStats[row.account].superblocks += 1;
            totalSuperBlocks += 1;
          }
        }

        processedRecords++;
        lastProcessedBlock = row.id;

        if (processedRecords % 10000 === 0) {
          console.log(`šŸ“¢ Processed: ${processedRecords} records`);
        }
      });

      console.log(`šŸ”¹ Processed up to ID: ${lastProcessedBlock}`);

      processBatch(lastProcessedBlock + 1);
    });
  }

  processBatch(START_BLOCK);
})();

function generateMerkleTree(csvFilePath, totals, timestamp) {
  console.log(`šŸ”¹ Generating Merkle Tree from ${csvFilePath}`);

  if (!fs.existsSync(csvFilePath)) {
    console.error(`āŒ CSV file not found: ${csvFilePath}`);
    return;
  }

  const lines = fs.readFileSync(csvFilePath, "utf-8").trim().split("\n");
  const data = lines.slice(1).map((line) => {
    let [account, regularblocks, superblocks, xuniblocks] = line.split(",");

    if (!isAddress(account)) {
      console.warn(`āš ļø Skipping invalid address: ${account}`);
      return null;
    }

    account = getAddress(account.toLowerCase());

    return {
      account,
      regularblocks: BigInt(regularblocks || "0"),
      superblocks: BigInt(superblocks || "0"),
      xuniblocks: BigInt(xuniblocks || "0"),
    };
  }).filter((entry) => entry !== null);

  const leaves = data.map(({ account, regularblocks, superblocks, xuniblocks }) => {
    return solidityPackedKeccak256(
      ["address", "uint256", "uint256", "uint256"],
      [account, regularblocks, superblocks, xuniblocks]
    );
  });

  const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true });
  const merkleRoot = merkleTree.getHexRoot();

  console.log("Merkle Root:", merkleRoot);

  const proofs = data.map(({ account, regularblocks, superblocks, xuniblocks }) => {
    const leaf = solidityPackedKeccak256(
      ["address", "uint256", "uint256", "uint256"],
      [account, regularblocks, superblocks, xuniblocks]
    );

    return {
      account,
      regularblocks: regularblocks.toString(),
      superblocks: superblocks.toString(),
      xuniblocks: xuniblocks.toString(),
      proof: merkleTree.getHexProof(leaf),
    };
  });

  const jsonFileName = `${MERKLE_DIR}proofs_snapshot_${timestamp}.json`;
  fs.writeFileSync(jsonFileName, JSON.stringify({ merkleRoot, totals, timestamp, proofs }, null, 2));

  console.log(`āœ… Proofs saved to ${jsonFileName}`);
  notifySmartContract(merkleRoot, totals);
}

async function notifySmartContract(merkleRoot, totals) {
  const RPC_URL = process.env.RPC_URL;
  const PRIVATE_KEY = process.env.PRIVATE_KEY;
  const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;

  const ABI = [
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "_merkleRoot",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "totalWNM",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "totalWBLK",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "totalWUNI",
				"type": "uint256"
			}
		],
		"name": "createAirdrop",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
  ];

  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
  const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet);

  // Convert totals to 18 decimals (BigInt)
  const totalWNM = BigInt(totals.regularblocks) * 10n ** 18n;
  const totalWBLK = BigInt(totals.superblocks) * 10n ** 18n;
  const totalWUNI = BigInt(totals.xuniblocks) * 10n ** 18n;

  console.log(`šŸ“¦ Preparing to call createAirdrop with:`);
  console.log(`- Merkle Root: ${merkleRoot}`);
  console.log(`- Total WNM:  ${totalWNM.toString()}`);
  console.log(`- Total WBLK: ${totalWBLK.toString()}`);
  console.log(`- Total WUNI: ${totalWUNI.toString()}`);

  try {
    console.log("šŸ“” Calling createAirdrop()...");
    const tx = await contract.createAirdrop(merkleRoot, totalWNM, totalWBLK, totalWUNI);
    console.log(`ā³ Transaction submitted: ${tx.hash}`);

    const receipt = await tx.wait();

    if (receipt.status === 1) {
      console.log("āœ… Airdrop successfully created on-chain.");
    } else {
      console.error("āŒ Transaction reverted or failed.");
    }
  } catch (err) {
    console.error("āŒ Error calling createAirdrop:", err);
  }
}
Previoussyncnode.pyNextdayTrigger.js

Last updated 1 month ago