@shelby-protocol/sdk 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/browser/index.d.ts +9 -9
  2. package/dist/browser/index.mjs +1068 -589
  3. package/dist/core/clients/ShelbyBlobClient.d.ts +155 -40
  4. package/dist/core/clients/ShelbyBlobClient.mjs +314 -112
  5. package/dist/core/clients/ShelbyClient.d.ts +27 -4
  6. package/dist/core/clients/ShelbyClient.mjs +869 -551
  7. package/dist/core/clients/ShelbyClientConfig.d.ts +9 -1
  8. package/dist/core/clients/ShelbyMetadataClient.d.ts +38 -6
  9. package/dist/core/clients/ShelbyMetadataClient.mjs +95 -13
  10. package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +42 -0
  11. package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +62 -7
  12. package/dist/core/clients/ShelbyPlacementGroupClient.mjs +10 -7
  13. package/dist/core/clients/ShelbyRPCClient.d.ts +55 -66
  14. package/dist/core/clients/ShelbyRPCClient.mjs +344 -360
  15. package/dist/core/clients/index.d.ts +3 -2
  16. package/dist/core/clients/index.mjs +933 -551
  17. package/dist/core/clients/utils.mjs +1 -1
  18. package/dist/core/commitments.d.ts +12 -1
  19. package/dist/core/commitments.mjs +7 -0
  20. package/dist/core/constants.d.ts +1 -1
  21. package/dist/core/constants.mjs +1 -1
  22. package/dist/core/erasure/constants.d.ts +16 -1
  23. package/dist/core/erasure/constants.mjs +15 -1
  24. package/dist/core/erasure/index.d.ts +1 -1
  25. package/dist/core/erasure/index.mjs +15 -1
  26. package/dist/core/errors.d.ts +20 -1
  27. package/dist/core/errors.mjs +29 -0
  28. package/dist/core/index.d.ts +9 -9
  29. package/dist/core/index.mjs +1068 -589
  30. package/dist/core/operations/generated/sdk.d.ts +798 -42
  31. package/dist/core/operations/generated/sdk.mjs +93 -9
  32. package/dist/core/operations/index.d.ts +1 -1
  33. package/dist/core/operations/index.mjs +94 -10
  34. package/dist/core/rpc-responses.d.ts +1 -57
  35. package/dist/core/rpc-responses.mjs +1 -21
  36. package/dist/core/sp/chunk_proof.d.ts +23 -0
  37. package/dist/core/sp/chunk_proof.mjs +113 -0
  38. package/dist/core/sp/index.d.ts +3 -0
  39. package/dist/core/sp/index.mjs +402 -0
  40. package/dist/core/sp/sp_write_client.d.ts +53 -0
  41. package/dist/core/sp/sp_write_client.mjs +302 -0
  42. package/dist/core/types/blobs.d.ts +24 -5
  43. package/dist/core/types/blobs.mjs +24 -0
  44. package/dist/core/types/index.d.ts +2 -2
  45. package/dist/core/types/index.mjs +46 -2
  46. package/dist/core/types/payments.mjs +1 -1
  47. package/dist/core/types/storage_providers.d.ts +32 -6
  48. package/dist/core/types/storage_providers.mjs +22 -0
  49. package/dist/gen/rpc_server_pb.d.ts +295 -0
  50. package/dist/gen/rpc_server_pb.mjs +28 -0
  51. package/dist/node/clients/ShelbyNodeClient.d.ts +1 -0
  52. package/dist/node/clients/ShelbyNodeClient.mjs +869 -551
  53. package/dist/node/clients/index.d.ts +1 -0
  54. package/dist/node/clients/index.mjs +867 -551
  55. package/dist/node/index.d.ts +9 -9
  56. package/dist/node/index.mjs +1068 -589
  57. package/dist/node/parallel/commitment_worker.mjs +36 -249
  58. package/dist/node/parallel/commitment_worker_pool.mjs +56 -3
  59. package/dist/node/parallel/coverage_flush.d.ts +16 -0
  60. package/dist/node/parallel/coverage_flush.mjs +43 -0
  61. package/dist/node/parallel/default_commitment_worker_pool.mjs +56 -3
  62. package/dist/node/parallel/index.d.ts +1 -0
  63. package/dist/node/parallel/index.mjs +72 -4
  64. package/dist/node/parallel/parallel_commitments.mjs +56 -3
  65. package/dist/node/parallel/worker_pool.mjs +56 -3
  66. package/package.json +10 -3
