@vechain/vebetterdao-contracts
Version:
Open-source repository that houses the smart contracts powering the decentralized VeBetterDAO on the VeChain Thor blockchain.
216 lines (215 loc) • 10.5 kB
JavaScript
import { mintStargateNFTs } from "../helpers";
import { airdropB3trFromTreasury, airdropVTHO } from "../helpers/airdrop";
import { bootstrapEmissions, startEmissions } from "../helpers/emissions";
import { getSeedAccounts, getTestKeys, SeedStrategy } from "../helpers/seedAccounts";
import { convertB3trForVot3 } from "../helpers/swap";
import { ethers } from "hardhat";
import { Address } from "@vechain/sdk-core";
import { assignAppCategories, endorseXApps, registerXDapps } from "../helpers/xApp";
const accounts = getTestKeys(17);
const xDappCreatorAccounts = accounts.slice(0, 8);
export const APPS = [
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Mugshot",
metadataURI: "bafkreiba646cx2y2pv7vrj4sd4fw7m4kdebdthkum2p6jczs5umid5cjgy",
categories: ["nutrition", "plastic-waste-recycling"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Cleanify",
metadataURI: "bafkreigefkdis3kxspa5ovz4wybcbebecrqw256m7yimwj2jmrmsx57fui",
categories: ["plastic-waste-recycling"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "GreenCart",
metadataURI: "bafkreigu2rw4psbpazeudqhcpjdxsrhrtz3unckcnlidsjsiqjsgje3kli",
categories: ["sustainable-shopping"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Green Ambassador Challenge",
metadataURI: "bafkreibjxezzffohborgmtqsp6w7koxxdc55bd4dsiud4dlfp2vno5f4le",
categories: ["education-learning", "green-mobility-travel"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Oily",
metadataURI: "bafkreiakrtehu5xmzrxiwnwo2l6yfgt7ejhjhwxtgkn57vnflpfklx7ioe",
categories: ["renewable-energy-efficiency"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "EVearn",
metadataURI: "bafkreiajy2ldq3o3rhw2gmzmvqbqego76oh2gtmkcz3fxsmfcre6jzuzfu",
categories: ["green-finance-defi", "renewable-energy-efficiency"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Vyvo",
metadataURI: "bafkreib6uklcdfpudaqlu4budf7f2ngays2rrhqcxshqnjvme5umtykiui",
categories: ["fitness-wellness"],
},
{
admin: accounts[6].address.toString(),
teamWalletAddress: accounts[6].address.toString(),
name: "Non Fungible Book Club (NFBC)",
metadataURI: "bafkreieyqnioauddqzpscgo3gm5nlxu6r3sm2zriz4ft23rw2efc6e252q",
categories: ["education-learning"],
},
];
export const setupEnvironment = async (config, emissions, treasury, x2EarnApps, _governor, _xAllocationVoting, b3tr, vot3, stargateMock) => {
switch (config) {
case "local":
case "testnet":
case "testnet-staging":
await setupLocalEnvironment(config, emissions, treasury, x2EarnApps, b3tr, vot3, stargateMock);
break;
case "mainnet":
await setupMainnetEnvironment(emissions, x2EarnApps);
break;
default:
throw new Error(`Unsupported app environment: ${config}`);
}
};
export const updateGMMultipliers = async (levels, multipliers, voterRewards) => {
for (let i = 0; i < levels.length; i++) {
const level = levels[i];
const multiplier = multipliers[i];
// Update the multiplier for the level
await voterRewards.setLevelToMultiplierNow(level, multiplier);
}
};
export const endorseAndVerifySeededApps = async (x2EarnApps, stargateMock, endorserSigners) => {
const mintAccounts = [];
const mintLevels = [];
for (const acct of endorserSigners) {
mintAccounts.push(acct, acct, acct);
mintLevels.push(7, 7, 7);
}
await mintStargateNFTs(stargateMock, mintAccounts, mintLevels);
const unendorsedApps = await x2EarnApps.unendorsedAppIds();
await endorseXApps(endorserSigners, x2EarnApps, unendorsedApps, stargateMock);
await verifyEndorsedApps(x2EarnApps);
};
const verifyEndorsedApps = async (x2EarnApps) => {
const remaining = await x2EarnApps.unendorsedAppIds();
console.log(`Remaining apps: ${remaining.length} ${remaining.join(", ")}`);
if (remaining.length > 0) {
const threshold = Number(await x2EarnApps.endorsementScoreThreshold());
console.log(`Threshold: ${threshold}`);
for (const appId of remaining) {
const score = Number(await x2EarnApps.getScore(appId));
console.error(`App ${appId} has ${score}/${threshold} endorsement points`);
}
throw new Error(`${remaining.length} app(s) did not reach endorsement threshold`);
}
console.log(`All apps verified as endorsed (>= ${await x2EarnApps.endorsementScoreThreshold()} pts)`);
};
export const setupLocalEnvironment = async (env, emissions, treasury, x2EarnApps, b3tr, vot3, stargateMock) => {
const start = performance.now();
// Define specific accounts
const admin = accounts[0];
// Make sure the first 10 accounts have a VTHO balance
await airdropVTHO(accounts.slice(1, 10).map(acct => acct.address), 5000n, admin);
// Bootstrap emissions
const emissionsContract = await emissions.getAddress();
await bootstrapEmissions(emissionsContract, admin);
// Add x-apps to the XAllocationPool
const x2EarnAppsAddress = await x2EarnApps.getAddress();
await registerXDapps(x2EarnAppsAddress, xDappCreatorAccounts, APPS);
// Assign categories to apps (deployer has DEFAULT_ADMIN_ROLE)
const deployer = (await ethers.getSigners())[0];
await assignAppCategories(x2EarnApps, deployer, APPS);
// Seed the first 10 accounts with B3TR + VOT3
const treasuryAddress = await treasury.getAddress();
const allAccounts = getSeedAccounts(SeedStrategy.FIXED, 10 + APPS.length, 0);
const first10 = allAccounts.slice(0, 10);
const appCreatorSeedAccounts = allAccounts.slice(10);
await airdropVTHO(first10.map(acct => acct.key.address), 500n, admin);
// Step 1: small airdrop (10k B3TR) → convert all to VOT3
const vot3Accounts = first10.map(a => ({ ...a, amount: ethers.parseEther("10000") }));
await airdropB3trFromTreasury(treasuryAddress, admin, [...vot3Accounts, ...appCreatorSeedAccounts]);
await convertB3trForVot3(b3tr, vot3, vot3Accounts);
// Step 2: airdrop 50k B3TR — stays as B3TR for challenges etc.
const b3trAccounts = first10.map(a => ({ ...a, amount: ethers.parseEther("50000") }));
await airdropB3trFromTreasury(treasuryAddress, admin, b3trAccounts);
// If the first 8 accounts does not have the correct nodes, run the following line
await startEmissions(emissionsContract, admin);
// Endorsement strategy: on solo (local) we can mint mock Stargate NFTs because accounts
// are pre-funded with VET. On real networks (testnet/testnet-staging) accounts lack VET,
// so we set endorsement threshold to 0 and call checkEndorsement to auto-endorse.
if (env === "local") {
const allSigners = await ethers.getSigners();
const endorserSigners = allSigners.slice(0, 10);
await airdropVTHO(endorserSigners.map(acct => Address.of(acct.address)), 5000n, admin);
// Every endorser gets 3 MjolnirX nodes (need 3 per app since max 49 pts/node/app)
// Total: 30 nodes x 100 pts = 3000 pts >> 8 apps x 100 threshold
await endorseAndVerifySeededApps(x2EarnApps, stargateMock, endorserSigners);
}
else {
// testnet and testnet-staging: set threshold to 0 so apps auto-endorse via checkEndorsement
await x2EarnApps
.connect(deployer)
.updateEndorsementScoreThreshold(0)
.then(async (tx) => await tx.wait());
console.log("Endorsement score threshold set to 0");
const unendorsedApps = await x2EarnApps.unendorsedAppIds();
for (const appId of unendorsedApps) {
const tx = await x2EarnApps.checkEndorsement(appId);
await tx.wait();
}
console.log(`checkEndorsement called for ${unendorsedApps.length} apps`);
await verifyEndorsedApps(x2EarnApps);
}
const end = new Date(performance.now() - start);
console.log(`Setup complete in ${end.getMinutes()}m ${end.getSeconds()}s`);
};
export const setupMainnetEnvironment = async (emissions, x2EarnApps) => {
console.log("================ Setup Mainnet environment");
const start = performance.now();
const mainnet_admin_addresses = new Map([
["Mugshot", "0xbfe2122a82c0aea091514f57c7713c3118101eda"],
["Cleanify", "0x6b020e5c8e8574388a275cc498b27e3eb91ec3f2"],
["GreenCart", "0x4e506ee842ba8ccce88e424522506f5b860e5c9b"],
["Green Ambassador Challenge", "0x15e74aeb00d367a5a20c61b469df30a25f0e602f"],
["Oily", "0xd52e3356231c9fa86bb9fab731f8c0c3f1018753"],
["EVearn", "0xb2919e12d035a484f8414643b606b2a180224f54"],
["Vyvo", "0x61ffc950b04090f5ce857ebf056852a6d27b0c3c"],
["Non Fungible Book Club (NFBC)", "0xbe50d2fae95b23082f351e290548365e84ec1780"],
]);
const admin = accounts[0];
// Bootstrap emissions
const emissionsContract = await emissions.getAddress();
await bootstrapEmissions(emissionsContract, admin);
// Add x-apps to the XAllocationPool
console.log("Adding x-apps...");
// Add x-apps to the XAllocationPool
const x2EarnAppsAddress = await x2EarnApps.getAddress();
// Overwrite the admin and teamWalletAddress with the mainnet addresses
APPS.forEach(app => {
const newAddress = mainnet_admin_addresses.get(app.name);
if (newAddress) {
app.admin = newAddress;
app.teamWalletAddress = newAddress;
}
else {
throw new Error(`Mainnet admin address not found for ${app.name}`);
}
console.log(app.name);
console.log("Admin: ", app.admin);
console.log("Team Wallet Address: ", app.teamWalletAddress);
});
await registerXDapps(x2EarnAppsAddress, xDappCreatorAccounts, APPS);
console.log("x-apps added");
const end = performance.now();
console.log(`Setup complete in ${end - start}ms`);
};