addPieces can commit many pieces to a data set in one call, but the real ceiling is ~40 pieces, not the 61 the extraData size cap implies.PiecesAdded event, and it is independent of metadata. Below it sit two more limits: message size (~37 pieces with maxed metadata) and, on mainnet, block gas (worsened by the create-and-add combo, which shares one message's budget).extraData byte cap (8192, mirrored in FWSS and Curio) is not the binding limit and is not pulling its weight.struct Cid { bytes } reused across the event, calldata and storage; reworking it is a larger, breaking change for later.extraData caps, add an explicit 40-piece batch cap (FWSS, Curio, Synapse), tighten metadata caps (MAX_KEYS_PER_PIECE 5 to 3, MAX_VALUE_LENGTH 128 to 96), and charge addPieces per piece (0.0005 + 0.0003 x N, so N=1 is 0.0008) instead of a flat per-operation fee.We are currently constrained by MAX_ADD_PIECES_EXTRA_DATA_SIZE = 8192 in FWSS. This constant intentionally limits the extraData parameter for the addPieces operation. extraData caries the client's signature, nonce and the metadata associated with all pieces being added.
This limit creates a theoretical max of 61 pieces with empty metadata and when every piece carries the full allowed metadata we reach a theoretical cap of 5 pieces.
This existing cap is enforced in two places: FWSS MAX_ADD_PIECES_EXTRA_DATA_SIZE and Curio MaxAddPiecesExtraDataSize (the SDK has no cap and currently relies on Curio rejection for erroring).
Further we constrain the metadata within extraData in the following ways:
MAX_KEYS_PER_PIECE = 5MAX_KEY_LENGTH = 32MAX_VALUE_LENGTH = 128But there are additional bounds that come into play that even stop us from reaching the 61 piece theoretical max. The effective cap is the smallest of these: with no or light metadata we are event-bound at 41, with heavy metadata we are message-bound at around 37, and on mainnet gas is a soft ceiling on top (made worse by the create-and-add combo, below).
MAX_TOTAL_VALUES_LEN = 8192 B)This bites us in PDPVerifier's PiecesAdded which doesn't even use extraData. They look like this: event PiecesAdded(uint256 indexed setId, uint256[] pieceIds, Cids.Cid[] pieceCids). A PieceCIDv2 is 39 raw bytes, but abi.encode of Cid[] (a dynamic array of
struct { bytes data }) inflates each one to 160 bytes, and the parallel pieceIds array adds another 32 each for a total of 192 bytes per piece. Cid[] is a dynamic array of tuples, so for each element we have a 32-byte offset, a 32-byte tuple inner offset (the bytes inner), a 32-byte bytes word length specifier (always 39) and the actual CID bytes which is 39 but has to be padded up to 64-bytes to fit.