* 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. * 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"; assertShapeMatchesRenderer, 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())) { `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`); const mPath = metaPath(id); if (!existsSync(mPath)) { console.log(`#${id}: missing ${mPath} — skipped`); const meta = readFileSync(mPath); const metaHex = "0x" + meta.toString("hex"); const key = artworkKey(id); const size = await blobs.sizeOf(key); console.log(`#${id}: nothing uploaded under "${keyName(key)}" yet — skipped`); const [onchainKey, onchainMeta] = await Promise.all([ const keyMatches = onchainKey === key; const metaMatches = onchainMeta === metaHex; if (keyMatches && metaMatches) { console.log(`#${id}: already pointed at "${keyName(key)}" (${size} B)`); `#${id}: -> "${keyName(key)}" (${size} B onchain)` + (keyMatches ? ", metadata only" : metaMatches ? ", pointer only" : ""), 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; 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}`); for (const id of ids) if (await ext.isRedeemable(id)) redeemable++; `\n${redeemable} of ${ids.length} token(s) redeemable` + (gasTotal > 0n ? `, ${gasTotal} gas spent` : ""),