@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
@@ -180,6 +180,18 @@ var ERASURE_CODE_PARAMS = {
180
180
  enumIndex: 1
181
181
  }
182
182
  };
183
+ function findErasureSchemeByErasureN(erasureN) {
184
+ return Object.entries(ERASURE_CODE_PARAMS).find(
185
+ ([, params]) => params.erasure_n === erasureN
186
+ );
187
+ }
188
+ function requiredAckCount(erasureN) {
189
+ const scheme = findErasureSchemeByErasureN(erasureN);
190
+ if (!scheme) {
191
+ throw new Error(`Unknown erasure coding scheme with erasure_n=${erasureN}`);
192
+ }
193
+ return scheme[1].erasure_d;
194
+ }
183
195
  var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
184
196
  var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
185
197
  var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
@@ -250,6 +262,12 @@ function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BY
250
262
  if (rawSize === 0) return 1;
251
263
  return Math.ceil(rawSize / chunksetSize);
252
264
  }
265
+ function expectedChunksetInclusionProofHashes(numChunksets) {
266
+ if (numChunksets < 1) {
267
+ throw new Error("numChunksets must be positive");
268
+ }
269
+ return Math.ceil(Math.log2(numChunksets));
270
+ }
253
271
  var BlobCommitmentsSchema = z.object({
254
272
  schema_version: z.string(),
255
273
  raw_data_size: z.number(),
@@ -363,7 +381,7 @@ import { Network as Network2 } from "@aptos-labs/ts-sdk";
363
381
  var DEFAULT_PROJECT_NAME = "shelby";
364
382
  var DEFAULT_PROJECT_DESCRIPTION = "High performance, decentralized storage";
365
383
  var NetworkToShelbyRPCBaseUrl = {
366
- [Network2.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
384
+ [Network2.SHELBYNET]: "https://shelby.shelbynet.shelby.xyz/shelby",
367
385
  [Network2.NETNA]: void 0,
368
386
  [Network2.DEVNET]: void 0,
369
387
  [Network2.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
@@ -398,6 +416,27 @@ var SHELBYUSD_TOKEN_MODULE = "shelby_usd";
398
416
  var SHELBYUSD_TOKEN_NAME = "ShelbyUSD";
399
417
  var SHELBYUSD_FA_METADATA_ADDRESS = "0x1b18363a9f1fe5e6ebf247daba5cc1c18052bb232efdc4c50f556053922d98e1";
400
418
 
419
+ // src/core/types/blobs.ts
420
+ var BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX = {
421
+ Unencrypted: 0,
422
+ AES_GCM_V1: 1
423
+ };
424
+ function blobEncryptionToMoveEnumIndex(encryption) {
425
+ return BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX[encryption];
426
+ }
427
+ function blobEncryptionFromMoveVariant(variant) {
428
+ switch (variant) {
429
+ case "Unencrypted":
430
+ return "Unencrypted";
431
+ case "AES_GCM_V1":
432
+ return "AES_GCM_V1";
433
+ default:
434
+ throw new Error(
435
+ "Could not parse encryption from Shelby Smart Contract, this SDK is out of date."
436
+ );
437
+ }
438
+ }
439
+
401
440
  // src/core/erasure/clay-codes.ts
402
441
  import {
403
442
  createDecoder,
@@ -646,31 +685,80 @@ var getShelbyRPCBaseUrl = (config) => {
646
685
 
647
686
  // src/core/operations/generated/sdk.ts
648
687
  import gql from "graphql-tag";
688
+ var Blob_Activities_Constraint = /* @__PURE__ */ ((Blob_Activities_Constraint2) => {
689
+ Blob_Activities_Constraint2["BlobActivitiesPkey"] = "blob_activities_pkey";
690
+ return Blob_Activities_Constraint2;
691
+ })(Blob_Activities_Constraint || {});
649
692
  var Blob_Activities_Select_Column = /* @__PURE__ */ ((Blob_Activities_Select_Column2) => {
650
- Blob_Activities_Select_Column2["BlobName"] = "blob_name";
651
693
  Blob_Activities_Select_Column2["EventIndex"] = "event_index";
652
694
  Blob_Activities_Select_Column2["EventType"] = "event_type";
695
+ Blob_Activities_Select_Column2["ObjectName"] = "object_name";
653
696
  Blob_Activities_Select_Column2["Owner"] = "owner";
654
697
  Blob_Activities_Select_Column2["Timestamp"] = "timestamp";
655
698
  Blob_Activities_Select_Column2["TransactionHash"] = "transaction_hash";
656
699
  Blob_Activities_Select_Column2["TransactionVersion"] = "transaction_version";
700
+ Blob_Activities_Select_Column2["Uid"] = "uid";
657
701
  return Blob_Activities_Select_Column2;
658
702
  })(Blob_Activities_Select_Column || {});
703
+ var Blob_Activities_Update_Column = /* @__PURE__ */ ((Blob_Activities_Update_Column2) => {
704
+ Blob_Activities_Update_Column2["EventIndex"] = "event_index";
705
+ Blob_Activities_Update_Column2["EventType"] = "event_type";
706
+ Blob_Activities_Update_Column2["ObjectName"] = "object_name";
707
+ Blob_Activities_Update_Column2["Owner"] = "owner";
708
+ Blob_Activities_Update_Column2["Timestamp"] = "timestamp";
709
+ Blob_Activities_Update_Column2["TransactionHash"] = "transaction_hash";
710
+ Blob_Activities_Update_Column2["TransactionVersion"] = "transaction_version";
711
+ Blob_Activities_Update_Column2["Uid"] = "uid";
712
+ return Blob_Activities_Update_Column2;
713
+ })(Blob_Activities_Update_Column || {});
714
+ var Blobs_Constraint = /* @__PURE__ */ ((Blobs_Constraint2) => {
715
+ Blobs_Constraint2["BlobsPkey"] = "blobs_pkey";
716
+ return Blobs_Constraint2;
717
+ })(Blobs_Constraint || {});
659
718
  var Blobs_Select_Column = /* @__PURE__ */ ((Blobs_Select_Column2) => {
660
719
  Blobs_Select_Column2["BlobCommitment"] = "blob_commitment";
661
- Blobs_Select_Column2["BlobName"] = "blob_name";
662
720
  Blobs_Select_Column2["CreatedAt"] = "created_at";
721
+ Blobs_Select_Column2["DeletionReason"] = "deletion_reason";
722
+ Blobs_Select_Column2["Encoding"] = "encoding";
723
+ Blobs_Select_Column2["Encryption"] = "encryption";
724
+ Blobs_Select_Column2["Etag"] = "etag";
663
725
  Blobs_Select_Column2["ExpiresAt"] = "expires_at";
726
+ Blobs_Select_Column2["IsCommitted"] = "is_committed";
664
727
  Blobs_Select_Column2["IsDeleted"] = "is_deleted";
665
- Blobs_Select_Column2["IsWritten"] = "is_written";
728
+ Blobs_Select_Column2["IsPersisted"] = "is_persisted";
666
729
  Blobs_Select_Column2["NumChunksets"] = "num_chunksets";
730
+ Blobs_Select_Column2["ObjectName"] = "object_name";
667
731
  Blobs_Select_Column2["Owner"] = "owner";
732
+ Blobs_Select_Column2["PaymentAmount"] = "payment_amount";
668
733
  Blobs_Select_Column2["PlacementGroup"] = "placement_group";
669
734
  Blobs_Select_Column2["Size"] = "size";
670
735
  Blobs_Select_Column2["SliceAddress"] = "slice_address";
736
+ Blobs_Select_Column2["Uid"] = "uid";
671
737
  Blobs_Select_Column2["UpdatedAt"] = "updated_at";
672
738
  return Blobs_Select_Column2;
673
739
  })(Blobs_Select_Column || {});
740
+ var Blobs_Update_Column = /* @__PURE__ */ ((Blobs_Update_Column2) => {
741
+ Blobs_Update_Column2["BlobCommitment"] = "blob_commitment";
742
+ Blobs_Update_Column2["CreatedAt"] = "created_at";
743
+ Blobs_Update_Column2["DeletionReason"] = "deletion_reason";
744
+ Blobs_Update_Column2["Encoding"] = "encoding";
745
+ Blobs_Update_Column2["Encryption"] = "encryption";
746
+ Blobs_Update_Column2["Etag"] = "etag";
747
+ Blobs_Update_Column2["ExpiresAt"] = "expires_at";
748
+ Blobs_Update_Column2["IsCommitted"] = "is_committed";
749
+ Blobs_Update_Column2["IsDeleted"] = "is_deleted";
750
+ Blobs_Update_Column2["IsPersisted"] = "is_persisted";
751
+ Blobs_Update_Column2["NumChunksets"] = "num_chunksets";
752
+ Blobs_Update_Column2["ObjectName"] = "object_name";
753
+ Blobs_Update_Column2["Owner"] = "owner";
754
+ Blobs_Update_Column2["PaymentAmount"] = "payment_amount";
755
+ Blobs_Update_Column2["PlacementGroup"] = "placement_group";
756
+ Blobs_Update_Column2["Size"] = "size";
757
+ Blobs_Update_Column2["SliceAddress"] = "slice_address";
758
+ Blobs_Update_Column2["Uid"] = "uid";
759
+ Blobs_Update_Column2["UpdatedAt"] = "updated_at";
760
+ return Blobs_Update_Column2;
761
+ })(Blobs_Update_Column || {});
674
762
  var Cursor_Ordering = /* @__PURE__ */ ((Cursor_Ordering2) => {
675
763
  Cursor_Ordering2["Asc"] = "ASC";
676
764
  Cursor_Ordering2["Desc"] = "DESC";
@@ -685,6 +773,10 @@ var Order_By = /* @__PURE__ */ ((Order_By2) => {
685
773
  Order_By2["DescNullsLast"] = "desc_nulls_last";
686
774
  return Order_By2;
687
775
  })(Order_By || {});
776
+ var Placement_Group_Slots_Constraint = /* @__PURE__ */ ((Placement_Group_Slots_Constraint2) => {
777
+ Placement_Group_Slots_Constraint2["PlacementGroupSlotsPkey"] = "placement_group_slots_pkey";
778
+ return Placement_Group_Slots_Constraint2;
779
+ })(Placement_Group_Slots_Constraint || {});
688
780
  var Placement_Group_Slots_Select_Column = /* @__PURE__ */ ((Placement_Group_Slots_Select_Column2) => {
689
781
  Placement_Group_Slots_Select_Column2["PlacementGroup"] = "placement_group";
690
782
  Placement_Group_Slots_Select_Column2["SlotIndex"] = "slot_index";
@@ -693,27 +785,49 @@ var Placement_Group_Slots_Select_Column = /* @__PURE__ */ ((Placement_Group_Slot
693
785
  Placement_Group_Slots_Select_Column2["UpdatedAt"] = "updated_at";
694
786
  return Placement_Group_Slots_Select_Column2;
695
787
  })(Placement_Group_Slots_Select_Column || {});
788
+ var Placement_Group_Slots_Update_Column = /* @__PURE__ */ ((Placement_Group_Slots_Update_Column2) => {
789
+ Placement_Group_Slots_Update_Column2["PlacementGroup"] = "placement_group";
790
+ Placement_Group_Slots_Update_Column2["SlotIndex"] = "slot_index";
791
+ Placement_Group_Slots_Update_Column2["Status"] = "status";
792
+ Placement_Group_Slots_Update_Column2["StorageProvider"] = "storage_provider";
793
+ Placement_Group_Slots_Update_Column2["UpdatedAt"] = "updated_at";
794
+ return Placement_Group_Slots_Update_Column2;
795
+ })(Placement_Group_Slots_Update_Column || {});
796
+ var Processor_Status_Constraint = /* @__PURE__ */ ((Processor_Status_Constraint2) => {
797
+ Processor_Status_Constraint2["ProcessorStatusPkey"] = "processor_status_pkey";
798
+ return Processor_Status_Constraint2;
799
+ })(Processor_Status_Constraint || {});
696
800
  var Processor_Status_Select_Column = /* @__PURE__ */ ((Processor_Status_Select_Column2) => {
697
801
  Processor_Status_Select_Column2["LastSuccessVersion"] = "last_success_version";
698
802
  Processor_Status_Select_Column2["LastTransactionTimestamp"] = "last_transaction_timestamp";
699
803
  Processor_Status_Select_Column2["LastUpdated"] = "last_updated";
804
+ Processor_Status_Select_Column2["Processor"] = "processor";
700
805
  return Processor_Status_Select_Column2;
701
806
  })(Processor_Status_Select_Column || {});
807
+ var Processor_Status_Update_Column = /* @__PURE__ */ ((Processor_Status_Update_Column2) => {
808
+ Processor_Status_Update_Column2["LastSuccessVersion"] = "last_success_version";
809
+ Processor_Status_Update_Column2["LastTransactionTimestamp"] = "last_transaction_timestamp";
810
+ Processor_Status_Update_Column2["LastUpdated"] = "last_updated";
811
+ Processor_Status_Update_Column2["Processor"] = "processor";
812
+ return Processor_Status_Update_Column2;
813
+ })(Processor_Status_Update_Column || {});
702
814
  var GetBlobsDocument = gql`
703
815
  query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
704
816
  blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
817
+ uid
818
+ object_name
705
819
  owner
706
820
  blob_commitment
707
- blob_name
708
821
  created_at
709
822
  expires_at
823
+ updated_at
710
824
  num_chunksets
711
- is_deleted
712
- is_written
713
- placement_group
714
825
  size
715
- updated_at
716
826
  slice_address
827
+ placement_group
828
+ is_persisted
829
+ is_committed
830
+ is_deleted
717
831
  }
718
832
  }
719
833
  `;
@@ -725,7 +839,8 @@ var GetBlobActivitiesDocument = gql`
725
839
  limit: $limit
726
840
  offset: $offset
727
841
  ) {
728
- blob_name
842
+ uid
843
+ object_name
729
844
  event_index
730
845
  event_type
731
846
  transaction_hash
@@ -871,6 +986,33 @@ var MissingTransactionSubmitterError = class extends Error {
871
986
  this.name = "MissingTransactionSubmitterError";
872
987
  }
873
988
  };
989
+ var COMMIT_REJECTION_REASONS = /* @__PURE__ */ new Set([
990
+ "AlreadyExists",
991
+ "NoPriorVersion",
992
+ "EtagMismatch"
993
+ ]);
994
+ var ObjectCommitRejectedError = class extends Error {
995
+ constructor(blobName, uid, reason) {
996
+ super(
997
+ `commit_object rejected the write for '${blobName}' (uid ${uid}): ${reason}`
998
+ );
999
+ this.blobName = blobName;
1000
+ this.uid = uid;
1001
+ this.reason = reason;
1002
+ this.name = "ObjectCommitRejectedError";
1003
+ }
1004
+ };
1005
+ function encryptionFunctionArgs(params) {
1006
+ if (params.omitEncryptionArg) {
1007
+ if (params.encryption && params.encryption !== "Unencrypted") {
1008
+ throw new Error(
1009
+ `Blob encryption (${params.encryption}) is not supported on this network: the deployed contract predates the encryption upgrade (#1739).`
1010
+ );
1011
+ }
1012
+ return [];
1013
+ }
1014
+ return [blobEncryptionToMoveEnumIndex(params.encryption ?? "Unencrypted")];
1015
+ }
874
1016
  var ShelbyBlobClient = class _ShelbyBlobClient {
875
1017
  aptos;
876
1018
  deployer;
@@ -919,7 +1061,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
919
1061
  this.aptos = new Aptos(getAptosConfig(config));
920
1062
  this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
921
1063
  this.indexer = getShelbyIndexerClient(config);
922
- this.defaultOptions = defaultOptions ?? {};
1064
+ this.defaultOptions = {
1065
+ locationHint: config.locationHint,
1066
+ ...defaultOptions
1067
+ };
923
1068
  this.orderless = config.orderless ?? false;
924
1069
  }
925
1070
  /**
@@ -930,7 +1075,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
930
1075
  build: options?.build ?? this.defaultOptions.build,
931
1076
  submit: options?.submit ?? this.defaultOptions.submit,
932
1077
  usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
933
- chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
1078
+ chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes,
1079
+ selectedLocation: options?.selectedLocation ?? this.defaultOptions.selectedLocation,
1080
+ locationHint: options?.locationHint ?? this.defaultOptions.locationHint
934
1081
  };
935
1082
  }
936
1083
  /**
@@ -972,17 +1119,17 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
972
1119
  *
973
1120
  * @example
974
1121
  * ```typescript
975
- * const metadata = await client.getBlobMetadata({
1122
+ * const metadata = await client.getFullObjectMetadata({
976
1123
  * account: AccountAddress.fromString("0x1"),
977
1124
  * name: "foo/bar.txt",
978
1125
  * });
979
1126
  * ```
980
1127
  */
981
- async getBlobMetadata(params) {
1128
+ async getFullObjectMetadata(params) {
982
1129
  try {
983
1130
  const rawMetadata = await this.aptos.view({
984
1131
  payload: {
985
- function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
1132
+ function: `${this.deployer.toString()}::blob_metadata::get_full_object_metadata`,
986
1133
  functionArguments: [
987
1134
  createBlobKey({
988
1135
  account: params.account,
@@ -994,47 +1141,94 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
994
1141
  if (!rawMetadata?.[0]?.vec?.[0]) {
995
1142
  return void 0;
996
1143
  }
997
- const metadata = rawMetadata[0].vec[0];
998
- let encoding;
999
- if (metadata.encoding.__variant__ === "ClayCode_16Total_10Data_13Helper") {
1000
- encoding = {
1001
- variant: "clay",
1002
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
1003
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
1004
- };
1005
- } else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
1006
- encoding = {
1007
- variant: "clay",
1008
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
1009
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
1010
- };
1011
- } else {
1012
- throw new Error(
1013
- "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
1014
- );
1015
- }
1016
- return {
1017
- blobMerkleRoot: Hex3.fromHexInput(
1018
- metadata.blob_commitment
1019
- ).toUint8Array(),
1020
- owner: normalizeAddress(metadata.owner),
1144
+ const view = rawMetadata[0].vec[0];
1145
+ return this.parseBlobMetadata(view.blob_metadata, {
1146
+ uid: BigInt(view.object_metadata.current_blob_uid),
1021
1147
  name: params.name,
1022
- blobNameSuffix: getBlobNameSuffix(params.name),
1023
- size: Number(metadata.blob_size),
1024
- encoding,
1025
- expirationMicros: Number(metadata.expiration_micros),
1026
- creationMicros: Number(metadata.creation_micros),
1027
- sliceAddress: normalizeAddress(metadata.slice.inner),
1028
- isWritten: metadata.is_written
1029
- };
1148
+ // Any blob bound under an object name has cleared the commit-time
1149
+ // `is_written()` check, so a resolved object is always written.
1150
+ isWritten: true
1151
+ });
1030
1152
  } catch (error) {
1031
1153
  if (error instanceof Error && // Depending on the network, the error message may show up differently.
1032
- (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
1154
+ (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND") || error.message?.includes("EOBJECT_NOT_FOUND"))) {
1155
+ return void 0;
1156
+ }
1157
+ throw error;
1158
+ }
1159
+ }
1160
+ /**
1161
+ * Retrieves blob metadata directly by its on-chain UID, including blobs in
1162
+ * the pending (registered-but-not-yet-committed) state that
1163
+ * {@link getFullObjectMetadata} cannot resolve by object name. Returns `undefined`
1164
+ * if no blob has that UID.
1165
+ *
1166
+ * The returned `name`/`blobNameSuffix` are empty: the blob layer is keyed by
1167
+ * UID and carries no object name (a name binding is established only at
1168
+ * commit). `isWritten` reflects whether the blob has been committed.
1169
+ */
1170
+ async getFullObjectMetadataByUid(uid) {
1171
+ try {
1172
+ const rawMetadata = await this.aptos.view(
1173
+ {
1174
+ payload: {
1175
+ function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
1176
+ functionArguments: [uid]
1177
+ }
1178
+ }
1179
+ );
1180
+ if (!rawMetadata?.[0]?.vec?.[0]) {
1181
+ return void 0;
1182
+ }
1183
+ const raw = rawMetadata[0].vec[0];
1184
+ return this.parseBlobMetadata(raw, {
1185
+ uid,
1186
+ name: "",
1187
+ isWritten: raw.state.__variant__ === "CommittedObject"
1188
+ });
1189
+ } catch (error) {
1190
+ if (error instanceof Error && (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
1033
1191
  return void 0;
1034
1192
  }
1035
1193
  throw error;
1036
1194
  }
1037
1195
  }
1196
+ /**
1197
+ * Parse the on-chain `BlobMetadata::V1` view shape into the SDK
1198
+ * {@link FullObjectMetadata}. `uid` / `name` / `isWritten` are supplied by the
1199
+ * caller since they depend on the lookup path (by object name vs by UID).
1200
+ */
1201
+ parseBlobMetadata(raw, extra) {
1202
+ const { content } = raw;
1203
+ const variant = content.encoding.__variant__;
1204
+ if (variant !== "ClayCode_16Total_10Data_13Helper" && variant !== "ClayCode_4Total_2Data_3Helper") {
1205
+ throw new Error(
1206
+ "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
1207
+ );
1208
+ }
1209
+ const encoding = {
1210
+ variant: "clay",
1211
+ ...ERASURE_CODE_PARAMS[variant],
1212
+ ...ERASURE_CODE_AND_CHUNK_MAPPING[variant]
1213
+ };
1214
+ const encryption = blobEncryptionFromMoveVariant(
1215
+ content.encryption?.__variant__ ?? "Unencrypted"
1216
+ );
1217
+ return {
1218
+ uid: extra.uid,
1219
+ blobMerkleRoot: Hex3.fromHexInput(content.blob_commitment).toUint8Array(),
1220
+ owner: normalizeAddress(raw.owner),
1221
+ name: extra.name,
1222
+ blobNameSuffix: extra.name ? getBlobNameSuffix(extra.name) : "",
1223
+ size: Number(content.blob_size),
1224
+ encoding,
1225
+ encryption,
1226
+ expirationMicros: Number(raw.expiration_micros),
1227
+ creationMicros: Number(raw.creation_micros),
1228
+ sliceAddress: normalizeAddress(raw.slice.inner),
1229
+ isWritten: extra.isWritten
1230
+ };
1231
+ }
1038
1232
  /**
1039
1233
  * Retrieves all the blobs and their metadata for an account from the
1040
1234
  * blockchain.
@@ -1046,7 +1240,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1046
1240
  *
1047
1241
  * @example
1048
1242
  * ```typescript
1049
- * // BlobMetadata[]
1243
+ * // FullObjectMetadata[]
1050
1244
  * const blobs = await client.getAccountBlobs({
1051
1245
  * account: AccountAddress.fromString("0x1"),
1052
1246
  * });
@@ -1063,6 +1257,25 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1063
1257
  orderBy: rest.orderBy
1064
1258
  });
1065
1259
  }
1260
+ /**
1261
+ * Object-facing default filter: only committed, non-deleted, unexpired rows.
1262
+ * The blobs table is UID-keyed, so during an atomic overwrite a single
1263
+ * object_name transiently has two non-deleted rows — the currently committed
1264
+ * blob and the new pending (is_committed = "0") blob. Filtering on
1265
+ * is_committed keeps name lookups/listings pinned to the committed object and
1266
+ * avoids returning or duplicating the in-flight pending row. Applied to
1267
+ * getBlobs *and* the getBlobsCount / getTotalBlobsSize aggregates so counts
1268
+ * and sizes agree with the listing mid-overwrite. Callers can override any
1269
+ * key (e.g. is_committed) via their own `where`.
1270
+ */
1271
+ activeBlobsWhere(where) {
1272
+ const defaultActiveFilter = {
1273
+ expires_at: { _gte: String(Date.now() * 1e3) },
1274
+ is_deleted: { _eq: "0" },
1275
+ is_committed: { _eq: "1" }
1276
+ };
1277
+ return { ...defaultActiveFilter, ...where };
1278
+ }
1066
1279
  /**
1067
1280
  * Retrieves blobs and their metadata from the blockchain.
1068
1281
  *
@@ -1073,7 +1286,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1073
1286
  *
1074
1287
  * @example
1075
1288
  * ```typescript
1076
- * // BlobMetadata[]
1289
+ * // FullObjectMetadata[]
1077
1290
  * const blobs = await client.getBlobs({
1078
1291
  * where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
1079
1292
  * });
@@ -1082,12 +1295,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1082
1295
  async getBlobs(params = {}) {
1083
1296
  const { limit, offset } = params.pagination ?? {};
1084
1297
  const { orderBy, where } = params;
1085
- const currentMicros = String(Date.now() * 1e3);
1086
- const defaultActiveFilter = {
1087
- expires_at: { _gte: currentMicros },
1088
- is_deleted: { _eq: "0" }
1089
- };
1090
- const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
1298
+ const finalWhere = this.activeBlobsWhere(where);
1091
1299
  const { blobs } = await this.indexer.getBlobs({
1092
1300
  where: finalWhere,
1093
1301
  limit,
@@ -1096,9 +1304,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1096
1304
  });
1097
1305
  return blobs.map(
1098
1306
  (blob) => ({
1307
+ uid: BigInt(blob.uid),
1099
1308
  owner: normalizeAddress(blob.owner),
1100
- name: blob.blob_name,
1101
- blobNameSuffix: getBlobNameSuffix(blob.blob_name),
1309
+ name: blob.object_name,
1310
+ blobNameSuffix: getBlobNameSuffix(blob.object_name),
1102
1311
  blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
1103
1312
  size: Number(blob.size),
1104
1313
  // TODO: Add encoding when supported in NCI
@@ -1110,7 +1319,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1110
1319
  expirationMicros: Number(blob.expires_at),
1111
1320
  creationMicros: Number(blob.created_at),
1112
1321
  sliceAddress: normalizeAddress(blob.slice_address),
1113
- isWritten: Boolean(Number(blob.is_written)),
1322
+ isWritten: Boolean(Number(blob.is_persisted)),
1114
1323
  isDeleted: Boolean(Number(blob.is_deleted))
1115
1324
  })
1116
1325
  );
@@ -1128,12 +1337,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1128
1337
  [`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
1129
1338
  [`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
1130
1339
  [`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
1131
- [`${this.deployer.toStringLong()}::blob_metadata::BlobWrittenEvent`]: "write_blob"
1340
+ [`${this.deployer.toStringLong()}::blob_metadata::BlobPersistedEvent`]: "write_blob",
1341
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommittedEvent`]: "commit_object",
1342
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectDeletedEvent`]: "delete_object",
1343
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`]: "reject_object_commit"
1132
1344
  };
1133
1345
  return blob_activities.map(
1134
1346
  (activity) => ({
1135
- blobName: activity.blob_name,
1136
- accountAddress: normalizeAddress(activity.blob_name.substring(1, 65)),
1347
+ blobName: activity.object_name,
1348
+ accountAddress: normalizeAddress(activity.object_name.substring(1, 65)),
1137
1349
  type: activityTypeMapping[activity.event_type] ?? "unknown",
1138
1350
  eventType: activity.event_type,
1139
1351
  eventIndex: Number(activity.event_index),
@@ -1156,9 +1368,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1156
1368
  * const count = await client.getBlobsCount();
1157
1369
  * ```
1158
1370
  */
1159
- async getBlobsCount(params) {
1160
- const { where } = params;
1161
- const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
1371
+ async getBlobsCount(params = {}) {
1372
+ const { blobs_aggregate } = await this.indexer.getBlobsCount({
1373
+ where: this.activeBlobsWhere(params.where)
1374
+ });
1162
1375
  return blobs_aggregate?.aggregate?.count ?? 0;
1163
1376
  }
1164
1377
  /**
@@ -1173,8 +1386,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1173
1386
  * ```
1174
1387
  */
1175
1388
  async getTotalBlobsSize(params = {}) {
1176
- const { where } = params;
1177
- const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({ where });
1389
+ const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({
1390
+ where: this.activeBlobsWhere(params.where)
1391
+ });
1178
1392
  return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
1179
1393
  }
1180
1394
  /**
@@ -1233,12 +1447,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1233
1447
  deployer: this.deployer,
1234
1448
  account: params.account.accountAddress,
1235
1449
  blobName: params.blobName,
1450
+ selectedLocation: options.selectedLocation,
1451
+ locationHint: options.locationHint,
1236
1452
  blobSize: params.size,
1237
1453
  blobMerkleRoot: params.blobMerkleRoot,
1238
1454
  numChunksets: expectedTotalChunksets(params.size, chunksetSize),
1239
1455
  expirationMicros: params.expirationMicros,
1240
1456
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1241
- encoding: config.enumIndex
1457
+ encoding: config.enumIndex,
1458
+ encryption: params.encryption
1242
1459
  }),
1243
1460
  sender: params.account.accountAddress
1244
1461
  };
@@ -1266,16 +1483,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1266
1483
  * @example
1267
1484
  * ```typescript
1268
1485
  *
1269
- * const { transaction } = await client.deleteBlob({
1486
+ * const { transaction } = await client.deleteObject({
1270
1487
  * account: signer,
1271
1488
  * blobName: "foo/bar.txt",
1272
1489
  * });
1273
1490
  * ```
1274
1491
  */
1275
- async deleteBlob(params) {
1492
+ async deleteObject(params) {
1276
1493
  const transaction = await this.aptos.transaction.build.simple({
1277
1494
  options: this.orderlessTxOptions(params.options),
1278
- data: _ShelbyBlobClient.createDeleteBlobPayload({
1495
+ data: _ShelbyBlobClient.createDeleteObjectPayload({
1279
1496
  deployer: this.deployer,
1280
1497
  blobName: params.blobName
1281
1498
  }),
@@ -1291,10 +1508,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1291
1508
  /**
1292
1509
  * Deletes multiple blobs on the blockchain in a single atomic transaction.
1293
1510
  *
1294
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1295
- * which will be deployed to the smart contract on 2026-02-04. Using this
1296
- * function before that date will result in a transaction failure.
1297
- *
1298
1511
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1299
1512
  * the entire transaction fails and no blobs are deleted.
1300
1513
  *
@@ -1309,16 +1522,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1309
1522
  * @example
1310
1523
  * ```typescript
1311
1524
  *
1312
- * const { transaction } = await client.deleteMultipleBlobs({
1525
+ * const { transaction } = await client.deleteMultipleObjects({
1313
1526
  * account: signer,
1314
1527
  * blobNames: ["foo/bar.txt", "baz.txt"],
1315
1528
  * });
1316
1529
  * ```
1317
1530
  */
1318
- async deleteMultipleBlobs(params) {
1531
+ async deleteMultipleObjects(params) {
1319
1532
  const transaction = await this.aptos.transaction.build.simple({
1320
1533
  options: this.orderlessTxOptions(params.options),
1321
- data: _ShelbyBlobClient.createDeleteMultipleBlobsPayload({
1534
+ data: _ShelbyBlobClient.createDeleteMultipleObjectsPayload({
1322
1535
  deployer: this.deployer,
1323
1536
  blobNames: params.blobNames
1324
1537
  }),
@@ -1376,6 +1589,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1376
1589
  data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
1377
1590
  deployer: this.deployer,
1378
1591
  account: params.account.accountAddress,
1592
+ selectedLocation: options.selectedLocation,
1593
+ locationHint: options.locationHint,
1379
1594
  expirationMicros: params.expirationMicros,
1380
1595
  blobs: params.blobs.map((blob) => ({
1381
1596
  blobName: blob.blobName,
@@ -1384,7 +1599,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1384
1599
  numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
1385
1600
  })),
1386
1601
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1387
- encoding: config.enumIndex
1602
+ encoding: config.enumIndex,
1603
+ encryption: params.encryption
1388
1604
  })
1389
1605
  };
1390
1606
  const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
@@ -1399,6 +1615,55 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1399
1615
  })
1400
1616
  };
1401
1617
  }
1618
+ /**
1619
+ * Extracts the on-chain UIDs assigned at registration from a committed
1620
+ * register transaction's events.
1621
+ *
1622
+ * `register_blob` / `register_multiple_blobs` create *pending* blobs and emit
1623
+ * one `BlobRegisteredEvent` per blob. The UID is published only on this event
1624
+ * (the blob is not yet in the `objects` map, so it cannot be read back by
1625
+ * name), so callers must parse it here before uploading bytes or committing.
1626
+ *
1627
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1628
+ * @param deployer - The contract deployer address.
1629
+ * @returns One entry per registered blob, keyed by its full object name
1630
+ * (`@<owner>/<suffix>`, matching {@link createBlobKey}).
1631
+ */
1632
+ static registeredBlobUids(events, deployer) {
1633
+ const eventType = `${deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`;
1634
+ return events.filter((event) => event.type === eventType).map((event) => {
1635
+ const data = event.data;
1636
+ return { objectName: data.object_name, uid: BigInt(data.uid) };
1637
+ });
1638
+ }
1639
+ /**
1640
+ * Detects whether `commit_object` rejected the write for `uid` rather than
1641
+ * applying it. A rejected commit is still a *successful* transaction — the
1642
+ * contract tears down the pending blob and emits `ObjectCommitRejectedEvent`
1643
+ * instead of aborting — so callers must inspect the finalized transaction's
1644
+ * events to tell a durable write apart from a silent no-op.
1645
+ *
1646
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1647
+ * @param deployer - The contract deployer address.
1648
+ * @param uid - The UID passed to `commit_object`.
1649
+ * @returns The rejection reason, or `undefined` if the commit was applied.
1650
+ */
1651
+ static findObjectCommitRejection(events, deployer, uid) {
1652
+ const eventType = `${deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`;
1653
+ const rejection = events.find(
1654
+ (event) => event.type === eventType && BigInt(event.data.uid) === uid
1655
+ );
1656
+ if (!rejection) {
1657
+ return void 0;
1658
+ }
1659
+ const reason = rejection.data.rejection_reason.__variant__;
1660
+ if (!COMMIT_REJECTION_REASONS.has(reason)) {
1661
+ throw new Error(
1662
+ `Unrecognized ObjectCommitRejectedEvent rejection_reason '${reason}' for uid ${uid}`
1663
+ );
1664
+ }
1665
+ return reason;
1666
+ }
1402
1667
  /**
1403
1668
  * Creates a transaction payload to register a blob on the blockchain.
1404
1669
  * This is a static helper method for constructing the Move function call payload.
@@ -1412,8 +1677,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1412
1677
  * @param params.numChunksets - The total number of chunksets in the blob.
1413
1678
  *
1414
1679
  * @returns An Aptos transaction payload data object for the register_blob Move function.
1415
- *
1416
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1417
1680
  */
1418
1681
  static createRegisterBlobPayload(params) {
1419
1682
  const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
@@ -1421,6 +1684,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1421
1684
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1422
1685
  functionArguments: [
1423
1686
  params.blobName,
1687
+ params.selectedLocation ?? null,
1688
+ params.locationHint ?? null,
1424
1689
  params.expirationMicros,
1425
1690
  Hex3.fromHexString(params.blobMerkleRoot).toUint8Array(),
1426
1691
  params.numChunksets,
@@ -1428,7 +1693,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1428
1693
  // TODO
1429
1694
  0,
1430
1695
  // payment tier
1431
- params.encoding
1696
+ params.encoding,
1697
+ ...encryptionFunctionArgs(params)
1432
1698
  ]
1433
1699
  };
1434
1700
  }
@@ -1446,8 +1712,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1446
1712
  * @param params.blobs.numChunksets - The total number of chunksets in the blob.
1447
1713
  *
1448
1714
  * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
1449
- *
1450
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1451
1715
  */
1452
1716
  static createBatchRegisterBlobsPayload(params) {
1453
1717
  const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
@@ -1467,6 +1731,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1467
1731
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1468
1732
  functionArguments: [
1469
1733
  blobNames,
1734
+ params.selectedLocation ?? null,
1735
+ params.locationHint ?? null,
1470
1736
  params.expirationMicros,
1471
1737
  blobMerkleRoots,
1472
1738
  blobNumChunksets,
@@ -1474,7 +1740,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1474
1740
  // TODO
1475
1741
  0,
1476
1742
  // payment tier
1477
- params.encoding
1743
+ params.encoding,
1744
+ ...encryptionFunctionArgs(params)
1478
1745
  ]
1479
1746
  };
1480
1747
  }
@@ -1485,24 +1752,20 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1485
1752
  * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
1486
1753
  * @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
1487
1754
  *
1488
- * @returns An Aptos transaction payload data object for the delete_blob Move function.
1489
- *
1490
- * @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
1755
+ * @returns An Aptos transaction payload data object for the delete_object Move function.
1491
1756
  */
1492
- static createDeleteBlobPayload(params) {
1757
+ static createDeleteObjectPayload(params) {
1493
1758
  return {
1494
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_blob`,
1495
- functionArguments: [params.blobName]
1759
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_object`,
1760
+ // Second arg is `if_match_etag: Option<vector<u8>>`; `null` => None
1761
+ // (unconditional delete).
1762
+ functionArguments: [params.blobName, null]
1496
1763
  };
1497
1764
  }
1498
1765
  /**
1499
1766
  * Creates a transaction payload to delete multiple blobs on the blockchain.
1500
1767
  * This is a static helper method for constructing the Move function call payload.
1501
1768
  *
1502
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1503
- * which will be deployed to the smart contract on 2026-02-04. Using this
1504
- * function before that date will result in a transaction failure.
1505
- *
1506
1769
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1507
1770
  * the entire transaction fails and no blobs are deleted.
1508
1771
  *
@@ -1511,42 +1774,71 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1511
1774
  * (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
1512
1775
  * prefix is automatically derived from the transaction sender.
1513
1776
  *
1514
- * @returns An Aptos transaction payload data object for the delete_multiple_blobs Move function.
1515
- *
1516
- * @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
1777
+ * @returns An Aptos transaction payload data object for the delete_multiple_objects Move function.
1517
1778
  */
1518
- static createDeleteMultipleBlobsPayload(params) {
1779
+ static createDeleteMultipleObjectsPayload(params) {
1519
1780
  return {
1520
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_blobs`,
1521
- functionArguments: [params.blobNames]
1781
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_objects`,
1782
+ // Second arg is `if_match_etags: vector<Option<vector<u8>>>`; an empty
1783
+ // vector means "no etag check on any object" (unconditional delete).
1784
+ functionArguments: [params.blobNames, []]
1522
1785
  };
1523
1786
  }
1524
- static createBlobAcknowledgementsPayload(params) {
1525
- const ackBitMask = params.storageProviderAcks.reduce(
1787
+ /**
1788
+ * Sort acks by slot and reduce to the `(ack_bits, signatures)` pair the
1789
+ * contract expects: it walks the set bits low-to-high and consumes the
1790
+ * signatures in that same order.
1791
+ */
1792
+ static encodeAcks(storageProviderAcks) {
1793
+ const sortedAcks = [...storageProviderAcks].sort((a, b) => a.slot - b.slot);
1794
+ const ackBitMask = sortedAcks.reduce(
1526
1795
  (acc, ack) => acc | 1 << ack.slot,
1527
1796
  0
1528
1797
  );
1529
1798
  return {
1530
- function: `${SHELBY_DEPLOYER}::blob_metadata::add_blob_acknowledgements`,
1799
+ ackBits: new U32(Number(ackBitMask)),
1800
+ signatures: sortedAcks.map((ack) => ack.signature)
1801
+ };
1802
+ }
1803
+ /**
1804
+ * Payload for `commit_object(uid, object_name_suffix, overwrite, if_match_etag,
1805
+ * ack_bits, signatures)` — binds a written pending blob under its object name,
1806
+ * finalizing the upload. SP acks may be batched in here (the contract applies
1807
+ * them before the `is_written` check), so register → upload → commit needs
1808
+ * only a single finalize transaction.
1809
+ *
1810
+ * @param params.uid - The blob UID returned at registration.
1811
+ * @param params.blobName - The object name suffix the blob was registered under.
1812
+ * @param params.overwrite - Allow replacing an existing binding under this name.
1813
+ * @param params.storageProviderAcks - Acks applied atomically with the commit.
1814
+ */
1815
+ static createCommitObjectPayload(params) {
1816
+ const { ackBits, signatures } = _ShelbyBlobClient.encodeAcks(
1817
+ params.storageProviderAcks
1818
+ );
1819
+ return {
1820
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::commit_object`,
1531
1821
  functionArguments: [
1532
- createBlobKey({
1533
- account: params.blobOwner,
1534
- blobName: params.blobName
1535
- }),
1536
- params.creationMicros,
1537
- new U32(Number(ackBitMask)),
1538
- params.storageProviderAcks.map((ack) => ack.signature)
1822
+ params.uid,
1823
+ params.blobName,
1824
+ params.overwrite,
1825
+ // `if_match_etag: Option<vector<u8>>` is `null` => None; conditional
1826
+ // (CAS) writes are not yet exposed by the SDK.
1827
+ null,
1828
+ ackBits,
1829
+ signatures
1539
1830
  ]
1540
1831
  };
1541
1832
  }
1542
- async addBlobAcknowledgements(params) {
1833
+ async commitObject(params) {
1543
1834
  const transaction = await this.aptos.transaction.build.simple({
1544
1835
  ...params.options?.build,
1545
1836
  options: this.orderlessTxOptions(params.options?.build?.options),
1546
- data: _ShelbyBlobClient.createBlobAcknowledgementsPayload({
1547
- blobOwner: params.blobOwner,
1837
+ data: _ShelbyBlobClient.createCommitObjectPayload({
1838
+ deployer: this.deployer,
1839
+ uid: params.uid,
1548
1840
  blobName: params.blobName,
1549
- creationMicros: params.creationMicros,
1841
+ overwrite: params.overwrite,
1550
1842
  storageProviderAcks: params.storageProviderAcks
1551
1843
  }),
1552
1844
  sender: params.account.accountAddress
@@ -1569,12 +1861,99 @@ import {
1569
1861
  } from "@aptos-labs/ts-sdk";
1570
1862
  import pLimit from "p-limit";
1571
1863
 
1864
+ // src/core/errors.ts
1865
+ var ShelbyErrorCodes = {
1866
+ // blob_metadata.move errors
1867
+ E_BLOB_NOT_FOUND: "E_BLOB_NOT_FOUND",
1868
+ E_INVALID_EXPIRATION_TIME: "E_INVALID_EXPIRATION_TIME",
1869
+ E_TEST_ENCODING_NOT_ALLOWED: "E_TEST_ENCODING_NOT_ALLOWED",
1870
+ E_BLOB_NOT_EXPIRED: "E_BLOB_NOT_EXPIRED",
1871
+ E_INVALID_CHUNKSET_COUNT: "E_INVALID_CHUNKSET_COUNT",
1872
+ E_NOT_BLOB_OWNER: "E_NOT_BLOB_OWNER",
1873
+ E_NOT_ADMIN: "E_NOT_ADMIN",
1874
+ E_BLOB_EXPIRED: "E_BLOB_EXPIRED",
1875
+ E_BLOB_NAME_TOO_LONG: "E_BLOB_NAME_TOO_LONG",
1876
+ E_INVALID_PAYMENT_EPOCHS: "E_INVALID_PAYMENT_EPOCHS",
1877
+ E_NO_SLICES_AVAILABLE: "E_NO_SLICES_AVAILABLE",
1878
+ // Common Aptos/Move errors
1879
+ EALREADY_EXISTS: "EALREADY_EXISTS"
1880
+ };
1881
+ function isBlobAlreadyExistsError(errorMessage) {
1882
+ return errorMessage.includes(ShelbyErrorCodes.EALREADY_EXISTS) || errorMessage.includes("already exists");
1883
+ }
1884
+ function isAccessDeniedError(errorMessage) {
1885
+ return errorMessage.includes(ShelbyErrorCodes.E_NOT_BLOB_OWNER) || errorMessage.includes(ShelbyErrorCodes.E_NOT_ADMIN) || errorMessage.includes("ENOT_AUTHORIZED") || errorMessage.includes("not authorized") || errorMessage.includes("permission denied");
1886
+ }
1887
+ function isBlobNotFoundError(errorMessage) {
1888
+ return errorMessage.includes(ShelbyErrorCodes.E_BLOB_NOT_FOUND);
1889
+ }
1890
+ function isBlobExpiredError(errorMessage) {
1891
+ return errorMessage.includes(ShelbyErrorCodes.E_BLOB_EXPIRED);
1892
+ }
1893
+ var ShelbyLocationErrorCodes = {
1894
+ E_LOCATION_NOT_FOUND: "E_LOCATION_NOT_FOUND",
1895
+ E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK: "E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK",
1896
+ E_NO_LOCATION_SELECTED: "E_NO_LOCATION_SELECTED",
1897
+ E_LOCATION_WRITES_FROZEN: "E_LOCATION_WRITES_FROZEN",
1898
+ E_LOCATION_NOT_ACTIVATED: "E_LOCATION_NOT_ACTIVATED"
1899
+ };
1900
+ function describeLocationError(errorMessage) {
1901
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_WRITES_FROZEN)) {
1902
+ return "Cannot write: the resolved location is frozen and not currently accepting new data.";
1903
+ }
1904
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_ACTIVATED)) {
1905
+ return "Cannot write: the resolved location is not activated yet.";
1906
+ }
1907
+ if (errorMessage.includes(
1908
+ ShelbyLocationErrorCodes.E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK
1909
+ )) {
1910
+ return "Cannot write: the selected location conflicts with the account's locked location preference.";
1911
+ }
1912
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_NO_LOCATION_SELECTED)) {
1913
+ return "No write location could be resolved: none was selected and the account has no default location or location hint.";
1914
+ }
1915
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_FOUND)) {
1916
+ return "Cannot write: the requested location does not exist.";
1917
+ }
1918
+ return void 0;
1919
+ }
1920
+
1572
1921
  // src/core/clients/ShelbyMetadataClient.ts
1573
1922
  import {
1574
1923
  AccountAddress as AccountAddress3,
1575
1924
  Aptos as Aptos2,
1576
1925
  Hex as Hex4
1577
1926
  } from "@aptos-labs/ts-sdk";
1927
+
1928
+ // src/core/types/storage_providers.ts
1929
+ var ACTIVE_PROVIDER_CONDITIONS = [
1930
+ "Normal",
1931
+ "Faulty",
1932
+ "Leaving",
1933
+ "Evicted",
1934
+ "PendingLeaving"
1935
+ ];
1936
+ function isActiveProviderCondition(value) {
1937
+ return typeof value === "string" && ACTIVE_PROVIDER_CONDITIONS.includes(value);
1938
+ }
1939
+ var TASK_ELIGIBLE_CONDITIONS = /* @__PURE__ */ new Set([
1940
+ "Normal",
1941
+ "PendingLeaving"
1942
+ ]);
1943
+ function isActiveForTasks(condition) {
1944
+ return TASK_ELIGIBLE_CONDITIONS.has(condition);
1945
+ }
1946
+
1947
+ // src/core/clients/ShelbyMetadataClient.ts
1948
+ function parseActiveProviderCondition(raw) {
1949
+ const variant = raw?.__variant__;
1950
+ if (!isActiveProviderCondition(variant)) {
1951
+ throw new Error(
1952
+ `Unknown ActiveProviderCondition variant: ${JSON.stringify(raw)}`
1953
+ );
1954
+ }
1955
+ return variant;
1956
+ }
1578
1957
  function parseStorageProviderState(raw) {
1579
1958
  switch (raw.__variant__) {
1580
1959
  case "Active":
@@ -1582,8 +1961,7 @@ function parseStorageProviderState(raw) {
1582
1961
  variant: "Active",
1583
1962
  quota: raw.quota.value,
1584
1963
  stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
1585
- faulty: raw.faulty,
1586
- leaving: raw.leaving
1964
+ condition: parseActiveProviderCondition(raw.condition)
1587
1965
  };
1588
1966
  case "Waitlisted":
1589
1967
  return {
@@ -1596,6 +1974,10 @@ function parseStorageProviderState(raw) {
1596
1974
  frozenFrom: raw.frozen_from,
1597
1975
  frozenTill: raw.frozen_till
1598
1976
  };
1977
+ default:
1978
+ throw new Error(
1979
+ `Unknown StorageProviderStateDetails variant: ${JSON.stringify(raw)}`
1980
+ );
1599
1981
  }
1600
1982
  }
1601
1983
  var ShelbyMetadataClient = class {
@@ -1654,28 +2036,52 @@ var ShelbyMetadataClient = class {
1654
2036
  }
1655
2037
  }
1656
2038
  /**
1657
- * Retrieves the list of placement group addresses.
2039
+ * Retrieves the names of every activated location (region). Locations that are
2040
+ * registered but not yet brought online are admin-internal and not listed.
2041
+ *
2042
+ * @returns The location name list.
2043
+ *
2044
+ * @example
2045
+ * ```typescript
2046
+ * const locations = await client.getLocationNames();
2047
+ * ```
2048
+ */
2049
+ async getLocationNames() {
2050
+ const names = await this.aptos.view({
2051
+ payload: {
2052
+ function: `${this.deployer.toString()}::location::activated_location_names`,
2053
+ functionArguments: []
2054
+ }
2055
+ });
2056
+ return names[0];
2057
+ }
2058
+ /**
2059
+ * Retrieves the list of placement group addresses in a location.
1658
2060
  *
2061
+ * @param locationName - The location whose placement groups to list.
1659
2062
  * @returns The placement group address list, or an empty array if none exist.
1660
2063
  *
1661
2064
  * @example
1662
2065
  * ```typescript
1663
- * const pgList = await client.getPlacementGroupAddresses();
2066
+ * const pgList = await client.getPlacementGroupAddresses("us-east-1");
1664
2067
  * ```
1665
2068
  */
1666
- async getPlacementGroupAddresses() {
2069
+ async getPlacementGroupAddresses(locationName) {
1667
2070
  try {
1668
2071
  const pgSizeMetadata = await this.aptos.view({
1669
2072
  payload: {
1670
2073
  function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
1671
- functionArguments: []
2074
+ functionArguments: [locationName]
1672
2075
  }
1673
2076
  });
2077
+ if (Number(pgSizeMetadata[0]) === 0) {
2078
+ return [];
2079
+ }
1674
2080
  const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
1675
2081
  const addressMetadataArray = await this.aptos.view({
1676
2082
  payload: {
1677
2083
  function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
1678
- functionArguments: [0, finalPlacementGroupIndex]
2084
+ functionArguments: [locationName, 0, finalPlacementGroupIndex]
1679
2085
  }
1680
2086
  });
1681
2087
  const metadata = addressMetadataArray[0];
@@ -1689,28 +2095,32 @@ var ShelbyMetadataClient = class {
1689
2095
  }
1690
2096
  }
1691
2097
  /**
1692
- * Retrieves the list of slice addresses.
2098
+ * Retrieves the list of slice addresses in a location.
1693
2099
  *
2100
+ * @param locationName - The location whose slices to list.
1694
2101
  * @returns The slice group list, or an empty array if none exist.
1695
2102
  *
1696
2103
  * @example
1697
2104
  * ```typescript
1698
- * const pgList = await client.getSliceAddresses();
2105
+ * const pgList = await client.getSliceAddresses("us-east-1");
1699
2106
  * ```
1700
2107
  */
1701
- async getSliceAddresses() {
2108
+ async getSliceAddresses(locationName) {
1702
2109
  try {
1703
2110
  const sliceSizeMetadata = await this.aptos.view({
1704
2111
  payload: {
1705
2112
  function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
1706
- functionArguments: []
2113
+ functionArguments: [locationName]
1707
2114
  }
1708
2115
  });
2116
+ if (Number(sliceSizeMetadata[0]) === 0) {
2117
+ return [];
2118
+ }
1709
2119
  const finalSliceIndex = sliceSizeMetadata[0] - 1;
1710
2120
  const addressMetadataArray = await this.aptos.view({
1711
2121
  payload: {
1712
2122
  function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
1713
- functionArguments: [0, finalSliceIndex]
2123
+ functionArguments: [locationName, 0, finalSliceIndex]
1714
2124
  }
1715
2125
  });
1716
2126
  const metadata = addressMetadataArray[0];
@@ -1770,6 +2180,36 @@ var ShelbyMetadataClient = class {
1770
2180
  (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
1771
2181
  );
1772
2182
  }
2183
+ /**
2184
+ * Retrieves the active storage providers for a slice.
2185
+ *
2186
+ * Active SPs have a complete copy of the data for the slot, and are not in any data transfer/repair/reconstruction phase.
2187
+ * Active SP can be audited for the data it contains.
2188
+ * Each slot has at most one active SP.
2189
+ *
2190
+ * @param params.account - The address of the slice account.
2191
+ * @returns An array where result[i] is the active SP for slot i, or null if no SP is active.
2192
+ *
2193
+ * @example
2194
+ * ```typescript
2195
+ * const providers = await client.getActiveStorageProvidersForSlice({ account: sliceAddress });
2196
+ * ```
2197
+ */
2198
+ async getActiveStorageProvidersForSlice(params) {
2199
+ const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
2200
+ params.account
2201
+ );
2202
+ const rawMetadata = await this.aptos.view({
2203
+ payload: {
2204
+ function: `${this.deployer.toString()}::placement_group::get_active_storage_providers`,
2205
+ functionArguments: [placementGroupAddress]
2206
+ }
2207
+ });
2208
+ const providers = rawMetadata[0];
2209
+ return providers.map(
2210
+ (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
2211
+ );
2212
+ }
1773
2213
  /**
1774
2214
  * Retrieves the serving storage providers for a slice.
1775
2215
  *
@@ -1858,22 +2298,6 @@ var ChallengeResponseSchema = z3.object({
1858
2298
  challenge: z3.string(),
1859
2299
  expiresAt: z3.number()
1860
2300
  });
1861
- var MultipartUploadStatusResponseSchema = z3.object({
1862
- uploadId: z3.string(),
1863
- completedParts: z3.array(z3.number()),
1864
- partSize: z3.number(),
1865
- nParts: z3.number(),
1866
- uploadedBytes: z3.number()
1867
- });
1868
- var StartMultipartUploadResponseSchema = z3.object({
1869
- uploadId: z3.string()
1870
- });
1871
- var UploadPartResponseSchema = z3.object({
1872
- success: z3.literal(true)
1873
- });
1874
- var CompleteMultipartUploadResponseSchema = z3.object({
1875
- success: z3.literal(true)
1876
- });
1877
2301
  var RPCErrorResponseSchema = z3.object({
1878
2302
  error: z3.string()
1879
2303
  });
@@ -2090,6 +2514,7 @@ var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
2090
2514
  var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
2091
2515
  var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
2092
2516
  var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
2517
+ var INCLUSION_PROOF_HEADER = "X-Shelby-Inclusion-Proof";
2093
2518
  function buildAuthHeaders(auth) {
2094
2519
  const signatureBase64 = btoa(
2095
2520
  Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
@@ -2113,6 +2538,58 @@ function buildAuthHeaders(auth) {
2113
2538
  function encodeURIComponentKeepSlashes(str) {
2114
2539
  return encodeURIComponent(str).replace(/%2F/g, "/");
2115
2540
  }
2541
+ async function generateChunksetInclusionProof(chunksetRoots, chunksetIndex) {
2542
+ if (chunksetRoots.length === 0) {
2543
+ throw new Error("Cannot generate inclusion proof for empty chunkset roots");
2544
+ }
2545
+ if (chunksetIndex < 0 || chunksetIndex >= chunksetRoots.length) {
2546
+ throw new Error(
2547
+ `Chunkset index ${chunksetIndex} out of range [0, ${chunksetRoots.length})`
2548
+ );
2549
+ }
2550
+ if (chunksetRoots.length === 1) {
2551
+ return [];
2552
+ }
2553
+ const zeroHash = new Uint8Array(32);
2554
+ const siblings = [];
2555
+ let currentLeaves = chunksetRoots.map((h) => h.toUint8Array());
2556
+ let currentIdx = chunksetIndex;
2557
+ while (currentLeaves.length > 1) {
2558
+ if (currentLeaves.length % 2 !== 0) {
2559
+ currentLeaves.push(zeroHash);
2560
+ }
2561
+ const siblingIdx = currentIdx % 2 === 0 ? currentIdx + 1 : currentIdx - 1;
2562
+ siblings.push(currentLeaves[siblingIdx]);
2563
+ const nextLeaves = [];
2564
+ for (let i = 0; i < currentLeaves.length; i += 2) {
2565
+ const combined = await concatHashes([
2566
+ currentLeaves[i],
2567
+ currentLeaves[i + 1]
2568
+ ]);
2569
+ nextLeaves.push(combined.toUint8Array());
2570
+ }
2571
+ currentLeaves = nextLeaves;
2572
+ currentIdx = Math.floor(currentIdx / 2);
2573
+ }
2574
+ return siblings;
2575
+ }
2576
+ function encodeInclusionProof(siblings) {
2577
+ if (siblings.length === 0) {
2578
+ return "NONE";
2579
+ }
2580
+ const totalBytes = siblings.length * 32;
2581
+ const combined = new Uint8Array(totalBytes);
2582
+ let offset = 0;
2583
+ for (const sibling of siblings) {
2584
+ combined.set(sibling, offset);
2585
+ offset += 32;
2586
+ }
2587
+ const binaryString = Array.from(
2588
+ combined,
2589
+ (byte) => String.fromCharCode(byte)
2590
+ ).join("");
2591
+ return btoa(binaryString);
2592
+ }
2116
2593
  function validateTotalBytes(totalBytes) {
2117
2594
  if (!Number.isInteger(totalBytes) || totalBytes < 0) {
2118
2595
  throw new Error("totalBytes must be a non-negative integer");
@@ -2144,7 +2621,7 @@ var ShelbyRPCClient = class {
2144
2621
  * @param config - The client configuration object.
2145
2622
  * @param config.network - The Shelby network to use.
2146
2623
  * @param options.signChallengeHandler - Optional override for challenge
2147
- * signing. When set, `putBlobResumable` uses this instead of the built-in
2624
+ * signing. When set, `putBlobChunksets` uses this instead of the built-in
2148
2625
  * `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
2149
2626
  *
2150
2627
  * @example
@@ -2195,43 +2672,6 @@ var ShelbyRPCClient = class {
2195
2672
  }
2196
2673
  return ChallengeResponseSchema.parse(await response.json());
2197
2674
  }
2198
- /**
2199
- * Check if there's an existing multipart upload for a blob.
2200
- * Returns the upload status including which parts have been uploaded.
2201
- *
2202
- * @param account - The account that owns the blob.
2203
- * @param blobName - The name of the blob.
2204
- * @returns The upload status, or undefined if no pending upload exists.
2205
- *
2206
- * @example
2207
- * ```typescript
2208
- * const status = await client.getMultipartUploadStatus(account, "myblob.txt");
2209
- * if (status) {
2210
- * console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
2211
- * }
2212
- * ```
2213
- */
2214
- async getMultipartUploadStatus(account, blobName) {
2215
- const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
2216
- url.searchParams.set("account", account.toString());
2217
- url.searchParams.set("blobName", blobName);
2218
- const response = await fetch(url.toString(), {
2219
- method: "GET",
2220
- headers: {
2221
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2222
- }
2223
- });
2224
- if (response.status === 404) {
2225
- return void 0;
2226
- }
2227
- if (!response.ok) {
2228
- const errorBody = await response.text().catch(() => "");
2229
- throw new Error(
2230
- `Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
2231
- );
2232
- }
2233
- return MultipartUploadStatusResponseSchema.parse(await response.json());
2234
- }
2235
2675
  /**
2236
2676
  * Sign a challenge using the given account and return auth credentials.
2237
2677
  *
@@ -2248,164 +2688,142 @@ var ShelbyRPCClient = class {
2248
2688
  publicKey: account.publicKey.toUint8Array()
2249
2689
  };
2250
2690
  }
2251
- async #uploadPart(uploadId, partIdx, partData) {
2252
- const nRetries = 5;
2691
+ /**
2692
+ * Upload a single chunkset via the v2 chunkset API.
2693
+ */
2694
+ async #uploadChunkset(params) {
2695
+ const maxNonBusyRetries = 5;
2253
2696
  let lastResponse;
2254
2697
  let lastError;
2255
2698
  let attempts = 0;
2256
- const partUrl = buildRequestUrl(
2257
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2699
+ let consecutive429s = 0;
2700
+ let nonBusyRetries = 0;
2701
+ const chunksetUrl = buildRequestUrl(
2702
+ `/v2/chunksets/${params.account}/${params.chunksetIndex}/${params.uid}`,
2258
2703
  this.baseUrl
2259
2704
  );
2260
- for (let i = 0; i < nRetries; ++i) {
2705
+ const authHeaders = buildAuthHeaders(params.auth);
2706
+ while (true) {
2707
+ if (params.signal?.aborted) {
2708
+ throw new DOMException("Upload aborted", "AbortError");
2709
+ }
2261
2710
  attempts++;
2262
2711
  try {
2263
- lastResponse = await fetch(partUrl, {
2712
+ lastResponse = await fetch(chunksetUrl, {
2264
2713
  method: "PUT",
2265
2714
  headers: {
2266
2715
  "Content-Type": "application/octet-stream",
2267
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2716
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2717
+ ...authHeaders,
2718
+ [INCLUSION_PROOF_HEADER]: params.inclusionProof
2268
2719
  },
2269
- body: partData
2720
+ body: params.chunksetData,
2721
+ signal: params.signal
2270
2722
  });
2271
2723
  lastError = void 0;
2272
2724
  } catch (error) {
2725
+ if (error instanceof DOMException && error.name === "AbortError") {
2726
+ throw error;
2727
+ }
2273
2728
  lastError = error;
2274
- if (i < nRetries - 1) {
2275
- const delay = 2 ** i * 100;
2276
- await sleep(delay);
2729
+ consecutive429s = 0;
2730
+ nonBusyRetries++;
2731
+ if (nonBusyRetries < maxNonBusyRetries) {
2732
+ const delay2 = 2 ** nonBusyRetries * 100;
2733
+ await sleep(delay2);
2277
2734
  continue;
2278
2735
  }
2279
2736
  break;
2280
2737
  }
2281
- if (lastResponse.ok) return;
2738
+ if (lastResponse.ok) {
2739
+ const json = await lastResponse.json();
2740
+ const spAcks = json.spAcks?.map((ack) => ({
2741
+ slot: ack.slot,
2742
+ signature: Uint8Array.from(
2743
+ atob(ack.signature),
2744
+ (c) => c.charCodeAt(0)
2745
+ )
2746
+ }));
2747
+ return {
2748
+ success: json.success,
2749
+ acksReceived: json.acksReceived,
2750
+ spAcks
2751
+ };
2752
+ }
2753
+ if (lastResponse.status === 429) {
2754
+ consecutive429s++;
2755
+ const baseDelay = 500;
2756
+ const jitter = Math.random() * 200;
2757
+ const delay2 = Math.min(
2758
+ 3e4,
2759
+ 2 ** consecutive429s * baseDelay + jitter
2760
+ );
2761
+ await sleep(delay2);
2762
+ continue;
2763
+ }
2764
+ consecutive429s = 0;
2282
2765
  if (!isRetryableStatus(lastResponse.status)) {
2283
2766
  break;
2284
2767
  }
2285
- if (i < nRetries - 1) {
2286
- const delay = 2 ** i * 100;
2287
- await sleep(delay);
2768
+ nonBusyRetries++;
2769
+ if (nonBusyRetries >= maxNonBusyRetries) {
2770
+ break;
2288
2771
  }
2772
+ const delay = 2 ** nonBusyRetries * 100;
2773
+ await sleep(delay);
2289
2774
  }
2290
2775
  if (lastError !== void 0) {
2291
2776
  const errorCode = getErrorCode(lastError);
2292
2777
  const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2293
2778
  throw new Error(
2294
- `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}`,
2779
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2295
2780
  { cause: lastError }
2296
2781
  );
2297
2782
  }
2298
2783
  const errorBody = await lastResponse?.text().catch(() => "");
2299
2784
  throw new Error(
2300
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2785
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2301
2786
  );
2302
2787
  }
2303
- async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
2304
- validateTotalBytes(totalBytes);
2305
- const startResponse = await fetch(
2306
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2307
- {
2308
- method: "POST",
2309
- headers: {
2310
- "Content-Type": "application/json",
2311
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2312
- },
2313
- body: JSON.stringify({
2314
- rawAccount: account.toString(),
2315
- rawBlobName: blobName,
2316
- rawPartSize: partSize
2317
- })
2318
- }
2319
- );
2320
- if (!startResponse.ok) {
2321
- let errorBodyText = "Could not read error body";
2322
- try {
2323
- errorBodyText = await startResponse.text();
2324
- } catch (_e) {
2325
- }
2326
- throw new Error(
2327
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2328
- );
2329
- }
2330
- const { uploadId } = StartMultipartUploadResponseSchema.parse(
2331
- await startResponse.json()
2332
- );
2333
- const totalParts = Math.ceil(totalBytes / partSize);
2334
- let uploadedBytes = 0;
2335
- for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
2336
- await this.#uploadPart(uploadId, partIdx, partData);
2337
- uploadedBytes += partData.length;
2338
- onProgress?.({
2339
- phase: "uploading",
2340
- partIdx,
2341
- totalParts,
2342
- partBytes: partData.length,
2343
- uploadedBytes,
2344
- totalBytes
2345
- });
2346
- }
2347
- if (uploadedBytes !== totalBytes) {
2348
- throw new Error(
2349
- `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
2350
- );
2351
- }
2352
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2353
- onProgress?.({
2354
- phase: "finalizing",
2355
- partIdx: finalPartIdx,
2356
- totalParts,
2357
- // no part uploaded in this phase
2358
- partBytes: 0,
2359
- uploadedBytes: totalBytes,
2360
- totalBytes
2361
- });
2362
- const completeResponse = await fetch(
2363
- buildRequestUrl(
2364
- `/v1/multipart-uploads/${uploadId}/complete`,
2365
- this.baseUrl
2366
- ),
2367
- {
2368
- method: "POST",
2369
- headers: {
2370
- "Content-Type": "application/json",
2371
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2372
- }
2373
- }
2374
- );
2375
- if (!completeResponse.ok) {
2376
- let errorBodyText = "Could not read error body";
2377
- try {
2378
- errorBodyText = await completeResponse.text();
2379
- } catch (_e) {
2380
- }
2381
- throw new Error(
2382
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2383
- );
2384
- }
2385
- }
2386
2788
  /**
2387
- * Uploads blob data to the Shelby RPC node for storage by storage providers.
2388
- * This method should be called after blob commitments have been registered on the blockchain.
2389
- * Uses multipart upload for efficient handling of large files.
2789
+ * Uploads blob data to the Shelby RPC node using the v2 chunkset API.
2790
+ * This method authenticates using challenge-response and uploads chunksets
2791
+ * directly to storage providers via the RPC's worker pool.
2390
2792
  *
2391
- * @param params.account - The account that owns the blob.
2392
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2793
+ * This method:
2794
+ * - Sends raw chunkset data directly to the RPC for erasure encoding
2795
+ * - Requires pre-computed blob commitments to generate inclusion proofs
2796
+ * - Does not support resume (each chunkset is idempotent on the SP side)
2797
+ *
2798
+ * @param params.account - The Aptos Account (with signing capability) that owns the blob.
2799
+ * @param params.uid - The blob's on-chain UID (from `BlobRegisteredEvent` at registration).
2393
2800
  * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2801
+ * @param params.commitments - Pre-computed blob commitments (from generateCommitments).
2394
2802
  * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2803
+ * @param params.chunksetConcurrency - Number of chunksets to upload in parallel. Defaults to 4.
2804
+ * @param params.onProgress - Optional callback for upload progress.
2805
+ * @param params.signal - Optional AbortSignal for cancellation. When aborted, in-flight
2806
+ * HTTP requests are cancelled and an AbortError is thrown.
2395
2807
  *
2396
2808
  * @example
2397
2809
  * ```typescript
2398
- * const blobData = new TextEncoder().encode("Hello, world!");
2810
+ * // First, generate commitments for the blob
2811
+ * const commitments = await generateCommitments(provider, fileData);
2399
2812
  *
2400
- * await client.putBlob({
2401
- * account: AccountAddress.from("0x1"),
2402
- * blobName: "greetings/hello.txt",
2403
- * blobData,
2813
+ * // Register the blob on chain, then read its UID from the register tx's
2814
+ * // BlobRegisteredEvent (see ShelbyBlobClient.registeredBlobUids).
2815
+ *
2816
+ * // Upload using chunkset API
2817
+ * await rpcClient.putBlobChunksets({
2818
+ * account: myAccount,
2819
+ * uid: blobUid,
2820
+ * blobData: fileData,
2821
+ * commitments,
2822
+ * chunksetConcurrency: 8,
2404
2823
  * });
2405
2824
  * ```
2406
2825
  */
2407
- async putBlob(params) {
2408
- BlobNameSchema.parse(params.blobName);
2826
+ async putBlobChunksets(params) {
2409
2827
  let totalBytes;
2410
2828
  if (params.blobData instanceof Uint8Array) {
2411
2829
  totalBytes = params.totalBytes ?? params.blobData.length;
@@ -2423,215 +2841,146 @@ var ShelbyRPCClient = class {
2423
2841
  totalBytes = params.totalBytes;
2424
2842
  }
2425
2843
  validateTotalBytes(totalBytes);
2426
- await this.#putBlobMultipart(
2427
- params.account,
2428
- params.blobName,
2429
- params.blobData,
2430
- totalBytes,
2431
- void 0,
2432
- params.onProgress
2433
- );
2434
- }
2435
- /**
2436
- * Upload a part with authentication headers.
2437
- */
2438
- async #uploadPartWithAuth(uploadId, partIdx, partData, auth) {
2439
- const nRetries = 5;
2440
- let lastResponse;
2441
- let lastError;
2442
- let attempts = 0;
2443
- const partUrl = buildRequestUrl(
2444
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2445
- this.baseUrl
2446
- );
2447
- const authHeaders = buildAuthHeaders(auth);
2448
- for (let i = 0; i < nRetries; ++i) {
2449
- attempts++;
2450
- try {
2451
- lastResponse = await fetch(partUrl, {
2452
- method: "PUT",
2453
- headers: {
2454
- "Content-Type": "application/octet-stream",
2455
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2456
- ...authHeaders
2457
- },
2458
- body: partData
2459
- });
2460
- lastError = void 0;
2461
- } catch (error) {
2462
- lastError = error;
2463
- if (i < nRetries - 1) {
2464
- const delay = 2 ** i * 100;
2465
- await sleep(delay);
2466
- continue;
2467
- }
2468
- break;
2469
- }
2470
- if (lastResponse.ok) return;
2471
- if (!isRetryableStatus(lastResponse.status)) {
2472
- break;
2473
- }
2474
- if (i < nRetries - 1) {
2475
- const delay = 2 ** i * 100;
2476
- await sleep(delay);
2477
- }
2478
- }
2479
- if (lastError !== void 0) {
2480
- const errorCode = getErrorCode(lastError);
2481
- const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2844
+ const chunksetConcurrency = params.chunksetConcurrency ?? 4;
2845
+ const totalChunksets = params.commitments.chunkset_commitments.length;
2846
+ const rawDataSizeFromCommitments = params.commitments.raw_data_size;
2847
+ if (rawDataSizeFromCommitments !== totalBytes) {
2482
2848
  throw new Error(
2483
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2484
- { cause: lastError }
2849
+ `Data size mismatch: commitments were generated for ${rawDataSizeFromCommitments} bytes, but totalBytes=${totalBytes}. This will cause inclusion proof verification to fail.`
2485
2850
  );
2486
2851
  }
2487
- const errorBody = await lastResponse?.text().catch(() => "");
2488
- throw new Error(
2489
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2852
+ const chunksetRoots = params.commitments.chunkset_commitments.map(
2853
+ (c) => Hex6.fromHexString(c.chunkset_root)
2490
2854
  );
2491
- }
2492
- /**
2493
- * Uploads blob data to the Shelby RPC node with authentication and resume support.
2494
- * This method authenticates using challenge-response and can resume interrupted uploads.
2495
- *
2496
- * @param params.account - The Aptos Account (with signing capability) that owns the blob.
2497
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2498
- * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2499
- * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2500
- * @param params.onProgress - Optional callback for upload progress.
2501
- *
2502
- * @example
2503
- * ```typescript
2504
- * await client.putBlobResumable({
2505
- * account: myAccount, // Aptos Account with signing capability
2506
- * blobName: "documents/report.pdf",
2507
- * blobData: fileData,
2508
- * totalBytes: fileData.length,
2509
- * });
2510
- * ```
2511
- */
2512
- async putBlobResumable(params) {
2513
- BlobNameSchema.parse(params.blobName);
2514
- let totalBytes;
2515
- if (params.blobData instanceof Uint8Array) {
2516
- totalBytes = params.totalBytes ?? params.blobData.length;
2517
- if (totalBytes !== params.blobData.length) {
2518
- throw new Error(
2519
- "totalBytes must match blobData.length when blobData is a Uint8Array"
2520
- );
2521
- }
2522
- } else {
2523
- if (params.totalBytes === void 0) {
2524
- throw new Error(
2525
- "totalBytes is required when blobData is a ReadableStream"
2526
- );
2527
- }
2528
- totalBytes = params.totalBytes;
2529
- }
2530
- validateTotalBytes(totalBytes);
2531
- const partSize = params.partSize ?? 5 * 1024 * 1024;
2532
2855
  const { challenge } = await this.getChallenge(
2533
2856
  params.account.accountAddress
2534
2857
  );
2535
2858
  const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
2536
2859
  const auth = signFn(params.account, challenge);
2537
- const authHeaders = buildAuthHeaders(auth);
2538
- const existingUpload = await this.getMultipartUploadStatus(
2539
- params.account.accountAddress,
2540
- params.blobName
2541
- );
2542
- let uploadId;
2543
- let completedPartsSet;
2544
- let totalParts;
2545
- if (existingUpload) {
2546
- uploadId = existingUpload.uploadId;
2547
- completedPartsSet = new Set(existingUpload.completedParts);
2548
- totalParts = existingUpload.nParts;
2549
- if (existingUpload.partSize !== partSize) {
2550
- throw new Error(
2551
- `Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
2552
- );
2553
- }
2554
- } else {
2555
- const startResponse = await fetch(
2556
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2557
- {
2558
- method: "POST",
2559
- headers: {
2560
- "Content-Type": "application/json",
2561
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2562
- ...authHeaders
2563
- },
2564
- body: JSON.stringify({
2565
- rawAccount: params.account.accountAddress.toString(),
2566
- rawBlobName: params.blobName,
2567
- rawPartSize: partSize
2568
- })
2569
- }
2860
+ const firstChunkset = params.commitments.chunkset_commitments[0];
2861
+ if (!firstChunkset) {
2862
+ throw new Error("Commitments must have at least one chunkset");
2863
+ }
2864
+ const erasure_n = firstChunkset.chunk_commitments.length;
2865
+ const matchingEntry = findErasureSchemeByErasureN(erasure_n);
2866
+ if (!matchingEntry) {
2867
+ throw new Error(
2868
+ `Unknown erasure coding scheme with erasure_n=${erasure_n}`
2570
2869
  );
2571
- if (!startResponse.ok) {
2572
- const errorBodyText = await startResponse.text().catch(() => "");
2573
- throw new Error(
2574
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2575
- );
2576
- }
2577
- const parsed = StartMultipartUploadResponseSchema.parse(
2578
- await startResponse.json()
2870
+ }
2871
+ const [schemeKey, matchingParams] = matchingEntry;
2872
+ const erasure_k = matchingParams.erasure_k;
2873
+ const { chunkSizeBytes } = ERASURE_CODE_AND_CHUNK_MAPPING[schemeKey];
2874
+ const chunksetSize = erasure_k * chunkSizeBytes;
2875
+ const expectedChunksetCount = totalBytes === 0 ? 1 : Math.ceil(totalBytes / chunksetSize);
2876
+ if (expectedChunksetCount !== totalChunksets) {
2877
+ throw new Error(
2878
+ `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.`
2579
2879
  );
2580
- uploadId = parsed.uploadId;
2581
- completedPartsSet = /* @__PURE__ */ new Set();
2582
- totalParts = Math.ceil(totalBytes / partSize);
2583
2880
  }
2584
2881
  let uploadedBytes = 0;
2585
- let partIdx = 0;
2586
- for await (const [idx, partData] of readInChunks(
2587
- params.blobData,
2588
- partSize
2589
- )) {
2590
- partIdx = idx;
2591
- if (completedPartsSet.has(partIdx)) {
2592
- uploadedBytes += partData.length;
2593
- continue;
2882
+ let bytesReadFromSource = 0;
2883
+ const inFlight = /* @__PURE__ */ new Set();
2884
+ async function* chunksetsFromBlob() {
2885
+ if (totalBytes === 0) {
2886
+ yield [0, new Uint8Array(chunksetSize)];
2887
+ return;
2594
2888
  }
2595
- await this.#uploadPartWithAuth(uploadId, partIdx, partData, auth);
2596
- uploadedBytes += partData.length;
2597
- params.onProgress?.({
2598
- phase: "uploading",
2599
- partIdx,
2600
- totalParts,
2601
- partBytes: partData.length,
2602
- uploadedBytes,
2603
- totalBytes
2889
+ yield* readInChunks(params.blobData, chunksetSize);
2890
+ }
2891
+ const chunksetIterator = chunksetsFromBlob();
2892
+ const aggregatedAcks = /* @__PURE__ */ new Map();
2893
+ const internalAbort = new AbortController();
2894
+ if (params.signal?.aborted) {
2895
+ internalAbort.abort();
2896
+ } else if (params.signal) {
2897
+ params.signal.addEventListener("abort", () => internalAbort.abort(), {
2898
+ once: true
2604
2899
  });
2605
2900
  }
2606
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2607
- params.onProgress?.({
2608
- phase: "finalizing",
2609
- partIdx: finalPartIdx,
2610
- totalParts,
2611
- partBytes: 0,
2612
- uploadedBytes: totalBytes,
2613
- totalBytes
2614
- });
2615
- const completeResponse = await fetch(
2616
- buildRequestUrl(
2617
- `/v1/multipart-uploads/${uploadId}/complete`,
2618
- this.baseUrl
2619
- ),
2620
- {
2621
- method: "POST",
2622
- headers: {
2623
- "Content-Type": "application/json",
2624
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2625
- ...authHeaders
2901
+ let iteratorDone = false;
2902
+ let firstError = null;
2903
+ while (!iteratorDone || inFlight.size > 0) {
2904
+ if (internalAbort.signal.aborted) {
2905
+ throw firstError ?? new DOMException("Upload aborted", "AbortError");
2906
+ }
2907
+ while (inFlight.size < chunksetConcurrency && !iteratorDone) {
2908
+ const { value, done } = await chunksetIterator.next();
2909
+ if (done) {
2910
+ iteratorDone = true;
2911
+ break;
2912
+ }
2913
+ const [chunksetIdx, chunksetData] = value;
2914
+ bytesReadFromSource += totalBytes === 0 ? 0 : chunksetData.byteLength;
2915
+ const expectedCommitment = params.commitments.chunkset_commitments[chunksetIdx];
2916
+ if (!expectedCommitment) {
2917
+ throw new Error(
2918
+ `Chunkset index ${chunksetIdx} out of range. Commitments only have ${totalChunksets} chunksets. This suggests a chunkset size mismatch between commitment generation and upload.`
2919
+ );
2626
2920
  }
2921
+ const proofSiblings = await generateChunksetInclusionProof(
2922
+ chunksetRoots,
2923
+ chunksetIdx
2924
+ );
2925
+ const inclusionProof = encodeInclusionProof(proofSiblings);
2926
+ const chunksetDataCopy = new Uint8Array(chunksetData);
2927
+ const chunksetBytes = chunksetDataCopy.length;
2928
+ const uploadPromise = (async () => {
2929
+ const result = await this.#uploadChunkset({
2930
+ account: params.account.accountAddress.toString(),
2931
+ uid: params.uid,
2932
+ chunksetIndex: chunksetIdx,
2933
+ inclusionProof,
2934
+ chunksetData: chunksetDataCopy,
2935
+ auth,
2936
+ signal: internalAbort.signal
2937
+ });
2938
+ const chunksetSpAcks = result.spAcks?.map((ack) => ({
2939
+ slot: ack.slot,
2940
+ signature: ack.signature
2941
+ }));
2942
+ if (chunksetSpAcks) {
2943
+ for (const ack of chunksetSpAcks) {
2944
+ aggregatedAcks.set(ack.slot, ack.signature);
2945
+ }
2946
+ }
2947
+ uploadedBytes += chunksetBytes;
2948
+ params.onProgress?.({
2949
+ phase: "uploading",
2950
+ chunksetIdx,
2951
+ totalChunksets,
2952
+ chunksetBytes,
2953
+ uploadedBytes,
2954
+ totalBytes,
2955
+ acksReceived: result.acksReceived,
2956
+ spAcks: chunksetSpAcks
2957
+ });
2958
+ })().catch((err) => {
2959
+ if (!firstError) {
2960
+ firstError = err;
2961
+ internalAbort.abort();
2962
+ }
2963
+ }).finally(() => {
2964
+ inFlight.delete(uploadPromise);
2965
+ });
2966
+ inFlight.add(uploadPromise);
2627
2967
  }
2628
- );
2629
- if (!completeResponse.ok) {
2630
- const errorBodyText = await completeResponse.text().catch(() => "");
2968
+ if (inFlight.size > 0) {
2969
+ await Promise.race(inFlight);
2970
+ }
2971
+ }
2972
+ if (firstError) {
2973
+ throw firstError;
2974
+ }
2975
+ if (bytesReadFromSource !== totalBytes) {
2631
2976
  throw new Error(
2632
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2977
+ `Data source produced ${bytesReadFromSource} bytes, but expected ${totalBytes} bytes. The stream may have ended prematurely, resulting in an incomplete upload.`
2633
2978
  );
2634
2979
  }
2980
+ const spAcks = Array.from(
2981
+ aggregatedAcks.entries()
2982
+ ).map(([slot, signature]) => ({ slot, signature }));
2983
+ return { spAcks };
2635
2984
  }
2636
2985
  /**
2637
2986
  * Downloads a blob from the Shelby RPC node.
@@ -2883,6 +3232,59 @@ var ShelbyClient = class {
2883
3232
  }
2884
3233
  return this._provider;
2885
3234
  }
3235
+ /**
3236
+ * Build orderless transaction options (a random replay-protection nonce) for
3237
+ * the per-blob commit transactions. batchUpload finalizes blobs
3238
+ * concurrently, so without orderless replay protection the parallel
3239
+ * same-account submissions collide on the sequence number ("transaction
3240
+ * already in mempool with a different payload"). The nonce makes each commit
3241
+ * independent regardless of the client's `orderless` config flag.
3242
+ */
3243
+ orderlessCommitOptions() {
3244
+ return {
3245
+ build: {
3246
+ options: {
3247
+ replayProtectionNonce: crypto.getRandomValues(new Uint32Array(1))[0]
3248
+ }
3249
+ }
3250
+ };
3251
+ }
3252
+ /**
3253
+ * Resolve the on-chain UID assigned to `blobName` from a committed register
3254
+ * transaction. `register_blob` only publishes the uid -> name mapping via
3255
+ * `BlobRegisteredEvent` (a pending blob is not yet in the `objects` map, so it
3256
+ * cannot be read back by name), so the upload flow must parse it here before
3257
+ * uploading bytes or committing.
3258
+ */
3259
+ uidFromRegisterTx(tx, account, blobName) {
3260
+ const events = "events" in tx ? tx.events : [];
3261
+ const objectName = createBlobKey({ account, blobName });
3262
+ const match = ShelbyBlobClient.registeredBlobUids(
3263
+ events,
3264
+ this.coordination.deployer
3265
+ ).find((registered) => registered.objectName === objectName);
3266
+ if (match === void 0) {
3267
+ throw new Error(
3268
+ `No BlobRegisteredEvent for '${blobName}' in register transaction ${tx.hash}`
3269
+ );
3270
+ }
3271
+ return match.uid;
3272
+ }
3273
+ /**
3274
+ * Await a blob-registration transaction, rephrasing on-chain location
3275
+ * failures into a human-readable message.
3276
+ */
3277
+ async waitForRegistration(transactionHash) {
3278
+ try {
3279
+ return await this.coordination.aptos.waitForTransaction({
3280
+ transactionHash
3281
+ });
3282
+ } catch (error) {
3283
+ const message = error instanceof Error ? error.message : String(error);
3284
+ const described = describeLocationError(message);
3285
+ throw described ? new Error(described, { cause: error }) : error;
3286
+ }
3287
+ }
2886
3288
  /**
2887
3289
  * The base URL for the Shelby RPC node.
2888
3290
  */
@@ -2896,8 +3298,8 @@ var ShelbyClient = class {
2896
3298
  *
2897
3299
  * Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
2898
3300
  * For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
2899
- * using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2900
- * with a `ReadableStream`.
3301
+ * using `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
3302
+ * and `coordination.commitObject()` with a `ReadableStream`.
2901
3303
  *
2902
3304
  * @param params.blobData - The raw data to upload as a Uint8Array.
2903
3305
  * @param params.signer - The account that signs and pays for the transaction.
@@ -2920,34 +3322,57 @@ var ShelbyClient = class {
2920
3322
  * ```
2921
3323
  */
2922
3324
  async upload(params) {
2923
- const existingBlobMetadata = await this.coordination.getBlobMetadata({
2924
- account: params.signer.accountAddress,
2925
- name: params.blobName
3325
+ const provider = await this.getProvider();
3326
+ const blobCommitments = await generateCommitments(
3327
+ provider,
3328
+ params.blobData
3329
+ );
3330
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
3331
+ account: params.signer,
3332
+ blobName: params.blobName,
3333
+ blobMerkleRoot: blobCommitments.blob_merkle_root,
3334
+ size: params.blobData.length,
3335
+ expirationMicros: params.expirationMicros,
3336
+ config: provider.config,
3337
+ options: params.options
3338
+ });
3339
+ const registerTx = await this.waitForRegistration(
3340
+ pendingRegisterBlobTransaction.hash
3341
+ );
3342
+ const uid = this.uidFromRegisterTx(
3343
+ registerTx,
3344
+ params.signer.accountAddress,
3345
+ params.blobName
3346
+ );
3347
+ const { spAcks } = await this.rpc.putBlobChunksets({
3348
+ account: params.signer,
3349
+ uid,
3350
+ blobData: params.blobData,
3351
+ commitments: blobCommitments,
3352
+ totalBytes: params.blobData.length
2926
3353
  });
2927
- if (!existingBlobMetadata) {
2928
- const provider = await this.getProvider();
2929
- const blobCommitments = await generateCommitments(
2930
- provider,
2931
- params.blobData
3354
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
3355
+ if (spAcks.length < requiredAcks) {
3356
+ throw new Error(
3357
+ `Insufficient storage provider acknowledgements for '${params.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
2932
3358
  );
2933
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2934
- account: params.signer,
2935
- blobName: params.blobName,
2936
- blobMerkleRoot: blobCommitments.blob_merkle_root,
2937
- size: params.blobData.length,
2938
- expirationMicros: params.expirationMicros,
2939
- config: provider.config,
2940
- options: params.options
2941
- });
2942
- await this.coordination.aptos.waitForTransaction({
2943
- transactionHash: pendingRegisterBlobTransaction.hash
2944
- });
2945
3359
  }
2946
- await this.rpc.putBlobResumable({
3360
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
2947
3361
  account: params.signer,
3362
+ uid,
2948
3363
  blobName: params.blobName,
2949
- blobData: params.blobData
3364
+ overwrite: true,
3365
+ storageProviderAcks: spAcks,
3366
+ options: this.orderlessCommitOptions()
2950
3367
  });
3368
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3369
+ transactionHash: pendingCommitTransaction.hash
3370
+ });
3371
+ if (!confirmedTx.success) {
3372
+ throw new Error(
3373
+ `commit_object tx failed for '${params.blobName}': ${confirmedTx.vm_status}`
3374
+ );
3375
+ }
2951
3376
  }
2952
3377
  /**
2953
3378
  * Uploads a batch of blobs to the Shelby network.
@@ -2956,8 +3381,8 @@ var ShelbyClient = class {
2956
3381
  *
2957
3382
  * Note: This method accepts only `Uint8Array` and buffers each blob in memory.
2958
3383
  * For streaming uploads of large files, orchestrate the steps manually using
2959
- * `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2960
- * with a `ReadableStream`.
3384
+ * `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
3385
+ * and `coordination.commitObject()` with a `ReadableStream`.
2961
3386
  *
2962
3387
  * @param params.blobs - The blobs to upload.
2963
3388
  * @param params.blobs.blobData - The raw data to upload as a Uint8Array.
@@ -2982,59 +3407,77 @@ var ShelbyClient = class {
2982
3407
  * ```
2983
3408
  */
2984
3409
  async batchUpload(params) {
2985
- const existingBlobs = await this.coordination.getBlobs({
2986
- where: {
2987
- blob_name: {
2988
- _in: params.blobs.map(
2989
- (blob) => createBlobKey({
2990
- account: params.signer.accountAddress,
2991
- blobName: blob.blobName
2992
- })
2993
- )
2994
- }
2995
- }
3410
+ const provider = await this.getProvider();
3411
+ const prepared = await Promise.all(
3412
+ params.blobs.map(async (blob) => ({
3413
+ blob,
3414
+ commitments: await generateCommitments(provider, blob.blobData)
3415
+ }))
3416
+ );
3417
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
3418
+ account: params.signer,
3419
+ expirationMicros: params.expirationMicros,
3420
+ blobs: prepared.map((p) => ({
3421
+ blobName: p.blob.blobName,
3422
+ blobSize: p.blob.blobData.length,
3423
+ blobMerkleRoot: p.commitments.blob_merkle_root
3424
+ })),
3425
+ config: provider.config,
3426
+ options: params.options
2996
3427
  });
2997
- const blobsToRegister = params.blobs.filter(
2998
- (blob) => !existingBlobs.some(
2999
- (existingBlob) => existingBlob.name === createBlobKey({
3000
- account: params.signer.accountAddress,
3001
- blobName: blob.blobName
3002
- })
3003
- )
3428
+ const registerTx = await this.waitForRegistration(
3429
+ pendingRegisterBlobTransaction.hash
3004
3430
  );
3005
- if (blobsToRegister.length > 0) {
3006
- const provider = await this.getProvider();
3007
- const blobCommitments = await Promise.all(
3008
- blobsToRegister.map(
3009
- async (blob) => generateCommitments(provider, blob.blobData)
3010
- )
3011
- );
3012
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
3013
- account: params.signer,
3014
- expirationMicros: params.expirationMicros,
3015
- blobs: blobsToRegister.map((blob, index) => ({
3016
- blobName: blob.blobName,
3017
- blobSize: blob.blobData.length,
3018
- blobMerkleRoot: blobCommitments[index].blob_merkle_root
3019
- })),
3020
- config: provider.config,
3021
- options: params.options
3022
- });
3023
- await this.coordination.aptos.waitForTransaction({
3024
- transactionHash: pendingRegisterBlobTransaction.hash,
3025
- options: { waitForIndexer: true }
3026
- });
3027
- }
3431
+ const uidByObjectName = new Map(
3432
+ ShelbyBlobClient.registeredBlobUids(
3433
+ "events" in registerTx ? registerTx.events : [],
3434
+ this.coordination.deployer
3435
+ ).map((registered) => [registered.objectName, registered.uid])
3436
+ );
3437
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
3028
3438
  const limit = pLimit(3);
3029
3439
  await Promise.all(
3030
- params.blobs.map(
3031
- (blob) => limit(
3032
- () => this.rpc.putBlobResumable({
3440
+ prepared.map(
3441
+ (p) => limit(async () => {
3442
+ const objectName = createBlobKey({
3443
+ account: params.signer.accountAddress,
3444
+ blobName: p.blob.blobName
3445
+ });
3446
+ const uid = uidByObjectName.get(objectName);
3447
+ if (uid === void 0) {
3448
+ throw new Error(
3449
+ `No BlobRegisteredEvent for '${p.blob.blobName}' in batch register transaction ${registerTx.hash}`
3450
+ );
3451
+ }
3452
+ const { spAcks } = await this.rpc.putBlobChunksets({
3033
3453
  account: params.signer,
3034
- blobName: blob.blobName,
3035
- blobData: blob.blobData
3036
- })
3037
- )
3454
+ uid,
3455
+ blobData: p.blob.blobData,
3456
+ commitments: p.commitments,
3457
+ totalBytes: p.blob.blobData.length
3458
+ });
3459
+ if (spAcks.length < requiredAcks) {
3460
+ throw new Error(
3461
+ `Insufficient storage provider acknowledgements for '${p.blob.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
3462
+ );
3463
+ }
3464
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
3465
+ account: params.signer,
3466
+ uid,
3467
+ blobName: p.blob.blobName,
3468
+ overwrite: true,
3469
+ storageProviderAcks: spAcks,
3470
+ options: this.orderlessCommitOptions()
3471
+ });
3472
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3473
+ transactionHash: pendingCommitTransaction.hash
3474
+ });
3475
+ if (!confirmedTx.success) {
3476
+ throw new Error(
3477
+ `commit_object tx failed for '${p.blob.blobName}': ${confirmedTx.vm_status}`
3478
+ );
3479
+ }
3480
+ })
3038
3481
  )
3039
3482
  );
3040
3483
  }
@@ -3280,6 +3723,58 @@ var ShelbyMicropaymentChannelClient = class _ShelbyMicropaymentChannelClient {
3280
3723
  ]
3281
3724
  };
3282
3725
  }
3726
+ /**
3727
+ * Withdraws funds from a micropayment channel as the sender.
3728
+ *
3729
+ * @param params.sender - The account that is withdrawing funds.
3730
+ * @param params.receiver - The account the channel is configured to send funds to.
3731
+ * @param params.fungibleAssetAddress - The account address of the fungible asset.
3732
+ * @param params.options - Optional transaction generation options.
3733
+ *
3734
+ * @returns An object containing the pending transaction.
3735
+ *
3736
+ * @example
3737
+ * ```typescript
3738
+ * const { transaction } = await client.senderWithdraw({
3739
+ * sender: sender,
3740
+ * receiver: receiver,
3741
+ * fungibleAssetAddress: fungibleAssetAddress,
3742
+ * });
3743
+ * ```
3744
+ */
3745
+ async senderWithdraw(params) {
3746
+ const transaction = await this.aptos.transaction.build.simple({
3747
+ options: params.options,
3748
+ data: _ShelbyMicropaymentChannelClient.createSenderWithdrawPayload({
3749
+ deployer: this.deployer,
3750
+ receiver: params.receiver,
3751
+ fungibleAssetAddress: params.fungibleAssetAddress
3752
+ }),
3753
+ sender: params.sender.accountAddress
3754
+ });
3755
+ return {
3756
+ transaction: await this.aptos.signAndSubmitTransaction({
3757
+ signer: params.sender,
3758
+ transaction
3759
+ })
3760
+ };
3761
+ }
3762
+ /**
3763
+ * Creates a static payload for the sender_withdraw Move function.
3764
+ * This is a helper method for constructing the transaction payload without signing.
3765
+ *
3766
+ * @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
3767
+ * @param params.receiver - The account address of the receiver.
3768
+ * @param params.fungibleAssetAddress - The account address of the fungible asset.
3769
+ *
3770
+ * @returns An Aptos transaction payload data object for the sender_withdraw Move function.
3771
+ */
3772
+ static createSenderWithdrawPayload(params) {
3773
+ return {
3774
+ function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::sender_withdraw`,
3775
+ functionArguments: [params.receiver, params.fungibleAssetAddress]
3776
+ };
3777
+ }
3283
3778
  /**
3284
3779
  * Creates a micropayment that can be sent to a receiver.
3285
3780
  * The sender signs a WithdrawApproval message that authorizes the receiver
@@ -3651,36 +4146,6 @@ var ShelbyPlacementGroupClient = class {
3651
4146
  }
3652
4147
  };
3653
4148
 
3654
- // src/core/errors.ts
3655
- var ShelbyErrorCodes = {
3656
- // blob_metadata.move errors
3657
- E_BLOB_NOT_FOUND: "E_BLOB_NOT_FOUND",
3658
- E_INVALID_EXPIRATION_TIME: "E_INVALID_EXPIRATION_TIME",
3659
- E_TEST_ENCODING_NOT_ALLOWED: "E_TEST_ENCODING_NOT_ALLOWED",
3660
- E_BLOB_NOT_EXPIRED: "E_BLOB_NOT_EXPIRED",
3661
- E_INVALID_CHUNKSET_COUNT: "E_INVALID_CHUNKSET_COUNT",
3662
- E_NOT_BLOB_OWNER: "E_NOT_BLOB_OWNER",
3663
- E_NOT_ADMIN: "E_NOT_ADMIN",
3664
- E_BLOB_EXPIRED: "E_BLOB_EXPIRED",
3665
- E_BLOB_NAME_TOO_LONG: "E_BLOB_NAME_TOO_LONG",
3666
- E_INVALID_PAYMENT_EPOCHS: "E_INVALID_PAYMENT_EPOCHS",
3667
- E_NO_SLICES_AVAILABLE: "E_NO_SLICES_AVAILABLE",
3668
- // Common Aptos/Move errors
3669
- EALREADY_EXISTS: "EALREADY_EXISTS"
3670
- };
3671
- function isBlobAlreadyExistsError(errorMessage) {
3672
- return errorMessage.includes(ShelbyErrorCodes.EALREADY_EXISTS) || errorMessage.includes("already exists");
3673
- }
3674
- function isAccessDeniedError(errorMessage) {
3675
- return errorMessage.includes(ShelbyErrorCodes.E_NOT_BLOB_OWNER) || errorMessage.includes(ShelbyErrorCodes.E_NOT_ADMIN) || errorMessage.includes("ENOT_AUTHORIZED") || errorMessage.includes("not authorized") || errorMessage.includes("permission denied");
3676
- }
3677
- function isBlobNotFoundError(errorMessage) {
3678
- return errorMessage.includes(ShelbyErrorCodes.E_BLOB_NOT_FOUND);
3679
- }
3680
- function isBlobExpiredError(errorMessage) {
3681
- return errorMessage.includes(ShelbyErrorCodes.E_BLOB_EXPIRED);
3682
- }
3683
-
3684
4149
  // src/core/networks.ts
3685
4150
  import { Network as Network5 } from "@aptos-labs/ts-sdk";
3686
4151
  var shelbyNetworks = [
@@ -3743,8 +4208,12 @@ function makeChunk(n) {
3743
4208
  export {
3744
4209
  BlobCommitmentsSchema,
3745
4210
  BlobNameSchema,
4211
+ Blob_Activities_Constraint,
3746
4212
  Blob_Activities_Select_Column,
4213
+ Blob_Activities_Update_Column,
4214
+ Blobs_Constraint,
3747
4215
  Blobs_Select_Column,
4216
+ Blobs_Update_Column,
3748
4217
  CHUNK_SIZE_PARAMS,
3749
4218
  COMMITMENT_SCHEMA_VERSION,
3750
4219
  ChallengeResponseSchema,
@@ -3752,7 +4221,6 @@ export {
3752
4221
  ChunkSizeScheme,
3753
4222
  ChunksetCommitmentSchema,
3754
4223
  ClayErasureCodingProvider,
3755
- CompleteMultipartUploadResponseSchema,
3756
4224
  Cursor_Ordering,
3757
4225
  DEFAULT_CHUNKSET_SIZE_BYTES,
3758
4226
  DEFAULT_CHUNK_SIZE_BYTES,
@@ -3776,13 +4244,17 @@ export {
3776
4244
  GetTotalBlobsSizeDocument,
3777
4245
  MICROPAYMENTS_DEPLOYER,
3778
4246
  MissingTransactionSubmitterError,
3779
- MultipartUploadStatusResponseSchema,
3780
4247
  NetworkToGasStationBaseUrl,
3781
4248
  NetworkToShelbyBlobIndexerBaseUrl,
3782
4249
  NetworkToShelbyRPCBaseUrl,
4250
+ ObjectCommitRejectedError,
3783
4251
  Order_By,
4252
+ Placement_Group_Slots_Constraint,
3784
4253
  Placement_Group_Slots_Select_Column,
4254
+ Placement_Group_Slots_Update_Column,
4255
+ Processor_Status_Constraint,
3785
4256
  Processor_Status_Select_Column,
4257
+ Processor_Status_Update_Column,
3786
4258
  RPCErrorResponseSchema,
3787
4259
  ReedSolomonErasureCodingProvider,
3788
4260
  SHELBYUSD_FA_METADATA_ADDRESS,
@@ -3794,6 +4266,7 @@ export {
3794
4266
  ShelbyBlobClient,
3795
4267
  ShelbyClient,
3796
4268
  ShelbyErrorCodes,
4269
+ ShelbyLocationErrorCodes,
3797
4270
  ShelbyMetadataClient,
3798
4271
  ShelbyMicropaymentChannelClient,
3799
4272
  ShelbyNodeClient,
@@ -3801,19 +4274,22 @@ export {
3801
4274
  ShelbyRPCClient,
3802
4275
  StaleChannelStateError,
3803
4276
  StaleMicropaymentErrorResponseSchema,
3804
- StartMultipartUploadResponseSchema,
3805
4277
  TOKEN_DEPLOYER,
3806
4278
  TOKEN_OBJECT_ADDRESS,
3807
- UploadPartResponseSchema,
4279
+ blobEncryptionFromMoveVariant,
4280
+ blobEncryptionToMoveEnumIndex,
3808
4281
  buildRequestUrl,
3809
4282
  concatHashes,
3810
4283
  createBlobKey,
3811
4284
  createDefaultErasureCodingProvider,
3812
4285
  createShelbyIndexerClient,
3813
4286
  defaultErasureCodingConfig,
4287
+ describeLocationError,
3814
4288
  erasureCodingConfig16Total10Data13Helper,
3815
4289
  erasureCodingConfig4Total2Data3Helper,
4290
+ expectedChunksetInclusionProofHashes,
3816
4291
  expectedTotalChunksets,
4292
+ findErasureSchemeByErasureN,
3817
4293
  generateCommitments,
3818
4294
  generateMerkleRoot,
3819
4295
  getAptosAccountExplorerUrl,
@@ -3825,12 +4301,15 @@ export {
3825
4301
  getShelbyBlobExplorerUrl,
3826
4302
  getShelbyIndexerClient,
3827
4303
  isAccessDeniedError,
4304
+ isActiveForTasks,
4305
+ isActiveProviderCondition,
3828
4306
  isBlobAlreadyExistsError,
3829
4307
  isBlobExpiredError,
3830
4308
  isBlobNotFoundError,
3831
4309
  isShelbyNetwork,
3832
4310
  normalizeAddress,
3833
4311
  readInChunks,
4312
+ requiredAckCount,
3834
4313
  shelbyNetworks,
3835
4314
  testUtil_exports as testUtil,
3836
4315
  zeroPadBytes