185 lines
5.5 KiB
JavaScript
185 lines
5.5 KiB
JavaScript
const hre = require("hardhat");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const DEFAULT_OFFERS = [
|
|
"edut.solo.core",
|
|
"edut.workspace.core",
|
|
"edut.workspace.ai",
|
|
"edut.workspace.lane24",
|
|
"edut.workspace.sovereign",
|
|
];
|
|
|
|
function requiredEnv(name) {
|
|
const value = process.env[name];
|
|
if (!value || !value.trim()) {
|
|
throw new Error(`Missing required env: ${name}`);
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
function requireAddress(name, value, { allowZero = false } = {}) {
|
|
if (!hre.ethers.utils.isAddress(value)) {
|
|
throw new Error(`Invalid address for ${name}: ${value}`);
|
|
}
|
|
if (!allowZero && value === hre.ethers.constants.AddressZero) {
|
|
throw new Error(`${name} cannot be zero address`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function parseUint(name, value) {
|
|
try {
|
|
const parsed = hre.ethers.BigNumber.from(value);
|
|
if (parsed.lt(0)) {
|
|
throw new Error("must be >= 0");
|
|
}
|
|
return parsed;
|
|
} catch (err) {
|
|
throw new Error(`Invalid uint for ${name}: ${value} (${err.message})`);
|
|
}
|
|
}
|
|
|
|
function readOfferOverrides() {
|
|
const inlineRaw = (process.env.OFFERS_INLINE_JSON || "").trim();
|
|
if (inlineRaw) {
|
|
try {
|
|
return JSON.parse(inlineRaw);
|
|
} catch (err) {
|
|
throw new Error(`Invalid OFFERS_INLINE_JSON (${err.message})`);
|
|
}
|
|
}
|
|
|
|
const filePath = (process.env.OFFERS_JSON || "").trim();
|
|
if (!filePath) {
|
|
return null;
|
|
}
|
|
const absolute = path.resolve(filePath);
|
|
const raw = fs.readFileSync(absolute, "utf8");
|
|
try {
|
|
return JSON.parse(raw);
|
|
} catch (err) {
|
|
throw new Error(`Invalid OFFERS_JSON at ${absolute} (${err.message})`);
|
|
}
|
|
}
|
|
|
|
function buildOfferPlan(defaultPriceAtomic) {
|
|
const overrides = readOfferOverrides();
|
|
if (!overrides) {
|
|
return DEFAULT_OFFERS.map((offerID) => ({
|
|
offerID,
|
|
priceAtomic: defaultPriceAtomic,
|
|
active: true,
|
|
membershipRequired: true,
|
|
}));
|
|
}
|
|
if (!Array.isArray(overrides) || overrides.length === 0) {
|
|
throw new Error("Offer override list must be a non-empty JSON array");
|
|
}
|
|
|
|
return overrides.map((entry, index) => {
|
|
if (!entry || typeof entry !== "object") {
|
|
throw new Error(`Offer override at index ${index} must be an object`);
|
|
}
|
|
const offerID = String(entry.offer_id || "").trim();
|
|
if (!offerID) {
|
|
throw new Error(`Offer override at index ${index} missing offer_id`);
|
|
}
|
|
const rawPrice = String(
|
|
entry.price_atomic !== undefined ? entry.price_atomic : defaultPriceAtomic.toString()
|
|
).trim();
|
|
const priceAtomic = parseUint(`offer[${offerID}].price_atomic`, rawPrice);
|
|
const active = entry.active !== undefined ? Boolean(entry.active) : true;
|
|
const membershipRequired =
|
|
entry.membership_required !== undefined ? Boolean(entry.membership_required) : true;
|
|
return { offerID, priceAtomic, active, membershipRequired };
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const treasury = requireAddress(
|
|
"ENTITLEMENT_TREASURY_WALLET",
|
|
requiredEnv("ENTITLEMENT_TREASURY_WALLET"),
|
|
);
|
|
const paymentToken = requireAddress(
|
|
"PAYMENT_TOKEN_ADDRESS",
|
|
(process.env.PAYMENT_TOKEN_ADDRESS || hre.ethers.constants.AddressZero).trim(),
|
|
{ allowZero: true },
|
|
);
|
|
const membershipContract = requireAddress(
|
|
"MEMBERSHIP_CONTRACT_ADDRESS",
|
|
requiredEnv("MEMBERSHIP_CONTRACT_ADDRESS"),
|
|
);
|
|
const offerPriceAtomic = parseUint(
|
|
"OFFER_PRICE_ATOMIC",
|
|
(process.env.OFFER_PRICE_ATOMIC || "1000000000").trim(),
|
|
);
|
|
|
|
const [deployer] = await hre.ethers.getSigners();
|
|
console.log("deployer:", deployer.address);
|
|
console.log("network:", hre.network.name);
|
|
console.log("treasury:", treasury);
|
|
console.log("payment_token:", paymentToken);
|
|
console.log("membership_contract:", membershipContract);
|
|
console.log("offer_price_atomic:", offerPriceAtomic.toString());
|
|
|
|
const factory = await hre.ethers.getContractFactory("EdutOfferEntitlement");
|
|
const contract = await factory.deploy(treasury, paymentToken, membershipContract);
|
|
await contract.deployed();
|
|
console.log("entitlement_contract:", contract.address);
|
|
|
|
const offerPlan = buildOfferPlan(offerPriceAtomic);
|
|
const seededOffers = [];
|
|
for (const offer of offerPlan) {
|
|
const tx = await contract.upsertOffer(
|
|
offer.offerID,
|
|
offer.priceAtomic,
|
|
offer.active,
|
|
offer.membershipRequired
|
|
);
|
|
const receipt = await tx.wait();
|
|
seededOffers.push({
|
|
offerID: offer.offerID,
|
|
priceAtomic: offer.priceAtomic.toString(),
|
|
active: offer.active,
|
|
membershipRequired: offer.membershipRequired,
|
|
txHash: receipt.transactionHash,
|
|
});
|
|
console.log(
|
|
"offer_seeded:",
|
|
offer.offerID,
|
|
offer.priceAtomic.toString(),
|
|
offer.active,
|
|
offer.membershipRequired,
|
|
receipt.transactionHash
|
|
);
|
|
}
|
|
|
|
const output = {
|
|
network: hre.network.name,
|
|
chainId: hre.network.config.chainId || null,
|
|
deployer: deployer.address,
|
|
treasury,
|
|
paymentToken,
|
|
membershipContract,
|
|
offerPriceAtomic: offerPriceAtomic.toString(),
|
|
defaultOfferPriceAtomic: offerPriceAtomic.toString(),
|
|
entitlementContract: contract.address,
|
|
txHash: contract.deployTransaction.hash,
|
|
seededOffers,
|
|
};
|
|
|
|
const outputPath = (process.env.ENTITLEMENT_DEPLOY_OUTPUT_PATH || "").trim();
|
|
if (outputPath) {
|
|
const absolute = path.resolve(outputPath);
|
|
fs.mkdirSync(path.dirname(absolute), { recursive: true });
|
|
fs.writeFileSync(absolute, JSON.stringify(output, null, 2));
|
|
console.log("deployment_output:", absolute);
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exitCode = 1;
|
|
});
|