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