/** * 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/.json` from scripts/fetch-metadata.js, and the artwork * already uploaded by scripts/upload.js. * * Env: * EXTENSION / STORAGE default: deployments/.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` : ""), ); });