@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
@@ -130,6 +130,18 @@ var ERASURE_CODE_PARAMS = {
130
130
  enumIndex: 1
131
131
  }
132
132
  };
133
+ function findErasureSchemeByErasureN(erasureN) {
134
+ return Object.entries(ERASURE_CODE_PARAMS).find(
135
+ ([, params]) => params.erasure_n === erasureN
136
+ );
137
+ }
138
+ function requiredAckCount(erasureN) {
139
+ const scheme = findErasureSchemeByErasureN(erasureN);
140
+ if (!scheme) {
141
+ throw new Error(`Unknown erasure coding scheme with erasure_n=${erasureN}`);
142
+ }
143
+ return scheme[1].erasure_d;
144
+ }
133
145
  var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
134
146
  var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
135
147
  var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
@@ -297,7 +309,7 @@ async function generateCommitments(provider, fullData, onChunk) {
297
309
  // src/core/constants.ts
298
310
  import { Network } from "@aptos-labs/ts-sdk";
299
311
  var NetworkToShelbyRPCBaseUrl = {
300
- [Network.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
312
+ [Network.SHELBYNET]: "https://shelby.shelbynet.shelby.xyz/shelby",
301
313
  [Network.NETNA]: void 0,
302
314
  [Network.DEVNET]: void 0,
303
315
  [Network.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
@@ -326,6 +338,27 @@ var NetworkToGasStationBaseUrl = {
326
338
  var SHELBY_DEPLOYER = "0x85fdb9a176ab8ef1d9d9c1b60d60b3924f0800ac1de1cc2085fb0b8bb4988e6a";
327
339
  var MICROPAYMENTS_DEPLOYER = "0x1ae7275148bf6ef742b658fd9cbcc2e094201606f4a7bc707bab0201da8043ee";
328
340
 
341
+ // src/core/types/blobs.ts
342
+ var BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX = {
343
+ Unencrypted: 0,
344
+ AES_GCM_V1: 1
345
+ };
346
+ function blobEncryptionToMoveEnumIndex(encryption) {
347
+ return BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX[encryption];
348
+ }
349
+ function blobEncryptionFromMoveVariant(variant) {
350
+ switch (variant) {
351
+ case "Unencrypted":
352
+ return "Unencrypted";
353
+ case "AES_GCM_V1":
354
+ return "AES_GCM_V1";
355
+ default:
356
+ throw new Error(
357
+ "Could not parse encryption from Shelby Smart Contract, this SDK is out of date."
358
+ );
359
+ }
360
+ }
361
+
329
362
  // src/core/erasure/clay-codes.ts
330
363
  import {
331
364
  createDecoder,
@@ -503,18 +536,20 @@ import gql from "graphql-tag";
503
536
  var GetBlobsDocument = gql`
504
537
  query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
505
538
  blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
539
+ uid
540
+ object_name
506
541
  owner
507
542
  blob_commitment
508
- blob_name
509
543
  created_at
510
544
  expires_at
545
+ updated_at
511
546
  num_chunksets
512
- is_deleted
513
- is_written
514
- placement_group
515
547
  size
516
- updated_at
517
548
  slice_address
549
+ placement_group
550
+ is_persisted
551
+ is_committed
552
+ is_deleted
518
553
  }
519
554
  }
520
555
  `;
@@ -526,7 +561,8 @@ var GetBlobActivitiesDocument = gql`
526
561
  limit: $limit
527
562
  offset: $offset
528
563
  ) {
529
- blob_name
564
+ uid
565
+ object_name
530
566
  event_index
531
567
  event_type
532
568
  transaction_hash
@@ -672,6 +708,33 @@ var MissingTransactionSubmitterError = class extends Error {
672
708
  this.name = "MissingTransactionSubmitterError";
673
709
  }
674
710
  };
711
+ var COMMIT_REJECTION_REASONS = /* @__PURE__ */ new Set([
712
+ "AlreadyExists",
713
+ "NoPriorVersion",
714
+ "EtagMismatch"
715
+ ]);
716
+ var ObjectCommitRejectedError = class extends Error {
717
+ constructor(blobName, uid, reason) {
718
+ super(
719
+ `commit_object rejected the write for '${blobName}' (uid ${uid}): ${reason}`
720
+ );
721
+ this.blobName = blobName;
722
+ this.uid = uid;
723
+ this.reason = reason;
724
+ this.name = "ObjectCommitRejectedError";
725
+ }
726
+ };
727
+ function encryptionFunctionArgs(params) {
728
+ if (params.omitEncryptionArg) {
729
+ if (params.encryption && params.encryption !== "Unencrypted") {
730
+ throw new Error(
731
+ `Blob encryption (${params.encryption}) is not supported on this network: the deployed contract predates the encryption upgrade (#1739).`
732
+ );
733
+ }
734
+ return [];
735
+ }
736
+ return [blobEncryptionToMoveEnumIndex(params.encryption ?? "Unencrypted")];
737
+ }
675
738
  var ShelbyBlobClient = class _ShelbyBlobClient {
676
739
  aptos;
677
740
  deployer;
@@ -720,7 +783,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
720
783
  this.aptos = new Aptos(getAptosConfig(config));
721
784
  this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
722
785
  this.indexer = getShelbyIndexerClient(config);
723
- this.defaultOptions = defaultOptions ?? {};
786
+ this.defaultOptions = {
787
+ locationHint: config.locationHint,
788
+ ...defaultOptions
789
+ };
724
790
  this.orderless = config.orderless ?? false;
725
791
  }
726
792
  /**
@@ -731,7 +797,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
731
797
  build: options?.build ?? this.defaultOptions.build,
732
798
  submit: options?.submit ?? this.defaultOptions.submit,
733
799
  usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
734
- chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
800
+ chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes,
801
+ selectedLocation: options?.selectedLocation ?? this.defaultOptions.selectedLocation,
802
+ locationHint: options?.locationHint ?? this.defaultOptions.locationHint
735
803
  };
736
804
  }
737
805
  /**
@@ -773,17 +841,17 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
773
841
  *
774
842
  * @example
775
843
  * ```typescript
776
- * const metadata = await client.getBlobMetadata({
844
+ * const metadata = await client.getFullObjectMetadata({
777
845
  * account: AccountAddress.fromString("0x1"),
778
846
  * name: "foo/bar.txt",
779
847
  * });
780
848
  * ```
781
849
  */
782
- async getBlobMetadata(params) {
850
+ async getFullObjectMetadata(params) {
783
851
  try {
784
852
  const rawMetadata = await this.aptos.view({
785
853
  payload: {
786
- function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
854
+ function: `${this.deployer.toString()}::blob_metadata::get_full_object_metadata`,
787
855
  functionArguments: [
788
856
  createBlobKey({
789
857
  account: params.account,
@@ -795,47 +863,94 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
795
863
  if (!rawMetadata?.[0]?.vec?.[0]) {
796
864
  return void 0;
797
865
  }
798
- const metadata = rawMetadata[0].vec[0];
799
- let encoding;
800
- if (metadata.encoding.__variant__ === "ClayCode_16Total_10Data_13Helper") {
801
- encoding = {
802
- variant: "clay",
803
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
804
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
805
- };
806
- } else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
807
- encoding = {
808
- variant: "clay",
809
- ...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
810
- ...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
811
- };
812
- } else {
813
- throw new Error(
814
- "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
815
- );
816
- }
817
- return {
818
- blobMerkleRoot: Hex3.fromHexInput(
819
- metadata.blob_commitment
820
- ).toUint8Array(),
821
- owner: normalizeAddress(metadata.owner),
866
+ const view = rawMetadata[0].vec[0];
867
+ return this.parseBlobMetadata(view.blob_metadata, {
868
+ uid: BigInt(view.object_metadata.current_blob_uid),
822
869
  name: params.name,
823
- blobNameSuffix: getBlobNameSuffix(params.name),
824
- size: Number(metadata.blob_size),
825
- encoding,
826
- expirationMicros: Number(metadata.expiration_micros),
827
- creationMicros: Number(metadata.creation_micros),
828
- sliceAddress: normalizeAddress(metadata.slice.inner),
829
- isWritten: metadata.is_written
830
- };
870
+ // Any blob bound under an object name has cleared the commit-time
871
+ // `is_written()` check, so a resolved object is always written.
872
+ isWritten: true
873
+ });
831
874
  } catch (error) {
832
875
  if (error instanceof Error && // Depending on the network, the error message may show up differently.
833
- (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
876
+ (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND") || error.message?.includes("EOBJECT_NOT_FOUND"))) {
877
+ return void 0;
878
+ }
879
+ throw error;
880
+ }
881
+ }
882
+ /**
883
+ * Retrieves blob metadata directly by its on-chain UID, including blobs in
884
+ * the pending (registered-but-not-yet-committed) state that
885
+ * {@link getFullObjectMetadata} cannot resolve by object name. Returns `undefined`
886
+ * if no blob has that UID.
887
+ *
888
+ * The returned `name`/`blobNameSuffix` are empty: the blob layer is keyed by
889
+ * UID and carries no object name (a name binding is established only at
890
+ * commit). `isWritten` reflects whether the blob has been committed.
891
+ */
892
+ async getFullObjectMetadataByUid(uid) {
893
+ try {
894
+ const rawMetadata = await this.aptos.view(
895
+ {
896
+ payload: {
897
+ function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
898
+ functionArguments: [uid]
899
+ }
900
+ }
901
+ );
902
+ if (!rawMetadata?.[0]?.vec?.[0]) {
903
+ return void 0;
904
+ }
905
+ const raw = rawMetadata[0].vec[0];
906
+ return this.parseBlobMetadata(raw, {
907
+ uid,
908
+ name: "",
909
+ isWritten: raw.state.__variant__ === "CommittedObject"
910
+ });
911
+ } catch (error) {
912
+ if (error instanceof Error && (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
834
913
  return void 0;
835
914
  }
836
915
  throw error;
837
916
  }
838
917
  }
918
+ /**
919
+ * Parse the on-chain `BlobMetadata::V1` view shape into the SDK
920
+ * {@link FullObjectMetadata}. `uid` / `name` / `isWritten` are supplied by the
921
+ * caller since they depend on the lookup path (by object name vs by UID).
922
+ */
923
+ parseBlobMetadata(raw, extra) {
924
+ const { content } = raw;
925
+ const variant = content.encoding.__variant__;
926
+ if (variant !== "ClayCode_16Total_10Data_13Helper" && variant !== "ClayCode_4Total_2Data_3Helper") {
927
+ throw new Error(
928
+ "Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
929
+ );
930
+ }
931
+ const encoding = {
932
+ variant: "clay",
933
+ ...ERASURE_CODE_PARAMS[variant],
934
+ ...ERASURE_CODE_AND_CHUNK_MAPPING[variant]
935
+ };
936
+ const encryption = blobEncryptionFromMoveVariant(
937
+ content.encryption?.__variant__ ?? "Unencrypted"
938
+ );
939
+ return {
940
+ uid: extra.uid,
941
+ blobMerkleRoot: Hex3.fromHexInput(content.blob_commitment).toUint8Array(),
942
+ owner: normalizeAddress(raw.owner),
943
+ name: extra.name,
944
+ blobNameSuffix: extra.name ? getBlobNameSuffix(extra.name) : "",
945
+ size: Number(content.blob_size),
946
+ encoding,
947
+ encryption,
948
+ expirationMicros: Number(raw.expiration_micros),
949
+ creationMicros: Number(raw.creation_micros),
950
+ sliceAddress: normalizeAddress(raw.slice.inner),
951
+ isWritten: extra.isWritten
952
+ };
953
+ }
839
954
  /**
840
955
  * Retrieves all the blobs and their metadata for an account from the
841
956
  * blockchain.
@@ -847,7 +962,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
847
962
  *
848
963
  * @example
849
964
  * ```typescript
850
- * // BlobMetadata[]
965
+ * // FullObjectMetadata[]
851
966
  * const blobs = await client.getAccountBlobs({
852
967
  * account: AccountAddress.fromString("0x1"),
853
968
  * });
@@ -864,6 +979,25 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
864
979
  orderBy: rest.orderBy
865
980
  });
866
981
  }
982
+ /**
983
+ * Object-facing default filter: only committed, non-deleted, unexpired rows.
984
+ * The blobs table is UID-keyed, so during an atomic overwrite a single
985
+ * object_name transiently has two non-deleted rows — the currently committed
986
+ * blob and the new pending (is_committed = "0") blob. Filtering on
987
+ * is_committed keeps name lookups/listings pinned to the committed object and
988
+ * avoids returning or duplicating the in-flight pending row. Applied to
989
+ * getBlobs *and* the getBlobsCount / getTotalBlobsSize aggregates so counts
990
+ * and sizes agree with the listing mid-overwrite. Callers can override any
991
+ * key (e.g. is_committed) via their own `where`.
992
+ */
993
+ activeBlobsWhere(where) {
994
+ const defaultActiveFilter = {
995
+ expires_at: { _gte: String(Date.now() * 1e3) },
996
+ is_deleted: { _eq: "0" },
997
+ is_committed: { _eq: "1" }
998
+ };
999
+ return { ...defaultActiveFilter, ...where };
1000
+ }
867
1001
  /**
868
1002
  * Retrieves blobs and their metadata from the blockchain.
869
1003
  *
@@ -874,7 +1008,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
874
1008
  *
875
1009
  * @example
876
1010
  * ```typescript
877
- * // BlobMetadata[]
1011
+ * // FullObjectMetadata[]
878
1012
  * const blobs = await client.getBlobs({
879
1013
  * where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
880
1014
  * });
@@ -883,12 +1017,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
883
1017
  async getBlobs(params = {}) {
884
1018
  const { limit, offset } = params.pagination ?? {};
885
1019
  const { orderBy, where } = params;
886
- const currentMicros = String(Date.now() * 1e3);
887
- const defaultActiveFilter = {
888
- expires_at: { _gte: currentMicros },
889
- is_deleted: { _eq: "0" }
890
- };
891
- const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
1020
+ const finalWhere = this.activeBlobsWhere(where);
892
1021
  const { blobs } = await this.indexer.getBlobs({
893
1022
  where: finalWhere,
894
1023
  limit,
@@ -897,9 +1026,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
897
1026
  });
898
1027
  return blobs.map(
899
1028
  (blob) => ({
1029
+ uid: BigInt(blob.uid),
900
1030
  owner: normalizeAddress(blob.owner),
901
- name: blob.blob_name,
902
- blobNameSuffix: getBlobNameSuffix(blob.blob_name),
1031
+ name: blob.object_name,
1032
+ blobNameSuffix: getBlobNameSuffix(blob.object_name),
903
1033
  blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
904
1034
  size: Number(blob.size),
905
1035
  // TODO: Add encoding when supported in NCI
@@ -911,7 +1041,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
911
1041
  expirationMicros: Number(blob.expires_at),
912
1042
  creationMicros: Number(blob.created_at),
913
1043
  sliceAddress: normalizeAddress(blob.slice_address),
914
- isWritten: Boolean(Number(blob.is_written)),
1044
+ isWritten: Boolean(Number(blob.is_persisted)),
915
1045
  isDeleted: Boolean(Number(blob.is_deleted))
916
1046
  })
917
1047
  );
@@ -929,12 +1059,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
929
1059
  [`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
930
1060
  [`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
931
1061
  [`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
932
- [`${this.deployer.toStringLong()}::blob_metadata::BlobWrittenEvent`]: "write_blob"
1062
+ [`${this.deployer.toStringLong()}::blob_metadata::BlobPersistedEvent`]: "write_blob",
1063
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommittedEvent`]: "commit_object",
1064
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectDeletedEvent`]: "delete_object",
1065
+ [`${this.deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`]: "reject_object_commit"
933
1066
  };
934
1067
  return blob_activities.map(
935
1068
  (activity) => ({
936
- blobName: activity.blob_name,
937
- accountAddress: normalizeAddress(activity.blob_name.substring(1, 65)),
1069
+ blobName: activity.object_name,
1070
+ accountAddress: normalizeAddress(activity.object_name.substring(1, 65)),
938
1071
  type: activityTypeMapping[activity.event_type] ?? "unknown",
939
1072
  eventType: activity.event_type,
940
1073
  eventIndex: Number(activity.event_index),
@@ -957,9 +1090,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
957
1090
  * const count = await client.getBlobsCount();
958
1091
  * ```
959
1092
  */
960
- async getBlobsCount(params) {
961
- const { where } = params;
962
- const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
1093
+ async getBlobsCount(params = {}) {
1094
+ const { blobs_aggregate } = await this.indexer.getBlobsCount({
1095
+ where: this.activeBlobsWhere(params.where)
1096
+ });
963
1097
  return blobs_aggregate?.aggregate?.count ?? 0;
964
1098
  }
965
1099
  /**
@@ -974,8 +1108,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
974
1108
  * ```
975
1109
  */
976
1110
  async getTotalBlobsSize(params = {}) {
977
- const { where } = params;
978
- const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({ where });
1111
+ const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({
1112
+ where: this.activeBlobsWhere(params.where)
1113
+ });
979
1114
  return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
980
1115
  }
981
1116
  /**
@@ -1034,12 +1169,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1034
1169
  deployer: this.deployer,
1035
1170
  account: params.account.accountAddress,
1036
1171
  blobName: params.blobName,
1172
+ selectedLocation: options.selectedLocation,
1173
+ locationHint: options.locationHint,
1037
1174
  blobSize: params.size,
1038
1175
  blobMerkleRoot: params.blobMerkleRoot,
1039
1176
  numChunksets: expectedTotalChunksets(params.size, chunksetSize),
1040
1177
  expirationMicros: params.expirationMicros,
1041
1178
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1042
- encoding: config.enumIndex
1179
+ encoding: config.enumIndex,
1180
+ encryption: params.encryption
1043
1181
  }),
1044
1182
  sender: params.account.accountAddress
1045
1183
  };
@@ -1067,16 +1205,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1067
1205
  * @example
1068
1206
  * ```typescript
1069
1207
  *
1070
- * const { transaction } = await client.deleteBlob({
1208
+ * const { transaction } = await client.deleteObject({
1071
1209
  * account: signer,
1072
1210
  * blobName: "foo/bar.txt",
1073
1211
  * });
1074
1212
  * ```
1075
1213
  */
1076
- async deleteBlob(params) {
1214
+ async deleteObject(params) {
1077
1215
  const transaction = await this.aptos.transaction.build.simple({
1078
1216
  options: this.orderlessTxOptions(params.options),
1079
- data: _ShelbyBlobClient.createDeleteBlobPayload({
1217
+ data: _ShelbyBlobClient.createDeleteObjectPayload({
1080
1218
  deployer: this.deployer,
1081
1219
  blobName: params.blobName
1082
1220
  }),
@@ -1092,10 +1230,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1092
1230
  /**
1093
1231
  * Deletes multiple blobs on the blockchain in a single atomic transaction.
1094
1232
  *
1095
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1096
- * which will be deployed to the smart contract on 2026-02-04. Using this
1097
- * function before that date will result in a transaction failure.
1098
- *
1099
1233
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1100
1234
  * the entire transaction fails and no blobs are deleted.
1101
1235
  *
@@ -1110,16 +1244,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1110
1244
  * @example
1111
1245
  * ```typescript
1112
1246
  *
1113
- * const { transaction } = await client.deleteMultipleBlobs({
1247
+ * const { transaction } = await client.deleteMultipleObjects({
1114
1248
  * account: signer,
1115
1249
  * blobNames: ["foo/bar.txt", "baz.txt"],
1116
1250
  * });
1117
1251
  * ```
1118
1252
  */
1119
- async deleteMultipleBlobs(params) {
1253
+ async deleteMultipleObjects(params) {
1120
1254
  const transaction = await this.aptos.transaction.build.simple({
1121
1255
  options: this.orderlessTxOptions(params.options),
1122
- data: _ShelbyBlobClient.createDeleteMultipleBlobsPayload({
1256
+ data: _ShelbyBlobClient.createDeleteMultipleObjectsPayload({
1123
1257
  deployer: this.deployer,
1124
1258
  blobNames: params.blobNames
1125
1259
  }),
@@ -1177,6 +1311,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1177
1311
  data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
1178
1312
  deployer: this.deployer,
1179
1313
  account: params.account.accountAddress,
1314
+ selectedLocation: options.selectedLocation,
1315
+ locationHint: options.locationHint,
1180
1316
  expirationMicros: params.expirationMicros,
1181
1317
  blobs: params.blobs.map((blob) => ({
1182
1318
  blobName: blob.blobName,
@@ -1185,7 +1321,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1185
1321
  numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
1186
1322
  })),
1187
1323
  useSponsoredUsdVariant: options.usdSponsor !== void 0,
1188
- encoding: config.enumIndex
1324
+ encoding: config.enumIndex,
1325
+ encryption: params.encryption
1189
1326
  })
1190
1327
  };
1191
1328
  const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
@@ -1200,6 +1337,55 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1200
1337
  })
1201
1338
  };
1202
1339
  }
1340
+ /**
1341
+ * Extracts the on-chain UIDs assigned at registration from a committed
1342
+ * register transaction's events.
1343
+ *
1344
+ * `register_blob` / `register_multiple_blobs` create *pending* blobs and emit
1345
+ * one `BlobRegisteredEvent` per blob. The UID is published only on this event
1346
+ * (the blob is not yet in the `objects` map, so it cannot be read back by
1347
+ * name), so callers must parse it here before uploading bytes or committing.
1348
+ *
1349
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1350
+ * @param deployer - The contract deployer address.
1351
+ * @returns One entry per registered blob, keyed by its full object name
1352
+ * (`@<owner>/<suffix>`, matching {@link createBlobKey}).
1353
+ */
1354
+ static registeredBlobUids(events, deployer) {
1355
+ const eventType = `${deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`;
1356
+ return events.filter((event) => event.type === eventType).map((event) => {
1357
+ const data = event.data;
1358
+ return { objectName: data.object_name, uid: BigInt(data.uid) };
1359
+ });
1360
+ }
1361
+ /**
1362
+ * Detects whether `commit_object` rejected the write for `uid` rather than
1363
+ * applying it. A rejected commit is still a *successful* transaction — the
1364
+ * contract tears down the pending blob and emits `ObjectCommitRejectedEvent`
1365
+ * instead of aborting — so callers must inspect the finalized transaction's
1366
+ * events to tell a durable write apart from a silent no-op.
1367
+ *
1368
+ * @param events - The committed transaction's events (from `waitForTransaction`).
1369
+ * @param deployer - The contract deployer address.
1370
+ * @param uid - The UID passed to `commit_object`.
1371
+ * @returns The rejection reason, or `undefined` if the commit was applied.
1372
+ */
1373
+ static findObjectCommitRejection(events, deployer, uid) {
1374
+ const eventType = `${deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`;
1375
+ const rejection = events.find(
1376
+ (event) => event.type === eventType && BigInt(event.data.uid) === uid
1377
+ );
1378
+ if (!rejection) {
1379
+ return void 0;
1380
+ }
1381
+ const reason = rejection.data.rejection_reason.__variant__;
1382
+ if (!COMMIT_REJECTION_REASONS.has(reason)) {
1383
+ throw new Error(
1384
+ `Unrecognized ObjectCommitRejectedEvent rejection_reason '${reason}' for uid ${uid}`
1385
+ );
1386
+ }
1387
+ return reason;
1388
+ }
1203
1389
  /**
1204
1390
  * Creates a transaction payload to register a blob on the blockchain.
1205
1391
  * This is a static helper method for constructing the Move function call payload.
@@ -1213,8 +1399,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1213
1399
  * @param params.numChunksets - The total number of chunksets in the blob.
1214
1400
  *
1215
1401
  * @returns An Aptos transaction payload data object for the register_blob Move function.
1216
- *
1217
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1218
1402
  */
1219
1403
  static createRegisterBlobPayload(params) {
1220
1404
  const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
@@ -1222,6 +1406,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1222
1406
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1223
1407
  functionArguments: [
1224
1408
  params.blobName,
1409
+ params.selectedLocation ?? null,
1410
+ params.locationHint ?? null,
1225
1411
  params.expirationMicros,
1226
1412
  Hex3.fromHexString(params.blobMerkleRoot).toUint8Array(),
1227
1413
  params.numChunksets,
@@ -1229,7 +1415,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1229
1415
  // TODO
1230
1416
  0,
1231
1417
  // payment tier
1232
- params.encoding
1418
+ params.encoding,
1419
+ ...encryptionFunctionArgs(params)
1233
1420
  ]
1234
1421
  };
1235
1422
  }
@@ -1247,8 +1434,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1247
1434
  * @param params.blobs.numChunksets - The total number of chunksets in the blob.
1248
1435
  *
1249
1436
  * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
1250
- *
1251
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
1252
1437
  */
1253
1438
  static createBatchRegisterBlobsPayload(params) {
1254
1439
  const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
@@ -1268,6 +1453,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1268
1453
  function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
1269
1454
  functionArguments: [
1270
1455
  blobNames,
1456
+ params.selectedLocation ?? null,
1457
+ params.locationHint ?? null,
1271
1458
  params.expirationMicros,
1272
1459
  blobMerkleRoots,
1273
1460
  blobNumChunksets,
@@ -1275,7 +1462,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1275
1462
  // TODO
1276
1463
  0,
1277
1464
  // payment tier
1278
- params.encoding
1465
+ params.encoding,
1466
+ ...encryptionFunctionArgs(params)
1279
1467
  ]
1280
1468
  };
1281
1469
  }
@@ -1286,24 +1474,20 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1286
1474
  * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
1287
1475
  * @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
1288
1476
  *
1289
- * @returns An Aptos transaction payload data object for the delete_blob Move function.
1290
- *
1291
- * @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
1477
+ * @returns An Aptos transaction payload data object for the delete_object Move function.
1292
1478
  */
1293
- static createDeleteBlobPayload(params) {
1479
+ static createDeleteObjectPayload(params) {
1294
1480
  return {
1295
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_blob`,
1296
- functionArguments: [params.blobName]
1481
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_object`,
1482
+ // Second arg is `if_match_etag: Option<vector<u8>>`; `null` => None
1483
+ // (unconditional delete).
1484
+ functionArguments: [params.blobName, null]
1297
1485
  };
1298
1486
  }
1299
1487
  /**
1300
1488
  * Creates a transaction payload to delete multiple blobs on the blockchain.
1301
1489
  * This is a static helper method for constructing the Move function call payload.
1302
1490
  *
1303
- * **Note:** This function requires the `delete_multiple_blobs` entry function
1304
- * which will be deployed to the smart contract on 2026-02-04. Using this
1305
- * function before that date will result in a transaction failure.
1306
- *
1307
1491
  * This operation is atomic: if any blob deletion fails (e.g., blob not found),
1308
1492
  * the entire transaction fails and no blobs are deleted.
1309
1493
  *
@@ -1312,42 +1496,71 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
1312
1496
  * (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
1313
1497
  * prefix is automatically derived from the transaction sender.
1314
1498
  *
1315
- * @returns An Aptos transaction payload data object for the delete_multiple_blobs Move function.
1316
- *
1317
- * @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
1499
+ * @returns An Aptos transaction payload data object for the delete_multiple_objects Move function.
1318
1500
  */
1319
- static createDeleteMultipleBlobsPayload(params) {
1501
+ static createDeleteMultipleObjectsPayload(params) {
1320
1502
  return {
1321
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_blobs`,
1322
- functionArguments: [params.blobNames]
1503
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_objects`,
1504
+ // Second arg is `if_match_etags: vector<Option<vector<u8>>>`; an empty
1505
+ // vector means "no etag check on any object" (unconditional delete).
1506
+ functionArguments: [params.blobNames, []]
1323
1507
  };
1324
1508
  }
1325
- static createBlobAcknowledgementsPayload(params) {
1326
- const ackBitMask = params.storageProviderAcks.reduce(
1509
+ /**
1510
+ * Sort acks by slot and reduce to the `(ack_bits, signatures)` pair the
1511
+ * contract expects: it walks the set bits low-to-high and consumes the
1512
+ * signatures in that same order.
1513
+ */
1514
+ static encodeAcks(storageProviderAcks) {
1515
+ const sortedAcks = [...storageProviderAcks].sort((a, b) => a.slot - b.slot);
1516
+ const ackBitMask = sortedAcks.reduce(
1327
1517
  (acc, ack) => acc | 1 << ack.slot,
1328
1518
  0
1329
1519
  );
1330
1520
  return {
1331
- function: `${SHELBY_DEPLOYER}::blob_metadata::add_blob_acknowledgements`,
1521
+ ackBits: new U32(Number(ackBitMask)),
1522
+ signatures: sortedAcks.map((ack) => ack.signature)
1523
+ };
1524
+ }
1525
+ /**
1526
+ * Payload for `commit_object(uid, object_name_suffix, overwrite, if_match_etag,
1527
+ * ack_bits, signatures)` — binds a written pending blob under its object name,
1528
+ * finalizing the upload. SP acks may be batched in here (the contract applies
1529
+ * them before the `is_written` check), so register → upload → commit needs
1530
+ * only a single finalize transaction.
1531
+ *
1532
+ * @param params.uid - The blob UID returned at registration.
1533
+ * @param params.blobName - The object name suffix the blob was registered under.
1534
+ * @param params.overwrite - Allow replacing an existing binding under this name.
1535
+ * @param params.storageProviderAcks - Acks applied atomically with the commit.
1536
+ */
1537
+ static createCommitObjectPayload(params) {
1538
+ const { ackBits, signatures } = _ShelbyBlobClient.encodeAcks(
1539
+ params.storageProviderAcks
1540
+ );
1541
+ return {
1542
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::commit_object`,
1332
1543
  functionArguments: [
1333
- createBlobKey({
1334
- account: params.blobOwner,
1335
- blobName: params.blobName
1336
- }),
1337
- params.creationMicros,
1338
- new U32(Number(ackBitMask)),
1339
- params.storageProviderAcks.map((ack) => ack.signature)
1544
+ params.uid,
1545
+ params.blobName,
1546
+ params.overwrite,
1547
+ // `if_match_etag: Option<vector<u8>>` is `null` => None; conditional
1548
+ // (CAS) writes are not yet exposed by the SDK.
1549
+ null,
1550
+ ackBits,
1551
+ signatures
1340
1552
  ]
1341
1553
  };
1342
1554
  }
1343
- async addBlobAcknowledgements(params) {
1555
+ async commitObject(params) {
1344
1556
  const transaction = await this.aptos.transaction.build.simple({
1345
1557
  ...params.options?.build,
1346
1558
  options: this.orderlessTxOptions(params.options?.build?.options),
1347
- data: _ShelbyBlobClient.createBlobAcknowledgementsPayload({
1348
- blobOwner: params.blobOwner,
1559
+ data: _ShelbyBlobClient.createCommitObjectPayload({
1560
+ deployer: this.deployer,
1561
+ uid: params.uid,
1349
1562
  blobName: params.blobName,
1350
- creationMicros: params.creationMicros,
1563
+ overwrite: params.overwrite,
1351
1564
  storageProviderAcks: params.storageProviderAcks
1352
1565
  }),
1353
1566
  sender: params.account.accountAddress
@@ -1370,12 +1583,64 @@ import {
1370
1583
  } from "@aptos-labs/ts-sdk";
1371
1584
  import pLimit from "p-limit";
1372
1585
 
1586
+ // src/core/errors.ts
1587
+ var ShelbyLocationErrorCodes = {
1588
+ E_LOCATION_NOT_FOUND: "E_LOCATION_NOT_FOUND",
1589
+ E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK: "E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK",
1590
+ E_NO_LOCATION_SELECTED: "E_NO_LOCATION_SELECTED",
1591
+ E_LOCATION_WRITES_FROZEN: "E_LOCATION_WRITES_FROZEN",
1592
+ E_LOCATION_NOT_ACTIVATED: "E_LOCATION_NOT_ACTIVATED"
1593
+ };
1594
+ function describeLocationError(errorMessage) {
1595
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_WRITES_FROZEN)) {
1596
+ return "Cannot write: the resolved location is frozen and not currently accepting new data.";
1597
+ }
1598
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_ACTIVATED)) {
1599
+ return "Cannot write: the resolved location is not activated yet.";
1600
+ }
1601
+ if (errorMessage.includes(
1602
+ ShelbyLocationErrorCodes.E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK
1603
+ )) {
1604
+ return "Cannot write: the selected location conflicts with the account's locked location preference.";
1605
+ }
1606
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_NO_LOCATION_SELECTED)) {
1607
+ return "No write location could be resolved: none was selected and the account has no default location or location hint.";
1608
+ }
1609
+ if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_FOUND)) {
1610
+ return "Cannot write: the requested location does not exist.";
1611
+ }
1612
+ return void 0;
1613
+ }
1614
+
1373
1615
  // src/core/clients/ShelbyMetadataClient.ts
1374
1616
  import {
1375
1617
  AccountAddress as AccountAddress3,
1376
1618
  Aptos as Aptos2,
1377
1619
  Hex as Hex4
1378
1620
  } from "@aptos-labs/ts-sdk";
1621
+
1622
+ // src/core/types/storage_providers.ts
1623
+ var ACTIVE_PROVIDER_CONDITIONS = [
1624
+ "Normal",
1625
+ "Faulty",
1626
+ "Leaving",
1627
+ "Evicted",
1628
+ "PendingLeaving"
1629
+ ];
1630
+ function isActiveProviderCondition(value) {
1631
+ return typeof value === "string" && ACTIVE_PROVIDER_CONDITIONS.includes(value);
1632
+ }
1633
+
1634
+ // src/core/clients/ShelbyMetadataClient.ts
1635
+ function parseActiveProviderCondition(raw) {
1636
+ const variant = raw?.__variant__;
1637
+ if (!isActiveProviderCondition(variant)) {
1638
+ throw new Error(
1639
+ `Unknown ActiveProviderCondition variant: ${JSON.stringify(raw)}`
1640
+ );
1641
+ }
1642
+ return variant;
1643
+ }
1379
1644
  function parseStorageProviderState(raw) {
1380
1645
  switch (raw.__variant__) {
1381
1646
  case "Active":
@@ -1383,8 +1648,7 @@ function parseStorageProviderState(raw) {
1383
1648
  variant: "Active",
1384
1649
  quota: raw.quota.value,
1385
1650
  stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
1386
- faulty: raw.faulty,
1387
- leaving: raw.leaving
1651
+ condition: parseActiveProviderCondition(raw.condition)
1388
1652
  };
1389
1653
  case "Waitlisted":
1390
1654
  return {
@@ -1397,6 +1661,10 @@ function parseStorageProviderState(raw) {
1397
1661
  frozenFrom: raw.frozen_from,
1398
1662
  frozenTill: raw.frozen_till
1399
1663
  };
1664
+ default:
1665
+ throw new Error(
1666
+ `Unknown StorageProviderStateDetails variant: ${JSON.stringify(raw)}`
1667
+ );
1400
1668
  }
1401
1669
  }
1402
1670
  var ShelbyMetadataClient = class {
@@ -1455,28 +1723,52 @@ var ShelbyMetadataClient = class {
1455
1723
  }
1456
1724
  }
1457
1725
  /**
1458
- * Retrieves the list of placement group addresses.
1726
+ * Retrieves the names of every activated location (region). Locations that are
1727
+ * registered but not yet brought online are admin-internal and not listed.
1728
+ *
1729
+ * @returns The location name list.
1730
+ *
1731
+ * @example
1732
+ * ```typescript
1733
+ * const locations = await client.getLocationNames();
1734
+ * ```
1735
+ */
1736
+ async getLocationNames() {
1737
+ const names = await this.aptos.view({
1738
+ payload: {
1739
+ function: `${this.deployer.toString()}::location::activated_location_names`,
1740
+ functionArguments: []
1741
+ }
1742
+ });
1743
+ return names[0];
1744
+ }
1745
+ /**
1746
+ * Retrieves the list of placement group addresses in a location.
1459
1747
  *
1748
+ * @param locationName - The location whose placement groups to list.
1460
1749
  * @returns The placement group address list, or an empty array if none exist.
1461
1750
  *
1462
1751
  * @example
1463
1752
  * ```typescript
1464
- * const pgList = await client.getPlacementGroupAddresses();
1753
+ * const pgList = await client.getPlacementGroupAddresses("us-east-1");
1465
1754
  * ```
1466
1755
  */
1467
- async getPlacementGroupAddresses() {
1756
+ async getPlacementGroupAddresses(locationName) {
1468
1757
  try {
1469
1758
  const pgSizeMetadata = await this.aptos.view({
1470
1759
  payload: {
1471
1760
  function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
1472
- functionArguments: []
1761
+ functionArguments: [locationName]
1473
1762
  }
1474
1763
  });
1764
+ if (Number(pgSizeMetadata[0]) === 0) {
1765
+ return [];
1766
+ }
1475
1767
  const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
1476
1768
  const addressMetadataArray = await this.aptos.view({
1477
1769
  payload: {
1478
1770
  function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
1479
- functionArguments: [0, finalPlacementGroupIndex]
1771
+ functionArguments: [locationName, 0, finalPlacementGroupIndex]
1480
1772
  }
1481
1773
  });
1482
1774
  const metadata = addressMetadataArray[0];
@@ -1490,28 +1782,32 @@ var ShelbyMetadataClient = class {
1490
1782
  }
1491
1783
  }
1492
1784
  /**
1493
- * Retrieves the list of slice addresses.
1785
+ * Retrieves the list of slice addresses in a location.
1494
1786
  *
1787
+ * @param locationName - The location whose slices to list.
1495
1788
  * @returns The slice group list, or an empty array if none exist.
1496
1789
  *
1497
1790
  * @example
1498
1791
  * ```typescript
1499
- * const pgList = await client.getSliceAddresses();
1792
+ * const pgList = await client.getSliceAddresses("us-east-1");
1500
1793
  * ```
1501
1794
  */
1502
- async getSliceAddresses() {
1795
+ async getSliceAddresses(locationName) {
1503
1796
  try {
1504
1797
  const sliceSizeMetadata = await this.aptos.view({
1505
1798
  payload: {
1506
1799
  function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
1507
- functionArguments: []
1800
+ functionArguments: [locationName]
1508
1801
  }
1509
1802
  });
1803
+ if (Number(sliceSizeMetadata[0]) === 0) {
1804
+ return [];
1805
+ }
1510
1806
  const finalSliceIndex = sliceSizeMetadata[0] - 1;
1511
1807
  const addressMetadataArray = await this.aptos.view({
1512
1808
  payload: {
1513
1809
  function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
1514
- functionArguments: [0, finalSliceIndex]
1810
+ functionArguments: [locationName, 0, finalSliceIndex]
1515
1811
  }
1516
1812
  });
1517
1813
  const metadata = addressMetadataArray[0];
@@ -1571,6 +1867,36 @@ var ShelbyMetadataClient = class {
1571
1867
  (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
1572
1868
  );
1573
1869
  }
1870
+ /**
1871
+ * Retrieves the active storage providers for a slice.
1872
+ *
1873
+ * Active SPs have a complete copy of the data for the slot, and are not in any data transfer/repair/reconstruction phase.
1874
+ * Active SP can be audited for the data it contains.
1875
+ * Each slot has at most one active SP.
1876
+ *
1877
+ * @param params.account - The address of the slice account.
1878
+ * @returns An array where result[i] is the active SP for slot i, or null if no SP is active.
1879
+ *
1880
+ * @example
1881
+ * ```typescript
1882
+ * const providers = await client.getActiveStorageProvidersForSlice({ account: sliceAddress });
1883
+ * ```
1884
+ */
1885
+ async getActiveStorageProvidersForSlice(params) {
1886
+ const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
1887
+ params.account
1888
+ );
1889
+ const rawMetadata = await this.aptos.view({
1890
+ payload: {
1891
+ function: `${this.deployer.toString()}::placement_group::get_active_storage_providers`,
1892
+ functionArguments: [placementGroupAddress]
1893
+ }
1894
+ });
1895
+ const providers = rawMetadata[0];
1896
+ return providers.map(
1897
+ (opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
1898
+ );
1899
+ }
1574
1900
  /**
1575
1901
  * Retrieves the serving storage providers for a slice.
1576
1902
  *
@@ -1630,22 +1956,6 @@ var ChallengeResponseSchema = z3.object({
1630
1956
  challenge: z3.string(),
1631
1957
  expiresAt: z3.number()
1632
1958
  });
1633
- var MultipartUploadStatusResponseSchema = z3.object({
1634
- uploadId: z3.string(),
1635
- completedParts: z3.array(z3.number()),
1636
- partSize: z3.number(),
1637
- nParts: z3.number(),
1638
- uploadedBytes: z3.number()
1639
- });
1640
- var StartMultipartUploadResponseSchema = z3.object({
1641
- uploadId: z3.string()
1642
- });
1643
- var UploadPartResponseSchema = z3.object({
1644
- success: z3.literal(true)
1645
- });
1646
- var CompleteMultipartUploadResponseSchema = z3.object({
1647
- success: z3.literal(true)
1648
- });
1649
1959
  var RPCErrorResponseSchema = z3.object({
1650
1960
  error: z3.string()
1651
1961
  });
@@ -1862,6 +2172,7 @@ var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
1862
2172
  var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
1863
2173
  var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
1864
2174
  var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
2175
+ var INCLUSION_PROOF_HEADER = "X-Shelby-Inclusion-Proof";
1865
2176
  function buildAuthHeaders(auth) {
1866
2177
  const signatureBase64 = btoa(
1867
2178
  Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
@@ -1885,6 +2196,58 @@ function buildAuthHeaders(auth) {
1885
2196
  function encodeURIComponentKeepSlashes(str) {
1886
2197
  return encodeURIComponent(str).replace(/%2F/g, "/");
1887
2198
  }
2199
+ async function generateChunksetInclusionProof(chunksetRoots, chunksetIndex) {
2200
+ if (chunksetRoots.length === 0) {
2201
+ throw new Error("Cannot generate inclusion proof for empty chunkset roots");
2202
+ }
2203
+ if (chunksetIndex < 0 || chunksetIndex >= chunksetRoots.length) {
2204
+ throw new Error(
2205
+ `Chunkset index ${chunksetIndex} out of range [0, ${chunksetRoots.length})`
2206
+ );
2207
+ }
2208
+ if (chunksetRoots.length === 1) {
2209
+ return [];
2210
+ }
2211
+ const zeroHash = new Uint8Array(32);
2212
+ const siblings = [];
2213
+ let currentLeaves = chunksetRoots.map((h) => h.toUint8Array());
2214
+ let currentIdx = chunksetIndex;
2215
+ while (currentLeaves.length > 1) {
2216
+ if (currentLeaves.length % 2 !== 0) {
2217
+ currentLeaves.push(zeroHash);
2218
+ }
2219
+ const siblingIdx = currentIdx % 2 === 0 ? currentIdx + 1 : currentIdx - 1;
2220
+ siblings.push(currentLeaves[siblingIdx]);
2221
+ const nextLeaves = [];
2222
+ for (let i = 0; i < currentLeaves.length; i += 2) {
2223
+ const combined = await concatHashes([
2224
+ currentLeaves[i],
2225
+ currentLeaves[i + 1]
2226
+ ]);
2227
+ nextLeaves.push(combined.toUint8Array());
2228
+ }
2229
+ currentLeaves = nextLeaves;
2230
+ currentIdx = Math.floor(currentIdx / 2);
2231
+ }
2232
+ return siblings;
2233
+ }
2234
+ function encodeInclusionProof(siblings) {
2235
+ if (siblings.length === 0) {
2236
+ return "NONE";
2237
+ }
2238
+ const totalBytes = siblings.length * 32;
2239
+ const combined = new Uint8Array(totalBytes);
2240
+ let offset = 0;
2241
+ for (const sibling of siblings) {
2242
+ combined.set(sibling, offset);
2243
+ offset += 32;
2244
+ }
2245
+ const binaryString = Array.from(
2246
+ combined,
2247
+ (byte) => String.fromCharCode(byte)
2248
+ ).join("");
2249
+ return btoa(binaryString);
2250
+ }
1888
2251
  function validateTotalBytes(totalBytes) {
1889
2252
  if (!Number.isInteger(totalBytes) || totalBytes < 0) {
1890
2253
  throw new Error("totalBytes must be a non-negative integer");
@@ -1916,7 +2279,7 @@ var ShelbyRPCClient = class {
1916
2279
  * @param config - The client configuration object.
1917
2280
  * @param config.network - The Shelby network to use.
1918
2281
  * @param options.signChallengeHandler - Optional override for challenge
1919
- * signing. When set, `putBlobResumable` uses this instead of the built-in
2282
+ * signing. When set, `putBlobChunksets` uses this instead of the built-in
1920
2283
  * `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
1921
2284
  *
1922
2285
  * @example
@@ -1967,43 +2330,6 @@ var ShelbyRPCClient = class {
1967
2330
  }
1968
2331
  return ChallengeResponseSchema.parse(await response.json());
1969
2332
  }
1970
- /**
1971
- * Check if there's an existing multipart upload for a blob.
1972
- * Returns the upload status including which parts have been uploaded.
1973
- *
1974
- * @param account - The account that owns the blob.
1975
- * @param blobName - The name of the blob.
1976
- * @returns The upload status, or undefined if no pending upload exists.
1977
- *
1978
- * @example
1979
- * ```typescript
1980
- * const status = await client.getMultipartUploadStatus(account, "myblob.txt");
1981
- * if (status) {
1982
- * console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
1983
- * }
1984
- * ```
1985
- */
1986
- async getMultipartUploadStatus(account, blobName) {
1987
- const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
1988
- url.searchParams.set("account", account.toString());
1989
- url.searchParams.set("blobName", blobName);
1990
- const response = await fetch(url.toString(), {
1991
- method: "GET",
1992
- headers: {
1993
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
1994
- }
1995
- });
1996
- if (response.status === 404) {
1997
- return void 0;
1998
- }
1999
- if (!response.ok) {
2000
- const errorBody = await response.text().catch(() => "");
2001
- throw new Error(
2002
- `Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
2003
- );
2004
- }
2005
- return MultipartUploadStatusResponseSchema.parse(await response.json());
2006
- }
2007
2333
  /**
2008
2334
  * Sign a challenge using the given account and return auth credentials.
2009
2335
  *
@@ -2020,269 +2346,142 @@ var ShelbyRPCClient = class {
2020
2346
  publicKey: account.publicKey.toUint8Array()
2021
2347
  };
2022
2348
  }
2023
- async #uploadPart(uploadId, partIdx, partData) {
2024
- const nRetries = 5;
2025
- let lastResponse;
2026
- let lastError;
2027
- let attempts = 0;
2028
- const partUrl = buildRequestUrl(
2029
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2030
- this.baseUrl
2031
- );
2032
- for (let i = 0; i < nRetries; ++i) {
2033
- attempts++;
2034
- try {
2035
- lastResponse = await fetch(partUrl, {
2036
- method: "PUT",
2037
- headers: {
2038
- "Content-Type": "application/octet-stream",
2039
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2040
- },
2041
- body: partData
2042
- });
2043
- lastError = void 0;
2044
- } catch (error) {
2045
- lastError = error;
2046
- if (i < nRetries - 1) {
2047
- const delay = 2 ** i * 100;
2048
- await sleep(delay);
2049
- continue;
2050
- }
2051
- break;
2052
- }
2053
- if (lastResponse.ok) return;
2054
- if (!isRetryableStatus(lastResponse.status)) {
2055
- break;
2056
- }
2057
- if (i < nRetries - 1) {
2058
- const delay = 2 ** i * 100;
2059
- await sleep(delay);
2060
- }
2061
- }
2062
- if (lastError !== void 0) {
2063
- const errorCode = getErrorCode(lastError);
2064
- const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2065
- throw new Error(
2066
- `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}`,
2067
- { cause: lastError }
2068
- );
2069
- }
2070
- const errorBody = await lastResponse?.text().catch(() => "");
2071
- throw new Error(
2072
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2073
- );
2074
- }
2075
- async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
2076
- validateTotalBytes(totalBytes);
2077
- const startResponse = await fetch(
2078
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2079
- {
2080
- method: "POST",
2081
- headers: {
2082
- "Content-Type": "application/json",
2083
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2084
- },
2085
- body: JSON.stringify({
2086
- rawAccount: account.toString(),
2087
- rawBlobName: blobName,
2088
- rawPartSize: partSize
2089
- })
2090
- }
2091
- );
2092
- if (!startResponse.ok) {
2093
- let errorBodyText = "Could not read error body";
2094
- try {
2095
- errorBodyText = await startResponse.text();
2096
- } catch (_e) {
2097
- }
2098
- throw new Error(
2099
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2100
- );
2101
- }
2102
- const { uploadId } = StartMultipartUploadResponseSchema.parse(
2103
- await startResponse.json()
2104
- );
2105
- const totalParts = Math.ceil(totalBytes / partSize);
2106
- let uploadedBytes = 0;
2107
- for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
2108
- await this.#uploadPart(uploadId, partIdx, partData);
2109
- uploadedBytes += partData.length;
2110
- onProgress?.({
2111
- phase: "uploading",
2112
- partIdx,
2113
- totalParts,
2114
- partBytes: partData.length,
2115
- uploadedBytes,
2116
- totalBytes
2117
- });
2118
- }
2119
- if (uploadedBytes !== totalBytes) {
2120
- throw new Error(
2121
- `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
2122
- );
2123
- }
2124
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2125
- onProgress?.({
2126
- phase: "finalizing",
2127
- partIdx: finalPartIdx,
2128
- totalParts,
2129
- // no part uploaded in this phase
2130
- partBytes: 0,
2131
- uploadedBytes: totalBytes,
2132
- totalBytes
2133
- });
2134
- const completeResponse = await fetch(
2135
- buildRequestUrl(
2136
- `/v1/multipart-uploads/${uploadId}/complete`,
2137
- this.baseUrl
2138
- ),
2139
- {
2140
- method: "POST",
2141
- headers: {
2142
- "Content-Type": "application/json",
2143
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
2144
- }
2145
- }
2146
- );
2147
- if (!completeResponse.ok) {
2148
- let errorBodyText = "Could not read error body";
2149
- try {
2150
- errorBodyText = await completeResponse.text();
2151
- } catch (_e) {
2152
- }
2153
- throw new Error(
2154
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2155
- );
2156
- }
2157
- }
2158
- /**
2159
- * Uploads blob data to the Shelby RPC node for storage by storage providers.
2160
- * This method should be called after blob commitments have been registered on the blockchain.
2161
- * Uses multipart upload for efficient handling of large files.
2162
- *
2163
- * @param params.account - The account that owns the blob.
2164
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2165
- * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2166
- * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2167
- *
2168
- * @example
2169
- * ```typescript
2170
- * const blobData = new TextEncoder().encode("Hello, world!");
2171
- *
2172
- * await client.putBlob({
2173
- * account: AccountAddress.from("0x1"),
2174
- * blobName: "greetings/hello.txt",
2175
- * blobData,
2176
- * });
2177
- * ```
2178
- */
2179
- async putBlob(params) {
2180
- BlobNameSchema.parse(params.blobName);
2181
- let totalBytes;
2182
- if (params.blobData instanceof Uint8Array) {
2183
- totalBytes = params.totalBytes ?? params.blobData.length;
2184
- if (totalBytes !== params.blobData.length) {
2185
- throw new Error(
2186
- "totalBytes must match blobData.length when blobData is a Uint8Array"
2187
- );
2188
- }
2189
- } else {
2190
- if (params.totalBytes === void 0) {
2191
- throw new Error(
2192
- "totalBytes is required when blobData is a ReadableStream"
2193
- );
2194
- }
2195
- totalBytes = params.totalBytes;
2196
- }
2197
- validateTotalBytes(totalBytes);
2198
- await this.#putBlobMultipart(
2199
- params.account,
2200
- params.blobName,
2201
- params.blobData,
2202
- totalBytes,
2203
- void 0,
2204
- params.onProgress
2205
- );
2206
- }
2207
2349
  /**
2208
- * Upload a part with authentication headers.
2350
+ * Upload a single chunkset via the v2 chunkset API.
2209
2351
  */
2210
- async #uploadPartWithAuth(uploadId, partIdx, partData, auth) {
2211
- const nRetries = 5;
2352
+ async #uploadChunkset(params) {
2353
+ const maxNonBusyRetries = 5;
2212
2354
  let lastResponse;
2213
2355
  let lastError;
2214
2356
  let attempts = 0;
2215
- const partUrl = buildRequestUrl(
2216
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
2357
+ let consecutive429s = 0;
2358
+ let nonBusyRetries = 0;
2359
+ const chunksetUrl = buildRequestUrl(
2360
+ `/v2/chunksets/${params.account}/${params.chunksetIndex}/${params.uid}`,
2217
2361
  this.baseUrl
2218
2362
  );
2219
- const authHeaders = buildAuthHeaders(auth);
2220
- for (let i = 0; i < nRetries; ++i) {
2363
+ const authHeaders = buildAuthHeaders(params.auth);
2364
+ while (true) {
2365
+ if (params.signal?.aborted) {
2366
+ throw new DOMException("Upload aborted", "AbortError");
2367
+ }
2221
2368
  attempts++;
2222
2369
  try {
2223
- lastResponse = await fetch(partUrl, {
2370
+ lastResponse = await fetch(chunksetUrl, {
2224
2371
  method: "PUT",
2225
2372
  headers: {
2226
2373
  "Content-Type": "application/octet-stream",
2227
2374
  ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2228
- ...authHeaders
2375
+ ...authHeaders,
2376
+ [INCLUSION_PROOF_HEADER]: params.inclusionProof
2229
2377
  },
2230
- body: partData
2378
+ body: params.chunksetData,
2379
+ signal: params.signal
2231
2380
  });
2232
2381
  lastError = void 0;
2233
2382
  } catch (error) {
2383
+ if (error instanceof DOMException && error.name === "AbortError") {
2384
+ throw error;
2385
+ }
2234
2386
  lastError = error;
2235
- if (i < nRetries - 1) {
2236
- const delay = 2 ** i * 100;
2237
- await sleep(delay);
2387
+ consecutive429s = 0;
2388
+ nonBusyRetries++;
2389
+ if (nonBusyRetries < maxNonBusyRetries) {
2390
+ const delay2 = 2 ** nonBusyRetries * 100;
2391
+ await sleep(delay2);
2238
2392
  continue;
2239
2393
  }
2240
2394
  break;
2241
2395
  }
2242
- if (lastResponse.ok) return;
2396
+ if (lastResponse.ok) {
2397
+ const json = await lastResponse.json();
2398
+ const spAcks = json.spAcks?.map((ack) => ({
2399
+ slot: ack.slot,
2400
+ signature: Uint8Array.from(
2401
+ atob(ack.signature),
2402
+ (c) => c.charCodeAt(0)
2403
+ )
2404
+ }));
2405
+ return {
2406
+ success: json.success,
2407
+ acksReceived: json.acksReceived,
2408
+ spAcks
2409
+ };
2410
+ }
2411
+ if (lastResponse.status === 429) {
2412
+ consecutive429s++;
2413
+ const baseDelay = 500;
2414
+ const jitter = Math.random() * 200;
2415
+ const delay2 = Math.min(
2416
+ 3e4,
2417
+ 2 ** consecutive429s * baseDelay + jitter
2418
+ );
2419
+ await sleep(delay2);
2420
+ continue;
2421
+ }
2422
+ consecutive429s = 0;
2243
2423
  if (!isRetryableStatus(lastResponse.status)) {
2244
2424
  break;
2245
2425
  }
2246
- if (i < nRetries - 1) {
2247
- const delay = 2 ** i * 100;
2248
- await sleep(delay);
2426
+ nonBusyRetries++;
2427
+ if (nonBusyRetries >= maxNonBusyRetries) {
2428
+ break;
2249
2429
  }
2430
+ const delay = 2 ** nonBusyRetries * 100;
2431
+ await sleep(delay);
2250
2432
  }
2251
2433
  if (lastError !== void 0) {
2252
2434
  const errorCode = getErrorCode(lastError);
2253
2435
  const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
2254
2436
  throw new Error(
2255
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2437
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
2256
2438
  { cause: lastError }
2257
2439
  );
2258
2440
  }
2259
2441
  const errorBody = await lastResponse?.text().catch(() => "");
2260
2442
  throw new Error(
2261
- `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2443
+ `Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
2262
2444
  );
2263
2445
  }
2264
2446
  /**
2265
- * Uploads blob data to the Shelby RPC node with authentication and resume support.
2266
- * This method authenticates using challenge-response and can resume interrupted uploads.
2447
+ * Uploads blob data to the Shelby RPC node using the v2 chunkset API.
2448
+ * This method authenticates using challenge-response and uploads chunksets
2449
+ * directly to storage providers via the RPC's worker pool.
2450
+ *
2451
+ * This method:
2452
+ * - Sends raw chunkset data directly to the RPC for erasure encoding
2453
+ * - Requires pre-computed blob commitments to generate inclusion proofs
2454
+ * - Does not support resume (each chunkset is idempotent on the SP side)
2267
2455
  *
2268
2456
  * @param params.account - The Aptos Account (with signing capability) that owns the blob.
2269
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
2457
+ * @param params.uid - The blob's on-chain UID (from `BlobRegisteredEvent` at registration).
2270
2458
  * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
2459
+ * @param params.commitments - Pre-computed blob commitments (from generateCommitments).
2271
2460
  * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
2461
+ * @param params.chunksetConcurrency - Number of chunksets to upload in parallel. Defaults to 4.
2272
2462
  * @param params.onProgress - Optional callback for upload progress.
2463
+ * @param params.signal - Optional AbortSignal for cancellation. When aborted, in-flight
2464
+ * HTTP requests are cancelled and an AbortError is thrown.
2273
2465
  *
2274
2466
  * @example
2275
2467
  * ```typescript
2276
- * await client.putBlobResumable({
2277
- * account: myAccount, // Aptos Account with signing capability
2278
- * blobName: "documents/report.pdf",
2468
+ * // First, generate commitments for the blob
2469
+ * const commitments = await generateCommitments(provider, fileData);
2470
+ *
2471
+ * // Register the blob on chain, then read its UID from the register tx's
2472
+ * // BlobRegisteredEvent (see ShelbyBlobClient.registeredBlobUids).
2473
+ *
2474
+ * // Upload using chunkset API
2475
+ * await rpcClient.putBlobChunksets({
2476
+ * account: myAccount,
2477
+ * uid: blobUid,
2279
2478
  * blobData: fileData,
2280
- * totalBytes: fileData.length,
2479
+ * commitments,
2480
+ * chunksetConcurrency: 8,
2281
2481
  * });
2282
2482
  * ```
2283
2483
  */
2284
- async putBlobResumable(params) {
2285
- BlobNameSchema.parse(params.blobName);
2484
+ async putBlobChunksets(params) {
2286
2485
  let totalBytes;
2287
2486
  if (params.blobData instanceof Uint8Array) {
2288
2487
  totalBytes = params.totalBytes ?? params.blobData.length;
@@ -2300,110 +2499,146 @@ var ShelbyRPCClient = class {
2300
2499
  totalBytes = params.totalBytes;
2301
2500
  }
2302
2501
  validateTotalBytes(totalBytes);
2303
- const partSize = params.partSize ?? 5 * 1024 * 1024;
2502
+ const chunksetConcurrency = params.chunksetConcurrency ?? 4;
2503
+ const totalChunksets = params.commitments.chunkset_commitments.length;
2504
+ const rawDataSizeFromCommitments = params.commitments.raw_data_size;
2505
+ if (rawDataSizeFromCommitments !== totalBytes) {
2506
+ throw new Error(
2507
+ `Data size mismatch: commitments were generated for ${rawDataSizeFromCommitments} bytes, but totalBytes=${totalBytes}. This will cause inclusion proof verification to fail.`
2508
+ );
2509
+ }
2510
+ const chunksetRoots = params.commitments.chunkset_commitments.map(
2511
+ (c) => Hex6.fromHexString(c.chunkset_root)
2512
+ );
2304
2513
  const { challenge } = await this.getChallenge(
2305
2514
  params.account.accountAddress
2306
2515
  );
2307
2516
  const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
2308
2517
  const auth = signFn(params.account, challenge);
2309
- const authHeaders = buildAuthHeaders(auth);
2310
- const existingUpload = await this.getMultipartUploadStatus(
2311
- params.account.accountAddress,
2312
- params.blobName
2313
- );
2314
- let uploadId;
2315
- let completedPartsSet;
2316
- let totalParts;
2317
- if (existingUpload) {
2318
- uploadId = existingUpload.uploadId;
2319
- completedPartsSet = new Set(existingUpload.completedParts);
2320
- totalParts = existingUpload.nParts;
2321
- if (existingUpload.partSize !== partSize) {
2322
- throw new Error(
2323
- `Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
2324
- );
2325
- }
2326
- } else {
2327
- const startResponse = await fetch(
2328
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
2329
- {
2330
- method: "POST",
2331
- headers: {
2332
- "Content-Type": "application/json",
2333
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2334
- ...authHeaders
2335
- },
2336
- body: JSON.stringify({
2337
- rawAccount: params.account.accountAddress.toString(),
2338
- rawBlobName: params.blobName,
2339
- rawPartSize: partSize
2340
- })
2341
- }
2518
+ const firstChunkset = params.commitments.chunkset_commitments[0];
2519
+ if (!firstChunkset) {
2520
+ throw new Error("Commitments must have at least one chunkset");
2521
+ }
2522
+ const erasure_n = firstChunkset.chunk_commitments.length;
2523
+ const matchingEntry = findErasureSchemeByErasureN(erasure_n);
2524
+ if (!matchingEntry) {
2525
+ throw new Error(
2526
+ `Unknown erasure coding scheme with erasure_n=${erasure_n}`
2342
2527
  );
2343
- if (!startResponse.ok) {
2344
- const errorBodyText = await startResponse.text().catch(() => "");
2345
- throw new Error(
2346
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
2347
- );
2348
- }
2349
- const parsed = StartMultipartUploadResponseSchema.parse(
2350
- await startResponse.json()
2528
+ }
2529
+ const [schemeKey, matchingParams] = matchingEntry;
2530
+ const erasure_k = matchingParams.erasure_k;
2531
+ const { chunkSizeBytes } = ERASURE_CODE_AND_CHUNK_MAPPING[schemeKey];
2532
+ const chunksetSize = erasure_k * chunkSizeBytes;
2533
+ const expectedChunksetCount = totalBytes === 0 ? 1 : Math.ceil(totalBytes / chunksetSize);
2534
+ if (expectedChunksetCount !== totalChunksets) {
2535
+ throw new Error(
2536
+ `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.`
2351
2537
  );
2352
- uploadId = parsed.uploadId;
2353
- completedPartsSet = /* @__PURE__ */ new Set();
2354
- totalParts = Math.ceil(totalBytes / partSize);
2355
2538
  }
2356
2539
  let uploadedBytes = 0;
2357
- let partIdx = 0;
2358
- for await (const [idx, partData] of readInChunks(
2359
- params.blobData,
2360
- partSize
2361
- )) {
2362
- partIdx = idx;
2363
- if (completedPartsSet.has(partIdx)) {
2364
- uploadedBytes += partData.length;
2365
- continue;
2540
+ let bytesReadFromSource = 0;
2541
+ const inFlight = /* @__PURE__ */ new Set();
2542
+ async function* chunksetsFromBlob() {
2543
+ if (totalBytes === 0) {
2544
+ yield [0, new Uint8Array(chunksetSize)];
2545
+ return;
2366
2546
  }
2367
- await this.#uploadPartWithAuth(uploadId, partIdx, partData, auth);
2368
- uploadedBytes += partData.length;
2369
- params.onProgress?.({
2370
- phase: "uploading",
2371
- partIdx,
2372
- totalParts,
2373
- partBytes: partData.length,
2374
- uploadedBytes,
2375
- totalBytes
2547
+ yield* readInChunks(params.blobData, chunksetSize);
2548
+ }
2549
+ const chunksetIterator = chunksetsFromBlob();
2550
+ const aggregatedAcks = /* @__PURE__ */ new Map();
2551
+ const internalAbort = new AbortController();
2552
+ if (params.signal?.aborted) {
2553
+ internalAbort.abort();
2554
+ } else if (params.signal) {
2555
+ params.signal.addEventListener("abort", () => internalAbort.abort(), {
2556
+ once: true
2376
2557
  });
2377
2558
  }
2378
- const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
2379
- params.onProgress?.({
2380
- phase: "finalizing",
2381
- partIdx: finalPartIdx,
2382
- totalParts,
2383
- partBytes: 0,
2384
- uploadedBytes: totalBytes,
2385
- totalBytes
2386
- });
2387
- const completeResponse = await fetch(
2388
- buildRequestUrl(
2389
- `/v1/multipart-uploads/${uploadId}/complete`,
2390
- this.baseUrl
2391
- ),
2392
- {
2393
- method: "POST",
2394
- headers: {
2395
- "Content-Type": "application/json",
2396
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
2397
- ...authHeaders
2559
+ let iteratorDone = false;
2560
+ let firstError = null;
2561
+ while (!iteratorDone || inFlight.size > 0) {
2562
+ if (internalAbort.signal.aborted) {
2563
+ throw firstError ?? new DOMException("Upload aborted", "AbortError");
2564
+ }
2565
+ while (inFlight.size < chunksetConcurrency && !iteratorDone) {
2566
+ const { value, done } = await chunksetIterator.next();
2567
+ if (done) {
2568
+ iteratorDone = true;
2569
+ break;
2570
+ }
2571
+ const [chunksetIdx, chunksetData] = value;
2572
+ bytesReadFromSource += totalBytes === 0 ? 0 : chunksetData.byteLength;
2573
+ const expectedCommitment = params.commitments.chunkset_commitments[chunksetIdx];
2574
+ if (!expectedCommitment) {
2575
+ throw new Error(
2576
+ `Chunkset index ${chunksetIdx} out of range. Commitments only have ${totalChunksets} chunksets. This suggests a chunkset size mismatch between commitment generation and upload.`
2577
+ );
2398
2578
  }
2579
+ const proofSiblings = await generateChunksetInclusionProof(
2580
+ chunksetRoots,
2581
+ chunksetIdx
2582
+ );
2583
+ const inclusionProof = encodeInclusionProof(proofSiblings);
2584
+ const chunksetDataCopy = new Uint8Array(chunksetData);
2585
+ const chunksetBytes = chunksetDataCopy.length;
2586
+ const uploadPromise = (async () => {
2587
+ const result = await this.#uploadChunkset({
2588
+ account: params.account.accountAddress.toString(),
2589
+ uid: params.uid,
2590
+ chunksetIndex: chunksetIdx,
2591
+ inclusionProof,
2592
+ chunksetData: chunksetDataCopy,
2593
+ auth,
2594
+ signal: internalAbort.signal
2595
+ });
2596
+ const chunksetSpAcks = result.spAcks?.map((ack) => ({
2597
+ slot: ack.slot,
2598
+ signature: ack.signature
2599
+ }));
2600
+ if (chunksetSpAcks) {
2601
+ for (const ack of chunksetSpAcks) {
2602
+ aggregatedAcks.set(ack.slot, ack.signature);
2603
+ }
2604
+ }
2605
+ uploadedBytes += chunksetBytes;
2606
+ params.onProgress?.({
2607
+ phase: "uploading",
2608
+ chunksetIdx,
2609
+ totalChunksets,
2610
+ chunksetBytes,
2611
+ uploadedBytes,
2612
+ totalBytes,
2613
+ acksReceived: result.acksReceived,
2614
+ spAcks: chunksetSpAcks
2615
+ });
2616
+ })().catch((err) => {
2617
+ if (!firstError) {
2618
+ firstError = err;
2619
+ internalAbort.abort();
2620
+ }
2621
+ }).finally(() => {
2622
+ inFlight.delete(uploadPromise);
2623
+ });
2624
+ inFlight.add(uploadPromise);
2399
2625
  }
2400
- );
2401
- if (!completeResponse.ok) {
2402
- const errorBodyText = await completeResponse.text().catch(() => "");
2626
+ if (inFlight.size > 0) {
2627
+ await Promise.race(inFlight);
2628
+ }
2629
+ }
2630
+ if (firstError) {
2631
+ throw firstError;
2632
+ }
2633
+ if (bytesReadFromSource !== totalBytes) {
2403
2634
  throw new Error(
2404
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
2635
+ `Data source produced ${bytesReadFromSource} bytes, but expected ${totalBytes} bytes. The stream may have ended prematurely, resulting in an incomplete upload.`
2405
2636
  );
2406
2637
  }
2638
+ const spAcks = Array.from(
2639
+ aggregatedAcks.entries()
2640
+ ).map(([slot, signature]) => ({ slot, signature }));
2641
+ return { spAcks };
2407
2642
  }
2408
2643
  /**
2409
2644
  * Downloads a blob from the Shelby RPC node.
@@ -2655,6 +2890,59 @@ var ShelbyClient = class {
2655
2890
  }
2656
2891
  return this._provider;
2657
2892
  }
2893
+ /**
2894
+ * Build orderless transaction options (a random replay-protection nonce) for
2895
+ * the per-blob commit transactions. batchUpload finalizes blobs
2896
+ * concurrently, so without orderless replay protection the parallel
2897
+ * same-account submissions collide on the sequence number ("transaction
2898
+ * already in mempool with a different payload"). The nonce makes each commit
2899
+ * independent regardless of the client's `orderless` config flag.
2900
+ */
2901
+ orderlessCommitOptions() {
2902
+ return {
2903
+ build: {
2904
+ options: {
2905
+ replayProtectionNonce: crypto.getRandomValues(new Uint32Array(1))[0]
2906
+ }
2907
+ }
2908
+ };
2909
+ }
2910
+ /**
2911
+ * Resolve the on-chain UID assigned to `blobName` from a committed register
2912
+ * transaction. `register_blob` only publishes the uid -> name mapping via
2913
+ * `BlobRegisteredEvent` (a pending blob is not yet in the `objects` map, so it
2914
+ * cannot be read back by name), so the upload flow must parse it here before
2915
+ * uploading bytes or committing.
2916
+ */
2917
+ uidFromRegisterTx(tx, account, blobName) {
2918
+ const events = "events" in tx ? tx.events : [];
2919
+ const objectName = createBlobKey({ account, blobName });
2920
+ const match = ShelbyBlobClient.registeredBlobUids(
2921
+ events,
2922
+ this.coordination.deployer
2923
+ ).find((registered) => registered.objectName === objectName);
2924
+ if (match === void 0) {
2925
+ throw new Error(
2926
+ `No BlobRegisteredEvent for '${blobName}' in register transaction ${tx.hash}`
2927
+ );
2928
+ }
2929
+ return match.uid;
2930
+ }
2931
+ /**
2932
+ * Await a blob-registration transaction, rephrasing on-chain location
2933
+ * failures into a human-readable message.
2934
+ */
2935
+ async waitForRegistration(transactionHash) {
2936
+ try {
2937
+ return await this.coordination.aptos.waitForTransaction({
2938
+ transactionHash
2939
+ });
2940
+ } catch (error) {
2941
+ const message = error instanceof Error ? error.message : String(error);
2942
+ const described = describeLocationError(message);
2943
+ throw described ? new Error(described, { cause: error }) : error;
2944
+ }
2945
+ }
2658
2946
  /**
2659
2947
  * The base URL for the Shelby RPC node.
2660
2948
  */
@@ -2668,8 +2956,8 @@ var ShelbyClient = class {
2668
2956
  *
2669
2957
  * Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
2670
2958
  * For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
2671
- * using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2672
- * with a `ReadableStream`.
2959
+ * using `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
2960
+ * and `coordination.commitObject()` with a `ReadableStream`.
2673
2961
  *
2674
2962
  * @param params.blobData - The raw data to upload as a Uint8Array.
2675
2963
  * @param params.signer - The account that signs and pays for the transaction.
@@ -2692,34 +2980,57 @@ var ShelbyClient = class {
2692
2980
  * ```
2693
2981
  */
2694
2982
  async upload(params) {
2695
- const existingBlobMetadata = await this.coordination.getBlobMetadata({
2696
- account: params.signer.accountAddress,
2697
- name: params.blobName
2983
+ const provider = await this.getProvider();
2984
+ const blobCommitments = await generateCommitments(
2985
+ provider,
2986
+ params.blobData
2987
+ );
2988
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2989
+ account: params.signer,
2990
+ blobName: params.blobName,
2991
+ blobMerkleRoot: blobCommitments.blob_merkle_root,
2992
+ size: params.blobData.length,
2993
+ expirationMicros: params.expirationMicros,
2994
+ config: provider.config,
2995
+ options: params.options
2996
+ });
2997
+ const registerTx = await this.waitForRegistration(
2998
+ pendingRegisterBlobTransaction.hash
2999
+ );
3000
+ const uid = this.uidFromRegisterTx(
3001
+ registerTx,
3002
+ params.signer.accountAddress,
3003
+ params.blobName
3004
+ );
3005
+ const { spAcks } = await this.rpc.putBlobChunksets({
3006
+ account: params.signer,
3007
+ uid,
3008
+ blobData: params.blobData,
3009
+ commitments: blobCommitments,
3010
+ totalBytes: params.blobData.length
2698
3011
  });
2699
- if (!existingBlobMetadata) {
2700
- const provider = await this.getProvider();
2701
- const blobCommitments = await generateCommitments(
2702
- provider,
2703
- params.blobData
3012
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
3013
+ if (spAcks.length < requiredAcks) {
3014
+ throw new Error(
3015
+ `Insufficient storage provider acknowledgements for '${params.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
2704
3016
  );
2705
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
2706
- account: params.signer,
2707
- blobName: params.blobName,
2708
- blobMerkleRoot: blobCommitments.blob_merkle_root,
2709
- size: params.blobData.length,
2710
- expirationMicros: params.expirationMicros,
2711
- config: provider.config,
2712
- options: params.options
2713
- });
2714
- await this.coordination.aptos.waitForTransaction({
2715
- transactionHash: pendingRegisterBlobTransaction.hash
2716
- });
2717
3017
  }
2718
- await this.rpc.putBlobResumable({
3018
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
2719
3019
  account: params.signer,
3020
+ uid,
2720
3021
  blobName: params.blobName,
2721
- blobData: params.blobData
3022
+ overwrite: true,
3023
+ storageProviderAcks: spAcks,
3024
+ options: this.orderlessCommitOptions()
3025
+ });
3026
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3027
+ transactionHash: pendingCommitTransaction.hash
2722
3028
  });
3029
+ if (!confirmedTx.success) {
3030
+ throw new Error(
3031
+ `commit_object tx failed for '${params.blobName}': ${confirmedTx.vm_status}`
3032
+ );
3033
+ }
2723
3034
  }
2724
3035
  /**
2725
3036
  * Uploads a batch of blobs to the Shelby network.
@@ -2728,8 +3039,8 @@ var ShelbyClient = class {
2728
3039
  *
2729
3040
  * Note: This method accepts only `Uint8Array` and buffers each blob in memory.
2730
3041
  * For streaming uploads of large files, orchestrate the steps manually using
2731
- * `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
2732
- * with a `ReadableStream`.
3042
+ * `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
3043
+ * and `coordination.commitObject()` with a `ReadableStream`.
2733
3044
  *
2734
3045
  * @param params.blobs - The blobs to upload.
2735
3046
  * @param params.blobs.blobData - The raw data to upload as a Uint8Array.
@@ -2754,59 +3065,77 @@ var ShelbyClient = class {
2754
3065
  * ```
2755
3066
  */
2756
3067
  async batchUpload(params) {
2757
- const existingBlobs = await this.coordination.getBlobs({
2758
- where: {
2759
- blob_name: {
2760
- _in: params.blobs.map(
2761
- (blob) => createBlobKey({
2762
- account: params.signer.accountAddress,
2763
- blobName: blob.blobName
2764
- })
2765
- )
2766
- }
2767
- }
3068
+ const provider = await this.getProvider();
3069
+ const prepared = await Promise.all(
3070
+ params.blobs.map(async (blob) => ({
3071
+ blob,
3072
+ commitments: await generateCommitments(provider, blob.blobData)
3073
+ }))
3074
+ );
3075
+ const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
3076
+ account: params.signer,
3077
+ expirationMicros: params.expirationMicros,
3078
+ blobs: prepared.map((p) => ({
3079
+ blobName: p.blob.blobName,
3080
+ blobSize: p.blob.blobData.length,
3081
+ blobMerkleRoot: p.commitments.blob_merkle_root
3082
+ })),
3083
+ config: provider.config,
3084
+ options: params.options
2768
3085
  });
2769
- const blobsToRegister = params.blobs.filter(
2770
- (blob) => !existingBlobs.some(
2771
- (existingBlob) => existingBlob.name === createBlobKey({
2772
- account: params.signer.accountAddress,
2773
- blobName: blob.blobName
2774
- })
2775
- )
3086
+ const registerTx = await this.waitForRegistration(
3087
+ pendingRegisterBlobTransaction.hash
2776
3088
  );
2777
- if (blobsToRegister.length > 0) {
2778
- const provider = await this.getProvider();
2779
- const blobCommitments = await Promise.all(
2780
- blobsToRegister.map(
2781
- async (blob) => generateCommitments(provider, blob.blobData)
2782
- )
2783
- );
2784
- const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
2785
- account: params.signer,
2786
- expirationMicros: params.expirationMicros,
2787
- blobs: blobsToRegister.map((blob, index) => ({
2788
- blobName: blob.blobName,
2789
- blobSize: blob.blobData.length,
2790
- blobMerkleRoot: blobCommitments[index].blob_merkle_root
2791
- })),
2792
- config: provider.config,
2793
- options: params.options
2794
- });
2795
- await this.coordination.aptos.waitForTransaction({
2796
- transactionHash: pendingRegisterBlobTransaction.hash,
2797
- options: { waitForIndexer: true }
2798
- });
2799
- }
3089
+ const uidByObjectName = new Map(
3090
+ ShelbyBlobClient.registeredBlobUids(
3091
+ "events" in registerTx ? registerTx.events : [],
3092
+ this.coordination.deployer
3093
+ ).map((registered) => [registered.objectName, registered.uid])
3094
+ );
3095
+ const requiredAcks = requiredAckCount(provider.config.erasure_n);
2800
3096
  const limit = pLimit(3);
2801
3097
  await Promise.all(
2802
- params.blobs.map(
2803
- (blob) => limit(
2804
- () => this.rpc.putBlobResumable({
3098
+ prepared.map(
3099
+ (p) => limit(async () => {
3100
+ const objectName = createBlobKey({
3101
+ account: params.signer.accountAddress,
3102
+ blobName: p.blob.blobName
3103
+ });
3104
+ const uid = uidByObjectName.get(objectName);
3105
+ if (uid === void 0) {
3106
+ throw new Error(
3107
+ `No BlobRegisteredEvent for '${p.blob.blobName}' in batch register transaction ${registerTx.hash}`
3108
+ );
3109
+ }
3110
+ const { spAcks } = await this.rpc.putBlobChunksets({
3111
+ account: params.signer,
3112
+ uid,
3113
+ blobData: p.blob.blobData,
3114
+ commitments: p.commitments,
3115
+ totalBytes: p.blob.blobData.length
3116
+ });
3117
+ if (spAcks.length < requiredAcks) {
3118
+ throw new Error(
3119
+ `Insufficient storage provider acknowledgements for '${p.blob.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
3120
+ );
3121
+ }
3122
+ const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
2805
3123
  account: params.signer,
2806
- blobName: blob.blobName,
2807
- blobData: blob.blobData
2808
- })
2809
- )
3124
+ uid,
3125
+ blobName: p.blob.blobName,
3126
+ overwrite: true,
3127
+ storageProviderAcks: spAcks,
3128
+ options: this.orderlessCommitOptions()
3129
+ });
3130
+ const confirmedTx = await this.coordination.aptos.waitForTransaction({
3131
+ transactionHash: pendingCommitTransaction.hash
3132
+ });
3133
+ if (!confirmedTx.success) {
3134
+ throw new Error(
3135
+ `commit_object tx failed for '${p.blob.blobName}': ${confirmedTx.vm_status}`
3136
+ );
3137
+ }
3138
+ })
2810
3139
  )
2811
3140
  );
2812
3141
  }
@@ -3052,6 +3381,58 @@ var ShelbyMicropaymentChannelClient = class _ShelbyMicropaymentChannelClient {
3052
3381
  ]
3053
3382
  };
3054
3383
  }
3384
+ /**
3385
+ * Withdraws funds from a micropayment channel as the sender.
3386
+ *
3387
+ * @param params.sender - The account that is withdrawing funds.
3388
+ * @param params.receiver - The account the channel is configured to send funds to.
3389
+ * @param params.fungibleAssetAddress - The account address of the fungible asset.
3390
+ * @param params.options - Optional transaction generation options.
3391
+ *
3392
+ * @returns An object containing the pending transaction.
3393
+ *
3394
+ * @example
3395
+ * ```typescript
3396
+ * const { transaction } = await client.senderWithdraw({
3397
+ * sender: sender,
3398
+ * receiver: receiver,
3399
+ * fungibleAssetAddress: fungibleAssetAddress,
3400
+ * });
3401
+ * ```
3402
+ */
3403
+ async senderWithdraw(params) {
3404
+ const transaction = await this.aptos.transaction.build.simple({
3405
+ options: params.options,
3406
+ data: _ShelbyMicropaymentChannelClient.createSenderWithdrawPayload({
3407
+ deployer: this.deployer,
3408
+ receiver: params.receiver,
3409
+ fungibleAssetAddress: params.fungibleAssetAddress
3410
+ }),
3411
+ sender: params.sender.accountAddress
3412
+ });
3413
+ return {
3414
+ transaction: await this.aptos.signAndSubmitTransaction({
3415
+ signer: params.sender,
3416
+ transaction
3417
+ })
3418
+ };
3419
+ }
3420
+ /**
3421
+ * Creates a static payload for the sender_withdraw Move function.
3422
+ * This is a helper method for constructing the transaction payload without signing.
3423
+ *
3424
+ * @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
3425
+ * @param params.receiver - The account address of the receiver.
3426
+ * @param params.fungibleAssetAddress - The account address of the fungible asset.
3427
+ *
3428
+ * @returns An Aptos transaction payload data object for the sender_withdraw Move function.
3429
+ */
3430
+ static createSenderWithdrawPayload(params) {
3431
+ return {
3432
+ function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::sender_withdraw`,
3433
+ functionArguments: [params.receiver, params.fungibleAssetAddress]
3434
+ };
3435
+ }
3055
3436
  /**
3056
3437
  * Creates a micropayment that can be sent to a receiver.
3057
3438
  * The sender signs a WithdrawApproval message that authorizes the receiver
@@ -3424,6 +3805,7 @@ var ShelbyPlacementGroupClient = class {
3424
3805
  };
3425
3806
  export {
3426
3807
  MissingTransactionSubmitterError,
3808
+ ObjectCommitRejectedError,
3427
3809
  ShelbyBlobClient,
3428
3810
  ShelbyClient,
3429
3811
  ShelbyMetadataClient,