Skip to content

Chunk manager v2: support large on-chain file uploads #7

@JamesCarnley

Description

@JamesCarnley

Problem

The current SSTORE2 chunk manager deploys with all chunk addresses passed in the constructor. EIP-3860 limits contract init code to 49,152 bytes — at ~2,200 chunks (~50MB), the ABI-encoded constructor args exceed this limit and the deploy transaction fails.

Currently capped at ~24MB (~1000 chunks) in the UI with an error message suggesting IPFS/Arweave for larger files.

Context

  • SSTORE2 chunks are 24KB each, deployed as individual contracts — these are fine
  • The chunk manager contract is tiny (just chunkAddress(index) and chunkCount()) — also fine
  • The problem is purely that thousands of address values stuffed into constructor args push init code over 49KB
  • This is a client-side upload limitation, not a protocol/schema issue — the MIRROR schema just stores a web3:// URI pointing to whatever contract serves the bytes
  • Solady/solmate SSTORE2 libraries only provide single-pointer read/write primitives, not multi-chunk coordination

Proposed approach: mutable chunk manager with addChunks()

Deploy an empty manager, add chunk addresses via batched calls after deployment, finalize when complete.

// Sketch — not final
constructor() { owner = msg.sender; }

function addChunks(address[] calldata chunks) external onlyOwner {
    require(!finalized);
    for (uint i = 0; i < chunks.length; i++) {
        chunkList.push(chunks[i]);
    }
}

function finalize() external onlyOwner {
    finalized = true;
}

Design questions to resolve

Deployment lifecycle

  • Deploy manager → deploy chunks → addChunks() in batches → finalize() → mint MIRROR
  • Should the MIRROR be minted before or after finalization? Before = temporarily broken web3:// link. After = extra step but always consistent.
  • Should the manager be usable (serve bytes via web3://) before finalization? Partial files could confuse consumers.

Access control & immutability

  • Who can call addChunks()? Owner-only seems right, but owner = EOA means the manager is mutable until finalized. Is that acceptable?
  • Should finalize() renounce ownership to guarantee immutability? Or keep owner for future extension (e.g., appending to a log file)?
  • What if someone adds wrong chunks or in the wrong order? Ordering must be guaranteed by the caller.

Resumability

  • If upload fails at chunk 1500 of 2200, can we resume?
  • Current method: No. Chunk addresses are held in a JS array in memory. On failure, 1500 orphaned contracts exist on-chain with nothing pointing to them.
  • New method: Also not resumable by default. Need to persist progress.
  • Options: localStorage keyed by (manager address, expected chunk count), or query manager.chunkCount() on-chain to know where to resume.
  • Need to handle duplicate addChunks() calls (idempotency or at-least-once).

Error recovery

  • Partially-populated manager on-chain after a crash — how to detect and resume vs. abandon?
  • Should the manager store expected total chunk count at deploy time so it knows when it's complete?
  • constructor(uint256 expectedChunks) — then finalize() can assert chunkList.length == expectedChunks.

Gas considerations

  • addChunks() uses SSTORE (storage slots) instead of constructor-arg bytecode — roughly similar per-address cost
  • Batch size per addChunks() call — 500 addresses per tx? Need to test gas limits.
  • Per-transaction overhead (21,000 base gas) is negligible compared to SSTORE2 chunk deployments

Batch upload UX

  • Progress indicator showing chunks deployed vs. chunks registered vs. finalized
  • "Resume upload" UI for interrupted uploads (requires persisted state)
  • Estimated gas cost shown before starting

Files affected

  • New chunk manager contract (or modify existing MOCK_CHUNKED_FILE_BYTECODE)
  • packages/nextjs/components/explorer/Toolbar.tsx — upload logic
  • packages/nextjs/components/explorer/MirrorsPanel.tsx — "Upload File" tab in mirror panel
  • No schema or protocol changes needed — same MIRROR attestation, different web3:// target

Not urgent

The 24MB cap is generous for current gas economics — nobody is paying mainnet gas for 50MB files today. This becomes important when L2/L3 storage costs drop enough to make large on-chain files practical.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions