@shelby-protocol/sdk 0.0.8 → 0.1.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 (81) hide show
  1. package/dist/browser/index.d.ts +13 -7
  2. package/dist/browser/index.mjs +102 -49
  3. package/dist/{chunk-HFGEQP5N.mjs → chunk-3ZL3FSNA.mjs} +4 -4
  4. package/dist/chunk-4MG4XGY4.mjs +91 -0
  5. package/dist/chunk-66GI734H.mjs +493 -0
  6. package/dist/chunk-AABBONAF.mjs +34 -0
  7. package/dist/{chunk-BBOG5JSX.mjs → chunk-AGRRYZWV.mjs} +215 -44
  8. package/dist/chunk-CQ6QPIZK.mjs +37 -0
  9. package/dist/{chunk-LGNWAXBG.mjs → chunk-DI2K6OUG.mjs} +1 -1
  10. package/dist/{chunk-SEXQTDX6.mjs → chunk-FLLOQZVD.mjs} +4 -0
  11. package/dist/chunk-IE6LYVIA.mjs +26 -0
  12. package/dist/{chunk-WJKSPJSS.mjs → chunk-KJ24NKPH.mjs} +46 -0
  13. package/dist/{chunk-ZPW742E7.mjs → chunk-LZSIZJYR.mjs} +3 -1
  14. package/dist/chunk-MSCUDBMH.mjs +83 -0
  15. package/dist/{chunk-CPNZAQVY.mjs → chunk-OMZOR2ZF.mjs} +2 -2
  16. package/dist/chunk-QQ57OGQ2.mjs +0 -0
  17. package/dist/{chunk-XWAPNLU6.mjs → chunk-RLRI2533.mjs} +4 -2
  18. package/dist/{chunk-NLPIHQ7K.mjs → chunk-UCDAABAS.mjs} +1 -1
  19. package/dist/{chunk-GY5DCVVL.mjs → chunk-W5NRGZEP.mjs} +1 -1
  20. package/dist/{chunk-MGAN2SBA.mjs → chunk-W6YL46DT.mjs} +28 -14
  21. package/dist/{chunk-CTGCK3H2.mjs → chunk-YZXIPUVQ.mjs} +32 -8
  22. package/dist/{chunk-QRGZJBAG.mjs → chunk-ZAM2EUVN.mjs} +165 -36
  23. package/dist/core/chunk.mjs +1 -1
  24. package/dist/core/clients/ShelbyBlobClient.d.ts +160 -11
  25. package/dist/core/clients/ShelbyBlobClient.mjs +11 -9
  26. package/dist/core/clients/ShelbyClient.d.ts +14 -3
  27. package/dist/core/clients/ShelbyClient.mjs +15 -13
  28. package/dist/core/clients/ShelbyClientConfig.d.ts +7 -2
  29. package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +349 -0
  30. package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +16 -0
  31. package/dist/core/clients/ShelbyPlacementGroupClient.d.ts +73 -0
  32. package/dist/core/clients/ShelbyPlacementGroupClient.mjs +11 -0
  33. package/dist/core/clients/ShelbyRPCClient.d.ts +28 -4
  34. package/dist/core/clients/ShelbyRPCClient.mjs +9 -7
  35. package/dist/core/clients/index.d.ts +7 -3
  36. package/dist/core/clients/index.mjs +33 -17
  37. package/dist/core/clients/utils.d.ts +54 -0
  38. package/dist/core/clients/utils.mjs +1 -1
  39. package/dist/core/commitments.d.ts +5 -1
  40. package/dist/core/commitments.mjs +4 -2
  41. package/dist/core/constants.d.ts +4 -1
  42. package/dist/core/constants.mjs +3 -1
  43. package/dist/core/erasure/clay-codes.mjs +2 -2
  44. package/dist/core/erasure/constants.d.ts +5 -1
  45. package/dist/core/erasure/constants.mjs +3 -1
  46. package/dist/core/erasure/default.mjs +3 -3
  47. package/dist/core/erasure/index.d.ts +1 -1
  48. package/dist/core/erasure/index.mjs +8 -6
  49. package/dist/core/errors.d.ts +58 -0
  50. package/dist/core/errors.mjs +15 -0
  51. package/dist/core/index.d.ts +13 -7
  52. package/dist/core/index.mjs +102 -49
  53. package/dist/core/layout.mjs +2 -2
  54. package/dist/core/operations/generated/sdk.d.ts +207 -17
  55. package/dist/core/operations/generated/sdk.mjs +7 -1
  56. package/dist/core/operations/generated/types.d.ts +908 -0
  57. package/dist/core/operations/generated/types.mjs +63 -0
  58. package/dist/core/operations/index.d.ts +9 -3
  59. package/dist/core/operations/index.mjs +10 -4
  60. package/dist/core/rpc-responses.d.ts +69 -0
  61. package/dist/core/rpc-responses.mjs +15 -0
  62. package/dist/core/types/blobs.d.ts +7 -3
  63. package/dist/core/types/index.d.ts +3 -1
  64. package/dist/core/types/index.mjs +12 -2
  65. package/dist/core/types/payments.d.ts +94 -0
  66. package/dist/core/types/payments.mjs +9 -0
  67. package/dist/core/types/placement_groups.d.ts +30 -1
  68. package/dist/core/types/placement_groups.mjs +1 -0
  69. package/dist/core/types/storage_providers.d.ts +32 -2
  70. package/dist/node/clients/ShelbyMetadataClient.mjs +3 -3
  71. package/dist/node/clients/ShelbyNodeClient.d.ts +4 -3
  72. package/dist/node/clients/ShelbyNodeClient.mjs +16 -14
  73. package/dist/node/clients/index.d.ts +4 -3
  74. package/dist/node/clients/index.mjs +19 -17
  75. package/dist/node/index.d.ts +13 -7
  76. package/dist/node/index.mjs +106 -53
  77. package/package.json +3 -2
  78. package/dist/chunk-RBFWGDMY.mjs +0 -30
  79. /package/dist/{chunk-DJJD2AXO.mjs → chunk-AD2G3QYD.mjs} +0 -0
  80. /package/dist/{chunk-MWDW4ROU.mjs → chunk-EM67QTMR.mjs} +0 -0
  81. /package/dist/{chunk-RNXGC54D.mjs → chunk-FGUK6IBA.mjs} +0 -0
@@ -0,0 +1,83 @@
1
+ import {
2
+ getShelbyIndexerClient
3
+ } from "./chunk-3ZL3FSNA.mjs";
4
+
5
+ // src/core/clients/ShelbyPlacementGroupClient.ts
6
+ var ShelbyPlacementGroupClient = class {
7
+ indexer;
8
+ /**
9
+ * Creates a new ShelbyPlacementGroupClient.
10
+ *
11
+ * @param config - The client configuration object.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const pgClient = new ShelbyPlacementGroupClient({
16
+ * network: Network.SHELBYNET,
17
+ * });
18
+ * ```
19
+ */
20
+ constructor(config) {
21
+ this.indexer = getShelbyIndexerClient(config);
22
+ }
23
+ /**
24
+ * Retrieves placement group slots from the indexer.
25
+ *
26
+ * @param params.where (optional) - The where clause to filter slots by.
27
+ * @param params.pagination (optional) - The pagination options.
28
+ * @param params.orderBy (optional) - The order by clause to sort slots by.
29
+ * @returns The placement group slots that match the filter.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Get all active slots
34
+ * const slots = await client.getPlacementGroupSlots({
35
+ * where: { status: { _eq: "active" } },
36
+ * });
37
+ * ```
38
+ */
39
+ async getPlacementGroupSlots(params) {
40
+ const { limit, offset } = params.pagination ?? {};
41
+ const { orderBy, where } = params;
42
+ const { placement_group_slots } = await this.indexer.getPlacementGroupSlots(
43
+ {
44
+ where,
45
+ limit,
46
+ offset,
47
+ orderBy
48
+ }
49
+ );
50
+ return placement_group_slots.map(
51
+ (slot) => ({
52
+ placementGroup: slot.placement_group,
53
+ slotIndex: Number(slot.slot_index),
54
+ storageProvider: slot.storage_provider,
55
+ status: slot.status,
56
+ updatedAt: Number(slot.updated_at)
57
+ })
58
+ );
59
+ }
60
+ /**
61
+ * Retrieves the total count of placement group slots from the indexer.
62
+ *
63
+ * @param params.where (optional) - The where clause to filter slots by.
64
+ * @returns The count of placement group slots that match the filter.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // Get count of active slots
69
+ * const count = await client.getPlacementGroupSlotsCount({
70
+ * where: { status: { _eq: "active" } },
71
+ * });
72
+ * ```
73
+ */
74
+ async getPlacementGroupSlotsCount(params) {
75
+ const { where } = params;
76
+ const { placement_group_slots_aggregate } = await this.indexer.getPlacementGroupSlotsCount({ where });
77
+ return placement_group_slots_aggregate?.aggregate?.count ?? 0;
78
+ }
79
+ };
80
+
81
+ export {
82
+ ShelbyPlacementGroupClient
83
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ClayErasureCodingProvider
3
- } from "./chunk-NLPIHQ7K.mjs";
3
+ } from "./chunk-UCDAABAS.mjs";
4
4
  import {
5
5
  DEFAULT_CHUNK_SIZE_BYTES
6
6
  } from "./chunk-67F5YZ25.mjs";