@@ -8,7 +8,7 @@ import {
8
8
  // src/core/constants.ts
9
9
  import { Network } from "@aptos-labs/ts-sdk";
10
10
  var NetworkToShelbyRPCBaseUrl = {
11
- [Network.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
11
+ [Network.SHELBYNET]: "https://shelby.shelbynet.shelby.xyz/shelby",
12
12
  [Network.NETNA]: void 0,
13
13
  [Network.DEVNET]: void 0,
14
14
  [Network.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
@@ -37,6 +37,18 @@ var NetworkToGasStationBaseUrl = {
37
37
  var SHELBY_DEPLOYER = "0x85fdb9a176ab8ef1d9d9c1b60d60b3924f0800ac1de1cc2085fb0b8bb4988e6a";
38
38
  var MICROPAYMENTS_DEPLOYER = "0x1ae7275148bf6ef742b658fd9cbcc2e094201606f4a7bc707bab0201da8043ee";
39
39
 
40
+ // src/core/types/storage_providers.ts
41
+ var ACTIVE_PROVIDER_CONDITIONS = [
42
+ "Normal",
43
+ "Faulty",
44
+ "Leaving",
45
+ "Evicted",
46
+ "PendingLeaving"
47
+ ];
48
+ function isActiveProviderCondition(value) {
49
+ return typeof value === "string" && ACTIVE_PROVIDER_CONDITIONS.includes(value);
50
+ }
51
+
40
52
  // src/core/utils.ts
41
53
  import {
42
54
  AccountAddress,
@@ -168,6 +180,15 @@ var getShelbyRPCBaseUrl = (config) => {
168
180
  };
169
181
 
170
182
  // src/core/clients/ShelbyMetadataClient.ts
183
+ function parseActiveProviderCondition(raw) {
184
+ const variant = raw?.__variant__;
185
+ if (!isActiveProviderCondition(variant)) {
186
+ throw new Error(
187
+ `Unknown ActiveProviderCondition variant: ${JSON.stringify(raw)}`
188
+ );
189
+ }
190
+ return variant;
191
+ }
171
192
  function parseStorageProviderState(raw) {
172
193
  switch (raw.__variant__) {
173
194
  case "Active":
@@ -175,8 +196,7 @@ function parseStorageProviderState(raw) {
175
196
  variant: "Active",
176
197
  quota: raw.quota.value,
177
198
  stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
178
- faulty: raw.faulty,
179
- leaving: raw.leaving
199
+ condition: parseActiveProviderCondition(raw.condition)
180
200
  };
181
201
  case "Waitlisted":
182
202
  return {
@@ -189,6 +209,10 @@ function parseStorageProviderState(raw) {
189
209
  frozenFrom: raw.frozen_from,
190
210
  frozenTill: raw.frozen_till
191
211
  };
212
+ default:
213
+ throw new Error(
214
+ `Unknown StorageProviderStateDetails variant: ${JSON.stringify(raw)}`
215
+ );
192
216
  }
193
217
  }
194
218
  var ShelbyMetadataClient = class {
@@ -247,28 +271,52 @@ var ShelbyMetadataClient = class {
247
271
  }
248
272
  }
249
273
  /**
250
- * Retrieves the list of placement group addresses.
274
+ * Retrieves the names of every activated location (region). Locations that are
275
+ * registered but not yet brought online are admin-internal and not listed.
276
+ *
277
+ * @returns The location name list.
251
278
  *
279
+ * @example
280
+ * ```typescript
281
+ * const locations = await client.getLocationNames();
282
+ * ```
283
+ */
284
+ async getLocationNames() {
285
+ const names = await this.aptos.view({
286
+ payload: {
287
+ function: `${this.deployer.toString()}::location::activated_location_names`,
288
+ functionArguments: []
289
+ }
290
+ });
291
+ return names[0];
292
+ }
293
+ /**
294
+ * Retrieves the list of placement group addresses in a location.
295
+ *
296
+ * @param locationName - The location whose placement groups to list.
252
297
  * @returns The placement group address list, or an empty array if none exist.
253
298
  *
254
299
  * @example
255
300
  * ```typescript
256
- * const pgList = await client.getPlacementGroupAddresses();
301
+ * const pgList = await client.getPlacementGroupAddresses("us-east-1");
257
302
  * ```
258
303
  */
259
- async getPlacementGroupAddresses() {
304
+ async getPlacementGroupAddresses(locationName) {
260
305
  try {
261
306
  const pgSizeMetadata = await this.aptos.view({
262
307
  payload: {
263
308
  function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
264
- functionArguments: []
309
+ functionArguments: [locationName]
265
310
  }
266
311
  });
312
+ if (Number(pgSizeMetadata[0]) === 0) {
313
+ return [];
314
+ }
267
315
  const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
268
316
  const addressMetadataArray = await this.aptos.view({
269
317
  payload: {
270
318
  function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
271
- functionArguments: [0, finalPlacementGroupIndex]
319
+ functionArguments: [locationName, 0, finalPlacementGroupIndex]
272
320
  }
273
321
  });
274
322
  const metadata = addressMetadataArray[0];
@@ -282,28 +330,32 @@ var ShelbyMetadataClient = class {
282
330
  }
283
331
  }
284
332
  /**
285
- * Retrieves the list of slice addresses.
333
+ * Retrieves the list of slice addresses in a location.
286
334
  *
335
+ * @param locationName - The location whose slices to list.
287
336
  * @returns The slice group list, or an empty array if none exist.
288
337
  *
289
338
  * @example
290
339
  * ```typescript
291
- * const pgList = await client.getSliceAddresses();
340
+ * const pgList = await client.getSliceAddresses("us-east-1");
292
341
  * ```
293
342
  */
294
- async getSliceAddresses() {
343
+ async getSliceAddresses(locationName) {
295
344
  try {
296
345
  const sliceSizeMetadata = await this.aptos.view({
297
346
  payload: {
298
347
  function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
299
- functionArguments: []
348
+ functionArguments: [locationName]
300
349
  }
301
350
  });
351
+ if (Number(sliceSizeMetadata[0]) === 0) {
352
+ return [];
353
+ }
302
354
  const finalSliceIndex = sliceSizeMetadata[0] - 1;
303
355
  const addressMetadataArray = await this.aptos.view({
304
356
  payload: {
305
357
  function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
306
- functionArguments: [0, finalSliceIndex]
358
+ functionArguments: [locationName, 0, finalSliceIndex]
307
359
  }
308
360
  });
309
361
  const metadata = addressMetadataArray[0];
@@ -363,6 +415,36 @@ var ShelbyMetadataClient = class {
363
415
  (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
364
416
  );
365
417
  }
418
+ /**
419
+ * Retrieves the active storage providers for a slice.
420
+ *
421
+ * Active SPs have a complete copy of the data for the slot, and are not in any data transfer/repair/reconstruction phase.
422
+ * Active SP can be audited for the data it contains.
423
+ * Each slot has at most one active SP.
424
+ *
425
+ * @param params.account - The address of the slice account.
426
+ * @returns An array where result[i] is the active SP for slot i, or null if no SP is active.
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const providers = await client.getActiveStorageProvidersForSlice({ account: sliceAddress });
431
+ * ```
432
+ */
433
+ async getActiveStorageProvidersForSlice(params) {
434
+ const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
435
+ params.account
436
+ );
437
+ const rawMetadata = await this.aptos.view({
438
+ payload: {
439
+ function: `${this.deployer.toString()}::placement_group::get_active_storage_providers`,
440
+ functionArguments: [placementGroupAddress]
441
+ }
442
+ });
443
+ const providers = rawMetadata[0];
444
+ return providers.map(
445
+ (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
446
+ );
447
+ }
366
448
  /**
367
449
  * Retrieves the serving storage providers for a slice.
368
450
  *
@@ -438,6 +520,18 @@ var ERASURE_CODE_PARAMS = {
438
520
  enumIndex: 1
439
521
  }
440
522
  };
523
+ function findErasureSchemeByErasureN(erasureN) {
524
+ return Object.entries(ERASURE_CODE_PARAMS).find(
525
+ ([, params]) => params.erasure_n === erasureN
526
+ );
527
+ }
528
+ function requiredAckCount(erasureN) {
529
+ const scheme = findErasureSchemeByErasureN(erasureN);
530
+ if (!scheme) {
531
+ throw new Error(`Unknown erasure coding scheme with erasure_n=${erasureN}`);
532
+ }
533
+ return scheme[1].erasure_d;
534
+ }
441
535
  var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
442
536
  var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
443
537
  var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
@@ -734,6 +828,35 @@ function erasureCodingConfig(encodingScheme) {
734
828
  import { createWasmReedSolomonBinding } from "@shelby-protocol/reed-solomon";
735
829
  var DEFAULT_CHUNK_SIZE_BYTES2 = 2 * 1024 * 1024;
736
830
 
831
+ // src/core/errors.ts
832
+ var ShelbyLocationErrorCodes = {
833
+ E_LOCATION_NOT_FOUND: "E_LOCATION_NOT_FOUND",
834
+ E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK: "E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK",
835
+ E_NO_LOCATION_SELECTED: "E_NO_LOCATION_SELECTED",
836
+ E_LOCATION_WRITES_FROZEN: "E_LOCATION_WRITES_FROZEN",
837
+ E_LOCATION_NOT_ACTIVATED: "E_LOCATION_NOT_ACTIVATED"
838
+ };
839
+ function describeLocationError(errorMessage) {
840
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_WRITES_FROZEN)) {
841
+ return "Cannot write: the resolved location is frozen and not currently accepting new data.";
842
+ }
843
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_ACTIVATED)) {
844
+ return "Cannot write: the resolved location is not activated yet.";
845
+ }
846
+ if (errorMessage.includes(
847
+ ShelbyLocationErrorCodes.E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK
848
+ )) {
849
+ return "Cannot write: the selected location conflicts with the account's locked location preference.";
850
+ }
851
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_NO_LOCATION_SELECTED)) {
852
+ return "No write location could be resolved: none was selected and the account has no default location or location hint.";
853
+ }
854
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_FOUND)) {
855
+ return "Cannot write: the requested location does not exist.";
856
+ }
857
+ return void 0;
858
+ }
859
+
737
860
  // src/core/clients/ShelbyBlobClient.ts
738
861
  import {
739
862
  AccountAddress as AccountAddress3,
@@ -742,6 +865,27 @@ import {
742
865
  U32
743
866
  } from "@aptos-labs/ts-sdk";
744
867
 
868
+ // src/core/types/blobs.ts
869
+ var BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX = {
870
+ Unencrypted: 0,
871
+ AES_GCM_V1: 1
872
+ };
873
+ function blobEncryptionToMoveEnumIndex(encryption) {
874
+ return BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX[encryption];
875
+ }
876
+ function blobEncryptionFromMoveVariant(variant) {
877
+ switch (variant) {
878
+ case "Unencrypted":
879
+ return "Unencrypted";
880
+ case "AES_GCM_V1":
881
+ return "AES_GCM_V1";
882
+ default:
883
+ throw new Error(
884
+ "Could not parse encryption from Shelby Smart Contract, this SDK is out of date."
885
+ );
886
+ }
887
+ }
888
+
745
889
  // src/core/operations/index.ts
746
890
  import { Network as Network2 } from "@aptos-labs/ts-sdk";
747
891
  import { GraphQLClient } from "graphql-request";
@@ -751,18 +895,20 @@ import gql from "graphql-tag";
751
895
  var GetBlobsDocument = gql`
752
896
  query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
753
897
  blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
898
+ uid
899
+ object_name
754
900
  owner
755
901
  blob_commitment
756
- blob_name
757
902
  created_at
758
903
  expires_at
904
+ updated_at
759
905
  num_chunksets
760
- is_deleted
761
- is_written
762
- placement_group
763
906
  size
764
- updated_at
765
907
  slice_address
908
+ placement_group
909
+ is_persisted
910
+ is_committed
911
+ is_deleted
766
912
  }
767
913
  }
768
914
  `;
@@ -774,7 +920,8 @@ var GetBlobActivitiesDocument = gql`
774
920
  limit: $limit
775
921
  offset: $offset
776
922
  ) {
777
- blob_name
923
+ uid
924
+ object_name
778
925
  event_index
779
926
  event_type
780
927
  transaction_hash
@@ -920,6 +1067,22 @@ var MissingTransactionSubmitterError = class extends Error {
920
1067
  this.name = "MissingTransactionSubmitterError";
921
1068
  }
922
1069
  };
1070
+ var COMMIT_REJECTION_REASONS = /* @__PURE__ */ new Set([
1071
+ "AlreadyExists",
1072
+ "NoPriorVersion",
1073
+ "EtagMismatch"
1074
+ ]);
1075
+ function encryptionFunctionArgs(params) {
1076
+ if (params.omitEncryptionArg) {
1077
+ if (params.encryption && params.encryption !== "Unencrypted") {
1078
+ throw new Error(
1079
+ `Blob encryption (${params.encryption}) is not supported on this network: the deployed contract predates the encryption upgrade (#1739).`
1080
+ );
1081
+ }
1082
+ return [];
1083
+ }
1084
+ return [blobEncryptionToMoveEnumIndex(params.encryption ?? "Unencrypted")];
1085
+ }
923
1086
  var ShelbyBlobClient = class _ShelbyBlobClient {
924
1087
  aptos;
925
1088
  deployer;
@@ -968,7 +1131,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
968
1131
  this.aptos = new Aptos2(getAptosConfig(config));
969
1132
  this.deployer = config.deployer ?? AccountAddress3.fromString(SHELBY_DEPLOYER);
970
1133
  this.indexer = getShelbyIndexerClient(config);
971
- this.defaultOptions = defaultOptions ?? {};
1134
+ this.defaultOptions = {
1135
+ locationHint: config.locationHint,
1136
+ ...defaultOptions
1137
+ };
972
1138
  this.orderless = config.orderless ?? false;
973
1139
  }
974
1140
  /**
@@ -979,7 +1145,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
979
1145
  build: options?.build ?? this.defaultOptions.build,
980
1146
  submit: options?.submit ?? this.defaultOptions.submit,
981
1147
  usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
982
- chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
1148
+ chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes,
1149
+ selectedLocation: options?.selectedLocation ?? this.defaultOptions.selectedLocation,
1150
+ locationHint: options?.locationHint ?? this.defaultOptions.locationHint
983
1151
  };
984
1152
  }
985
1153
  /**
@@ -1021,17 +1189,17 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1021
1189
  *
1022
1190
  * @example
1023
1191
  * ```typescript
1024
- * const metadata = await client.getBlobMetadata({
1192
+ * const metadata = await client.getFullObjectMetadata({
1025
1193
  * account: AccountAddress.fromString("0x1"),
1026
1194
  * name: "foo/bar.txt",
1027
1195
  * });
1028
1196
  * ```
1029
1197
  */
1030
- async getBlobMetadata(params) {
1198
+ async getFullObjectMetadata(params) {
1031
1199
  try {
1032
1200
  const rawMetadata = await this.aptos.view({
1033
1201
  payload: {
1034
- function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
1202
+ function: `${this.deployer.toString()}::blob_metadata::get_full_object_metadata`,
1035
1203
  functionArguments: [
1036
1204
  createBlobKey({
1037
1205
  account: params.account,
@@ -1043,47 +1211,94 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1043
1211
  if (!rawMetadata?.[0]?.vec?.[0]) {
1044
1212
  return void 0;
1045
1213
  }
1046
- const metadata = rawMetadata[0].vec[0];
1047
- let encoding;
1048
- if (metadata.encoding.__variant__ === "ClayCode_16Total_10Data_13Helper") {
1049
- encoding = {
1050
- variant: "clay",
1051
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
1052
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
1053
- };
1054
- } else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
1055
- encoding = {
1056
- variant: "clay",
1057
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
1058
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
1059
- };
1060
- } else {
1061
- throw new Error(
1062
- "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
1063
- );
1064
- }
1065
- return {
1066
- blobMerkleRoot: Hex4.fromHexInput(
1067
- metadata.blob_commitment
1068
- ).toUint8Array(),
1069
- owner: normalizeAddress(metadata.owner),
1214
+ const view = rawMetadata[0].vec[0];
1215
+ return this.parseBlobMetadata(view.blob_metadata, {
1216
+ uid: BigInt(view.object_metadata.current_blob_uid),
1070
1217
  name: params.name,
1071
- blobNameSuffix: getBlobNameSuffix(params.name),
1072
- size: Number(metadata.blob_size),
1073
- encoding,
1074
- expirationMicros: Number(metadata.expiration_micros),
1075
- creationMicros: Number(metadata.creation_micros),
1076
- sliceAddress: normalizeAddress(metadata.slice.inner),
1077
- isWritten: metadata.is_written
1078
- };
1218
+ // Any blob bound under an object name has cleared the commit-time
1219
+ // `is_written()` check, so a resolved object is always written.
1220
+ isWritten: true
1221
+ });
1079
1222
  } catch (error) {
1080
1223
  if (error instanceof Error && // Depending on the network, the error message may show up differently.
1081
- (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
1224
+ (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND") || error.message?.includes("EOBJECT_NOT_FOUND"))) {
1225
+ return void 0;
1226
+ }
1227
+ throw error;
1228
+ }
1229
+ }
1230
+ /**
1231
+ * Retrieves blob metadata directly by its on-chain UID, including blobs in
1232
+ * the pending (registered-but-not-yet-committed) state that
1233
+ * {@link getFullObjectMetadata} cannot resolve by object name. Returns `undefined`
1234
+ * if no blob has that UID.
1235
+ *
1236
+ * The returned `name`/`blobNameSuffix` are empty: the blob layer is keyed by
1237
+ * UID and carries no object name (a name binding is established only at
1238
+ * commit). `isWritten` reflects whether the blob has been committed.
1239
+ */
1240
+ async getFullObjectMetadataByUid(uid) {
1241
+ try {
1242
+ const rawMetadata = await this.aptos.view(
1243
+ {
1244
+ payload: {
1245
+ function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
1246
+ functionArguments: [uid]
1247
+ }
1248
+ }
1249
+ );
1250
+ if (!rawMetadata?.[0]?.vec?.[0]) {
1251
+ return void 0;
1252
+ }
1253
+ const raw = rawMetadata[0].vec[0];
1254
+ return this.parseBlobMetadata(raw, {
1255
+ uid,
1256
+ name: "",
1257
+ isWritten: raw.state.__variant__ === "CommittedObject"
1258
+ });
1259
+ } catch (error) {
1260
+ if (error instanceof Error && (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
1082
1261
  return void 0;
1083
1262
  }
1084
1263
  throw error;
1085
1264
  }
1086
1265
  }
1266
+ /**
1267
+ * Parse the on-chain `BlobMetadata::V1` view shape into the SDK
1268
+ * {@link FullObjectMetadata}. `uid` / `name` / `isWritten` are supplied by the
1269
+ * caller since they depend on the lookup path (by object name vs by UID).
1270
+ */
1271
+ parseBlobMetadata(raw, extra) {
1272
+ const { content } = raw;
1273
+ const variant = content.encoding.__variant__;
1274
+ if (variant !== "ClayCode_16Total_10Data_13Helper" && variant !== "ClayCode_4Total_2Data_3Helper") {
1275
+ throw new Error(
1276
+ "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
1277
+ );
1278
+ }
1279
+ const encoding = {
1280
+ variant: "clay",
1281
+ ...ERASURE_CODE_PARAMS[variant],
1282
+ ...ERASURE_CODE_AND_CHUNK_MAPPING[variant]
1283
+ };
1284
+ const encryption = blobEncryptionFromMoveVariant(
1285
+ content.encryption?.__variant__ ?? "Unencrypted"
1286
+ );
1287
+ return {
1288
+ uid: extra.uid,
1289
+ blobMerkleRoot: Hex4.fromHexInput(content.blob_commitment).toUint8Array(),
1290
+ owner: normalizeAddress(raw.owner),
1291
+ name: extra.name,
1292
+ blobNameSuffix: extra.name ? getBlobNameSuffix(extra.name) : "",
1293
+ size: Number(content.blob_size),
1294
+ encoding,
1295
+ encryption,
1296
+ expirationMicros: Number(raw.expiration_micros),
1297
+ creationMicros: Number(raw.creation_micros),
1298
+ sliceAddress: normalizeAddress(raw.slice.inner),
1299
+ isWritten: extra.isWritten
1300
+ };
1301
+ }
1087
1302
  /**
1088
1303
  * Retrieves all the blobs and their metadata for an account from the
1089
1304
  * blockchain.
@@ -1095,7 +1310,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1095
1310
  *
1096
1311
  * @example
1097
1312
  * ```typescript
1098
- * // BlobMetadata[]
1313
+ * // FullObjectMetadata[]
1099
1314
  * const blobs = await client.getAccountBlobs({
1100
1315
  * account: AccountAddress.fromString("0x1"),
1101
1316
  * });
@@ -1112,6 +1327,25 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1112
1327
  orderBy: rest.orderBy
1113
1328
  });
1114
1329
  }
1330
+ /**
1331
+ * Object-facing default filter: only committed, non-deleted, unexpired rows.
1332
+ * The blobs table is UID-keyed, so during an atomic overwrite a single
1333
+ * object_name transiently has two non-deleted rows — the currently committed
1334
+ * blob and the new pending (is_committed = "0") blob. Filtering on
1335
+ * is_committed keeps name lookups/listings pinned to the committed object and
1336
+ * avoids returning or duplicating the in-flight pending row. Applied to
1337
+ * getBlobs *and* the getBlobsCount / getTotalBlobsSize aggregates so counts
1338
+ * and sizes agree with the listing mid-overwrite. Callers can override any
1339
+ * key (e.g. is_committed) via their own `where`.
1340
+ */
1341
+ activeBlobsWhere(where) {
1342
+ const defaultActiveFilter = {
1343
+ expires_at: { _gte: String(Date.now() * 1e3) },
1344
+ is_deleted: { _eq: "0" },
1345
+ is_committed: { _eq: "1" }
1346
+ };
1347
+ return { ...defaultActiveFilter, ...where };
1348
+ }
1115
1349
  /**
1116
1350
  * Retrieves blobs and their metadata from the blockchain.
1117
1351
  *
@@ -1122,7 +1356,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1122
1356
  *
1123
1357
  * @example
1124
1358
  * ```typescript
1125
- * // BlobMetadata[]
1359
+ * // FullObjectMetadata[]
1126
1360
  * const blobs = await client.getBlobs({
1127
1361
  * where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
1128
1362
  * });
@@ -1131,12 +1365,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1131
1365
  async getBlobs(params = {}) {
1132
1366
  const { limit, offset } = params.pagination ?? {};
1133
1367
  const { orderBy, where } = params;
1134
- const currentMicros = String(Date.now() * 1e3);
1135
- const defaultActiveFilter = {
1136
- expires_at: { _gte: currentMicros },
1137
- is_deleted: { _eq: "0" }
1138
- };
1139
- const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
1368
+ const finalWhere = this.activeBlobsWhere(where);
1140
1369
  const { blobs } = await this.indexer.getBlobs({
1141
1370
  where: finalWhere,
1142
1371
  limit,
@@ -1145,9 +1374,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1145
1374
  });
1146
1375
  return blobs.map(
1147
1376
  (blob) => ({
1377
+ uid: BigInt(blob.uid),
1148
1378
  owner: normalizeAddress(blob.owner),
1149
- name: blob.blob_name,
1150
- blobNameSuffix: getBlobNameSuffix(blob.blob_name),
1379
+ name: blob.object_name,
1380
+ blobNameSuffix: getBlobNameSuffix(blob.object_name),
1151
1381
  blobMerkleRoot: Hex4.fromHexInput(blob.blob_commitment).toUint8Array(),
1152
1382
  size: Number(blob.size),
1153
1383
  // TODO: Add encoding when supported in NCI
@@ -1159,7 +1389,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1159
1389
  expirationMicros: Number(blob.expires_at),
1160
1390
  creationMicros: Number(blob.created_at),
1161
1391
  sliceAddress: normalizeAddress(blob.slice_address),
1162
- isWritten: Boolean(Number(blob.is_written)),
1392
+ isWritten: Boolean(Number(blob.is_persisted)),
1163
1393
  isDeleted: Boolean(Number(blob.is_deleted))
1164
1394
  })
1165
1395
  );
@@ -1177,12 +1407,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1177
1407
  [`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
1178
1408
  [`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
1179
1409
  [`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
1180
- [`${this.deployer.toStringLong()}::blob_metadata::BlobWrittenEvent`]: "write_blob"
1410
+ [`${this.deployer.toStringLong()}::blob_metadata::BlobPersistedEvent`]: "write_blob",
1411
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommittedEvent`]: "commit_object",
1412
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectDeletedEvent`]: "delete_object",
1413
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`]: "reject_object_commit"
1181
1414
  };
1182
1415
  return blob_activities.map(
1183
1416
  (activity) => ({
1184
- blobName: activity.blob_name,
1185
- accountAddress: normalizeAddress(activity.blob_name.substring(1, 65)),
1417
+ blobName: activity.object_name,
1418
+ accountAddress: normalizeAddress(activity.object_name.substring(1, 65)),
1186
1419
  type: activityTypeMapping[activity.event_type] ?? "unknown",
1187
1420
  eventType: activity.event_type,
1188
1421
  eventIndex: Number(activity.event_index),
@@ -1205,9 +1438,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1205
1438
  * const count = await client.getBlobsCount();
1206
1439
  * ```
1207
1440
  */
1208
- async getBlobsCount(params) {
1209
- const { where } = params;
1210
- const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
1441
+ async getBlobsCount(params = {}) {
1442
+ const { blobs_aggregate } = await this.indexer.getBlobsCount({
1443
+ where: this.activeBlobsWhere(params.where)
1444
+ });
1211
1445
  return blobs_aggregate?.aggregate?.count ?? 0;
1212
1446
  }
1213
1447
  /**
@@ -1222,8 +1456,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1222
1456
  * ```
1223
1457
  */
1224
1458
  async getTotalBlobsSize(params = {}) {
1225
- const { where } = params;
1226
- const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({ where });
1459
+ const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({
1460
+ where: this.activeBlobsWhere(params.where)
1461
+ });
1227
1462
  return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
1228
1463
  }
1229
1464
  /**
@@ -1282,12 +1517,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1282
1517
  deployer: this.deployer,
1283
1518
  account: params.account.accountAddress,
1284
1519
  blobName: params.blobName,
1520
+ selectedLocation: options.selectedLocation,
1521
+ locationHint: options.locationHint,
1285
1522
  blobSize: params.size,
1286
1523
  blobMerkleRoot: params.blobMerkleRoot,
1287
1524
  numChunksets: expectedTotalChunksets(params.size, chunksetSize),
1288
1525
  expirationMicros: params.expirationMicros,
1289
1526
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1290
- encoding: config.enumIndex
1527
+ encoding: config.enumIndex,
1528
+ encryption: params.encryption
1291
1529
  }),
1292
1530
  sender: params.account.accountAddress
1293
1531
  };
@@ -1315,16 +1553,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1315
1553
  * @example
1316
1554
  * ```typescript
1317
1555
  *
1318
- * const { transaction } = await client.deleteBlob({
1556
+ * const { transaction } = await client.deleteObject({
1319
1557
  * account: signer,
1320
1558
  * blobName: "foo/bar.txt",
1321
1559
  * });
1322
1560
  * ```
1323
1561
  */
1324
- async deleteBlob(params) {
1562
+ async deleteObject(params) {
1325
1563
  const transaction = await this.aptos.transaction.build.simple({
1326
1564
  options: this.orderlessTxOptions(params.options),
1327
- data: _ShelbyBlobClient.createDeleteBlobPayload({
1565
+ data: _ShelbyBlobClient.createDeleteObjectPayload({
1328
1566
  deployer: this.deployer,
1329
1567
  blobName: params.blobName
1330
1568
  }),
@@ -1340,10 +1578,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1340
1578
  /**
1341
1579
  * Deletes multiple blobs on the blockchain in a single atomic transaction.
1342
1580
  *
1343
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1344
- * which will be deployed to the smart contract on 2026-02-04. Using this
1345
- * function before that date will result in a transaction failure.
1346
- *
1347
1581
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1348
1582
  * the entire transaction fails and no blobs are deleted.
1349
1583
  *
@@ -1358,16 +1592,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1358
1592
  * @example
1359
1593
  * ```typescript
1360
1594
  *
1361
- * const { transaction } = await client.deleteMultipleBlobs({
1595
+ * const { transaction } = await client.deleteMultipleObjects({
1362
1596
  * account: signer,
1363
1597
  * blobNames: ["foo/bar.txt", "baz.txt"],
1364
1598
  * });
1365
1599
  * ```
1366
1600
  */
1367
- async deleteMultipleBlobs(params) {
1601
+ async deleteMultipleObjects(params) {
1368
1602
  const transaction = await this.aptos.transaction.build.simple({
1369
1603
  options: this.orderlessTxOptions(params.options),
1370
- data: _ShelbyBlobClient.createDeleteMultipleBlobsPayload({
1604
+ data: _ShelbyBlobClient.createDeleteMultipleObjectsPayload({
1371
1605
  deployer: this.deployer,
1372
1606
  blobNames: params.blobNames
1373
1607
  }),
@@ -1425,6 +1659,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1425
1659
  data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
1426
1660
  deployer: this.deployer,
1427
1661
  account: params.account.accountAddress,
1662
+ selectedLocation: options.selectedLocation,
1663
+ locationHint: options.locationHint,
1428
1664
  expirationMicros: params.expirationMicros,
1429
1665
  blobs: params.blobs.map((blob) => ({
1430
1666
  blobName: blob.blobName,
@@ -1433,7 +1669,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1433
1669
  numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
1434
1670
  })),
1435
1671
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1436
- encoding: config.enumIndex
1672
+ encoding: config.enumIndex,
1673
+ encryption: params.encryption
1437
1674
  })
1438
1675
  };
1439
1676
  const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
@@ -1448,6 +1685,55 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1448
1685
  })
1449
1686
  };
1450
1687
  }
1688
+ /**
1689
+ * Extracts the on-chain UIDs assigned at registration from a committed
1690
+ * register transaction's events.
1691
+ *
1692
+ * `register_blob` / `register_multiple_blobs` create *pending* blobs and emit
1693
+ * one `BlobRegisteredEvent` per blob. The UID is published only on this event
1694
+ * (the blob is not yet in the `objects` map, so it cannot be read back by
1695
+ * name), so callers must parse it here before uploading bytes or committing.
1696
+ *
1697
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1698
+ * @param deployer - The contract deployer address.
1699
+ * @returns One entry per registered blob, keyed by its full object name
1700
+ * (`@<owner>/<suffix>`, matching {@link createBlobKey}).
1701
+ */
1702
+ static registeredBlobUids(events, deployer) {
1703
+ const eventType = `${deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`;
1704
+ return events.filter((event) => event.type === eventType).map((event) => {
1705
+ const data = event.data;
1706
+ return { objectName: data.object_name, uid: BigInt(data.uid) };
1707
+ });
1708
+ }
1709
+ /**
1710
+ * Detects whether `commit_object` rejected the write for `uid` rather than
1711
+ * applying it. A rejected commit is still a *successful* transaction — the
1712
+ * contract tears down the pending blob and emits `ObjectCommitRejectedEvent`
1713
+ * instead of aborting — so callers must inspect the finalized transaction's
1714
+ * events to tell a durable write apart from a silent no-op.
1715
+ *
1716
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1717
+ * @param deployer - The contract deployer address.
1718
+ * @param uid - The UID passed to `commit_object`.
1719
+ * @returns The rejection reason, or `undefined` if the commit was applied.
1720
+ */
1721
+ static findObjectCommitRejection(events, deployer, uid) {
1722
+ const eventType = `${deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`;
1723
+ const rejection = events.find(
1724
+ (event) => event.type === eventType && BigInt(event.data.uid) === uid
1725
+ );
1726
+ if (!rejection) {
1727
+ return void 0;
1728
+ }
1729
+ const reason = rejection.data.rejection_reason.__variant__;
1730
+ if (!COMMIT_REJECTION_REASONS.has(reason)) {
1731
+ throw new Error(
1732
+ `Unrecognized ObjectCommitRejectedEvent rejection_reason '${reason}' for uid ${uid}`
1733
+ );
1734
+ }
1735
+ return reason;
1736
+ }
1451
1737
  /**
1452
1738
  * Creates a transaction payload to register a blob on the blockchain.
1453
1739
  * This is a static helper method for constructing the Move function call payload.
@@ -1461,8 +1747,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1461
1747
  * @param params.numChunksets - The total number of chunksets in the blob.
1462
1748
  *
1463
1749
  * @returns An Aptos transaction payload data object for the register_blob Move function.
1464
- *
1465
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1466
1750
  */
1467
1751
  static createRegisterBlobPayload(params) {
1468
1752
  const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
@@ -1470,6 +1754,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1470
1754
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1471
1755
  functionArguments: [
1472
1756
  params.blobName,
1757
+ params.selectedLocation ?? null,
1758
+ params.locationHint ?? null,
1473
1759
  params.expirationMicros,
1474
1760
  Hex4.fromHexString(params.blobMerkleRoot).toUint8Array(),
1475
1761
  params.numChunksets,
@@ -1477,7 +1763,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1477
1763
  // TODO
1478
1764
  0,
1479
1765
  // payment tier
1480
- params.encoding
1766
+ params.encoding,
1767
+ ...encryptionFunctionArgs(params)
1481
1768
  ]
1482
1769
  };
1483
1770
  }
@@ -1495,8 +1782,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1495
1782
  * @param params.blobs.numChunksets - The total number of chunksets in the blob.
1496
1783
  *
1497
1784
  * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
1498
- *
1499
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1500
1785
  */
1501
1786
  static createBatchRegisterBlobsPayload(params) {
1502
1787
  const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
@@ -1516,6 +1801,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1516
1801
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1517
1802
  functionArguments: [
1518
1803
  blobNames,
1804
+ params.selectedLocation ?? null,
1805
+ params.locationHint ?? null,
1519
1806
  params.expirationMicros,
1520
1807
  blobMerkleRoots,
1521
1808
  blobNumChunksets,
@@ -1523,7 +1810,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1523
1810
  // TODO
1524
1811
  0,
1525
1812
  // payment tier
1526
- params.encoding
1813
+ params.encoding,
1814
+ ...encryptionFunctionArgs(params)
1527
1815
  ]
1528
1816
  };
1529
1817
  }
@@ -1534,24 +1822,20 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1534
1822
  * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
1535
1823
  * @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
1536
1824
  *
1537
- * @returns An Aptos transaction payload data object for the delete_blob Move function.
1538
- *
1539
- * @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
1825
+ * @returns An Aptos transaction payload data object for the delete_object Move function.
1540
1826
  */
1541
- static createDeleteBlobPayload(params) {
1827
+ static createDeleteObjectPayload(params) {
1542
1828
  return {
1543
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_blob`,
1544
- functionArguments: [params.blobName]
1829
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_object`,
1830
+ // Second arg is `if_match_etag: Option<vector<u8>>`; `null` => None
1831
+ // (unconditional delete).
1832
+ functionArguments: [params.blobName, null]
1545
1833
  };
1546
1834
  }
1547
1835
  /**
1548
1836
  * Creates a transaction payload to delete multiple blobs on the blockchain.
1549
1837
  * This is a static helper method for constructing the Move function call payload.
1550
1838
  *
1551
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1552
- * which will be deployed to the smart contract on 2026-02-04. Using this
1553
- * function before that date will result in a transaction failure.
1554
- *
1555
1839
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1556
1840
  * the entire transaction fails and no blobs are deleted.
1557
1841
  *
@@ -1560,42 +1844,71 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1560
1844
  * (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
1561
1845
  * prefix is automatically derived from the transaction sender.
1562
1846
  *
1563
- * @returns An Aptos transaction payload data object for the delete_multiple_blobs Move function.
1564
- *
1565
- * @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
1847
+ * @returns An Aptos transaction payload data object for the delete_multiple_objects Move function.
1566
1848
  */
1567
- static createDeleteMultipleBlobsPayload(params) {
1849
+ static createDeleteMultipleObjectsPayload(params) {
1568
1850
  return {
1569
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_blobs`,
1570
- functionArguments: [params.blobNames]
1851
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_objects`,
1852
+ // Second arg is `if_match_etags: vector<Option<vector<u8>>>`; an empty
1853
+ // vector means "no etag check on any object" (unconditional delete).
1854
+ functionArguments: [params.blobNames, []]
1571
1855
  };
1572
1856
  }
1573
- static createBlobAcknowledgementsPayload(params) {
1574
- const ackBitMask = params.storageProviderAcks.reduce(
1857
+ /**
1858
+ * Sort acks by slot and reduce to the `(ack_bits, signatures)` pair the
1859
+ * contract expects: it walks the set bits low-to-high and consumes the
1860
+ * signatures in that same order.
1861
+ */
1862
+ static encodeAcks(storageProviderAcks) {
1863
+ const sortedAcks = [...storageProviderAcks].sort((a, b) => a.slot - b.slot);
1864
+ const ackBitMask = sortedAcks.reduce(
1575
1865
  (acc, ack) => acc | 1 << ack.slot,
1576
1866
  0
1577
1867
  );
1578
1868
  return {
1579
- function: `${SHELBY_DEPLOYER}::blob_metadata::add_blob_acknowledgements`,
1869
+ ackBits: new U32(Number(ackBitMask)),
1870
+ signatures: sortedAcks.map((ack) => ack.signature)
1871
+ };
1872
+ }
1873
+ /**
1874
+ * Payload for `commit_object(uid, object_name_suffix, overwrite, if_match_etag,
1875
+ * ack_bits, signatures)` — binds a written pending blob under its object name,
1876
+ * finalizing the upload. SP acks may be batched in here (the contract applies
1877
+ * them before the `is_written` check), so register → upload → commit needs
1878
+ * only a single finalize transaction.
1879
+ *
1880
+ * @param params.uid - The blob UID returned at registration.
1881
+ * @param params.blobName - The object name suffix the blob was registered under.
1882
+ * @param params.overwrite - Allow replacing an existing binding under this name.
1883
+ * @param params.storageProviderAcks - Acks applied atomically with the commit.
1884
+ */
1885
+ static createCommitObjectPayload(params) {
1886
+ const { ackBits, signatures } = _ShelbyBlobClient.encodeAcks(
1887
+ params.storageProviderAcks
1888
+ );
1889
+ return {
1890
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::commit_object`,
1580
1891
  functionArguments: [
1581
- createBlobKey({
1582
- account: params.blobOwner,
1583
- blobName: params.blobName
1584
- }),
1585
- params.creationMicros,
1586
- new U32(Number(ackBitMask)),
1587
- params.storageProviderAcks.map((ack) => ack.signature)
1892
+ params.uid,
1893
+ params.blobName,
1894
+ params.overwrite,
1895
+ // `if_match_etag: Option<vector<u8>>` is `null` => None; conditional
1896
+ // (CAS) writes are not yet exposed by the SDK.
1897
+ null,
1898
+ ackBits,
1899
+ signatures
1588
1900
  ]
1589
1901
  };
1590
1902
  }
1591
- async addBlobAcknowledgements(params) {
1903
+ async commitObject(params) {
1592
1904
  const transaction = await this.aptos.transaction.build.simple({
1593
1905
  ...params.options?.build,
1594
1906
  options: this.orderlessTxOptions(params.options?.build?.options),
1595
- data: _ShelbyBlobClient.createBlobAcknowledgementsPayload({
1596
- blobOwner: params.blobOwner,
1907
+ data: _ShelbyBlobClient.createCommitObjectPayload({
1908
+ deployer: this.deployer,
1909
+ uid: params.uid,
1597
1910
  blobName: params.blobName,
1598
- creationMicros: params.creationMicros,
1911
+ overwrite: params.overwrite,
1599
1912
  storageProviderAcks: params.storageProviderAcks
1600
1913
  }),
1601
1914
  sender: params.account.accountAddress
@@ -1634,22 +1947,6 @@ var ChallengeResponseSchema = z3.object({
1634
1947
  challenge: z3.string(),
1635
1948
  expiresAt: z3.number()
1636
1949
  });
1637
- var MultipartUploadStatusResponseSchema = z3.object({
1638
- uploadId: z3.string(),
1639
- completedParts: z3.array(z3.number()),
1640
- partSize: z3.number(),
1641
- nParts: z3.number(),
1642
- uploadedBytes: z3.number()
1643
- });
1644
- var StartMultipartUploadResponseSchema = z3.object({
1645
- uploadId: z3.string()
1646
- });
1647
- var UploadPartResponseSchema = z3.object({
1648
- success: z3.literal(true)
1649
- });
1650
- var CompleteMultipartUploadResponseSchema = z3.object({
1651
- success: z3.literal(true)
1652
- });
1653
1950
  var RPCErrorResponseSchema = z3.object({
1654
1951
  error: z3.string()
1655
1952
  });
@@ -1866,6 +2163,7 @@ var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
1866
2163
  var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
1867
2164
  var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
1868
2165
  var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
2166
+ var INCLUSION_PROOF_HEADER = "X-Shelby-Inclusion-Proof";
1869
2167
  function buildAuthHeaders(auth) {
1870
2168
  const signatureBase64 = btoa(
1871
2169
  Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
@@ -1889,6 +2187,58 @@ function buildAuthHeaders(auth) {
1889
2187
  function encodeURIComponentKeepSlashes(str) {
1890
2188
  return encodeURIComponent(str).replace(/%2F/g, "/");
1891
2189
  }
2190
+ async function generateChunksetInclusionProof(chunksetRoots, chunksetIndex) {
2191
+ if (chunksetRoots.length === 0) {
2192
+ throw new Error("Cannot generate inclusion proof for empty chunkset roots");
2193
+ }
2194
+ if (chunksetIndex < 0 || chunksetIndex >= chunksetRoots.length) {
2195
+ throw new Error(
2196
+ `Chunkset index ${chunksetIndex} out of range [0, ${chunksetRoots.length})`
2197
+ );
2198
+ }
2199
+ if (chunksetRoots.length === 1) {
2200
+ return [];
2201
+ }
2202
+ const zeroHash = new Uint8Array(32);
2203
+ const siblings = [];
2204
+ let currentLeaves = chunksetRoots.map((h) => h.toUint8Array());
2205
+ let currentIdx = chunksetIndex;
2206
+ while (currentLeaves.length > 1) {
2207
+ if (currentLeaves.length % 2 !== 0) {
2208
+ currentLeaves.push(zeroHash);
2209
+ }
2210
+ const siblingIdx = currentIdx % 2 === 0 ? currentIdx + 1 : currentIdx - 1;
2211
+ siblings.push(currentLeaves[siblingIdx]);
2212
+ const nextLeaves = [];
2213
+ for (let i = 0; i < currentLeaves.length; i += 2) {
2214
+ const combined = await concatHashes([
2215
+ currentLeaves[i],
2216
+ currentLeaves[i + 1]
2217
+ ]);
2218
+ nextLeaves.push(combined.toUint8Array());
2219
+ }
2220
+ currentLeaves = nextLeaves;
2221
+ currentIdx = Math.floor(currentIdx / 2);
2222
+ }
2223
+ return siblings;
2224
+ }
2225
+ function encodeInclusionProof(siblings) {
2226
+ if (siblings.length === 0) {
2227
+ return "NONE";
2228
+ }
2229
+ const totalBytes = siblings.length * 32;
2230
+ const combined = new Uint8Array(totalBytes);
2231
+ let offset = 0;
2232
+ for (const sibling of siblings) {
2233
+ combined.set(sibling, offset);
2234
+ offset += 32;
2235
+ }
2236
+ const binaryString = Array.from(
2237
+ combined,
2238
+ (byte) => String.fromCharCode(byte)
2239
+ ).join("");
2240
+ return btoa(binaryString);
2241
+ }
1892
2242
  function validateTotalBytes(totalBytes) {
1893
2243
  if (!Number.isInteger(totalBytes) || totalBytes < 0) {
1894
2244
  throw new Error("totalBytes must be a non-negative integer");
@@ -1920,7 +2270,7 @@ var ShelbyRPCClient = class {
1920
2270
  * @param config - The client configuration object.
1921
2271
  * @param config.network - The Shelby network to use.
1922
2272
  * @param options.signChallengeHandler - Optional override for challenge
1923
- * signing. When set, `putBlobResumable` uses this instead of the built-in
2273
+ * signing. When set, `putBlobChunksets` uses this instead of the built-in
1924
2274
  * `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
1925
2275
  *
1926
2276
  * @example
@@ -1971,43 +2321,6 @@ var ShelbyRPCClient = class {
1971
2321
  }
1972
2322
  return ChallengeResponseSchema.parse(await response.json());
1973
2323
  }
1974
- /**
1975
- * Check if there's an existing multipart upload for a blob.
1976
- * Returns the upload status including which parts have been uploaded.
1977
- *
1978
- * @param account - The account that owns the blob.
1979
- * @param blobName - The name of the blob.
1980
- * @returns The upload status, or undefined if no pending upload exists.
1981
- *
1982
- * @example
1983
- * ```typescript
1984
- * const status = await client.getMultipartUploadStatus(account, "myblob.txt");
1985
- * if (status) {
1986
- * console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
1987
- * }
1988
- * ```
1989
- */
1990
- async getMultipartUploadStatus(account, blobName) {
1991
- const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
1992
- url.searchParams.set("account", account.toString());
1993
- url.searchParams.set("blobName", blobName);
1994
- const response = await fetch(url.toString(), {
1995
- method: "GET",
1996
- headers: {
1997
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
1998
- }
1999
- });
2000
- if (response.status === 404) {
2001
- return void 0;
2002
- }
2003
- if (!response.ok) {
2004
- const errorBody = await response.text().catch(() => "");
2005
- throw new Error(
2006
- `Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
2007
- );
2008
- }
2009
- return MultipartUploadStatusResponseSchema.parse(await response.json());
2010
- }
2011
2324
  /**
2012
2325
  * Sign a challenge using the given account and return auth credentials.
2013
2326
  *
@@ -2024,269 +2337,142 @@ var ShelbyRPCClient = class {
2024
2337
  publicKey: account.publicKey.toUint8Array()
2025
2338
  };
2026
2339
  }
2027
- async #uploadPart(uploadId, partIdx, partData) {
2028
- const nRetries = 5;
2029
- let lastResponse;
2030
- let lastError;
2031
- let attempts = 0;
2032
- const partUrl = buildRequestUrl(
2033
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2034
- this.baseUrl
2035
- );
2036
- for (let i = 0; i < nRetries; ++i) {
2037
- attempts++;
2038
- try {
2039
- lastResponse = await fetch(partUrl, {
2040
- method: "PUT",
2041
- headers: {
2042
- "Content-Type": "application/octet-stream",
2043
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2044
- },
2045
- body: partData
2046
- });
2047
- lastError = void 0;
2048
- } catch (error) {
2049
- lastError = error;
2050
- if (i < nRetries - 1) {
2051
- const delay = 2 ** i * 100;
2052
- await sleep(delay);
2053
- continue;
2054
- }
2055
- break;
2056
- }
2057
- if (lastResponse.ok) return;
2058
- if (!isRetryableStatus(lastResponse.status)) {
2059
- break;
2060
- }
2061
- if (i < nRetries - 1) {
2062
- const delay = 2 ** i * 100;
2063
- await sleep(delay);
2064
- }
2065
- }
2066
- if (lastError !== void 0) {
2067
- const errorCode = getErrorCode(lastError);
2068
- const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2069
- throw new Error(
2070
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. The connection to the Shelby RPC endpoint was interrupted while sending data${errorCode ? ` (${errorCode})` : ""}. Endpoint: ${partUrl.toString()}, partBytes: ${partData.length}. Last error: ${errorMessage}`,
2071
- { cause: lastError }
2072
- );
2073
- }
2074
- const errorBody = await lastResponse?.text().catch(() => "");
2075
- throw new Error(
2076
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2077
- );
2078
- }
2079
- async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
2080
- validateTotalBytes(totalBytes);
2081
- const startResponse = await fetch(
2082
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2083
- {
2084
- method: "POST",
2085
- headers: {
2086
- "Content-Type": "application/json",
2087
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2088
- },
2089
- body: JSON.stringify({
2090
- rawAccount: account.toString(),
2091
- rawBlobName: blobName,
2092
- rawPartSize: partSize
2093
- })
2094
- }
2095
- );
2096
- if (!startResponse.ok) {
2097
- let errorBodyText = "Could not read error body";
2098
- try {
2099
- errorBodyText = await startResponse.text();
2100
- } catch (_e) {
2101
- }
2102
- throw new Error(
2103
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2104
- );
2105
- }
2106
- const { uploadId } = StartMultipartUploadResponseSchema.parse(
2107
- await startResponse.json()
2108
- );
2109
- const totalParts = Math.ceil(totalBytes / partSize);
2110
- let uploadedBytes = 0;
2111
- for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
2112
- await this.#uploadPart(uploadId, partIdx, partData);
2113
- uploadedBytes += partData.length;
2114
- onProgress?.({
2115
- phase: "uploading",
2116
- partIdx,
2117
- totalParts,
2118
- partBytes: partData.length,
2119
- uploadedBytes,
2120
- totalBytes
2121
- });
2122
- }
2123
- if (uploadedBytes !== totalBytes) {
2124
- throw new Error(
2125
- `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
2126
- );
2127
- }
2128
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2129
- onProgress?.({
2130
- phase: "finalizing",
2131
- partIdx: finalPartIdx,
2132
- totalParts,
2133
- // no part uploaded in this phase
2134
- partBytes: 0,
2135
- uploadedBytes: totalBytes,
2136
- totalBytes
2137
- });
2138
- const completeResponse = await fetch(
2139
- buildRequestUrl(
2140
- `/v1/multipart-uploads/${uploadId}/complete`,
2141
- this.baseUrl
2142
- ),
2143
- {
2144
- method: "POST",
2145
- headers: {
2146
- "Content-Type": "application/json",
2147
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2148
- }
2149
- }
2150
- );
2151
- if (!completeResponse.ok) {
2152
- let errorBodyText = "Could not read error body";
2153
- try {
2154
- errorBodyText = await completeResponse.text();
2155
- } catch (_e) {
2156
- }
2157
- throw new Error(
2158
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2159
- );
2160
- }
2161
- }
2162
- /**
2163
- * Uploads blob data to the Shelby RPC node for storage by storage providers.
2164
- * This method should be called after blob commitments have been registered on the blockchain.
2165
- * Uses multipart upload for efficient handling of large files.
2166
- *
2167
- * @param params.account - The account that owns the blob.
2168
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2169
- * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2170
- * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2171
- *
2172
- * @example
2173
- * ```typescript
2174
- * const blobData = new TextEncoder().encode("Hello, world!");
2175
- *
2176
- * await client.putBlob({
2177
- * account: AccountAddress.from("0x1"),
2178
- * blobName: "greetings/hello.txt",
2179
- * blobData,
2180
- * });
2181
- * ```
2182
- */
2183
- async putBlob(params) {
2184
- BlobNameSchema.parse(params.blobName);
2185
- let totalBytes;
2186
- if (params.blobData instanceof Uint8Array) {
2187
- totalBytes = params.totalBytes ?? params.blobData.length;
2188
- if (totalBytes !== params.blobData.length) {
2189
- throw new Error(
2190
- "totalBytes must match blobData.length when blobData is a Uint8Array"
2191
- );
2192
- }
2193
- } else {
2194
- if (params.totalBytes === void 0) {
2195
- throw new Error(
2196
- "totalBytes is required when blobData is a ReadableStream"
2197
- );
2198
- }
2199
- totalBytes = params.totalBytes;
2200
- }
2201
- validateTotalBytes(totalBytes);
2202
- await this.#putBlobMultipart(
2203
- params.account,
2204
- params.blobName,
2205
- params.blobData,
2206
- totalBytes,
2207
- void 0,
2208
- params.onProgress
2209
- );
2210
- }
2211
2340
  /**
2212
- * Upload a part with authentication headers.
2341
+ * Upload a single chunkset via the v2 chunkset API.
2213
2342
  */
2214
- async #uploadPartWithAuth(uploadId, partIdx, partData, auth) {
2215
- const nRetries = 5;
2343
+ async #uploadChunkset(params) {
2344
+ const maxNonBusyRetries = 5;
2216
2345
  let lastResponse;
2217
2346
  let lastError;
2218
2347
  let attempts = 0;
2219
- const partUrl = buildRequestUrl(
2220
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2348
+ let consecutive429s = 0;
2349
+ let nonBusyRetries = 0;
2350
+ const chunksetUrl = buildRequestUrl(
2351
+ `/v2/chunksets/${params.account}/${params.chunksetIndex}/${params.uid}`,
2221
2352
  this.baseUrl
2222
2353
  );
2223
- const authHeaders = buildAuthHeaders(auth);
2224
- for (let i = 0; i < nRetries; ++i) {
2354
+ const authHeaders = buildAuthHeaders(params.auth);
2355
+ while (true) {
2356
+ if (params.signal?.aborted) {
2357
+ throw new DOMException("Upload aborted", "AbortError");
2358
+ }
2225
2359
  attempts++;
2226
2360
  try {
2227
- lastResponse = await fetch(partUrl, {
2361
+ lastResponse = await fetch(chunksetUrl, {
2228
2362
  method: "PUT",
2229
2363
  headers: {
2230
2364
  "Content-Type": "application/octet-stream",
2231
2365
  ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2232
- ...authHeaders
2366
+ ...authHeaders,
2367
+ [INCLUSION_PROOF_HEADER]: params.inclusionProof
2233
2368
  },
2234
- body: partData
2369
+ body: params.chunksetData,
2370
+ signal: params.signal
2235
2371
  });
2236
2372
  lastError = void 0;
2237
2373
  } catch (error) {
2374
+ if (error instanceof DOMException && error.name === "AbortError") {
2375
+ throw error;
2376
+ }
2238
2377
  lastError = error;
2239
- if (i < nRetries - 1) {
2240
- const delay = 2 ** i * 100;
2241
- await sleep(delay);
2378
+ consecutive429s = 0;
2379
+ nonBusyRetries++;
2380
+ if (nonBusyRetries < maxNonBusyRetries) {
2381
+ const delay2 = 2 ** nonBusyRetries * 100;
2382
+ await sleep(delay2);
2242
2383
  continue;
2243
2384
  }
2244
2385
  break;
2245
2386
  }
2246
- if (lastResponse.ok) return;
2387
+ if (lastResponse.ok) {
2388
+ const json = await lastResponse.json();
2389
+ const spAcks = json.spAcks?.map((ack) => ({
2390
+ slot: ack.slot,
2391
+ signature: Uint8Array.from(
2392
+ atob(ack.signature),
2393
+ (c) => c.charCodeAt(0)
2394
+ )
2395
+ }));
2396
+ return {
2397
+ success: json.success,
2398
+ acksReceived: json.acksReceived,
2399
+ spAcks
2400
+ };
2401
+ }
2402
+ if (lastResponse.status === 429) {
2403
+ consecutive429s++;
2404
+ const baseDelay = 500;
2405
+ const jitter = Math.random() * 200;
2406
+ const delay2 = Math.min(
2407
+ 3e4,
2408
+ 2 ** consecutive429s * baseDelay + jitter
2409
+ );
2410
+ await sleep(delay2);
2411
+ continue;
2412
+ }
2413
+ consecutive429s = 0;
2247
2414
  if (!isRetryableStatus(lastResponse.status)) {
2248
2415
  break;
2249
2416
  }
2250
- if (i < nRetries - 1) {
2251
- const delay = 2 ** i * 100;
2252
- await sleep(delay);
2417
+ nonBusyRetries++;
2418
+ if (nonBusyRetries >= maxNonBusyRetries) {
2419
+ break;
2253
2420
  }
2421
+ const delay = 2 ** nonBusyRetries * 100;
2422
+ await sleep(delay);
2254
2423
  }
2255
2424
  if (lastError !== void 0) {
2256
2425
  const errorCode = getErrorCode(lastError);
2257
2426
  const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2258
2427
  throw new Error(
2259
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2428
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2260
2429
  { cause: lastError }
2261
2430
  );
2262
2431
  }
2263
2432
  const errorBody = await lastResponse?.text().catch(() => "");
2264
2433
  throw new Error(
2265
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2434
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2266
2435
  );
2267
2436
  }
2268
2437
  /**
2269
- * Uploads blob data to the Shelby RPC node with authentication and resume support.
2270
- * This method authenticates using challenge-response and can resume interrupted uploads.
2438
+ * Uploads blob data to the Shelby RPC node using the v2 chunkset API.
2439
+ * This method authenticates using challenge-response and uploads chunksets
2440
+ * directly to storage providers via the RPC's worker pool.
2441
+ *
2442
+ * This method:
2443
+ * - Sends raw chunkset data directly to the RPC for erasure encoding
2444
+ * - Requires pre-computed blob commitments to generate inclusion proofs
2445
+ * - Does not support resume (each chunkset is idempotent on the SP side)
2271
2446
  *
2272
2447
  * @param params.account - The Aptos Account (with signing capability) that owns the blob.
2273
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2448
+ * @param params.uid - The blob's on-chain UID (from `BlobRegisteredEvent` at registration).
2274
2449
  * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2450
+ * @param params.commitments - Pre-computed blob commitments (from generateCommitments).
2275
2451
  * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2452
+ * @param params.chunksetConcurrency - Number of chunksets to upload in parallel. Defaults to 4.
2276
2453
  * @param params.onProgress - Optional callback for upload progress.
2454
+ * @param params.signal - Optional AbortSignal for cancellation. When aborted, in-flight
2455
+ * HTTP requests are cancelled and an AbortError is thrown.
2277
2456
  *
2278
2457
  * @example
2279
2458
  * ```typescript
2280
- * await client.putBlobResumable({
2281
- * account: myAccount, // Aptos Account with signing capability
2282
- * blobName: "documents/report.pdf",
2459
+ * // First, generate commitments for the blob
2460
+ * const commitments = await generateCommitments(provider, fileData);
2461
+ *
2462
+ * // Register the blob on chain, then read its UID from the register tx's
2463
+ * // BlobRegisteredEvent (see ShelbyBlobClient.registeredBlobUids).
2464
+ *
2465
+ * // Upload using chunkset API
2466
+ * await rpcClient.putBlobChunksets({
2467
+ * account: myAccount,
2468
+ * uid: blobUid,
2283
2469
  * blobData: fileData,
2284
- * totalBytes: fileData.length,
2470
+ * commitments,
2471
+ * chunksetConcurrency: 8,
2285
2472
  * });
2286
2473
  * ```
2287
2474
  */
2288
- async putBlobResumable(params) {
2289
- BlobNameSchema.parse(params.blobName);
2475
+ async putBlobChunksets(params) {
2290
2476
  let totalBytes;
2291
2477
  if (params.blobData instanceof Uint8Array) {
2292
2478
  totalBytes = params.totalBytes ?? params.blobData.length;
@@ -2304,110 +2490,146 @@ var ShelbyRPCClient = class {
2304
2490
  totalBytes = params.totalBytes;
2305
2491
  }
2306
2492
  validateTotalBytes(totalBytes);
2307
- const partSize = params.partSize ?? 5 * 1024 * 1024;
2493
+ const chunksetConcurrency = params.chunksetConcurrency ?? 4;
2494
+ const totalChunksets = params.commitments.chunkset_commitments.length;
2495
+ const rawDataSizeFromCommitments = params.commitments.raw_data_size;
2496
+ if (rawDataSizeFromCommitments !== totalBytes) {
2497
+ throw new Error(
2498
+ `Data size mismatch: commitments were generated for ${rawDataSizeFromCommitments} bytes, but totalBytes=${totalBytes}. This will cause inclusion proof verification to fail.`
2499
+ );
2500
+ }
2501
+ const chunksetRoots = params.commitments.chunkset_commitments.map(
2502
+ (c) => Hex6.fromHexString(c.chunkset_root)
2503
+ );
2308
2504
  const { challenge } = await this.getChallenge(
2309
2505
  params.account.accountAddress
2310
2506
  );
2311
2507
  const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
2312
2508
  const auth = signFn(params.account, challenge);
2313
- const authHeaders = buildAuthHeaders(auth);
2314
- const existingUpload = await this.getMultipartUploadStatus(
2315
- params.account.accountAddress,
2316
- params.blobName
2317
- );
2318
- let uploadId;
2319
- let completedPartsSet;
2320
- let totalParts;
2321
- if (existingUpload) {
2322
- uploadId = existingUpload.uploadId;
2323
- completedPartsSet = new Set(existingUpload.completedParts);
2324
- totalParts = existingUpload.nParts;
2325
- if (existingUpload.partSize !== partSize) {
2326
- throw new Error(
2327
- `Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
2328
- );
2329
- }
2330
- } else {
2331
- const startResponse = await fetch(
2332
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2333
- {
2334
- method: "POST",
2335
- headers: {
2336
- "Content-Type": "application/json",
2337
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2338
- ...authHeaders
2339
- },
2340
- body: JSON.stringify({
2341
- rawAccount: params.account.accountAddress.toString(),
2342
- rawBlobName: params.blobName,
2343
- rawPartSize: partSize
2344
- })
2345
- }
2509
+ const firstChunkset = params.commitments.chunkset_commitments[0];
2510
+ if (!firstChunkset) {
2511
+ throw new Error("Commitments must have at least one chunkset");
2512
+ }
2513
+ const erasure_n = firstChunkset.chunk_commitments.length;
2514
+ const matchingEntry = findErasureSchemeByErasureN(erasure_n);
2515
+ if (!matchingEntry) {
2516
+ throw new Error(
2517
+ `Unknown erasure coding scheme with erasure_n=${erasure_n}`
2346
2518
  );
2347
- if (!startResponse.ok) {
2348
- const errorBodyText = await startResponse.text().catch(() => "");
2349
- throw new Error(
2350
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2351
- );
2352
- }
2353
- const parsed = StartMultipartUploadResponseSchema.parse(
2354
- await startResponse.json()
2519
+ }
2520
+ const [schemeKey, matchingParams] = matchingEntry;
2521
+ const erasure_k = matchingParams.erasure_k;
2522
+ const { chunkSizeBytes } = ERASURE_CODE_AND_CHUNK_MAPPING[schemeKey];
2523
+ const chunksetSize = erasure_k * chunkSizeBytes;
2524
+ const expectedChunksetCount = totalBytes === 0 ? 1 : Math.ceil(totalBytes / chunksetSize);
2525
+ if (expectedChunksetCount !== totalChunksets) {
2526
+ throw new Error(
2527
+ `Chunkset count mismatch: expected ${expectedChunksetCount} chunksets for ${totalBytes} bytes with chunksetSize=${chunksetSize}, but commitments have ${totalChunksets} chunksets. This suggests the commitments were generated with a different encoding scheme.`
2355
2528
  );
2356
- uploadId = parsed.uploadId;
2357
- completedPartsSet = /* @__PURE__ */ new Set();
2358
- totalParts = Math.ceil(totalBytes / partSize);
2359
2529
  }
2360
2530
  let uploadedBytes = 0;
2361
- let partIdx = 0;
2362
- for await (const [idx, partData] of readInChunks(
2363
- params.blobData,
2364
- partSize
2365
- )) {
2366
- partIdx = idx;
2367
- if (completedPartsSet.has(partIdx)) {
2368
- uploadedBytes += partData.length;
2369
- continue;
2531
+ let bytesReadFromSource = 0;
2532
+ const inFlight = /* @__PURE__ */ new Set();
2533
+ async function* chunksetsFromBlob() {
2534
+ if (totalBytes === 0) {
2535
+ yield [0, new Uint8Array(chunksetSize)];
2536
+ return;
2370
2537
  }
2371
- await this.#uploadPartWithAuth(uploadId, partIdx, partData, auth);
2372
- uploadedBytes += partData.length;
2373
- params.onProgress?.({
2374
- phase: "uploading",
2375
- partIdx,
2376
- totalParts,
2377
- partBytes: partData.length,
2378
- uploadedBytes,
2379
- totalBytes
2538
+ yield* readInChunks(params.blobData, chunksetSize);
2539
+ }
2540
+ const chunksetIterator = chunksetsFromBlob();
2541
+ const aggregatedAcks = /* @__PURE__ */ new Map();
2542
+ const internalAbort = new AbortController();
2543
+ if (params.signal?.aborted) {
2544
+ internalAbort.abort();
2545
+ } else if (params.signal) {
2546
+ params.signal.addEventListener("abort", () => internalAbort.abort(), {
2547
+ once: true
2380
2548
  });
2381
2549
  }
2382
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2383
- params.onProgress?.({
2384
- phase: "finalizing",
2385
- partIdx: finalPartIdx,
2386
- totalParts,
2387
- partBytes: 0,
2388
- uploadedBytes: totalBytes,
2389
- totalBytes
2390
- });
2391
- const completeResponse = await fetch(
2392
- buildRequestUrl(
2393
- `/v1/multipart-uploads/${uploadId}/complete`,
2394
- this.baseUrl
2395
- ),
2396
- {
2397
- method: "POST",
2398
- headers: {
2399
- "Content-Type": "application/json",
2400
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2401
- ...authHeaders
2550
+ let iteratorDone = false;
2551
+ let firstError = null;
2552
+ while (!iteratorDone || inFlight.size > 0) {
2553
+ if (internalAbort.signal.aborted) {
2554
+ throw firstError ?? new DOMException("Upload aborted", "AbortError");
2555
+ }
2556
+ while (inFlight.size < chunksetConcurrency && !iteratorDone) {
2557
+ const { value, done } = await chunksetIterator.next();
2558
+ if (done) {
2559
+ iteratorDone = true;
2560
+ break;
2402
2561
  }
2562
+ const [chunksetIdx, chunksetData] = value;
2563
+ bytesReadFromSource += totalBytes === 0 ? 0 : chunksetData.byteLength;
2564
+ const expectedCommitment = params.commitments.chunkset_commitments[chunksetIdx];
2565
+ if (!expectedCommitment) {
2566
+ throw new Error(
2567
+ `Chunkset index ${chunksetIdx} out of range. Commitments only have ${totalChunksets} chunksets. This suggests a chunkset size mismatch between commitment generation and upload.`
2568
+ );
2569
+ }
2570
+ const proofSiblings = await generateChunksetInclusionProof(
2571
+ chunksetRoots,
2572
+ chunksetIdx
2573
+ );
2574
+ const inclusionProof = encodeInclusionProof(proofSiblings);
2575
+ const chunksetDataCopy = new Uint8Array(chunksetData);
2576
+ const chunksetBytes = chunksetDataCopy.length;
2577
+ const uploadPromise = (async () => {
2578
+ const result = await this.#uploadChunkset({
2579
+ account: params.account.accountAddress.toString(),
2580
+ uid: params.uid,
2581
+ chunksetIndex: chunksetIdx,
2582
+ inclusionProof,
2583
+ chunksetData: chunksetDataCopy,
2584
+ auth,
2585
+ signal: internalAbort.signal
2586
+ });
2587
+ const chunksetSpAcks = result.spAcks?.map((ack) => ({
2588
+ slot: ack.slot,
2589
+ signature: ack.signature
2590
+ }));
2591
+ if (chunksetSpAcks) {
2592
+ for (const ack of chunksetSpAcks) {
2593
+ aggregatedAcks.set(ack.slot, ack.signature);
2594
+ }
2595
+ }
2596
+ uploadedBytes += chunksetBytes;
2597
+ params.onProgress?.({
2598
+ phase: "uploading",
2599
+ chunksetIdx,
2600
+ totalChunksets,
2601
+ chunksetBytes,
2602
+ uploadedBytes,
2603
+ totalBytes,
2604
+ acksReceived: result.acksReceived,
2605
+ spAcks: chunksetSpAcks
2606
+ });
2607
+ })().catch((err) => {
2608
+ if (!firstError) {
2609
+ firstError = err;
2610
+ internalAbort.abort();
2611
+ }
2612
+ }).finally(() => {
2613
+ inFlight.delete(uploadPromise);
2614
+ });
2615
+ inFlight.add(uploadPromise);
2403
2616
  }
2404
- );
2405
- if (!completeResponse.ok) {
2406
- const errorBodyText = await completeResponse.text().catch(() => "");
2617
+ if (inFlight.size > 0) {
2618
+ await Promise.race(inFlight);
2619
+ }
2620
+ }
2621
+ if (firstError) {
2622
+ throw firstError;
2623
+ }
2624
+ if (bytesReadFromSource !== totalBytes) {
2407
2625
  throw new Error(
2408
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2626
+ `Data source produced ${bytesReadFromSource} bytes, but expected ${totalBytes} bytes. The stream may have ended prematurely, resulting in an incomplete upload.`
2409
2627
  );
2410
2628
  }
2629
+ const spAcks = Array.from(
2630
+ aggregatedAcks.entries()
2631
+ ).map(([slot, signature]) => ({ slot, signature }));
2632
+ return { spAcks };
2411
2633
  }
2412
2634
  /**
2413
2635
  * Downloads a blob from the Shelby RPC node.
@@ -2659,6 +2881,59 @@ var ShelbyClient = class {
2659
2881
  }
2660
2882
  return this._provider;
2661
2883
  }
2884
+ /**
2885
+ * Build orderless transaction options (a random replay-protection nonce) for
2886
+ * the per-blob commit transactions. batchUpload finalizes blobs
2887
+ * concurrently, so without orderless replay protection the parallel
2888
+ * same-account submissions collide on the sequence number ("transaction
2889
+ * already in mempool with a different payload"). The nonce makes each commit
2890
+ * independent regardless of the client's `orderless` config flag.
2891
+ */
2892
+ orderlessCommitOptions() {
2893
+ return {
2894
+ build: {
2895
+ options: {
2896
+ replayProtectionNonce: crypto.getRandomValues(new Uint32Array(1))[0]
2897
+ }
2898
+ }
2899
+ };
2900
+ }
2901
+ /**
2902
+ * Resolve the on-chain UID assigned to `blobName` from a committed register
2903
+ * transaction. `register_blob` only publishes the uid -> name mapping via
2904
+ * `BlobRegisteredEvent` (a pending blob is not yet in the `objects` map, so it
2905
+ * cannot be read back by name), so the upload flow must parse it here before
2906
+ * uploading bytes or committing.
2907
+ */
2908
+ uidFromRegisterTx(tx, account, blobName) {
2909
+ const events = "events" in tx ? tx.events : [];
2910
+ const objectName = createBlobKey({ account, blobName });
2911
+ const match = ShelbyBlobClient.registeredBlobUids(
2912
+ events,
2913
+ this.coordination.deployer
2914
+ ).find((registered) => registered.objectName === objectName);
2915
+ if (match === void 0) {
2916
+ throw new Error(
2917
+ `No BlobRegisteredEvent for '${blobName}' in register transaction ${tx.hash}`
2918
+ );
2919
+ }
2920
+ return match.uid;
2921
+ }
2922
+ /**
2923
+ * Await a blob-registration transaction, rephrasing on-chain location
2924
+ * failures into a human-readable message.
2925
+ */
2926
+ async waitForRegistration(transactionHash) {
2927
+ try {
2928
+ return await this.coordination.aptos.waitForTransaction({
2929
+ transactionHash
2930
+ });
2931
+ } catch (error) {
2932
+ const message = error instanceof Error ? error.message : String(error);
2933
+ const described = describeLocationError(message);
2934
+ throw described ? new Error(described, { cause: error }) : error;
2935
+ }
2936
+ }
2662
2937
  /**
2663
2938
  * The base URL for the Shelby RPC node.
2664
2939
  */
@@ -2672,8 +2947,8 @@ var ShelbyClient = class {
2672
2947
  *
2673
2948
  * Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
2674
2949
  * For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
2675
- * using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2676
- * with a `ReadableStream`.
2950
+ * using `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
2951
+ * and `coordination.commitObject()` with a `ReadableStream`.
2677
2952
  *
2678
2953
  * @param params.blobData - The raw data to upload as a Uint8Array.
2679
2954
  * @param params.signer - The account that signs and pays for the transaction.
@@ -2696,34 +2971,57 @@ var ShelbyClient = class {
2696
2971
  * ```
2697
2972
  */
2698
2973
  async upload(params) {
2699
- const existingBlobMetadata = await this.coordination.getBlobMetadata({
2700
- account: params.signer.accountAddress,
2701
- name: params.blobName
2974
+ const provider = await this.getProvider();
2975
+ const blobCommitments = await generateCommitments(
2976
+ provider,
2977
+ params.blobData
2978
+ );
2979
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2980
+ account: params.signer,
2981
+ blobName: params.blobName,
2982
+ blobMerkleRoot: blobCommitments.blob_merkle_root,
2983
+ size: params.blobData.length,
2984
+ expirationMicros: params.expirationMicros,
2985
+ config: provider.config,
2986
+ options: params.options
2702
2987
  });
2703
- if (!existingBlobMetadata) {
2704
- const provider = await this.getProvider();
2705
- const blobCommitments = await generateCommitments(
2706
- provider,
2707
- params.blobData
2988
+ const registerTx = await this.waitForRegistration(
2989
+ pendingRegisterBlobTransaction.hash
2990
+ );
2991
+ const uid = this.uidFromRegisterTx(
2992
+ registerTx,
2993
+ params.signer.accountAddress,
2994
+ params.blobName
2995
+ );
2996
+ const { spAcks } = await this.rpc.putBlobChunksets({
2997
+ account: params.signer,
2998
+ uid,
2999
+ blobData: params.blobData,
3000
+ commitments: blobCommitments,
3001
+ totalBytes: params.blobData.length
3002
+ });
3003
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
3004
+ if (spAcks.length < requiredAcks) {
3005
+ throw new Error(
3006
+ `Insufficient storage provider acknowledgements for '${params.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
2708
3007
  );
2709
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2710
- account: params.signer,
2711
- blobName: params.blobName,
2712
- blobMerkleRoot: blobCommitments.blob_merkle_root,
2713
- size: params.blobData.length,
2714
- expirationMicros: params.expirationMicros,
2715
- config: provider.config,
2716
- options: params.options
2717
- });
2718
- await this.coordination.aptos.waitForTransaction({
2719
- transactionHash: pendingRegisterBlobTransaction.hash
2720
- });
2721
3008
  }
2722
- await this.rpc.putBlobResumable({
3009
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
2723
3010
  account: params.signer,
3011
+ uid,
2724
3012
  blobName: params.blobName,
2725
- blobData: params.blobData
3013
+ overwrite: true,
3014
+ storageProviderAcks: spAcks,
3015
+ options: this.orderlessCommitOptions()
3016
+ });
3017
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3018
+ transactionHash: pendingCommitTransaction.hash
2726
3019
  });
3020
+ if (!confirmedTx.success) {
3021
+ throw new Error(
3022
+ `commit_object tx failed for '${params.blobName}': ${confirmedTx.vm_status}`
3023
+ );
3024
+ }
2727
3025
  }
2728
3026
  /**
2729
3027
  * Uploads a batch of blobs to the Shelby network.
@@ -2732,8 +3030,8 @@ var ShelbyClient = class {
2732
3030
  *
2733
3031
  * Note: This method accepts only `Uint8Array` and buffers each blob in memory.
2734
3032
  * For streaming uploads of large files, orchestrate the steps manually using
2735
- * `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2736
- * with a `ReadableStream`.
3033
+ * `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
3034
+ * and `coordination.commitObject()` with a `ReadableStream`.
2737
3035
  *
2738
3036
  * @param params.blobs - The blobs to upload.
2739
3037
  * @param params.blobs.blobData - The raw data to upload as a Uint8Array.
@@ -2758,59 +3056,77 @@ var ShelbyClient = class {
2758
3056
  * ```
2759
3057
  */
2760
3058
  async batchUpload(params) {
2761
- const existingBlobs = await this.coordination.getBlobs({
2762
- where: {
2763
- blob_name: {
2764
- _in: params.blobs.map(
2765
- (blob) => createBlobKey({
2766
- account: params.signer.accountAddress,
2767
- blobName: blob.blobName
2768
- })
2769
- )
2770
- }
2771
- }
3059
+ const provider = await this.getProvider();
3060
+ const prepared = await Promise.all(
3061
+ params.blobs.map(async (blob) => ({
3062
+ blob,
3063
+ commitments: await generateCommitments(provider, blob.blobData)
3064
+ }))
3065
+ );
3066
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
3067
+ account: params.signer,
3068
+ expirationMicros: params.expirationMicros,
3069
+ blobs: prepared.map((p) => ({
3070
+ blobName: p.blob.blobName,
3071
+ blobSize: p.blob.blobData.length,
3072
+ blobMerkleRoot: p.commitments.blob_merkle_root
3073
+ })),
3074
+ config: provider.config,
3075
+ options: params.options
2772
3076
  });
2773
- const blobsToRegister = params.blobs.filter(
2774
- (blob) => !existingBlobs.some(
2775
- (existingBlob) => existingBlob.name === createBlobKey({
2776
- account: params.signer.accountAddress,
2777
- blobName: blob.blobName
2778
- })
2779
- )
3077
+ const registerTx = await this.waitForRegistration(
3078
+ pendingRegisterBlobTransaction.hash
2780
3079
  );
2781
- if (blobsToRegister.length > 0) {
2782
- const provider = await this.getProvider();
2783
- const blobCommitments = await Promise.all(
2784
- blobsToRegister.map(
2785
- async (blob) => generateCommitments(provider, blob.blobData)
2786
- )
2787
- );
2788
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
2789
- account: params.signer,
2790
- expirationMicros: params.expirationMicros,
2791
- blobs: blobsToRegister.map((blob, index) => ({
2792
- blobName: blob.blobName,
2793
- blobSize: blob.blobData.length,
2794
- blobMerkleRoot: blobCommitments[index].blob_merkle_root
2795
- })),
2796
- config: provider.config,
2797
- options: params.options
2798
- });
2799
- await this.coordination.aptos.waitForTransaction({
2800
- transactionHash: pendingRegisterBlobTransaction.hash,
2801
- options: { waitForIndexer: true }
2802
- });
2803
- }
3080
+ const uidByObjectName = new Map(
3081
+ ShelbyBlobClient.registeredBlobUids(
3082
+ "events" in registerTx ? registerTx.events : [],
3083
+ this.coordination.deployer
3084
+ ).map((registered) => [registered.objectName, registered.uid])
3085
+ );
3086
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
2804
3087
  const limit = pLimit(3);
2805
3088
  await Promise.all(
2806
- params.blobs.map(
2807
- (blob) => limit(
2808
- () => this.rpc.putBlobResumable({
3089
+ prepared.map(
3090
+ (p) => limit(async () => {
3091
+ const objectName = createBlobKey({
3092
+ account: params.signer.accountAddress,
3093
+ blobName: p.blob.blobName
3094
+ });
3095
+ const uid = uidByObjectName.get(objectName);
3096
+ if (uid === void 0) {
3097
+ throw new Error(
3098
+ `No BlobRegisteredEvent for '${p.blob.blobName}' in batch register transaction ${registerTx.hash}`
3099
+ );
3100
+ }
3101
+ const { spAcks } = await this.rpc.putBlobChunksets({
2809
3102
  account: params.signer,
2810
- blobName: blob.blobName,
2811
- blobData: blob.blobData
2812
- })
2813
- )
3103
+ uid,
3104
+ blobData: p.blob.blobData,
3105
+ commitments: p.commitments,
3106
+ totalBytes: p.blob.blobData.length
3107
+ });
3108
+ if (spAcks.length < requiredAcks) {
3109
+ throw new Error(
3110
+ `Insufficient storage provider acknowledgements for '${p.blob.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
3111
+ );
3112
+ }
3113
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
3114
+ account: params.signer,
3115
+ uid,
3116
+ blobName: p.blob.blobName,
3117
+ overwrite: true,
3118
+ storageProviderAcks: spAcks,
3119
+ options: this.orderlessCommitOptions()
3120
+ });
3121
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3122
+ transactionHash: pendingCommitTransaction.hash
3123
+ });
3124
+ if (!confirmedTx.success) {
3125
+ throw new Error(
3126
+ `commit_object tx failed for '${p.blob.blobName}': ${confirmedTx.vm_status}`
3127
+ );
3128
+ }
3129
+ })
2814
3130
  )
2815
3131
  );
2816
3132
  }