- Artist Requirement: Artist wants to store their custom, unminified JS file (~40 KB) on-chain with ERC‑721 tokens, while keeping the rest of the front-end assets (HTML, CSS, other JS, media) off-chain (e.g., IPFS).
- Current Architecture: Each artwork series and its member tokens are managed by an ERC‑721 contract. Token metadata includes all source code and asset references.
- Limitation: The existing smart contract does not support arbitrary on-chain data storage beyond standard metadata fields, so embedding a 40 KB JS file directly on-chain is not currently possible without contract changes.
-
Series Data Mapping
-
Support multiple SSTORE2 pointers per series (for >24 KB payloads):
// zero or more contracts that hold the data chunks mapping(uint256 => address[]) public seriesDataPointers; // optional authenticity hash and off‑chain URI mapping(uint256 => bytes32) public seriesDataHash; mapping(uint256 => string) public seriesDataUri;
-
seriesDataPointers[seriesId]can hold one address (≤ 24 KB) or several addresses (e.g., two for 40 KB).
-
-
Data Storage Strategies**
- Raw Data via SSTORE2: Use SSTORE2 to write the unminified JS (~40 KB) to a separate contract, store its address in
seriesDataPointer[seriesId]. - Minified Raw Data: If the JS is minified (e.g., reduced to ~20 KB), gas costs roughly halve.
- On-Chain Hash Only: Compute
bytes32 hash = keccak256(data)and store inseriesDataHash[seriesId]. - IPFS URI Reference: Upload the JS to IPFS, then store its CID as
seriesDataUri[seriesId].
- Raw Data via SSTORE2: Use SSTORE2 to write the unminified JS (~40 KB) to a separate contract, store its address in
-
Series Registration Function
-
Register one or more pointers after minting:
function registerSeriesData( uint256 seriesId, address[] calldata dataPointers, // one or more bytes32 dataHash, string calldata dataUri ) external onlyOwner { seriesDataPointers[seriesId] = dataPointers; seriesDataHash[seriesId] = dataHash; seriesDataUri[seriesId] = dataUri; }
-
Back‑end mint flow remains unchanged; a separate script can call this function with one or multiple pointers.
-
Why “pointers”?
SSTORE2stores large blobs by deploying a tiny contract whose bytecode is the data, then keeps that contract’s address as a pointer. One contract can hold up to 24 576 bytes (~24 KB) of payload. A 40 KB blob therefore needs two such contracts (two pointers); a ~20 KB minified file fits in one. Each extra pointer adds an additionalCREATEcost plus per‑byte storage gas.
| Strategy | Size | Gas Estimate* | Cost (ETH) | USD (@ $3 000/ETH) |
|---|---|---|---|---|
| Raw via SSTORE2 (2 pointers) | 40 KB | ≈ 6 950 000 | ≈ 0.070 | ≈ $210 |
| Minified via SSTORE2 (1 ptr) | 20 KB | ≈ 3 800 000 | ≈ 0.038 | ≈ $114 |
| On‑chain Hash (SSTORE) | 32 B | ≈ 20 000 | ≈ 0.00020 | ≈ $0.60 |
| IPFS CID (string) | 46 B | ≈ 25 000 | ≈ 0.00025 | ≈ $0.75 |
*Gas estimates use empirical data from the 0xsequence/sstore2 benchmarks, which report ~4.15 M gas to write the maximum 24 576 B chunk and ~170 gas/B for large payloads (github.com). The 40 KB file exceeds the single‑chunk limit, so two pointers are required. Prices assume 10 gwei gas.
-
Gas Costs:
- Direct on-chain storage of 40 KB (via SSTORE2) remains the most expensive (~$210 per series).
- Hash‑only or IPFS URI strategies reduce gas to under ~$1 per series.
-
Redundancy:
- If token metadata already references the JS file, adding mapping entries may duplicate data.
-
Security & Validation:
- Off-chain systems should verify
seriesDataHash[seriesId]against retrieved JS to prevent tampering.
- Off-chain systems should verify