@@ -8,7 +8,7 @@ import {
8
8
  DEFAULT_ERASURE_D,
9
9
  DEFAULT_ERASURE_K,
10
10
  DEFAULT_ERASURE_N
11
- } from "./chunk-ZPW742E7.mjs";
11
+ } from "./chunk-LZSIZJYR.mjs";
12
12
 
13
13
  // src/core/erasure/default.ts
14
14
  var defaultProviderPromise;
File without changes
@@ -9,11 +9,12 @@ import {
9
9
  import {
10
10
  DEFAULT_ERASURE_K,
11
11
  DEFAULT_ERASURE_M
12
- } from "./chunk-ZPW742E7.mjs";
12
+ } from "./chunk-LZSIZJYR.mjs";
13
13
 
14
14
  // src/core/commitments.ts
15
15
  import { Hex } from "@aptos-labs/ts-sdk";
16
16
  import { z } from "zod";
17
+ var COMMITMENT_SCHEMA_VERSION = "1.3";
17
18
  var ChunksetCommitmentSchema = z.object({
18
19
  // Chunkset root (vector commitment of child chunks)
19
20
  chunkset_root: z.string(),
@@ -143,7 +144,7 @@ async function generateCommitments(provider, fullData, onChunk, options) {
143
144
  chunksetCommitmentHashes.push(h);
144
145
  }
145
146
  return {
146
- schema_version: "1.3",
147
+ schema_version: COMMITMENT_SCHEMA_VERSION,
147
148
  raw_data_size: rawDataSize,
148
149
  blob_merkle_root: (await generateMerkleRoot(chunksetCommitmentHashes)).toString(),
149
150
  chunkset_commitments: chunksetCommitments
@@ -159,6 +160,7 @@ function validatePrePaddedChunkset(chunkset, expectedSize, chunksetIdx) {
159
160
  }
160
161
 
161
162
  export {
163
+ COMMITMENT_SCHEMA_VERSION,
162
164
  ChunksetCommitmentSchema,
163
165
  expectedTotalChunksets,
164
166
  BlobCommitmentsSchema,
@@ -5,7 +5,7 @@ import {
5
5
  DEFAULT_ERASURE_D,
6
6
  DEFAULT_ERASURE_K,
7
7
  DEFAULT_ERASURE_N
8
- } from "./chunk-ZPW742E7.mjs";
8
+ } from "./chunk-LZSIZJYR.mjs";
9
9
 
10
10
  // src/core/erasure/clay-codes.ts
11
11
  import {
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  DEFAULT_ERASURE_K,
7
7
  DEFAULT_ERASURE_N
8
- } from "./chunk-ZPW742E7.mjs";
8
+ } from "./chunk-LZSIZJYR.mjs";
9
9
 
10
10
  // src/core/layout.ts
11
11
  import { AccountAddress } from "@aptos-labs/ts-sdk";
@@ -1,24 +1,24 @@
1
- import {
2
- ShelbyRPCClient
3
- } from "./chunk-QRGZJBAG.mjs";
4
1
  import {
5
2
  ShelbyBlobClient
6
- } from "./chunk-BBOG5JSX.mjs";
7
- import {
8
- getAptosConfig
9
- } from "./chunk-RBFWGDMY.mjs";
3
+ } from "./chunk-AGRRYZWV.mjs";
10
4
  import {
11
5
  ClayErasureCodingProvider
12
- } from "./chunk-NLPIHQ7K.mjs";
6
+ } from "./chunk-UCDAABAS.mjs";
7
+ import {
8
+ ShelbyRPCClient
9
+ } from "./chunk-ZAM2EUVN.mjs";
10
+ import {
11
+ getAptosConfig
12
+ } from "./chunk-AABBONAF.mjs";
13
13
  import {
14
14
  createBlobKey
15
15
  } from "./chunk-OTBLZL2S.mjs";
