@shelby-protocol/sdk 0.3.1 → 0.4.0

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 +11 -4
@@ -135,6 +135,18 @@ var ERASURE_CODE_PARAMS = {
135
135
  enumIndex: 1
136
136
  }
137
137
  };
138
+ function findErasureSchemeByErasureN(erasureN) {
139
+ return Object.entries(ERASURE_CODE_PARAMS).find(
140
+ ([, params]) => params.erasure_n === erasureN
141
+ );
142
+ }
143
+ function requiredAckCount(erasureN) {
144
+ const scheme = findErasureSchemeByErasureN(erasureN);
145
+ if (!scheme) {
146
+ throw new Error(`Unknown erasure coding scheme with erasure_n=${erasureN}`);
147
+ }
148
+ return scheme[1].erasure_d;
149
+ }
138
150
  var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
139
151
  var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
140
152
  var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
@@ -431,6 +443,35 @@ function erasureCodingConfig(encodingScheme) {
431
443
  import { createWasmReedSolomonBinding } from "@shelby-protocol/reed-solomon";
432
444
  var DEFAULT_CHUNK_SIZE_BYTES2 = 2 * 1024 * 1024;
433
445
 
446
+ // src/core/errors.ts
447
+ var ShelbyLocationErrorCodes = {
448
+ E_LOCATION_NOT_FOUND: "E_LOCATION_NOT_FOUND",
449
+ E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK: "E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK",
450
+ E_NO_LOCATION_SELECTED: "E_NO_LOCATION_SELECTED",
451
+ E_LOCATION_WRITES_FROZEN: "E_LOCATION_WRITES_FROZEN",
452
+ E_LOCATION_NOT_ACTIVATED: "E_LOCATION_NOT_ACTIVATED"
453
+ };
454
+ function describeLocationError(errorMessage) {
455
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_WRITES_FROZEN)) {
456
+ return "Cannot write: the resolved location is frozen and not currently accepting new data.";
457
+ }
458
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_ACTIVATED)) {
459
+ return "Cannot write: the resolved location is not activated yet.";
460
+ }
461
+ if (errorMessage.includes(
462
+ ShelbyLocationErrorCodes.E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK
463
+ )) {
464
+ return "Cannot write: the selected location conflicts with the account's locked location preference.";
465
+ }
466
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_NO_LOCATION_SELECTED)) {
467
+ return "No write location could be resolved: none was selected and the account has no default location or location hint.";
468
+ }
469
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_FOUND)) {
470
+ return "Cannot write: the requested location does not exist.";
471
+ }
472
+ return void 0;
473
+ }
474
+
434
475
  // src/core/clients/ShelbyBlobClient.ts
