@@ -146,12 +146,20 @@ export interface PieceAdditionStatusResponse {
146146 * Input for adding pieces to a data set
147147 */
148148export interface PDPAddPiecesInput {
149- pieces : {
150- pieceCid : string
151- subPieces : {
152- subPieceCid : string
153- } [ ]
149+ pieces : PDPPieces [ ]
150+ extraData : string
151+ }
152+
153+ export interface PDPPieces {
154+ pieceCid : string
155+ subPieces : {
156+ subPieceCid : string
154157 } [ ]
158+ }
159+
160+ export interface PDPCreateAndAddInput {
161+ recordKeeper : string
162+ pieces : PDPPieces [ ]
155163 extraData : string
156164}
157165
@@ -177,6 +185,7 @@ export class PDPServer {
177185 * Create a new data set on the PDP server
178186 * @param clientDataSetId - Unique ID for the client's dataset
179187 * @param payee - Address that will receive payments (service provider)
188+ * @param payer - Address that will pay for the storage (client)
180189 * @param metadata - Metadata entries for the data set (key-value pairs)
181190 * @param recordKeeper - Address of the Warm Storage contract
182191 * @returns Promise that resolves with transaction hash and status URL
@@ -245,36 +254,114 @@ export class PDPServer {
245254 }
246255
247256 /**
248- * Add pieces to an existing data set
249- * @param dataSetId - The ID of the data set to add pieces to
250- * @param clientDataSetId - The client's dataset ID used when creating the data set
251- * @param nextPieceId - The ID to assign to the first piece being added, this should be
252- * the next available ID on chain or the signature will fail to be validated
257+ * Creates a data set and adds pieces to it in a combined operation.
258+ * Users can poll the status of the operation using the returned data set status URL.
259+ * After which the user can use the returned transaction hash and data set ID to check the status of the piece addition.
260+ * @param clientDataSetId - Unique ID for the client's dataset
261+ * @param payee - Address that will receive payments (service provider)
262+ * @param payer - Address that will pay for the storage (client)
263+ * @param recordKeeper - Address of the Warm Storage contract
253264 * @param pieceDataArray - Array of piece data containing PieceCID CIDs and raw sizes
254- * @param metadata - Optional metadata for each piece (array of arrays, one per piece)
255- * @returns Promise that resolves when the pieces are added (201 Created)
256- * @throws Error if any CID is invalid
257- *
258- * @example
259- * ```typescript
260- * const pieceData = ['bafkzcibcd...']
261- * const metadata = [[{ key: 'snapshotDate', value: '20250711' }]]
262- * await pdpTool.addPieces(dataSetId, clientDataSetId, nextPieceId, pieceData, metadata)
263- * ```
265+ * @param metadata - Optional metadata for dataset and each of the pieces.
266+ * @returns Promise that resolves with transaction hash and status URL
264267 */
265- async addPieces (
266- dataSetId : number ,
268+ async createAndAddPieces (
267269 clientDataSetId : bigint ,
268- nextPieceId : number ,
270+ payee : string ,
271+ payer : string ,
272+ recordKeeper : string ,
273+ pieceDataArray : PieceCID [ ] | string [ ] ,
274+ metadata : {
275+ dataset ?: MetadataEntry [ ]
276+ pieces ?: MetadataEntry [ ] [ ]
277+ }
278+ ) : Promise < CreateDataSetResponse > {
279+ // Validate metadata against contract limits
280+ if ( metadata . dataset == null ) {
281+ metadata . dataset = [ ]
282+ }
283+ validateDataSetMetadata ( metadata . dataset )
284+ metadata . pieces = PDPServer . _processAddPiecesInputs ( pieceDataArray , metadata . pieces )
285+
286+ // Generate the EIP-712 signature for data set creation
287+ const createAuthData = await this . getAuthHelper ( ) . signCreateDataSet ( clientDataSetId , payee , metadata . dataset )
288+
289+ // Prepare the extra data for the contract call
290+ // This needs to match the DataSetCreateData struct in Warm Storage contract
291+ const createExtraData = this . _encodeDataSetCreateData ( {
292+ payer,
293+ clientDataSetId,
294+ metadata : metadata . dataset ,
295+ signature : createAuthData . signature ,
296+ } )
297+
298+ const addAuthData = await this . getAuthHelper ( ) . signAddPieces (
299+ clientDataSetId ,
300+ BigInt ( 0 ) ,
301+ pieceDataArray , // Pass PieceData[] directly to auth helper
302+ metadata . pieces
303+ )
304+
305+ const addExtraData = this . _encodeAddPiecesExtraData ( {
306+ signature : addAuthData . signature ,
307+ metadata : metadata . pieces ,
308+ } )
309+
310+ const abiCoder = ethers . AbiCoder . defaultAbiCoder ( )
311+ const encoded = abiCoder . encode ( [ 'bytes' , 'bytes' ] , [ `0x${ createExtraData } ` , `0x${ addExtraData } ` ] )
312+ const requestJson : PDPCreateAndAddInput = {
313+ recordKeeper : recordKeeper ,
314+ pieces : PDPServer . _formatPieceDataArrayForCurio ( pieceDataArray ) ,
315+ extraData : `${ encoded } ` ,
316+ }
317+
318+ // Make the POST request to add pieces to the data set
319+ const response = await fetch ( `${ this . _serviceURL } /pdp/data-sets/create-and-add` , {
320+ method : 'POST' ,
321+ headers : {
322+ 'Content-Type' : 'application/json' ,
323+ } ,
324+ body : JSON . stringify ( requestJson ) ,
325+ } )
326+
327+ if ( response . status !== 201 ) {
328+ const errorText = await response . text ( )
329+ throw new Error ( `Failed to create data set: ${ response . status } ${ response . statusText } - ${ errorText } ` )
330+ }
331+
332+ // Extract transaction hash from Location header
333+ const location = response . headers . get ( 'Location' )
334+ if ( location == null ) {
335+ throw new Error ( 'Server did not provide Location header in response' )
336+ }
337+
338+ // Parse the location to extract the transaction hash
339+ // Expected format: /pdp/data-sets/created/{txHash}
340+ const locationMatch = location . match ( / \/ p d p \/ d a t a - s e t s \/ c r e a t e d \/ ( .+ ) $ / )
341+ if ( locationMatch == null ) {
342+ throw new Error ( `Invalid Location header format: ${ location } ` )
343+ }
344+
345+ const txHash = locationMatch [ 1 ]
346+
347+ return {
348+ txHash,
349+ statusUrl : `${ this . _serviceURL } ${ location } ` ,
350+ }
351+ }
352+
353+ private static _processAddPiecesInputs (
269354 pieceDataArray : PieceCID [ ] | string [ ] ,
270355 metadata ?: MetadataEntry [ ] [ ]
271- ) : Promise < AddPiecesResponse > {
356+ ) : MetadataEntry [ ] [ ] {
272357 if ( pieceDataArray . length === 0 ) {
273358 throw new Error ( 'At least one piece must be provided' )
274359 }
275360
276- // Validate piece metadata against contract limits
277361 if ( metadata != null ) {
362+ if ( metadata . length !== pieceDataArray . length ) {
363+ throw new Error ( `Metadata length (${ metadata . length } ) must match pieces length (${ pieceDataArray . length } )` )
364+ }
278365 for ( let i = 0 ; i < metadata . length ; i ++ ) {
279366 if ( metadata [ i ] != null && metadata [ i ] . length > 0 ) {
280367 try {
@@ -293,15 +380,52 @@ export class PDPServer {
293380 throw new Error ( `Invalid PieceCID: ${ String ( pieceData ) } ` )
294381 }
295382 }
296-
297383 // If no metadata provided, create empty arrays for each piece
298384 const finalMetadata = metadata ?? pieceDataArray . map ( ( ) => [ ] )
385+ return finalMetadata
386+ }
299387
300- // Validate metadata length matches pieces
301- if ( finalMetadata . length !== pieceDataArray . length ) {
302- throw new Error ( `Metadata length (${ finalMetadata . length } ) must match pieces length (${ pieceDataArray . length } )` )
303- }
388+ private static _formatPieceDataArrayForCurio ( pieceDataArray : PieceCID [ ] | string [ ] ) : PDPPieces [ ] {
389+ return pieceDataArray . map ( ( pieceData ) => {
390+ // Convert to string for JSON serialization
391+ const cidString = typeof pieceData === 'string' ? pieceData : pieceData . toString ( )
392+ return {
393+ pieceCid : cidString ,
394+ subPieces : [
395+ {
396+ subPieceCid : cidString , // Piece is its own subpiece
397+ } ,
398+ ] ,
399+ }
400+ } )
401+ }
304402
403+ /**
404+ * Add pieces to an existing data set
405+ * @param dataSetId - The ID of the data set to add pieces to
406+ * @param clientDataSetId - The client's dataset ID used when creating the data set
407+ * @param nextPieceId - The ID to assign to the first piece being added, this should be
408+ * the next available ID on chain or the signature will fail to be validated
409+ * @param pieceDataArray - Array of piece data containing PieceCID CIDs and raw sizes
410+ * @param metadata - Optional metadata for each piece (array of arrays, one per piece)
411+ * @returns Promise that resolves when the pieces are added (201 Created)
412+ * @throws Error if any CID is invalid
413+ *
414+ * @example
415+ * ```typescript
416+ * const pieceData = ['bafkzcibcd...']
417+ * const metadata = [[{ key: 'snapshotDate', value: '20250711' }]]
418+ * await pdpTool.addPieces(dataSetId, clientDataSetId, nextPieceId, pieceData, metadata)
419+ * ```
420+ */
421+ async addPieces (
422+ dataSetId : number ,
423+ clientDataSetId : bigint ,
424+ nextPieceId : number ,
425+ pieceDataArray : PieceCID [ ] | string [ ] ,
426+ metadata ?: MetadataEntry [ ] [ ]
427+ ) : Promise < AddPiecesResponse > {
428+ const finalMetadata = PDPServer . _processAddPiecesInputs ( pieceDataArray , metadata )
305429 // Generate the EIP-712 signature for adding pieces
306430 const authData = await this . getAuthHelper ( ) . signAddPieces (
307431 clientDataSetId ,
@@ -320,18 +444,7 @@ export class PDPServer {
320444 // Prepare request body matching the Curio handler expectation
321445 // Each piece has itself as its only subPiece (internal implementation detail)
322446 const requestBody : PDPAddPiecesInput = {
323- pieces : pieceDataArray . map ( ( pieceData ) => {
324- // Convert to string for JSON serialization
325- const cidString = typeof pieceData === 'string' ? pieceData : pieceData . toString ( )
326- return {
327- pieceCid : cidString ,
328- subPieces : [
329- {
330- subPieceCid : cidString , // Piece is its own subpiece
331- } ,
332- ] ,
333- }
334- } ) ,
447+ pieces : PDPServer . _formatPieceDataArrayForCurio ( pieceDataArray ) ,
335448 extraData : `0x${ extraData } ` ,
336449 }
337450
0 commit comments