a manifold extension to port fully onchain an existing collection minted with offchain content
clgsms/scripts/point.js
JavaScript · 134 lines · 5 KBRAWDOWNLOAD
/**
* PHASE 1b — point each tokenId at its uploaded artwork and its metadata.
*
* IDS=57 npx hardhat run scripts/point.js --network mainnet
* FROM_ID=1 TO_ID=100 npx hardhat run scripts/point.js --network mainnet
*
* One small transaction per token (~140k gas), whatever the artwork weighs —
* the image never passes through the extension, so nothing here is anywhere
* near a gas cap. A token becomes redeemable the moment both its pointer and
* its metadata fragment are set.
*
* Expects `meta/<id>.json` from scripts/fetch-metadata.js, and the artwork
* already uploaded by scripts/upload.js.
*
* Env:
* EXTENSION / STORAGE default: deployments/<network>.json
* IDS comma-separated tokenIds, in any order
* FROM_ID / TO_ID token range, default 1..100
* BATCH=1 send pointers with setArtworkBatch and metadata
* separately (cheaper when metadata is already set)
* DRY_RUN=1 report what it would do, send nothing
*
* Re-running is safe while the extension is unlocked: a token whose pointer and
* metadata already match is skipped.
*/
import { existsSync, readFileSync } from "node:fs";
import {
addressFor,
artworkKey,
assertShapeMatchesRenderer,
connect,
keyName,
main,
metaPath,
storageShape,
tokenRange,
} from "./_lib.js";
main(async () => {
const { ethers, net } = await connect();
const ext = await ethers.getContractAt(
"ColorgasmRedeemExtension",
addressFor("extension", net),
);
const blobs = await ethers.getContractAt("ColorgasmStorage", addressFor("storage", net));
const ids = tokenRange();
const dryRun = process.env.DRY_RUN === "1";
const [owner, signer] = await Promise.all([ext.owner(), ethers.getSigners().then((s) => s[0])]);
if (owner !== signer.address) {
throw new Error(`setArtwork is owner-only: extension owner is ${owner}`);
}
if (await ext.storageLocked()) throw new Error("the extension is locked — pointers are final");
if ((await ext.blobStorage()) !== (await blobs.getAddress())) {
throw new Error(
`the extension points at ${await ext.blobStorage()}, not ${await blobs.getAddress()}`,
);
}
await assertShapeMatchesRenderer(ethers, ext, storageShape());
console.log(`extension ${await ext.getAddress()}\ntokens #${ids[0]}..#${ids.at(-1)}${dryRun ? " (DRY RUN)" : ""}\n`);
let gasTotal = 0n;
const pending = [];
for (const id of ids) {
const mPath = metaPath(id);
if (!existsSync(mPath)) {
console.log(`#${id}: missing ${mPath} — skipped`);
continue;
}
const meta = readFileSync(mPath);
const metaHex = "0x" + meta.toString("hex");
const key = artworkKey(id);
const size = await blobs.sizeOf(key);
if (size === 0n) {
console.log(`#${id}: nothing uploaded under "${keyName(key)}" yet — skipped`);
continue;
}
const [onchainKey, onchainMeta] = await Promise.all([
ext.artworkKeyOf(id),
ext.metadataOf(id),
]);
const keyMatches = onchainKey === key;
const metaMatches = onchainMeta === metaHex;
if (keyMatches && metaMatches) {
console.log(`#${id}: already pointed at "${keyName(key)}" (${size} B)`);
continue;
}
console.log(
`#${id}: -> "${keyName(key)}" (${size} B onchain)` +
(keyMatches ? ", metadata only" : metaMatches ? ", pointer only" : ""),
);
if (dryRun) continue;
pending.push({ id, key: keyMatches ? ethers.ZeroHash : key, meta: metaMatches ? "0x" : metaHex });
}
if (process.env.BATCH === "1") {
const withKeys = pending.filter((p) => p.key !== ethers.ZeroHash);
if (withKeys.length > 0) {
const tx = await ext.setArtworkBatch(
withKeys.map((p) => p.id),
withKeys.map((p) => p.key),
);
console.log(`\nsetArtworkBatch(${withKeys.length}) ${tx.hash}`);
gasTotal += (await tx.wait()).gasUsed;
}
for (const p of pending.filter((x) => x.meta !== "0x")) {
const tx = await ext.setArtwork(p.id, ethers.ZeroHash, p.meta);
console.log(` metadata #${p.id} ${tx.hash}`);
gasTotal += (await tx.wait()).gasUsed;
}
} else {
for (const p of pending) {
const tx = await ext.setArtwork(p.id, p.key, p.meta);
const receipt = await tx.wait();
gasTotal += receipt.gasUsed;
console.log(` #${p.id} ${receipt.gasUsed} gas ${tx.hash}`);
}
}
let redeemable = 0;
for (const id of ids) if (await ext.isRedeemable(id)) redeemable++;
console.log(
`\n${redeemable} of ${ids.length} token(s) redeemable` +
(gasTotal > 0n ? `, ${gasTotal} gas spent` : ""),
);
});