435
476
  import {
436
477
  AccountAddress as AccountAddress2,
@@ -442,7 +483,7 @@ import {
442
483
  // src/core/constants.ts
443
484
  import { Network } from "@aptos-labs/ts-sdk";
444
485
  var NetworkToShelbyRPCBaseUrl = {
445
- [Network.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
486
+ [Network.SHELBYNET]: "https://shelby.shelbynet.shelby.xyz/shelby",
446
487
  [Network.NETNA]: void 0,
447
488
  [Network.DEVNET]: void 0,
448
489
  [Network.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
@@ -471,6 +512,27 @@ var NetworkToGasStationBaseUrl = {
471
512
  var SHELBY_DEPLOYER = "0x85fdb9a176ab8ef1d9d9c1b60d60b3924f0800ac1de1cc2085fb0b8bb4988e6a";
472
513
  var MICROPAYMENTS_DEPLOYER = "0x1ae7275148bf6ef742b658fd9cbcc2e094201606f4a7bc707bab0201da8043ee";
473
514
 
515
+ // src/core/types/blobs.ts
516
+ var BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX = {
517
+ Unencrypted: 0,
518
+ AES_GCM_V1: 1
519
+ };
520
+ function blobEncryptionToMoveEnumIndex(encryption) {
521
+ return BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX[encryption];
522
+ }
523
+ function blobEncryptionFromMoveVariant(variant) {
524
+ switch (variant) {
525
+ case "Unencrypted":
526
+ return "Unencrypted";
527
+ case "AES_GCM_V1":
528
+ return "AES_GCM_V1";
529
+ default:
530
+ throw new Error(
531
+ "Could not parse encryption from Shelby Smart Contract, this SDK is out of date."
532
+ );
533
+ }
534
+ }
535
+
474
536
  // src/core/operations/index.ts
475
537
  import { Network as Network2 } from "@aptos-labs/ts-sdk";
476
538
  import { GraphQLClient } from "graphql-request";
@@ -514,18 +576,20 @@ import gql from "graphql-tag";
514
576
  var GetBlobsDocument = gql`
515
577
  query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
516
578
  blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
579
+ uid
580
+ object_name
517
581
  owner
518
582
  blob_commitment
519
- blob_name
520
583
  created_at
521
584
  expires_at
585
+ updated_at
522
586
  num_chunksets
523
- is_deleted
524
- is_written
525
- placement_group
526
587
  size
527
- updated_at
528
588
  slice_address
589
+ placement_group
590
+ is_persisted
591
+ is_committed
592
+ is_deleted
529
593
  }
530
594
  }
531
595
  `;
@@ -537,7 +601,8 @@ var GetBlobActivitiesDocument = gql`
537
601
  limit: $limit
538
602
  offset: $offset
539
603
  ) {
540
- blob_name
604
+ uid
605
+ object_name
541
606
  event_index
542
607
  event_type
543
608
  transaction_hash
@@ -683,6 +748,22 @@ var MissingTransactionSubmitterError = class extends Error {
683
748
  this.name = "MissingTransactionSubmitterError";
684
749
  }
685
750
  };
751
+ var COMMIT_REJECTION_REASONS = /* @__PURE__ */ new Set([
752
+ "AlreadyExists",
753
+ "NoPriorVersion",
754
+ "EtagMismatch"
755
+ ]);
756
+ function encryptionFunctionArgs(params) {
757
+ if (params.omitEncryptionArg) {
758
+ if (params.encryption && params.encryption !== "Unencrypted") {
759
+ throw new Error(
760
+ `Blob encryption (${params.encryption}) is not supported on this network: the deployed contract predates the encryption upgrade (#1739).`
761
+ );
762
+ }
763
+ return [];
764
+ }
765
+ return [blobEncryptionToMoveEnumIndex(params.encryption ?? "Unencrypted")];
766
+ }
686
767
  var ShelbyBlobClient = class _ShelbyBlobClient {
687
768
  aptos;
688
769
  deployer;
@@ -731,7 +812,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
731
812
  this.aptos = new Aptos(getAptosConfig(config));
732
813
  this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
733
814
  this.indexer = getShelbyIndexerClient(config);
734
- this.defaultOptions = defaultOptions ?? {};
815
+ this.defaultOptions = {
816
+ locationHint: config.locationHint,
817
+ ...defaultOptions
818
+ };
735
819
  this.orderless = config.orderless ?? false;
736
820
  }
737
821
  /**
@@ -742,7 +826,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
742
826
  build: options?.build ?? this.defaultOptions.build,
743
827
  submit: options?.submit ?? this.defaultOptions.submit,
744
828
  usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
745
- chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
829
+ chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes,
830
+ selectedLocation: options?.selectedLocation ?? this.defaultOptions.selectedLocation,
831
+ locationHint: options?.locationHint ?? this.defaultOptions.locationHint
746
832
  };
747
833
  }
748
834
  /**
@@ -784,17 +870,17 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
784
870
  *
785
871
  * @example
786
872
  * ```typescript
787
- * const metadata = await client.getBlobMetadata({
873
+ * const metadata = await client.getFullObjectMetadata({
788
874
  * account: AccountAddress.fromString("0x1"),
789
875
  * name: "foo/bar.txt",
790
876
  * });
791
877
  * ```
792
878
  */
793
- async getBlobMetadata(params) {
879
+ async getFullObjectMetadata(params) {
794
880
  try {
795
881
  const rawMetadata = await this.aptos.view({
796
882
  payload: {
797
- function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
883
+ function: `${this.deployer.toString()}::blob_metadata::get_full_object_metadata`,
798
884
  functionArguments: [
799
885
  createBlobKey({
800
886
  account: params.account,
@@ -806,47 +892,94 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
806
892
  if (!rawMetadata?.[0]?.vec?.[0]) {
807
893
  return void 0;
808
894
  }
809
- const metadata = rawMetadata[0].vec[0];
810
- let encoding;
811
- if (metadata.encoding.__variant__ === "ClayCode_16Total_10Data_13Helper") {
812
- encoding = {
813
- variant: "clay",
814
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
815
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
816
- };
817
- } else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
818
- encoding = {
819
- variant: "clay",
820
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
821
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
822
- };
823
- } else {
824
- throw new Error(
825
- "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
826
- );
827
- }
828
- return {
829
- blobMerkleRoot: Hex3.fromHexInput(
830
- metadata.blob_commitment
831
- ).toUint8Array(),
832
- owner: normalizeAddress(metadata.owner),
895
+ const view = rawMetadata[0].vec[0];
896
+ return this.parseBlobMetadata(view.blob_metadata, {
897
+ uid: BigInt(view.object_metadata.current_blob_uid),
833
898
  name: params.name,
834
- blobNameSuffix: getBlobNameSuffix(params.name),
835
- size: Number(metadata.blob_size),
836
- encoding,
837
- expirationMicros: Number(metadata.expiration_micros),
838
- creationMicros: Number(metadata.creation_micros),
839
- sliceAddress: normalizeAddress(metadata.slice.inner),
840
- isWritten: metadata.is_written
841
- };
899
+ // Any blob bound under an object name has cleared the commit-time
900
+ // `is_written()` check, so a resolved object is always written.
901
+ isWritten: true
902
+ });
842
903
  } catch (error) {
843
904
  if (error instanceof Error && // Depending on the network, the error message may show up differently.
844
- (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
905
+ (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND") || error.message?.includes("EOBJECT_NOT_FOUND"))) {
906
+ return void 0;
907
+ }
908
+ throw error;
909
+ }
910
+ }
911
+ /**
912
+ * Retrieves blob metadata directly by its on-chain UID, including blobs in
913
+ * the pending (registered-but-not-yet-committed) state that
914
+ * {@link getFullObjectMetadata} cannot resolve by object name. Returns `undefined`
915
+ * if no blob has that UID.
916
+ *
917
+ * The returned `name`/`blobNameSuffix` are empty: the blob layer is keyed by
918
+ * UID and carries no object name (a name binding is established only at
919
+ * commit). `isWritten` reflects whether the blob has been committed.
920
+ */
921
+ async getFullObjectMetadataByUid(uid) {
922
+ try {
923
+ const rawMetadata = await this.aptos.view(
924
+ {
925
+ payload: {
926
+ function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
927
+ functionArguments: [uid]
928
+ }
929
+ }
930
+ );
931
+ if (!rawMetadata?.[0]?.vec?.[0]) {
932
+ return void 0;
933
+ }
934
+ const raw = rawMetadata[0].vec[0];
935
+ return this.parseBlobMetadata(raw, {
936
+ uid,
937
+ name: "",
938
+ isWritten: raw.state.__variant__ === "CommittedObject"
939
+ });
940
+ } catch (error) {
941
+ if (error instanceof Error && (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
845
942
  return void 0;
846
943
  }
847
944
  throw error;
848
945
  }
849
946
  }
947
+ /**
948
+ * Parse the on-chain `BlobMetadata::V1` view shape into the SDK
949
+ * {@link FullObjectMetadata}. `uid` / `name` / `isWritten` are supplied by the
950
+ * caller since they depend on the lookup path (by object name vs by UID).
951
+ */
952
+ parseBlobMetadata(raw, extra) {
953
+ const { content } = raw;
954
+ const variant = content.encoding.__variant__;
955
+ if (variant !== "ClayCode_16Total_10Data_13Helper" && variant !== "ClayCode_4Total_2Data_3Helper") {
956
+ throw new Error(
957
+ "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
958
+ );
959
+ }
960
+ const encoding = {
961
+ variant: "clay",
962
+ ...ERASURE_CODE_PARAMS[variant],
963
+ ...ERASURE_CODE_AND_CHUNK_MAPPING[variant]
964
+ };
965
+ const encryption = blobEncryptionFromMoveVariant(
966
+ content.encryption?.__variant__ ?? "Unencrypted"
967
+ );
968
+ return {
969
+ uid: extra.uid,
970
+ blobMerkleRoot: Hex3.fromHexInput(content.blob_commitment).toUint8Array(),
971
+ owner: normalizeAddress(raw.owner),
972
+ name: extra.name,
973
+ blobNameSuffix: extra.name ? getBlobNameSuffix(extra.name) : "",
974
+ size: Number(content.blob_size),
975
+ encoding,
976
+ encryption,
977
+ expirationMicros: Number(raw.expiration_micros),
978
+ creationMicros: Number(raw.creation_micros),
979
+ sliceAddress: normalizeAddress(raw.slice.inner),
980
+ isWritten: extra.isWritten
981
+ };
982
+ }
850
983
  /**
851
984
  * Retrieves all the blobs and their metadata for an account from the
852
985
  * blockchain.
@@ -858,7 +991,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
858
991
  *
859
992
  * @example
860
993
  * ```typescript
861
- * // BlobMetadata[]
994
+ * // FullObjectMetadata[]
862
995
  * const blobs = await client.getAccountBlobs({
863
996
  * account: AccountAddress.fromString("0x1"),
864
997
  * });
@@ -875,6 +1008,25 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
875
1008
  orderBy: rest.orderBy
876
1009
  });
877
1010
  }
1011
+ /**
1012
+ * Object-facing default filter: only committed, non-deleted, unexpired rows.
1013
+ * The blobs table is UID-keyed, so during an atomic overwrite a single
1014
+ * object_name transiently has two non-deleted rows — the currently committed
1015
+ * blob and the new pending (is_committed = "0") blob. Filtering on
1016
+ * is_committed keeps name lookups/listings pinned to the committed object and
1017
+ * avoids returning or duplicating the in-flight pending row. Applied to
1018
+ * getBlobs *and* the getBlobsCount / getTotalBlobsSize aggregates so counts
1019
+ * and sizes agree with the listing mid-overwrite. Callers can override any
1020
+ * key (e.g. is_committed) via their own `where`.
1021
+ */
1022
+ activeBlobsWhere(where) {
1023
+ const defaultActiveFilter = {
1024
+ expires_at: { _gte: String(Date.now() * 1e3) },
1025
+ is_deleted: { _eq: "0" },
1026
+ is_committed: { _eq: "1" }
1027
+ };
1028
+ return { ...defaultActiveFilter, ...where };
1029
+ }
878
1030
  /**
879
1031
  * Retrieves blobs and their metadata from the blockchain.
880
1032
  *
@@ -885,7 +1037,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
885
1037
  *
886
1038
  * @example
887
1039
  * ```typescript
888
- * // BlobMetadata[]
1040
+ * // FullObjectMetadata[]
889
1041
  * const blobs = await client.getBlobs({
890
1042
  * where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
891
1043
  * });
@@ -894,12 +1046,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
894
1046
  async getBlobs(params = {}) {
895
1047
  const { limit, offset } = params.pagination ?? {};
896
1048
  const { orderBy, where } = params;
897
- const currentMicros = String(Date.now() * 1e3);
898
- const defaultActiveFilter = {
899
- expires_at: { _gte: currentMicros },
900
- is_deleted: { _eq: "0" }
901
- };
902
- const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
1049
+ const finalWhere = this.activeBlobsWhere(where);
903
1050
  const { blobs } = await this.indexer.getBlobs({
904
1051
  where: finalWhere,
905
1052
  limit,
@@ -908,9 +1055,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
908
1055
  });
909
1056
  return blobs.map(
910
1057
  (blob) => ({
1058
+ uid: BigInt(blob.uid),
911
1059
  owner: normalizeAddress(blob.owner),
912
- name: blob.blob_name,
913
- blobNameSuffix: getBlobNameSuffix(blob.blob_name),
1060
+ name: blob.object_name,
1061
+ blobNameSuffix: getBlobNameSuffix(blob.object_name),
914
1062
  blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
915
1063
  size: Number(blob.size),
916
1064
  // TODO: Add encoding when supported in NCI
@@ -922,7 +1070,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
922
1070
  expirationMicros: Number(blob.expires_at),
923
1071
  creationMicros: Number(blob.created_at),
924
1072
  sliceAddress: normalizeAddress(blob.slice_address),
925
- isWritten: Boolean(Number(blob.is_written)),
1073
+ isWritten: Boolean(Number(blob.is_persisted)),
926
1074
  isDeleted: Boolean(Number(blob.is_deleted))
927
1075
  })
928
1076
  );
@@ -940,12 +1088,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
940
1088
  [`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
941
1089
  [`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
942
1090
  [`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
943
- [`${this.deployer.toStringLong()}::blob_metadata::BlobWrittenEvent`]: "write_blob"
1091
+ [`${this.deployer.toStringLong()}::blob_metadata::BlobPersistedEvent`]: "write_blob",
1092
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommittedEvent`]: "commit_object",
1093
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectDeletedEvent`]: "delete_object",
1094
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`]: "reject_object_commit"
944
1095
  };
945
1096
  return blob_activities.map(
946
1097
  (activity) => ({
947
- blobName: activity.blob_name,
948
- accountAddress: normalizeAddress(activity.blob_name.substring(1, 65)),
1098
+ blobName: activity.object_name,
1099
+ accountAddress: normalizeAddress(activity.object_name.substring(1, 65)),
949
1100
  type: activityTypeMapping[activity.event_type] ?? "unknown",
950
1101
  eventType: activity.event_type,
951
1102
  eventIndex: Number(activity.event_index),
@@ -968,9 +1119,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
968
1119
  * const count = await client.getBlobsCount();
969
1120
  * ```
970
1121
  */
971
- async getBlobsCount(params) {
972
- const { where } = params;
973
- const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
1122
+ async getBlobsCount(params = {}) {
1123
+ const { blobs_aggregate } = await this.indexer.getBlobsCount({
1124
+ where: this.activeBlobsWhere(params.where)
1125
+ });
974
1126
  return blobs_aggregate?.aggregate?.count ?? 0;
975
1127
  }
976
1128
  /**
@@ -985,8 +1137,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
985
1137
  * ```
986
1138
  */
987
1139
  async getTotalBlobsSize(params = {}) {
988
- const { where } = params;
989
- const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({ where });
1140
+ const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({
1141
+ where: this.activeBlobsWhere(params.where)
1142
+ });
990
1143
  return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
991
1144
  }
992
1145
  /**
@@ -1045,12 +1198,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1045
1198
  deployer: this.deployer,
1046
1199
  account: params.account.accountAddress,
1047
1200
  blobName: params.blobName,
1201
+ selectedLocation: options.selectedLocation,
1202
+ locationHint: options.locationHint,
1048
1203
  blobSize: params.size,
1049
1204
  blobMerkleRoot: params.blobMerkleRoot,
1050
1205
  numChunksets: expectedTotalChunksets(params.size, chunksetSize),
1051
1206
  expirationMicros: params.expirationMicros,
1052
1207
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1053
- encoding: config.enumIndex
1208
+ encoding: config.enumIndex,
1209
+ encryption: params.encryption
1054
1210
  }),
1055
1211
  sender: params.account.accountAddress
1056
1212
  };
@@ -1078,16 +1234,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1078
1234
  * @example
1079
1235
  * ```typescript
1080
1236
  *
1081
- * const { transaction } = await client.deleteBlob({
1237
+ * const { transaction } = await client.deleteObject({
1082
1238
  * account: signer,
1083
1239
  * blobName: "foo/bar.txt",
1084
1240
  * });
1085
1241
  * ```
1086
1242
  */
1087
- async deleteBlob(params) {
1243
+ async deleteObject(params) {
1088
1244
  const transaction = await this.aptos.transaction.build.simple({
1089
1245
  options: this.orderlessTxOptions(params.options),
1090
- data: _ShelbyBlobClient.createDeleteBlobPayload({
1246
+ data: _ShelbyBlobClient.createDeleteObjectPayload({
1091
1247
  deployer: this.deployer,
1092
1248
  blobName: params.blobName
1093
1249
  }),
@@ -1103,10 +1259,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1103
1259
  /**
1104
1260
  * Deletes multiple blobs on the blockchain in a single atomic transaction.
1105
1261
  *
1106
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1107
- * which will be deployed to the smart contract on 2026-02-04. Using this
1108
- * function before that date will result in a transaction failure.
1109
- *
1110
1262
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1111
1263
  * the entire transaction fails and no blobs are deleted.
1112
1264
  *
@@ -1121,16 +1273,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1121
1273
  * @example
1122
1274
  * ```typescript
1123
1275
  *
1124
- * const { transaction } = await client.deleteMultipleBlobs({
1276
+ * const { transaction } = await client.deleteMultipleObjects({
1125
1277
  * account: signer,
1126
1278
  * blobNames: ["foo/bar.txt", "baz.txt"],
1127
1279
  * });
1128
1280
  * ```
1129
1281
  */
1130
- async deleteMultipleBlobs(params) {
1282
+ async deleteMultipleObjects(params) {
1131
1283
  const transaction = await this.aptos.transaction.build.simple({
1132
1284
  options: this.orderlessTxOptions(params.options),
1133
- data: _ShelbyBlobClient.createDeleteMultipleBlobsPayload({
1285
+ data: _ShelbyBlobClient.createDeleteMultipleObjectsPayload({
1134
1286
  deployer: this.deployer,
1135
1287
  blobNames: params.blobNames
1136
1288
  }),
@@ -1188,6 +1340,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1188
1340
  data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
1189
1341
  deployer: this.deployer,
1190
1342
  account: params.account.accountAddress,
1343
+ selectedLocation: options.selectedLocation,
1344
+ locationHint: options.locationHint,
1191
1345
  expirationMicros: params.expirationMicros,
1192
1346
  blobs: params.blobs.map((blob) => ({
1193
1347
  blobName: blob.blobName,
@@ -1196,7 +1350,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1196
1350
  numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
1197
1351
  })),
1198
1352
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1199
- encoding: config.enumIndex
1353
+ encoding: config.enumIndex,
1354
+ encryption: params.encryption
1200
1355
  })
1201
1356
  };
1202
1357
  const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
@@ -1211,6 +1366,55 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1211
1366
  })
1212
1367
  };
1213
1368
  }
1369
+ /**
1370
+ * Extracts the on-chain UIDs assigned at registration from a committed
1371
+ * register transaction's events.
1372
+ *
1373
+ * `register_blob` / `register_multiple_blobs` create *pending* blobs and emit
1374
+ * one `BlobRegisteredEvent` per blob. The UID is published only on this event
1375
+ * (the blob is not yet in the `objects` map, so it cannot be read back by
1376
+ * name), so callers must parse it here before uploading bytes or committing.
1377
+ *
1378
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1379
+ * @param deployer - The contract deployer address.
1380
+ * @returns One entry per registered blob, keyed by its full object name
1381
+ * (`@<owner>/<suffix>`, matching {@link createBlobKey}).
1382
+ */
1383
+ static registeredBlobUids(events, deployer) {
1384
+ const eventType = `${deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`;
1385
+ return events.filter((event) => event.type === eventType).map((event) => {
1386
+ const data = event.data;
1387
+ return { objectName: data.object_name, uid: BigInt(data.uid) };
1388
+ });
1389
+ }
1390
+ /**
1391
+ * Detects whether `commit_object` rejected the write for `uid` rather than
1392
+ * applying it. A rejected commit is still a *successful* transaction — the
1393
+ * contract tears down the pending blob and emits `ObjectCommitRejectedEvent`
1394
+ * instead of aborting — so callers must inspect the finalized transaction's
1395
+ * events to tell a durable write apart from a silent no-op.
1396
+ *
1397
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1398
+ * @param deployer - The contract deployer address.
1399
+ * @param uid - The UID passed to `commit_object`.
1400
+ * @returns The rejection reason, or `undefined` if the commit was applied.
1401
+ */
1402
+ static findObjectCommitRejection(events, deployer, uid) {
1403
+ const eventType = `${deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`;
1404
+ const rejection = events.find(
1405
+ (event) => event.type === eventType && BigInt(event.data.uid) === uid
1406
+ );
1407
+ if (!rejection) {
1408
+ return void 0;
1409
+ }
1410
+ const reason = rejection.data.rejection_reason.__variant__;
1411
+ if (!COMMIT_REJECTION_REASONS.has(reason)) {
1412
+ throw new Error(
1413
+ `Unrecognized ObjectCommitRejectedEvent rejection_reason '${reason}' for uid ${uid}`
1414
+ );
1415
+ }
1416
+ return reason;
1417
+ }
1214
1418
  /**
1215
1419
  * Creates a transaction payload to register a blob on the blockchain.
1216
1420
  * This is a static helper method for constructing the Move function call payload.
@@ -1224,8 +1428,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1224
1428
  * @param params.numChunksets - The total number of chunksets in the blob.
1225
1429
  *
1226
1430
  * @returns An Aptos transaction payload data object for the register_blob Move function.
1227
- *
1228
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1229
1431
  */
1230
1432
  static createRegisterBlobPayload(params) {
1231
1433
  const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
@@ -1233,6 +1435,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1233
1435
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1234
1436
  functionArguments: [
1235
1437
  params.blobName,
1438
+ params.selectedLocation ?? null,
1439
+ params.locationHint ?? null,
1236
1440
  params.expirationMicros,
1237
1441
  Hex3.fromHexString(params.blobMerkleRoot).toUint8Array(),
1238
1442
  params.numChunksets,
@@ -1240,7 +1444,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1240
1444
  // TODO
1241
1445
  0,
1242
1446
  // payment tier
1243
- params.encoding
1447
+ params.encoding,
1448
+ ...encryptionFunctionArgs(params)
1244
1449
  ]
1245
1450
  };
1246
1451
  }
@@ -1258,8 +1463,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1258
1463
  * @param params.blobs.numChunksets - The total number of chunksets in the blob.
1259
1464
  *
1260
1465
  * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
1261
- *
1262
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1263
1466
  */
1264
1467
  static createBatchRegisterBlobsPayload(params) {
1265
1468
  const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
@@ -1279,6 +1482,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1279
1482
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1280
1483
  functionArguments: [
1281
1484
  blobNames,
1485
+ params.selectedLocation ?? null,
1486
+ params.locationHint ?? null,
1282
1487
  params.expirationMicros,
1283
1488
  blobMerkleRoots,
1284
1489
  blobNumChunksets,
@@ -1286,7 +1491,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1286
1491
  // TODO
1287
1492
  0,
1288
1493
  // payment tier
1289
- params.encoding
1494
+ params.encoding,
1495
+ ...encryptionFunctionArgs(params)
1290
1496
  ]
1291
1497
  };
1292
1498
  }
@@ -1297,24 +1503,20 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1297
1503
  * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
1298
1504
  * @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
1299
1505
  *
1300
- * @returns An Aptos transaction payload data object for the delete_blob Move function.
1301
- *
1302
- * @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
1506
+ * @returns An Aptos transaction payload data object for the delete_object Move function.
1303
1507
  */
1304
- static createDeleteBlobPayload(params) {
1508
+ static createDeleteObjectPayload(params) {
1305
1509
  return {
1306
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_blob`,
1307
- functionArguments: [params.blobName]
1510
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_object`,
1511
+ // Second arg is `if_match_etag: Option<vector<u8>>`; `null` => None
1512
+ // (unconditional delete).
1513
+ functionArguments: [params.blobName, null]
1308
1514
  };
1309
1515
  }
1310
1516
  /**
1311
1517
  * Creates a transaction payload to delete multiple blobs on the blockchain.
1312
1518
  * This is a static helper method for constructing the Move function call payload.
1313
1519
  *
1314
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1315
- * which will be deployed to the smart contract on 2026-02-04. Using this
1316
- * function before that date will result in a transaction failure.
1317
- *
1318
1520
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1319
1521
  * the entire transaction fails and no blobs are deleted.
1320
1522
  *
@@ -1323,42 +1525,71 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1323
1525
  * (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
1324
1526
  * prefix is automatically derived from the transaction sender.
1325
1527
  *
1326
- * @returns An Aptos transaction payload data object for the delete_multiple_blobs Move function.
1327
- *
1328
- * @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
1528
+ * @returns An Aptos transaction payload data object for the delete_multiple_objects Move function.
1329
1529
  */
1330
- static createDeleteMultipleBlobsPayload(params) {
1530
+ static createDeleteMultipleObjectsPayload(params) {
1331
1531
  return {
1332
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_blobs`,
1333
- functionArguments: [params.blobNames]
1532
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_objects`,
1533
+ // Second arg is `if_match_etags: vector<Option<vector<u8>>>`; an empty
1534
+ // vector means "no etag check on any object" (unconditional delete).
1535
+ functionArguments: [params.blobNames, []]
1334
1536
  };
1335
1537
  }
1336
- static createBlobAcknowledgementsPayload(params) {
1337
- const ackBitMask = params.storageProviderAcks.reduce(
1538
+ /**
1539
+ * Sort acks by slot and reduce to the `(ack_bits, signatures)` pair the
1540
+ * contract expects: it walks the set bits low-to-high and consumes the
1541
+ * signatures in that same order.
1542
+ */
1543
+ static encodeAcks(storageProviderAcks) {
1544
+ const sortedAcks = [...storageProviderAcks].sort((a, b) => a.slot - b.slot);
1545
+ const ackBitMask = sortedAcks.reduce(
1338
1546
  (acc, ack) => acc | 1 << ack.slot,
1339
1547
  0
1340
1548
  );
1341
1549
  return {
1342
- function: `${SHELBY_DEPLOYER}::blob_metadata::add_blob_acknowledgements`,
1550
+ ackBits: new U32(Number(ackBitMask)),
1551
+ signatures: sortedAcks.map((ack) => ack.signature)
1552
+ };
1553
+ }
1554
+ /**
1555
+ * Payload for `commit_object(uid, object_name_suffix, overwrite, if_match_etag,
1556
+ * ack_bits, signatures)` — binds a written pending blob under its object name,
1557
+ * finalizing the upload. SP acks may be batched in here (the contract applies
1558
+ * them before the `is_written` check), so register → upload → commit needs
1559
+ * only a single finalize transaction.
1560
+ *
1561
+ * @param params.uid - The blob UID returned at registration.
1562
+ * @param params.blobName - The object name suffix the blob was registered under.
1563
+ * @param params.overwrite - Allow replacing an existing binding under this name.
1564
+ * @param params.storageProviderAcks - Acks applied atomically with the commit.
1565
+ */
1566
+ static createCommitObjectPayload(params) {
1567
+ const { ackBits, signatures } = _ShelbyBlobClient.encodeAcks(
1568
+ params.storageProviderAcks
1569
+ );
1570
+ return {
1571
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::commit_object`,
1343
1572
  functionArguments: [
1344
- createBlobKey({
1345
- account: params.blobOwner,
1346
- blobName: params.blobName
1347
- }),
1348
- params.creationMicros,
1349
- new U32(Number(ackBitMask)),
1350
- params.storageProviderAcks.map((ack) => ack.signature)
1573
+ params.uid,
1574
+ params.blobName,
1575
+ params.overwrite,
1576
+ // `if_match_etag: Option<vector<u8>>` is `null` => None; conditional
1577
+ // (CAS) writes are not yet exposed by the SDK.
1578
+ null,
1579
+ ackBits,
1580
+ signatures
1351
1581
  ]
1352
1582
  };
1353
1583
  }
1354
- async addBlobAcknowledgements(params) {
1584
+ async commitObject(params) {
1355
1585
  const transaction = await this.aptos.transaction.build.simple({
1356
1586
  ...params.options?.build,
1357
1587
  options: this.orderlessTxOptions(params.options?.build?.options),
1358
- data: _ShelbyBlobClient.createBlobAcknowledgementsPayload({
1359
- blobOwner: params.blobOwner,
1588
+ data: _ShelbyBlobClient.createCommitObjectPayload({
1589
+ deployer: this.deployer,
1590
+ uid: params.uid,
1360
1591
  blobName: params.blobName,
1361
- creationMicros: params.creationMicros,
1592
+ overwrite: params.overwrite,
1362
1593
  storageProviderAcks: params.storageProviderAcks
1363
1594
  }),
1364
1595
  sender: params.account.accountAddress
@@ -1378,6 +1609,29 @@ import {
1378
1609
  Aptos as Aptos2,
1379
1610
  Hex as Hex4
1380
1611
  } from "@aptos-labs/ts-sdk";
1612
+
1613
+ // src/core/types/storage_providers.ts
1614
+ var ACTIVE_PROVIDER_CONDITIONS = [
1615
+ "Normal",
1616
+ "Faulty",
1617
+ "Leaving",
1618
+ "Evicted",
1619
+ "PendingLeaving"
1620
+ ];
1621
+ function isActiveProviderCondition(value) {
1622
+ return typeof value === "string" && ACTIVE_PROVIDER_CONDITIONS.includes(value);
1623
+ }
1624
+
1625
+ // src/core/clients/ShelbyMetadataClient.ts
1626
+ function parseActiveProviderCondition(raw) {
1627
+ const variant = raw?.__variant__;
1628
+ if (!isActiveProviderCondition(variant)) {
1629
+ throw new Error(
1630
+ `Unknown ActiveProviderCondition variant: ${JSON.stringify(raw)}`
1631
+ );
1632
+ }
1633
+ return variant;
1634
+ }
1381
1635
  function parseStorageProviderState(raw) {
1382
1636
  switch (raw.__variant__) {
1383
1637
  case "Active":
@@ -1385,8 +1639,7 @@ function parseStorageProviderState(raw) {
1385
1639
  variant: "Active",
1386
1640
  quota: raw.quota.value,
1387
1641
  stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
1388
- faulty: raw.faulty,
1389
- leaving: raw.leaving
1642
+ condition: parseActiveProviderCondition(raw.condition)
1390
1643
  };
1391
1644
  case "Waitlisted":
1392
1645
  return {
@@ -1399,6 +1652,10 @@ function parseStorageProviderState(raw) {
1399
1652
  frozenFrom: raw.frozen_from,
1400
1653
  frozenTill: raw.frozen_till
1401
1654
  };
1655
+ default:
1656
+ throw new Error(
1657
+ `Unknown StorageProviderStateDetails variant: ${JSON.stringify(raw)}`
1658
+ );
1402
1659
  }
1403
1660
  }
1404
1661
  var ShelbyMetadataClient = class {
@@ -1457,28 +1714,52 @@ var ShelbyMetadataClient = class {
1457
1714
  }
1458
1715
  }
1459
1716
  /**
1460
- * Retrieves the list of placement group addresses.
1717
+ * Retrieves the names of every activated location (region). Locations that are
1718
+ * registered but not yet brought online are admin-internal and not listed.
1719
+ *
1720
+ * @returns The location name list.
1721
+ *
1722
+ * @example
1723
+ * ```typescript
1724
+ * const locations = await client.getLocationNames();
1725
+ * ```
1726
+ */
1727
+ async getLocationNames() {
1728
+ const names = await this.aptos.view({
1729
+ payload: {
1730
+ function: `${this.deployer.toString()}::location::activated_location_names`,
1731
+ functionArguments: []
1732
+ }
1733
+ });
1734
+ return names[0];
1735
+ }
1736
+ /**
1737
+ * Retrieves the list of placement group addresses in a location.
1461
1738
  *
1739
+ * @param locationName - The location whose placement groups to list.
1462
1740
  * @returns The placement group address list, or an empty array if none exist.
1463
1741
  *
1464
1742
  * @example
1465
1743
  * ```typescript
1466
- * const pgList = await client.getPlacementGroupAddresses();
1744
+ * const pgList = await client.getPlacementGroupAddresses("us-east-1");
1467
1745
  * ```
1468
1746
  */
1469
- async getPlacementGroupAddresses() {
1747
+ async getPlacementGroupAddresses(locationName) {
1470
1748
  try {
1471
1749
  const pgSizeMetadata = await this.aptos.view({
1472
1750
  payload: {
1473
1751
  function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
1474
- functionArguments: []
1752
+ functionArguments: [locationName]
1475
1753
  }
1476
1754
  });
1755
+ if (Number(pgSizeMetadata[0]) === 0) {
1756
+ return [];
1757
+ }
1477
1758
  const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
1478
1759
  const addressMetadataArray = await this.aptos.view({
1479
1760
  payload: {
1480
1761
  function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
1481
- functionArguments: [0, finalPlacementGroupIndex]
1762
+ functionArguments: [locationName, 0, finalPlacementGroupIndex]
1482
1763
  }
1483
1764
  });
1484
1765
  const metadata = addressMetadataArray[0];
@@ -1492,28 +1773,32 @@ var ShelbyMetadataClient = class {
1492
1773
  }
1493
1774
  }
1494
1775
  /**
1495
- * Retrieves the list of slice addresses.
1776
+ * Retrieves the list of slice addresses in a location.
1496
1777
  *
1778
+ * @param locationName - The location whose slices to list.
1497
1779
  * @returns The slice group list, or an empty array if none exist.
1498
1780
  *
1499
1781
  * @example
1500
1782
  * ```typescript
1501
- * const pgList = await client.getSliceAddresses();
1783
+ * const pgList = await client.getSliceAddresses("us-east-1");
1502
1784
  * ```
1503
1785
  */
1504
- async getSliceAddresses() {
1786
+ async getSliceAddresses(locationName) {
1505
1787
  try {
1506
1788
  const sliceSizeMetadata = await this.aptos.view({
1507
1789
  payload: {
1508
1790
  function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
1509
- functionArguments: []
1791
+ functionArguments: [locationName]
1510
1792
  }
1511
1793
  });
1794
+ if (Number(sliceSizeMetadata[0]) === 0) {
1795
+ return [];
1796
+ }
1512
1797
  const finalSliceIndex = sliceSizeMetadata[0] - 1;
1513
1798
  const addressMetadataArray = await this.aptos.view({
1514
1799
  payload: {
1515
1800
  function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
1516
- functionArguments: [0, finalSliceIndex]
1801
+ functionArguments: [locationName, 0, finalSliceIndex]
1517
1802
  }
1518
1803
  });
1519
1804
  const metadata = addressMetadataArray[0];
@@ -1573,6 +1858,36 @@ var ShelbyMetadataClient = class {
1573
1858
  (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
1574
1859
  );
1575
1860
  }
1861
+ /**
1862
+ * Retrieves the active storage providers for a slice.
1863
+ *
1864
+ * Active SPs have a complete copy of the data for the slot, and are not in any data transfer/repair/reconstruction phase.
1865
+ * Active SP can be audited for the data it contains.
1866
+ * Each slot has at most one active SP.
1867
+ *
1868
+ * @param params.account - The address of the slice account.
1869
+ * @returns An array where result[i] is the active SP for slot i, or null if no SP is active.
1870
+ *
1871
+ * @example
1872
+ * ```typescript
1873
+ * const providers = await client.getActiveStorageProvidersForSlice({ account: sliceAddress });
1874
+ * ```
1875
+ */
1876
+ async getActiveStorageProvidersForSlice(params) {
1877
+ const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
1878
+ params.account
1879
+ );
1880
+ const rawMetadata = await this.aptos.view({
1881
+ payload: {
1882
+ function: `${this.deployer.toString()}::placement_group::get_active_storage_providers`,
1883
+ functionArguments: [placementGroupAddress]
1884
+ }
1885
+ });
1886
+ const providers = rawMetadata[0];
1887
+ return providers.map(
1888
+ (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
1889
+ );
1890
+ }
1576
1891
  /**
1577
1892
  * Retrieves the serving storage providers for a slice.
1578
1893
  *
@@ -1632,22 +1947,6 @@ var ChallengeResponseSchema = z3.object({
1632
1947
  challenge: z3.string(),
1633
1948
  expiresAt: z3.number()
1634
1949
  });
1635
- var MultipartUploadStatusResponseSchema = z3.object({
1636
- uploadId: z3.string(),
1637
- completedParts: z3.array(z3.number()),
1638
- partSize: z3.number(),
1639
- nParts: z3.number(),
1640
- uploadedBytes: z3.number()
1641
- });
1642
- var StartMultipartUploadResponseSchema = z3.object({
1643
- uploadId: z3.string()
1644
- });
1645
- var UploadPartResponseSchema = z3.object({
1646
- success: z3.literal(true)
1647
- });
1648
- var CompleteMultipartUploadResponseSchema = z3.object({
1649
- success: z3.literal(true)
1650
- });
1651
1950
  var RPCErrorResponseSchema = z3.object({
1652
1951
  error: z3.string()
1653
1952
  });
@@ -1864,6 +2163,7 @@ var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
1864
2163
  var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
1865
2164
  var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
1866
2165
  var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
2166
+ var INCLUSION_PROOF_HEADER = "X-Shelby-Inclusion-Proof";
1867
2167
  function buildAuthHeaders(auth) {
1868
2168
  const signatureBase64 = btoa(
1869
2169
  Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
@@ -1887,6 +2187,58 @@ function buildAuthHeaders(auth) {
1887
2187
  function encodeURIComponentKeepSlashes(str) {
1888
2188
  return encodeURIComponent(str).replace(/%2F/g, "/");
1889
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
+ }
1890
2242
  function validateTotalBytes(totalBytes) {
1891
2243
  if (!Number.isInteger(totalBytes) || totalBytes < 0) {
1892
2244
  throw new Error("totalBytes must be a non-negative integer");
@@ -1918,7 +2270,7 @@ var ShelbyRPCClient = class {
1918
2270
  * @param config - The client configuration object.
1919
2271
  * @param config.network - The Shelby network to use.
1920
2272
  * @param options.signChallengeHandler - Optional override for challenge
1921
- * signing. When set, `putBlobResumable` uses this instead of the built-in
2273
+ * signing. When set, `putBlobChunksets` uses this instead of the built-in
1922
2274
  * `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
1923
2275
  *
1924
2276
  * @example
@@ -1969,43 +2321,6 @@ var ShelbyRPCClient = class {
1969
2321
  }
1970
2322
  return ChallengeResponseSchema.parse(await response.json());
1971
2323
  }
1972
- /**
1973
- * Check if there's an existing multipart upload for a blob.
1974
- * Returns the upload status including which parts have been uploaded.
1975
- *
1976
- * @param account - The account that owns the blob.
1977
- * @param blobName - The name of the blob.
1978
- * @returns The upload status, or undefined if no pending upload exists.
1979
- *
1980
- * @example
1981
- * ```typescript
1982
- * const status = await client.getMultipartUploadStatus(account, "myblob.txt");
1983
- * if (status) {
1984
- * console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
1985
- * }
1986
- * ```
1987
- */
1988
- async getMultipartUploadStatus(account, blobName) {
1989
- const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
1990
- url.searchParams.set("account", account.toString());
1991
- url.searchParams.set("blobName", blobName);
1992
- const response = await fetch(url.toString(), {
1993
- method: "GET",
1994
- headers: {
1995
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
1996
- }
1997
- });
1998
- if (response.status === 404) {
1999
- return void 0;
2000
- }
2001
- if (!response.ok) {
2002
- const errorBody = await response.text().catch(() => "");
2003
- throw new Error(
2004
- `Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
2005
- );
2006
- }
2007
- return MultipartUploadStatusResponseSchema.parse(await response.json());
2008
- }
2009
2324
  /**
2010
2325
  * Sign a challenge using the given account and return auth credentials.
2011
2326
  *
@@ -2022,269 +2337,142 @@ var ShelbyRPCClient = class {
2022
2337
  publicKey: account.publicKey.toUint8Array()
2023
2338
  };
2024
2339
  }
2025
- async #uploadPart(uploadId, partIdx, partData) {
2026
- const nRetries = 5;
2027
- let lastResponse;
2028
- let lastError;
2029
- let attempts = 0;
2030
- const partUrl = buildRequestUrl(
2031
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2032
- this.baseUrl
2033
- );
2034
- for (let i = 0; i < nRetries; ++i) {
2035
- attempts++;
2036
- try {
2037
- lastResponse = await fetch(partUrl, {
2038
- method: "PUT",
2039
- headers: {
2040
- "Content-Type": "application/octet-stream",
2041
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2042
- },
2043
- body: partData
2044
- });
2045
- lastError = void 0;
2046
- } catch (error) {
2047
- lastError = error;
2048
- if (i < nRetries - 1) {
2049
- const delay = 2 ** i * 100;
2050
- await sleep(delay);
2051
- continue;
2052
- }
2053
- break;
2054
- }
2055
- if (lastResponse.ok) return;
2056
- if (!isRetryableStatus(lastResponse.status)) {
2057
- break;
2058
- }
2059
- if (i < nRetries - 1) {
2060
- const delay = 2 ** i * 100;
2061
- await sleep(delay);
2062
- }
2063
- }
2064
- if (lastError !== void 0) {
2065
- const errorCode = getErrorCode(lastError);
2066
- const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2067
- throw new Error(
2068
- `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}`,
2069
- { cause: lastError }
2070
- );
2071
- }
2072
- const errorBody = await lastResponse?.text().catch(() => "");
2073
- throw new Error(
2074
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2075
- );
2076
- }
2077
- async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
2078
- validateTotalBytes(totalBytes);
2079
- const startResponse = await fetch(
2080
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2081
- {
2082
- method: "POST",
2083
- headers: {
2084
- "Content-Type": "application/json",
2085
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2086
- },
2087
- body: JSON.stringify({
2088
- rawAccount: account.toString(),
2089
- rawBlobName: blobName,
2090
- rawPartSize: partSize
2091
- })
2092
- }
2093
- );
2094
- if (!startResponse.ok) {
2095
- let errorBodyText = "Could not read error body";
2096
- try {
2097
- errorBodyText = await startResponse.text();
2098
- } catch (_e) {
2099
- }
2100
- throw new Error(
2101
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2102
- );
2103
- }
2104
- const { uploadId } = StartMultipartUploadResponseSchema.parse(
2105
- await startResponse.json()
2106
- );
2107
- const totalParts = Math.ceil(totalBytes / partSize);
2108
- let uploadedBytes = 0;
2109
- for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
2110
- await this.#uploadPart(uploadId, partIdx, partData);
2111
- uploadedBytes += partData.length;
2112
- onProgress?.({
2113
- phase: "uploading",
2114
- partIdx,
2115
- totalParts,
2116
- partBytes: partData.length,
2117
- uploadedBytes,
2118
- totalBytes
2119
- });
2120
- }
2121
- if (uploadedBytes !== totalBytes) {
2122
- throw new Error(
2123
- `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
2124
- );
2125
- }
2126
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2127
- onProgress?.({
2128
- phase: "finalizing",
2129
- partIdx: finalPartIdx,
2130
- totalParts,
2131
- // no part uploaded in this phase
2132
- partBytes: 0,
2133
- uploadedBytes: totalBytes,
2134
- totalBytes
2135
- });
2136
- const completeResponse = await fetch(
2137
- buildRequestUrl(
2138
- `/v1/multipart-uploads/${uploadId}/complete`,
2139
- this.baseUrl
2140
- ),
2141
- {
2142
- method: "POST",
2143
- headers: {
2144
- "Content-Type": "application/json",
2145
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2146
- }
2147
- }
2148
- );
2149
- if (!completeResponse.ok) {
2150
- let errorBodyText = "Could not read error body";
2151
- try {
2152
- errorBodyText = await completeResponse.text();
2153
- } catch (_e) {
2154
- }
2155
- throw new Error(
2156
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2157
- );
2158
- }
2159
- }
2160
- /**
2161
- * Uploads blob data to the Shelby RPC node for storage by storage providers.
2162
- * This method should be called after blob commitments have been registered on the blockchain.
2163
- * Uses multipart upload for efficient handling of large files.
2164
- *
2165
- * @param params.account - The account that owns the blob.
2166
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2167
- * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2168
- * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2169
- *
2170
- * @example
2171
- * ```typescript
2172
- * const blobData = new TextEncoder().encode("Hello, world!");
2173
- *
2174
- * await client.putBlob({
2175
- * account: AccountAddress.from("0x1"),
2176
- * blobName: "greetings/hello.txt",
2177
- * blobData,
2178
- * });
2179
- * ```
2180
- */
2181
- async putBlob(params) {
2182
- BlobNameSchema.parse(params.blobName);
2183
- let totalBytes;
2184
- if (params.blobData instanceof Uint8Array) {
2185
- totalBytes = params.totalBytes ?? params.blobData.length;
2186
- if (totalBytes !== params.blobData.length) {
2187
- throw new Error(
2188
- "totalBytes must match blobData.length when blobData is a Uint8Array"
2189
- );
2190
- }
2191
- } else {
2192
- if (params.totalBytes === void 0) {
2193
- throw new Error(
2194
- "totalBytes is required when blobData is a ReadableStream"
2195
- );
2196
- }
2197
- totalBytes = params.totalBytes;
2198
- }
2199
- validateTotalBytes(totalBytes);
2200
- await this.#putBlobMultipart(
2201
- params.account,
2202
- params.blobName,
2203
- params.blobData,
2204
- totalBytes,
2205
- void 0,
2206
- params.onProgress
2207
- );
2208
- }
2209
2340
  /**
2210
- * Upload a part with authentication headers.
2341
+ * Upload a single chunkset via the v2 chunkset API.
2211
2342
  */
2212
- async #uploadPartWithAuth(uploadId, partIdx, partData, auth) {
2213
- const nRetries = 5;
2343
+ async #uploadChunkset(params) {
2344
+ const maxNonBusyRetries = 5;
2214
2345
  let lastResponse;
2215
2346
  let lastError;
2216
2347
  let attempts = 0;
2217
- const partUrl = buildRequestUrl(
2218
- `/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}`,
2219
2352
  this.baseUrl
2220
2353
  );
2221
- const authHeaders = buildAuthHeaders(auth);
2222
- 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
+ }
2223
2359
  attempts++;
2224
2360
  try {
2225
- lastResponse = await fetch(partUrl, {
2361
+ lastResponse = await fetch(chunksetUrl, {
2226
2362
  method: "PUT",
2227
2363
  headers: {
2228
2364
  "Content-Type": "application/octet-stream",
2229
2365
  ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2230
- ...authHeaders
2366
+ ...authHeaders,
2367
+ [INCLUSION_PROOF_HEADER]: params.inclusionProof
2231
2368
  },
2232
- body: partData
2369
+ body: params.chunksetData,
2370
+ signal: params.signal
2233
2371
  });
2234
2372
  lastError = void 0;
2235
2373
  } catch (error) {
2374
+ if (error instanceof DOMException && error.name === "AbortError") {
2375
+ throw error;
2376
+ }
2236
2377
  lastError = error;
2237
- if (i < nRetries - 1) {
2238
- const delay = 2 ** i * 100;
2239
- await sleep(delay);
2378
+ consecutive429s = 0;
2379
+ nonBusyRetries++;
2380
+ if (nonBusyRetries < maxNonBusyRetries) {
2381
+ const delay2 = 2 ** nonBusyRetries * 100;
2382
+ await sleep(delay2);
2240
2383
  continue;
2241
2384
  }
2242
2385
  break;
2243
2386
  }
2244
- 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;
2245
2414
  if (!isRetryableStatus(lastResponse.status)) {
2246
2415
  break;
2247
2416
  }
2248
- if (i < nRetries - 1) {
2249
- const delay = 2 ** i * 100;
2250
- await sleep(delay);
2417
+ nonBusyRetries++;
2418
+ if (nonBusyRetries >= maxNonBusyRetries) {
2419
+ break;
2251
2420
  }
2421
+ const delay = 2 ** nonBusyRetries * 100;
2422
+ await sleep(delay);
2252
2423
  }
2253
2424
  if (lastError !== void 0) {
2254
2425
  const errorCode = getErrorCode(lastError);
2255
2426
  const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2256
2427
  throw new Error(
2257
- `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}`,
2258
2429
  { cause: lastError }
2259
2430
  );
2260
2431
  }
2261
2432
  const errorBody = await lastResponse?.text().catch(() => "");
2262
2433
  throw new Error(
2263
- `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}`
2264
2435
  );
2265
2436
  }
2266
2437
  /**
2267
- * Uploads blob data to the Shelby RPC node with authentication and resume support.
2268
- * 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)
2269
2446
  *
2270
2447
  * @param params.account - The Aptos Account (with signing capability) that owns the blob.
2271
- * @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).
2272
2449
  * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2450
+ * @param params.commitments - Pre-computed blob commitments (from generateCommitments).
2273
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.
2274
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.
2275
2456
  *
2276
2457
  * @example
2277
2458
  * ```typescript
2278
- * await client.putBlobResumable({
2279
- * account: myAccount, // Aptos Account with signing capability
2280
- * 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,
2281
2469
  * blobData: fileData,
2282
- * totalBytes: fileData.length,
2470
+ * commitments,
2471
+ * chunksetConcurrency: 8,
2283
2472
  * });
2284
2473
  * ```
2285
2474
  */
2286
- async putBlobResumable(params) {
2287
- BlobNameSchema.parse(params.blobName);
2475
+ async putBlobChunksets(params) {
2288
2476
  let totalBytes;
2289
2477
  if (params.blobData instanceof Uint8Array) {
2290
2478
  totalBytes = params.totalBytes ?? params.blobData.length;
@@ -2302,110 +2490,146 @@ var ShelbyRPCClient = class {
2302
2490
  totalBytes = params.totalBytes;
2303
2491
  }
2304
2492
  validateTotalBytes(totalBytes);
2305
- 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
+ );
2306
2504
  const { challenge } = await this.getChallenge(
2307
2505
  params.account.accountAddress
2308
2506
  );
2309
2507
  const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
2310
2508
  const auth = signFn(params.account, challenge);
2311
- const authHeaders = buildAuthHeaders(auth);
2312
- const existingUpload = await this.getMultipartUploadStatus(
2313
- params.account.accountAddress,
2314
- params.blobName
2315
- );
2316
- let uploadId;
2317
- let completedPartsSet;
2318
- let totalParts;
2319
- if (existingUpload) {
2320
- uploadId = existingUpload.uploadId;
2321
- completedPartsSet = new Set(existingUpload.completedParts);
2322
- totalParts = existingUpload.nParts;
2323
- if (existingUpload.partSize !== partSize) {
2324
- throw new Error(
2325
- `Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
2326
- );
2327
- }
2328
- } else {
2329
- const startResponse = await fetch(
2330
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2331
- {
2332
- method: "POST",
2333
- headers: {
2334
- "Content-Type": "application/json",
2335
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2336
- ...authHeaders
2337
- },
2338
- body: JSON.stringify({
2339
- rawAccount: params.account.accountAddress.toString(),
2340
- rawBlobName: params.blobName,
2341
- rawPartSize: partSize
2342
- })
2343
- }
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}`
2344
2518
  );
2345
- if (!startResponse.ok) {
2346
- const errorBodyText = await startResponse.text().catch(() => "");
2347
- throw new Error(
2348
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2349
- );
2350
- }
2351
- const parsed = StartMultipartUploadResponseSchema.parse(
2352
- 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.`
2353
2528
  );
2354
- uploadId = parsed.uploadId;
2355
- completedPartsSet = /* @__PURE__ */ new Set();
2356
- totalParts = Math.ceil(totalBytes / partSize);
2357
2529
  }
2358
2530
  let uploadedBytes = 0;
2359
- let partIdx = 0;
2360
- for await (const [idx, partData] of readInChunks(
2361
- params.blobData,
2362
- partSize
2363
- )) {
2364
- partIdx = idx;
2365
- if (completedPartsSet.has(partIdx)) {
2366
- uploadedBytes += partData.length;
2367
- 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;
2368
2537
  }
2369
- await this.#uploadPartWithAuth(uploadId, partIdx, partData, auth);
2370
- uploadedBytes += partData.length;
2371
- params.onProgress?.({
2372
- phase: "uploading",
2373
- partIdx,
2374
- totalParts,
2375
- partBytes: partData.length,
2376
- uploadedBytes,
2377
- 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
2378
2548
  });
2379
2549
  }
2380
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2381
- params.onProgress?.({
2382
- phase: "finalizing",
2383
- partIdx: finalPartIdx,
2384
- totalParts,
2385
- partBytes: 0,
2386
- uploadedBytes: totalBytes,
2387
- totalBytes
2388
- });
2389
- const completeResponse = await fetch(
2390
- buildRequestUrl(
2391
- `/v1/multipart-uploads/${uploadId}/complete`,
2392
- this.baseUrl
2393
- ),
2394
- {
2395
- method: "POST",
2396
- headers: {
2397
- "Content-Type": "application/json",
2398
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2399
- ...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;
2400
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);
2401
2616
  }
2402
- );
2403
- if (!completeResponse.ok) {
2404
- 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) {
2405
2625
  throw new Error(
2406
- `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.`
2407
2627
  );
2408
2628
  }
2629
+ const spAcks = Array.from(
2630
+ aggregatedAcks.entries()
2631
+ ).map(([slot, signature]) => ({ slot, signature }));
2632
+ return { spAcks };
2409
2633
  }
2410
2634
  /**
2411
2635
  * Downloads a blob from the Shelby RPC node.
@@ -2657,6 +2881,59 @@ var ShelbyClient = class {
2657
2881
  }
2658
2882
  return this._provider;
2659
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
+ }
2660
2937
  /**
2661
2938
  * The base URL for the Shelby RPC node.
2662
2939
  */
@@ -2670,8 +2947,8 @@ var ShelbyClient = class {
2670
2947
  *
2671
2948
  * Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
2672
2949
  * For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
2673
- * using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2674
- * with a `ReadableStream`.
2950
+ * using `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
2951
+ * and `coordination.commitObject()` with a `ReadableStream`.
2675
2952
  *
2676
2953
  * @param params.blobData - The raw data to upload as a Uint8Array.
2677
2954
  * @param params.signer - The account that signs and pays for the transaction.
@@ -2694,34 +2971,57 @@ var ShelbyClient = class {
2694
2971
  * ```
2695
2972
  */
2696
2973
  async upload(params) {
2697
- const existingBlobMetadata = await this.coordination.getBlobMetadata({
2698
- account: params.signer.accountAddress,
2699
- 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
2700
2987
  });
2701
- if (!existingBlobMetadata) {
2702
- const provider = await this.getProvider();
2703
- const blobCommitments = await generateCommitments(
2704
- provider,
2705
- 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`
2706
3007
  );
2707
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2708
- account: params.signer,
2709
- blobName: params.blobName,
2710
- blobMerkleRoot: blobCommitments.blob_merkle_root,
2711
- size: params.blobData.length,
2712
- expirationMicros: params.expirationMicros,
2713
- config: provider.config,
2714
- options: params.options
2715
- });
2716
- await this.coordination.aptos.waitForTransaction({
2717
- transactionHash: pendingRegisterBlobTransaction.hash
2718
- });
2719
3008
  }
2720
- await this.rpc.putBlobResumable({
3009
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
2721
3010
  account: params.signer,
3011
+ uid,
2722
3012
  blobName: params.blobName,
2723
- blobData: params.blobData
3013
+ overwrite: true,
3014
+ storageProviderAcks: spAcks,
3015
+ options: this.orderlessCommitOptions()
2724
3016
  });
3017
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3018
+ transactionHash: pendingCommitTransaction.hash
3019
+ });
3020
+ if (!confirmedTx.success) {
3021
+ throw new Error(
3022
+ `commit_object tx failed for '${params.blobName}': ${confirmedTx.vm_status}`
3023
+ );
3024
+ }
2725
3025
  }
2726
3026
  /**
2727
3027
  * Uploads a batch of blobs to the Shelby network.
@@ -2730,8 +3030,8 @@ var ShelbyClient = class {
2730
3030
  *
2731
3031
  * Note: This method accepts only `Uint8Array` and buffers each blob in memory.
2732
3032
  * For streaming uploads of large files, orchestrate the steps manually using
2733
- * `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2734
- * with a `ReadableStream`.
3033
+ * `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
3034
+ * and `coordination.commitObject()` with a `ReadableStream`.
2735
3035
  *
2736
3036
  * @param params.blobs - The blobs to upload.
2737
3037
  * @param params.blobs.blobData - The raw data to upload as a Uint8Array.
@@ -2756,59 +3056,77 @@ var ShelbyClient = class {
2756
3056
  * ```
2757
3057
  */
2758
3058
  async batchUpload(params) {
2759
- const existingBlobs = await this.coordination.getBlobs({
2760
- where: {
2761
- blob_name: {
2762
- _in: params.blobs.map(
2763
- (blob) => createBlobKey({
2764
- account: params.signer.accountAddress,
2765
- blobName: blob.blobName
2766
- })
2767
- )
2768
- }
2769
- }
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
2770
3076
  });
2771
- const blobsToRegister = params.blobs.filter(
2772
- (blob) => !existingBlobs.some(
2773
- (existingBlob) => existingBlob.name === createBlobKey({
2774
- account: params.signer.accountAddress,
2775
- blobName: blob.blobName
2776
- })
2777
- )
3077
+ const registerTx = await this.waitForRegistration(
3078
+ pendingRegisterBlobTransaction.hash
2778
3079
  );
2779
- if (blobsToRegister.length > 0) {
2780
- const provider = await this.getProvider();
2781
- const blobCommitments = await Promise.all(
2782
- blobsToRegister.map(
2783
- async (blob) => generateCommitments(provider, blob.blobData)
2784
- )
2785
- );
2786
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
2787
- account: params.signer,
2788
- expirationMicros: params.expirationMicros,
2789
- blobs: blobsToRegister.map((blob, index) => ({
2790
- blobName: blob.blobName,
2791
- blobSize: blob.blobData.length,
2792
- blobMerkleRoot: blobCommitments[index].blob_merkle_root
2793
- })),
2794
- config: provider.config,
2795
- options: params.options
2796
- });
2797
- await this.coordination.aptos.waitForTransaction({
2798
- transactionHash: pendingRegisterBlobTransaction.hash,
2799
- options: { waitForIndexer: true }
2800
- });
2801
- }
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);
2802
3087
  const limit = pLimit(3);
2803
3088
  await Promise.all(
2804
- params.blobs.map(
2805
- (blob) => limit(
2806
- () => 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({
2807
3102
  account: params.signer,
2808
- blobName: blob.blobName,
2809
- blobData: blob.blobData
2810
- })
2811
- )
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
+ })
2812
3130
  )
2813
3131
  );
2814
3132
  }