16
16
  import {
17
17
  generateCommitments
18
- } from "./chunk-XWAPNLU6.mjs";
18
+ } from "./chunk-RLRI2533.mjs";
19
19
  import {
20
20
  NetworkToShelbyRPCBaseUrl
21
- } from "./chunk-SEXQTDX6.mjs";
21
+ } from "./chunk-FLLOQZVD.mjs";
22
22
 
23
23
  // src/core/clients/ShelbyClient.ts
24
24
  import {
@@ -109,6 +109,11 @@ var ShelbyClient = class {
109
109
  * This method handles the complete upload flow including commitment generation,
110
110
  * blockchain registration, and storage upload.
111
111
  *
112
+ * Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
113
+ * For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
114
+ * using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
115
+ * with a `ReadableStream`.
116
+ *
112
117
  * @param params.blobData - The raw data to upload as a Uint8Array.
113
118
  * @param params.signer - The account that signs and pays for the transaction.
114
119
  * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
@@ -145,7 +150,8 @@ var ShelbyClient = class {
145
150
  blobName: params.blobName,
146
151
  blobMerkleRoot: blobCommitments.blob_merkle_root,
147
152
  size: params.blobData.length,
148
- expirationMicros: params.expirationMicros
153
+ expirationMicros: params.expirationMicros,
154
+ options: params.options
149
155
  });
150
156
  await this.coordination.aptos.waitForTransaction({
151
157
  transactionHash: pendingRegisterBlobTransaction.hash
@@ -162,6 +168,11 @@ var ShelbyClient = class {
162
168
  * This method handles the complete upload flow including commitment generation,
163
169
  * blockchain registration, and storage upload.
164
170
  *
171
+ * Note: This method accepts only `Uint8Array` and buffers each blob in memory.
172
+ * For streaming uploads of large files, orchestrate the steps manually using
173
+ * `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
174
+ * with a `ReadableStream`.
175
+ *
165
176
  * @param params.blobs - The blobs to upload.
166
177
  * @param params.blobs.blobData - The raw data to upload as a Uint8Array.
167
178
  * @param params.blobs.blobName - The name/path of the blob (e.g. "folder/file.txt").
@@ -279,7 +290,8 @@ var ShelbyClient = class {
279
290
  async fundAccountWithShelbyUSD(params) {
280
291
  const { address, amount } = params;
281
292
  try {
282
- const faucet = this.config.faucet ?? "https://faucet.shelbynet.shelby.xyz/fund?asset=shelbyusd";
293
+ const faucet = this.config.faucet?.baseUrl ?? "https://faucet.shelbynet.shelby.xyz/fund?asset=shelbyusd";
294
+ const authToken = this.config.faucet?.authToken;
283
295
  const response = await fetch(`${faucet}`, {
284
296
  method: "POST",
285
297
  body: JSON.stringify({
@@ -287,11 +299,13 @@ var ShelbyClient = class {
287
299
  amount
288
300
  }),
289
301
  headers: {
290
- "Content-Type": "application/json"
302
+ "Content-Type": "application/json",
303
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
291
304
  }
292
305
  });
293
306
  if (!response.ok) {
294
- throw new Error("Failed to fund account");
307
+ const errorBody = await response.text();
308
+ throw new Error(`Failed to fund account: ${errorBody}`);
295
309
  }
296
310
  const json = await response.json();
297
311
  const res = await this.aptos.waitForTransaction({
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  getAptosConfig
3
- } from "./chunk-RBFWGDMY.mjs";
3
+ } from "./chunk-AABBONAF.mjs";
4
4
  import {
5
5
  SHELBY_DEPLOYER
6
- } from "./chunk-SEXQTDX6.mjs";
6
+ } from "./chunk-FLLOQZVD.mjs";
7
7
 
8
8
  // src/node/clients/ShelbyMetadataClient.ts
9
9
  import {
@@ -11,6 +11,29 @@ import {
11
11
  Aptos,
12
12
  Hex
13
13
  } from "@aptos-labs/ts-sdk";
14
+ function parseStorageProviderState(raw) {
15
+ switch (raw.variant) {
16
+ case "Active":
17
+ return {
18
+ variant: "Active",
19
+ quota: raw.Active.quota.value,
20
+ stakeAtStartOfStakingEpoch: raw.Active.stake_at_start_of_staking_epoch,
21
+ faulty: raw.Active.faulty,
22
+ leaving: raw.Active.leaving
23
+ };
24
+ case "Waitlisted":
25
+ return {
26
+ variant: "Waitlisted"
27
+ };
28
+ case "Frozen":
29
+ return {
30
+ variant: "Frozen",
31
+ frozenReason: raw.Frozen.frozen_reason,
32
+ frozenFrom: raw.Frozen.frozen_from,
33
+ frozenTill: raw.Frozen.frozen_till
34
+ };
35
+ }
36
+ }
14
37
  var ShelbyMetadataClient = class {
15
38
  aptos;
16
39
  deployer;
@@ -45,7 +68,7 @@ var ShelbyMetadataClient = class {
45
68
  try {
46
69
  const rawMetadata = await this.aptos.view({
47
70
  payload: {
48
- function: `${this.deployer.toString()}::global_metadata::get_all_storage_providers`,
71
+ function: `${this.deployer.toString()}::storage_provider_registry::get_all_storage_providers`,
49
72
  functionArguments: []
50
73
  }
51
74
  });
@@ -55,7 +78,8 @@ var ShelbyMetadataClient = class {
55
78
  ipAddress: provider.ip_address,
56
79
  port: provider.port,
57
80
  blsPublicKey: Hex.fromHexInput(provider.bls_public_key).toUint8Array(),
58
- failureDomain: provider.failure_domain
81
+ failureDomain: provider.failure_domain,
82
+ state: parseStorageProviderState(provider.state)
59
83
  }));
60
84
  } catch (error) {
61
85
  if (error instanceof Error && // Depending on the network, the error message may show up differently.
@@ -79,14 +103,14 @@ var ShelbyMetadataClient = class {
79
103
  try {
80
104
  const pgSizeMetadata = await this.aptos.view({
81
105
  payload: {
82
- function: `${this.deployer.toString()}::global_metadata::get_number_of_placement_groups`,
106
+ function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
83
107
  functionArguments: []
84
108
  }
85
109
  });
86
110
  const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
87
111
  const addressMetadataArray = await this.aptos.view({
88
112
  payload: {
89
- function: `${this.deployer.toString()}::global_metadata::get_placement_group_addresses`,
113
+ function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
90
114
  functionArguments: [0, finalPlacementGroupIndex]
91
115
  }
92
116
  });
@@ -114,14 +138,14 @@ var ShelbyMetadataClient = class {
114
138
  try {
115
139
  const sliceSizeMetadata = await this.aptos.view({
116
140
  payload: {
117
- function: `${this.deployer.toString()}::global_metadata::get_number_of_slices`,
141
+ function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
118
142
  functionArguments: []
119
143
  }
120
144
  });
121
145
  const finalSliceIndex = sliceSizeMetadata[0] - 1;
122
146
  const addressMetadataArray = await this.aptos.view({
123
147
  payload: {
124
- function: `${this.deployer.toString()}::global_metadata::get_slice_addresses`,
148
+ function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
125
149
  functionArguments: [0, finalSliceIndex]
126
150
  }
127
151
  });
@@ -1,24 +1,48 @@
1
+ import {
2
+ StaleChannelStateError
3
+ } from "./chunk-4MG4XGY4.mjs";
4
+ import {
5
+ getShelbyIndexerClient
6
+ } from "./chunk-3ZL3FSNA.mjs";
7
+ import {
8
+ BlobNameSchema
9
+ } from "./chunk-W5NRGZEP.mjs";
1
10
  import {
2
11
  sleep
3
12
  } from "./chunk-I6NG5GNL.mjs";
4
13
  import {
5
- getShelbyIndexerClient
6
- } from "./chunk-HFGEQP5N.mjs";
14
+ StaleMicropaymentErrorResponseSchema,
15
+ StartMultipartUploadResponseSchema
16
+ } from "./chunk-IE6LYVIA.mjs";
7
17
  import {
8
- buildRequestUrl
18
+ buildRequestUrl,
19
+ readInChunks
9
20
  } from "./chunk-4JZO2D7T.mjs";
10
21
  import {
11
22
  NetworkToShelbyRPCBaseUrl
12
- } from "./chunk-SEXQTDX6.mjs";
13
- import {
14
- BlobNameSchema
15
- } from "./chunk-GY5DCVVL.mjs";
23
+ } from "./chunk-FLLOQZVD.mjs";
16
24
 
17
25
  // src/core/clients/ShelbyRPCClient.ts
18
26
  import { AccountAddress } from "@aptos-labs/ts-sdk";
27
+ var MICROPAYMENT_HEADER = "X-Shelby-Micropayment";
19
28
  function encodeURIComponentKeepSlashes(str) {
20
29
  return encodeURIComponent(str).replace(/%2F/g, "/");
21
30
  }
31
+ function validateTotalBytes(totalBytes) {
32
+ if (!Number.isInteger(totalBytes) || totalBytes < 0) {
33
+ throw new Error("totalBytes must be a non-negative integer");
34
+ }
35
+ }
36
+ function getErrorCode(error) {
37
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
38
+ return error.code;
39
+ }
40
+ if (error instanceof Error) {
41
+ const match = error.message.match(/\bE[A-Z0-9_]+\b/);
42
+ return match?.[0];
43
+ }
44
+ return void 0;
45
+ }
22
46
  var ShelbyRPCClient = class {
23
47
  baseUrl;
24
48
  apiKey;
@@ -47,30 +71,53 @@ var ShelbyRPCClient = class {
47
71
  }
48
72
  async #uploadPart(uploadId, partIdx, partData) {
49
73
  const nRetries = 5;
74
+ let lastResponse;
75
+ let lastError;
76
+ const partUrl = buildRequestUrl(
77
+ `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
78
+ this.baseUrl
79
+ );
50
80
  for (let i = 0; i < nRetries; ++i) {
51
- const partResponse = await fetch(
52
- buildRequestUrl(
53
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
54
- this.baseUrl
55
- ),
56
- {
81
+ try {
82
+ lastResponse = await fetch(partUrl, {
57
83
  method: "PUT",
58
84
  headers: {
59
85
  "Content-Type": "application/octet-stream",
60
86
  ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
61
87
  },
62
88
  body: partData
89
+ });
90
+ lastError = void 0;
91
+ } catch (error) {
92
+ lastError = error;
93
+ if (i < nRetries - 1) {
94
+ const delay = 2 ** i * 100;
95
+ await sleep(delay);
96
+ continue;
63
97
  }
64
- );
65
- if (partResponse.ok) return;
98
+ break;
99
+ }
100
+ if (lastResponse.ok) return;
66
101
  if (i < nRetries - 1) {
67
102
  const delay = 2 ** i * 100;
68
103
  await sleep(delay);
69
104
  }
70
105
  }
71
- throw new Error(`Failed to upload part ${partIdx}.`);
106
+ if (lastError !== void 0) {
107
+ const errorCode = getErrorCode(lastError);
108
+ const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
109
+ throw new Error(
110
+ `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${nRetries} attempts. The connection to the Shelby RPC endpoint was interrupted while sending data${errorCode ? ` (${errorCode})` : ""}. Endpoint: ${partUrl.toString()}, partBytes: ${partData.length}. Last error: ${errorMessage}`,
111
+ { cause: lastError }
112
+ );
113
+ }
114
+ const errorBody = await lastResponse?.text().catch(() => "");
115
+ throw new Error(
116
+ `Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${nRetries} attempts. status: ${lastResponse?.status}, body: ${errorBody}`
117
+ );
72
118
  }
73
- async #putBlobMultipart(account, blobName, blobData, partSize = 5 * 1024 * 1024) {
119
+ async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
120
+ validateTotalBytes(totalBytes);
74
121
  const startResponse = await fetch(
75
122
  buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
76
123
  {
@@ -96,14 +143,38 @@ var ShelbyRPCClient = class {
96
143
  `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
97
144
  );
98
145
  }
99
- const { uploadId } = await startResponse.json();
100
- const totalParts = Math.ceil(blobData.length / partSize);
101
- for (let partIdx = 0; partIdx < totalParts; partIdx++) {
102
- const start = partIdx * partSize;
103
- const end = Math.min(start + partSize, blobData.length);
104
- const partData = blobData.slice(start, end);
146
+ const { uploadId } = StartMultipartUploadResponseSchema.parse(
147
+ await startResponse.json()
148
+ );
149
+ const totalParts = Math.ceil(totalBytes / partSize);
150
+ let uploadedBytes = 0;
151
+ for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
105
152
  await this.#uploadPart(uploadId, partIdx, partData);
153
+ uploadedBytes += partData.length;
154
+ onProgress?.({
155
+ phase: "uploading",
156
+ partIdx,
157
+ totalParts,
158
+ partBytes: partData.length,
159
+ uploadedBytes,
160
+ totalBytes
161
+ });
162
+ }
163
+ if (uploadedBytes !== totalBytes) {
164
+ throw new Error(
165
+ `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
166
+ );
106
167
  }
168
+ const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
169
+ onProgress?.({
170
+ phase: "finalizing",
171
+ partIdx: finalPartIdx,
172
+ totalParts,
173
+ // no part uploaded in this phase
174
+ partBytes: 0,
175
+ uploadedBytes: totalBytes,
176
+ totalBytes
177
+ });
107
178
  const completeResponse = await fetch(
108
179
  buildRequestUrl(
109
180
  `/v1/multipart-uploads/${uploadId}/complete`,
@@ -135,7 +206,8 @@ var ShelbyRPCClient = class {
135
206
  *
136
207
  * @param params.account - The account that owns the blob.
137
208
  * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
138
- * @param params.blobData - The raw blob data as a Uint8Array.
209
+ * @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
210
+ * @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
139
211
  *
140
212
  * @example
141
213
  * ```typescript
@@ -144,19 +216,38 @@ var ShelbyRPCClient = class {
144
216
  * await client.putBlob({
145
217
  * account: AccountAddress.from("0x1"),
146
218
  * blobName: "greetings/hello.txt",
147
- * blobData
219
+ * blobData,
148
220
  * });
149
221
  * ```
150
222
  */
151
223
  async putBlob(params) {
152
224
  BlobNameSchema.parse(params.blobName);
225
+ let totalBytes;
226
+ if (params.blobData instanceof Uint8Array) {
227
+ totalBytes = params.totalBytes ?? params.blobData.length;
228
+ if (totalBytes !== params.blobData.length) {
229
+ throw new Error(
230
+ "totalBytes must match blobData.length when blobData is a Uint8Array"
231
+ );
232
+ }
233
+ } else {
234
+ if (params.totalBytes === void 0) {
235
+ throw new Error(
236
+ "totalBytes is required when blobData is a ReadableStream"
237
+ );
238
+ }
239
+ totalBytes = params.totalBytes;
240
+ }
241
+ validateTotalBytes(totalBytes);
153
242
  await this.#putBlobMultipart(
154
243
  params.account,
155
244
  params.blobName,
156
- params.blobData
245
+ params.blobData,
246
+ totalBytes,
247
+ void 0,
248
+ params.onProgress
157
249
  );
158
250
  }
159
- // FIXME make this possible to stream in put ^^^
160
251
  /**
161
252
  * Downloads a blob from the Shelby RPC node.
162
253
  * Returns a streaming response with validation to ensure data integrity.
@@ -166,10 +257,12 @@ var ShelbyRPCClient = class {
166
257
  * @param params.range - Optional byte range for partial downloads.
167
258
  * @param params.range.start - Starting byte position (inclusive).
168
259
  * @param params.range.end - Ending byte position (inclusive, optional).
260
+ * @param params.micropayment - Optional micropayment to attach to the request.
169
261
  *
170
262
  * @returns A ShelbyBlob object containing the account, name, readable stream, and content length.
171
263
  *
172
264
  * @throws Error if the download fails or content length doesn't match.
265
+ * @throws StaleChannelStateError if the micropayment is stale (server has newer state).
173
266
  *
174
267
  * @example
175
268
  * ```typescript
@@ -185,6 +278,13 @@ var ShelbyRPCClient = class {
185
278
  * blobName: "large-file.bin",
186
279
  * range: { start: 100, end: 199 }
187
280
  * });
281
+ *
282
+ * // Download with micropayment
283
+ * const blob = await client.getBlob({
284
+ * account: AccountAddress.from("0x1"),
285
+ * blobName: "documents/report.pdf",
286
+ * micropayment: senderBuiltMicropayment
287
+ * });
188
288
  * ```
189
289
  */
190
290
  async getBlob(params) {
@@ -195,9 +295,8 @@ var ShelbyRPCClient = class {
195
295
  )}`,
196
296
  this.baseUrl
197
297
  );
198
- const requestInit = {};
298
+ const headers = new Headers();
199
299
  if (params.range !== void 0) {
200
- const headers = new Headers();
201
300
  const { start, end } = params.range;
202
301
  if (end === void 0) {
203
302
  headers.set("Range", `bytes=${start}-`);
@@ -207,15 +306,45 @@ var ShelbyRPCClient = class {
207
306
  }
208
307
  headers.set("Range", `bytes=${start}-${end}`);
209
308
  }
210
- requestInit.headers = headers;
211
309
  }
212
- const response = await fetch(url, {
213
- ...requestInit,
214
- headers: {
215
- ...requestInit.headers,
216
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
310
+ if (this.apiKey) {
311
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
312
+ }
313
+ if (params.micropayment) {
314
+ const bytes = params.micropayment.bcsToBytes();
315
+ const binaryString = Array.from(
316
+ bytes,
317
+ (byte) => String.fromCharCode(byte)
318
+ ).join("");
319
+ headers.set(MICROPAYMENT_HEADER, btoa(binaryString));
320
+ }
321
+ const response = await fetch(url, { headers });
322
+ if (response.status === 409) {
323
+ let json;
324
+ try {
325
+ json = await response.json();
326
+ } catch {
327
+ throw new Error(
328
+ `Failed to download blob: ${response.status} ${response.statusText}`
329
+ );
217
330
  }
218
- });
331
+ const parseResult = StaleMicropaymentErrorResponseSchema.safeParse(json);
332
+ if (!parseResult.success) {
333
+ throw new Error(
334
+ `Failed to download blob: ${response.status} ${response.statusText}`
335
+ );
336
+ }
337
+ const errorBody = parseResult.data;
338
+ if (errorBody.storedMicropayment) {
339
+ throw StaleChannelStateError.fromBase64(
340
+ errorBody.storedMicropayment,
341
+ errorBody.error
342
+ );
343
+ }
344
+ throw new Error(
345
+ errorBody.error ?? `Failed to download blob: ${response.status} ${response.statusText}`
346
+ );
347
+ }
219
348
  if (!response.ok) {
220
349
  throw new Error(
221
350
  `Failed to download blob: ${response.status} ${response.statusText}`
@@ -5,7 +5,7 @@ import {
5
5
  DEFAULT_CHUNK_SIZE_BYTES,
6
6
  ERASURE_CODE_AND_CHUNK_MAPPING
7
7
  } from "../chunk-67F5YZ25.mjs";
8
- import "../chunk-ZPW742E7.mjs";
8
+ import "../chunk-LZSIZJYR.mjs";
9
9
  import "../chunk-7P6ASYW6.mjs";
10
10
  export {
11
11
  CHUNK_SIZE_PARAMS,