If you want to create a more traditional web2 type of app then this approach could be the one you would like to choose. You can handle all the logic related to the submission/validation of actions with your backend (as we showed in the previous point) and also use the backend to distribute the rewards, without the need to set up and deploy any smart contract.
To do that you will use the @vechain/sdk package to interact with the X2EarnRewardsPool contract to distribute the rewards.
In the following code snippet, you can see a javascript script that can be executed from the command line that will parse a CSV file containing a list of addresses and amounts to reward and then distribute the rewards to those addresses.
This script accepts a few command line parameters to function: the contract address of the X2EarnRewardsPool and the public and private keys of the wallet that will execute the transaction (which needs to be added as a Reward Distributor through the VeBetterDAO dApp).
const { ThorClient,HttpClient } =require("@vechain/sdk-network");const { ethers } =require("ethers");const { read } =require("read");constfs=require("fs");const { addressUtils,fragment,unitsUtils } =require("@vechain/sdk-core");///////////////////// User settings//////////////////require('dotenv').config();constcontractAddress=process.env.X2EARN_REWARDS_POOL_CONTRACT_ADDRESS;constfromAddress=process.env.FROM_ADDRESS_PUB;constdefaultPrivateKey=process.env.FROM_ADDRESS_PRIV;constbatchSize=parseInt(process.env.BATCH_SIZE,10);constAPP_ID="0xefdbfa0ad748787c7dac2e89c0733fbaee0b92ada73c6eb4c8dfd8b76769b96f"; // Generated by adding app to VeBetterDAO/////////////////////// end settings///////////////////// This function will read the private key from the prompt and parse the a CSV file// containing all the addresses and the amounts to distributeconststart=async () => {constprivateKey=awaitread({ prompt:"Private Key: ", default: defaultPrivateKey });constcsvFile=awaitread({ prompt:"CSV File: ", default:"addresses.csv", });if (!fs.existsSync(csvFile)) {console.log("File not found", csvFile);return; }constrecords=readCsv(csvFile);consttotalRecords=records.length;consttotalAmount=records.reduce((sum, record) => sum +parseFloat(record.amount),0);console.log(`Total Records: ${totalRecords}`);console.log(`Total Amount: ${totalAmount}`);constconfirm=awaitread({ prompt:"Proceed? (y/n): ", });if (confirm !=="y") {console.log("Aborted");return; }awaitairdrop(records, privateKey);};constclient=newThorClient(newHttpClient("https://mainnet.vechain.org/"));constdistributeRewardAbi= {"inputs": [ {"internalType":"bytes32","name":"appId","type":"bytes32" }, {"internalType":"uint256","name":"amount","type":"uint256" }, {"internalType":"address","name":"receiver","type":"address" }, {"internalType":"string","name":"proof","type":"string" } ],"name":"distributeReward","outputs": [],"stateMutability":"nonpayable","type":"function"};constdistributeRewardFragment=newfragment.Function(ethers.FunctionFragment.from(distributeRewardAbi),);constbuildClauses= (records) => {constclauses= [];for (let i =0; i <records.length; i++) {const { address,amount } = records[i];const_amount=unitsUtils.parseUnits(amount,18);constdata=distributeRewardFragment.encodeInput([APP_ID, _amount, address,""]);clauses.push({ to: contractAddress, data, value:"0x0", }); }if (clauses.length===0) {console.log("No clauses to build");thrownewError("No clauses to build"); }return clauses;};constairdropBatch=async (batch, privateKey) => {constclauses=buildClauses(batch);constfrom=addressUtils.fromPrivateKey(Buffer.from(privateKey,"hex"));// Options to use gasPaddingconstoptions= { gasPadding:0.2// 50% };constestimation=awaitclient.gas.estimateGas( clauses, fromAddress );if (estimation.reverted) {console.log("Estimation failed", estimation);thrownewError("Estimation failed"); }consttxBody=awaitclient.transactions.buildTransactionBody( clauses,estimation.totalGas, );constsigned=awaitclient.transactions.signTransaction(txBody, privateKey);consttx=awaitclient.transactions.sendTransaction(signed);//console.log(`Transaction ID: ${tx.id}`);console.log(`https://vechainstats.com/transaction/${tx.id}`);};constairdrop=async (records, privateKey) => {for (let i =0; i <records.length; i += batchSize) {constbatch=records.slice(i, i + batchSize);awaitairdropBatch(batch, privateKey); }};/** * @returns{Array} */constreadCsv= (csvFile) => {constdata=fs.readFileSync(csvFile,"utf8");constlines=data.split("\r\n");constrecords= [];for (let i =0; i <lines.length; i++) {const [address,amount] = lines[i].split(",");if (!addressUtils.isAddress(address)) {console.log(`Invalid address @ L${i +1}: ${address}`);thrownewError("Invalid address"); }if (isNaN(parseInt(amount))) {console.log(`Invalid amount @ L${i +1}: ${amount}`);thrownewError("Invalid amount"); }records.push({ address, amount }); }return records;};start();