Technical Whitepaper & Smart Contracts

Deployment & Keeper Bot Guide

This technical guide provides instructions for deploying the QanVault smart contracts and running the automated keeper bots that coordinate draws and rollovers.

Note
Who is this guide for? This document is intended for smart contract auditors, DevOps engineers, and decentralized infrastructure runners. As an everyday player or participant, you never need to deploy contracts or run bots. Draw state transitions occur autonomously in the background.

What is a Keeper Bot and Why Does It Exist?#

A fundamental reality of Ethereum and BNB Smart Chain is that smart contracts cannot execute on their own clock. Unlike traditional web applications with background cron daemons, a smart contract is a passive state machine that only runs when an external transaction is submitted to the network.

To resolve draws at exactly 00:00:00 UTC, verify commit-reveal randomness, and trigger 72-hour prize rollovers without centralized human intervention, decentralized protocols rely on Keepers:

  1. Stateless Automation: A keeper bot is simply a lightweight script that monitors block timestamps and calls public transition methods (e.g. commitDraw, revealDraw, and triggerRollover).
  2. Permissionless & Verifiable: Any community member or node runner can run this script using public RPC endpoints.
  3. Decentralization Proof: Publishing this guide in the technical whitepaper proves that QanVault does not depend on private admin keys or black-box servers to operate its prize cycles.

Toolchain & Dependencies#

  • Language: Solidity 0.8.24
  • Development Framework: Hardhat
  • Libraries: OpenZeppelin Contracts v5, Ethers v6, Viem
  • TypeScript: v5.4+
  • Node.js: v18.17+ or v20+

Contract Deployment Workflow#

1. Configure Environment Variables

Create a .env file in the project root:

ini
BSC_RPC_URL="https://bsc-dataseed.binance.org/"
ETH_RPC_URL="https://eth.llamarpc.com"
DEPLOYER_PRIVATE_KEY="0x..."
BSCSCAN_API_KEY="..."
ETHERSCAN_API_KEY="..."

2. Compile Contracts

Compile with Hardhat using viaIR optimization:

bash
npx hardhat compile

3. Deploy to BNB Smart Chain

Run the deployment script for the canonical settlement hub:

bash
npx hardhat run scripts/deploy-bsc-hub.ts --network bsc

This script:

  1. Deploys PrizeEscrowVault with the initial 5-bucket configuration.
  2. Deploys EverpotCore linked to the vault.
  3. Sets EverpotCore as the exclusive authorized drawer on PrizeEscrowVault.
  4. Deploys MultiChainRouter configured with PancakeSwap v3 router parameters.

Keeper Bot Architecture#

Keepers are stateless, automated worker scripts that interact with the contracts to advance the draw state machine.

1. Daily Draw Keeper

Runs once every 24 hours at 00:00:00 UTC:

typescript
import { ethers } from "ethers";

async function executeDailyDraw(coreContract: ethers.Contract, keeperSigner: ethers.Signer) {
  const drawId = await coreContract.getCurrentDrawId();
  
  // Step 1: Generate secret entropy & commitment
  const secretSeed = ethers.hexlify(ethers.randomBytes(32));
  const currentBlock = await keeperSigner.provider!.getBlockNumber();
  const commitment = ethers.keccak256(
    ethers.concat([secretSeed, ethers.toBeHex(drawId, 32), ethers.toBeHex(currentBlock, 32)])
  );
  
  // Step 2: Submit hash commitment
  const commitTx = await coreContract.connect(keeperSigner).commitDraw(drawId, commitment);
  await commitTx.wait(2);
  
  // Step 3: Reveal seed & finalize draw
  const revealTx = await coreContract.connect(keeperSigner).revealDraw(drawId, secretSeed);
  await revealTx.wait(1);
  
  console.log(`Draw #${drawId} successfully finalized!`);
}

2. Hourly Rollover Keeper

Runs every 60 minutes to detect and process expired claim windows:

typescript
async function processExpiredRollovers(coreContract: ethers.Contract, keeperSigner: ethers.Signer) {
  const expiredDraws = await coreContract.getUnclaimedExpiredDraws();
  
  for (const drawId of expiredDraws) {
    console.log(`Triggering rollover for draw #${drawId}...`);
    const tx = await coreContract.connect(keeperSigner).triggerRollover(drawId);
    await tx.wait(1);
    console.log(`Draw #${drawId} rolled over into subsequent pot.`);
  }
}

Next Steps#