Support JSON-configured per-offer entitlement seeding
This commit is contained in:
parent
16a1c5836c
commit
7300612b71
@ -59,6 +59,11 @@ Copy `.env.example` values into your shell/session before deploy:
|
|||||||
9. `PAYMENT_TOKEN_ADDRESS`
|
9. `PAYMENT_TOKEN_ADDRESS`
|
||||||
10. `OFFER_PRICE_ATOMIC`
|
10. `OFFER_PRICE_ATOMIC`
|
||||||
11. `ENTITLEMENT_DEPLOY_OUTPUT_PATH` (optional)
|
11. `ENTITLEMENT_DEPLOY_OUTPUT_PATH` (optional)
|
||||||
|
12. `OFFERS_JSON` (optional path to per-offer seed config JSON)
|
||||||
|
13. `OFFERS_INLINE_JSON` (optional inline JSON array alternative to `OFFERS_JSON`)
|
||||||
|
|
||||||
|
If no offer override JSON is provided, deploy script seeds default offers at `OFFER_PRICE_ATOMIC`.
|
||||||
|
Use `deploy/offers.template.json` to define per-offer prices and policy flags.
|
||||||
|
|
||||||
Example (Sepolia):
|
Example (Sepolia):
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ Template:
|
|||||||
|
|
||||||
- `membership-deploy.template.json`
|
- `membership-deploy.template.json`
|
||||||
- `entitlement-deploy.template.json`
|
- `entitlement-deploy.template.json`
|
||||||
|
- `offers.template.json`
|
||||||
|
|
||||||
Recommended process:
|
Recommended process:
|
||||||
|
|
||||||
@ -19,3 +20,4 @@ Recommended process:
|
|||||||
`npm run deploy:entitlement:sepolia` / `npm run deploy:entitlement:mainnet` for offer entitlements.
|
`npm run deploy:entitlement:sepolia` / `npm run deploy:entitlement:mainnet` for offer entitlements.
|
||||||
2. Copy the matching template to a dated file (for example `membership-base-sepolia-2026-02-18.json`).
|
2. Copy the matching template to a dated file (for example `membership-base-sepolia-2026-02-18.json`).
|
||||||
3. Fill all deployment fields from script output and explorer links.
|
3. Fill all deployment fields from script output and explorer links.
|
||||||
|
4. If you need per-offer pricing, copy `offers.template.json`, edit values, and pass it via `OFFERS_JSON=/path/to/file.json`.
|
||||||
|
|||||||
32
deploy/offers.template.json
Normal file
32
deploy/offers.template.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"offer_id": "edut.solo.core",
|
||||||
|
"price_atomic": "1000000000",
|
||||||
|
"active": true,
|
||||||
|
"membership_required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offer_id": "edut.workspace.core",
|
||||||
|
"price_atomic": "1000000000",
|
||||||
|
"active": true,
|
||||||
|
"membership_required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offer_id": "edut.workspace.ai",
|
||||||
|
"price_atomic": "1000000000",
|
||||||
|
"active": true,
|
||||||
|
"membership_required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offer_id": "edut.workspace.lane24",
|
||||||
|
"price_atomic": "1000000000",
|
||||||
|
"active": true,
|
||||||
|
"membership_required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offer_id": "edut.workspace.sovereign",
|
||||||
|
"price_atomic": "1000000000",
|
||||||
|
"active": true,
|
||||||
|
"membership_required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -40,6 +40,62 @@ function parseUint(name, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
async function main() {
|
||||||
const treasury = requireAddress(
|
const treasury = requireAddress(
|
||||||
"ENTITLEMENT_TREASURY_WALLET",
|
"ENTITLEMENT_TREASURY_WALLET",
|
||||||
@ -72,12 +128,31 @@ async function main() {
|
|||||||
await contract.deployed();
|
await contract.deployed();
|
||||||
console.log("entitlement_contract:", contract.address);
|
console.log("entitlement_contract:", contract.address);
|
||||||
|
|
||||||
|
const offerPlan = buildOfferPlan(offerPriceAtomic);
|
||||||
const seededOffers = [];
|
const seededOffers = [];
|
||||||
for (const offerID of DEFAULT_OFFERS) {
|
for (const offer of offerPlan) {
|
||||||
const tx = await contract.upsertOffer(offerID, offerPriceAtomic, true, true);
|
const tx = await contract.upsertOffer(
|
||||||
|
offer.offerID,
|
||||||
|
offer.priceAtomic,
|
||||||
|
offer.active,
|
||||||
|
offer.membershipRequired
|
||||||
|
);
|
||||||
const receipt = await tx.wait();
|
const receipt = await tx.wait();
|
||||||
seededOffers.push({ offerID, txHash: receipt.transactionHash });
|
seededOffers.push({
|
||||||
console.log("offer_seeded:", offerID, receipt.transactionHash);
|
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 = {
|
const output = {
|
||||||
@ -88,6 +163,7 @@ async function main() {
|
|||||||
paymentToken,
|
paymentToken,
|
||||||
membershipContract,
|
membershipContract,
|
||||||
offerPriceAtomic: offerPriceAtomic.toString(),
|
offerPriceAtomic: offerPriceAtomic.toString(),
|
||||||
|
defaultOfferPriceAtomic: offerPriceAtomic.toString(),
|
||||||
entitlementContract: contract.address,
|
entitlementContract: contract.address,
|
||||||
txHash: contract.deployTransaction.hash,
|
txHash: contract.deployTransaction.hash,
|
||||||
seededOffers,
|
seededOffers,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user