@shelby-protocol/sdk 0.0.9 → 0.2.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 (99) hide show
  1. package/dist/browser/index.d.ts +17 -10
  2. package/dist/browser/index.mjs +89 -28
  3. package/dist/{chunk-XWAPNLU6.mjs → chunk-2WEX3K7C.mjs} +6 -4
  4. package/dist/chunk-4MG4XGY4.mjs +91 -0
  5. package/dist/{chunk-SEXQTDX6.mjs → chunk-4ZOFT75Q.mjs} +16 -2
  6. package/dist/{chunk-BTHSKDJR.mjs → chunk-7PN65RDX.mjs} +44 -10
  7. package/dist/chunk-AABBONAF.mjs +34 -0
  8. package/dist/{chunk-ZPW742E7.mjs → chunk-AUQDI5BS.mjs} +17 -2
  9. package/dist/{chunk-HFGEQP5N.mjs → chunk-CGYJLKBU.mjs} +4 -4
  10. package/dist/chunk-CQ6QPIZK.mjs +37 -0
  11. package/dist/{chunk-VRLIOKWG.mjs → chunk-D6GQHO6G.mjs} +5 -1
  12. package/dist/chunk-E5QCRTBU.mjs +493 -0
  13. package/dist/chunk-HPPMI7DC.mjs +56 -0
  14. package/dist/chunk-IE6LYVIA.mjs +26 -0
  15. package/dist/{chunk-67F5YZ25.mjs → chunk-JTXYKO3U.mjs} +10 -0
  16. package/dist/{chunk-WJKSPJSS.mjs → chunk-KJ24NKPH.mjs} +46 -0
  17. package/dist/{chunk-WBFEX7OM.mjs → chunk-MV6FNYAU.mjs} +31 -7
  18. package/dist/{chunk-PZF2VTGP.mjs → chunk-NHWWORCH.mjs} +3 -1
  19. package/dist/{chunk-NLPIHQ7K.mjs → chunk-OGKZ575S.mjs} +6 -19
  20. package/dist/{chunk-5I3MBJGN.mjs → chunk-PJVWGMVI.mjs} +164 -35
  21. package/dist/{chunk-7UVMDCCR.mjs → chunk-SRV4YWFH.mjs} +232 -47
  22. package/dist/chunk-W37FZSMA.mjs +83 -0
  23. package/dist/chunk-WTICJPDB.mjs +0 -0
  24. package/dist/chunk-Z4FZ7W6L.mjs +39 -0
  25. package/dist/{chunk-QFWQ7FIC.mjs → chunk-ZEDD2MPU.mjs} +1 -1
  26. package/dist/{clay-codes-pdZFxI_B.d.ts → clay-codes-DHP-bYcP.d.ts} +6 -2
  27. package/dist/core/blobs.d.ts +2 -0
  28. package/dist/core/chunk.d.ts +10 -1
  29. package/dist/core/chunk.mjs +2 -2
  30. package/dist/core/clients/ShelbyBlobClient.d.ts +165 -12
  31. package/dist/core/clients/ShelbyBlobClient.mjs +13 -11
  32. package/dist/core/clients/ShelbyClient.d.ts +21 -4
  33. package/dist/core/clients/ShelbyClient.mjs +17 -14
  34. package/dist/core/clients/ShelbyClientConfig.d.ts +7 -2
  35. package/dist/{node → core}/clients/ShelbyMetadataClient.d.ts +3 -3
  36. package/dist/core/clients/ShelbyMetadataClient.mjs +9 -0
  37. package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +349 -0
  38. package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +16 -0
  39. package/dist/core/clients/ShelbyPlacementGroupClient.d.ts +73 -0
  40. package/dist/core/clients/ShelbyPlacementGroupClient.mjs +11 -0
  41. package/dist/core/clients/ShelbyRPCClient.d.ts +30 -4
  42. package/dist/core/clients/ShelbyRPCClient.mjs +8 -8
  43. package/dist/core/clients/index.d.ts +11 -5
  44. package/dist/core/clients/index.mjs +35 -15
  45. package/dist/core/clients/utils.d.ts +54 -0
  46. package/dist/core/clients/utils.mjs +1 -1
  47. package/dist/core/commitments.d.ts +6 -2
  48. package/dist/core/commitments.mjs +5 -3
  49. package/dist/core/constants.d.ts +18 -3
  50. package/dist/core/constants.mjs +5 -1
  51. package/dist/core/erasure/clay-codes.d.ts +1 -1
  52. package/dist/core/erasure/clay-codes.mjs +5 -5
  53. package/dist/core/erasure/constants.d.ts +15 -3
  54. package/dist/core/erasure/constants.mjs +3 -1
  55. package/dist/core/erasure/default.d.ts +5 -2
  56. package/dist/core/erasure/default.mjs +12 -6
  57. package/dist/core/erasure/index.d.ts +3 -3
  58. package/dist/core/erasure/index.mjs +15 -7
  59. package/dist/core/erasure/provider.d.ts +1 -1
  60. package/dist/core/erasure/reed-solomon.d.ts +1 -1
  61. package/dist/core/erasure/reed-solomon.mjs +1 -1
  62. package/dist/core/erasure/utils.d.ts +1 -1
  63. package/dist/core/errors.d.ts +58 -0
  64. package/dist/core/errors.mjs +15 -0
  65. package/dist/core/index.d.ts +17 -10
  66. package/dist/core/index.mjs +89 -28
  67. package/dist/core/layout.d.ts +5 -16
  68. package/dist/core/layout.mjs +3 -9
  69. package/dist/core/networks.d.ts +1 -1
  70. package/dist/core/networks.mjs +1 -1
  71. package/dist/core/operations/generated/sdk.d.ts +207 -17
  72. package/dist/core/operations/generated/sdk.mjs +7 -1
  73. package/dist/core/operations/index.d.ts +9 -3
  74. package/dist/core/operations/index.mjs +10 -4
  75. package/dist/core/rpc-responses.d.ts +69 -0
  76. package/dist/core/rpc-responses.mjs +15 -0
  77. package/dist/core/types/blobs.d.ts +8 -4
  78. package/dist/core/types/encodings.d.ts +1 -1
  79. package/dist/core/types/index.d.ts +4 -2
  80. package/dist/core/types/index.mjs +12 -2
  81. package/dist/core/types/payments.d.ts +94 -0
  82. package/dist/core/types/payments.mjs +9 -0
  83. package/dist/core/types/placement_groups.d.ts +30 -1
  84. package/dist/core/types/placement_groups.mjs +1 -0
  85. package/dist/core/types/storage_providers.d.ts +32 -2
  86. package/dist/node/clients/ShelbyNodeClient.d.ts +9 -6
  87. package/dist/node/clients/ShelbyNodeClient.mjs +18 -15
  88. package/dist/node/clients/index.d.ts +7 -6
  89. package/dist/node/clients/index.mjs +19 -17
  90. package/dist/node/index.d.ts +17 -11
  91. package/dist/node/index.mjs +91 -34
  92. package/package.json +3 -2
  93. package/dist/chunk-CPNZAQVY.mjs +0 -29
  94. package/dist/chunk-GY5DCVVL.mjs +0 -86
  95. package/dist/chunk-RBFWGDMY.mjs +0 -30
  96. package/dist/node/clients/ShelbyMetadataClient.mjs +0 -9
  97. /package/dist/{chunk-DJJD2AXO.mjs → chunk-AD2G3QYD.mjs} +0 -0
  98. /package/dist/{chunk-MWDW4ROU.mjs → chunk-EM67QTMR.mjs} +0 -0
  99. /package/dist/{chunk-RNXGC54D.mjs → chunk-QQ57OGQ2.mjs} +0 -0
@@ -1,6 +1,7 @@
1
1
  // src/core/chunk.ts
2
2
  var ChunkSizeScheme = /* @__PURE__ */ ((ChunkSizeScheme2) => {
3
3
  ChunkSizeScheme2["ChunkSet10MiB_Chunk1MiB"] = "ChunkSet10MiB_Chunk1MiB";
4
+ ChunkSizeScheme2["ChunkSet2MiB_Chunk1MiB"] = "ChunkSet2MiB_Chunk1MiB";
4
5
  return ChunkSizeScheme2;
5
6
  })(ChunkSizeScheme || {});
6
7
  var CHUNK_SIZE_PARAMS = {
@@ -9,6 +10,12 @@ var CHUNK_SIZE_PARAMS = {
9
10
  chunkSizeBytes: 1 * 1024 * 1024,
10
11
  // 10MiB
11
12
  chunksetSizeBytes: 10 * 1024 * 1024
13
+ },
14
+ ["ChunkSet2MiB_Chunk1MiB" /* ChunkSet2MiB_Chunk1MiB */]: {
15
+ // 1MiB
16
+ chunkSizeBytes: 1 * 1024 * 1024,
17
+ // 2MiB
18
+ chunksetSizeBytes: 2 * 1024 * 1024
12
19
  }
13
20
  };
14
21
  var DEFAULT_CHUNK_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */].chunkSizeBytes;
@@ -16,6 +23,9 @@ var DEFAULT_CHUNKSET_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /*
16
23
  var ERASURE_CODE_AND_CHUNK_MAPPING = {
17
24
  ["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */]: {
18
25
  ...CHUNK_SIZE_PARAMS.ChunkSet10MiB_Chunk1MiB
26
+ },
27
+ ["ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */]: {
28
+ ...CHUNK_SIZE_PARAMS.ChunkSet2MiB_Chunk1MiB
19
29
  }
20
30
  };
21
31
 
@@ -105,6 +105,40 @@ var GetBlobActivitiesCountDocument = gql`
105
105
  }
106
106
  }
107
107
  `;
108
+ var GetPlacementGroupSlotsDocument = gql`
109
+ query getPlacementGroupSlots($where: placement_group_slots_bool_exp, $orderBy: [placement_group_slots_order_by!], $limit: Int, $offset: Int) {
110
+ placement_group_slots(
111
+ where: $where
112
+ order_by: $orderBy
113
+ limit: $limit
114
+ offset: $offset
115
+ ) {
116
+ placement_group
117
+ slot_index
118
+ storage_provider
119
+ status
120
+ updated_at
121
+ }
122
+ }
123
+ `;
124
+ var GetPlacementGroupSlotsCountDocument = gql`
125
+ query getPlacementGroupSlotsCount($where: placement_group_slots_bool_exp) {
126
+ placement_group_slots_aggregate(where: $where) {
127
+ aggregate {
128
+ count
129
+ }
130
+ }
131
+ }
132
+ `;
133
+ var GetProcessorStatusDocument = gql`
134
+ query getProcessorStatus {
135
+ processor_status {
136
+ last_success_version
137
+ last_transaction_timestamp
138
+ last_updated
139
+ }
140
+ }
141
+ `;
108
142
  var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
109
143
  function getSdk(client, withWrapper = defaultWrapper) {
110
144
  return {
@@ -119,6 +153,15 @@ function getSdk(client, withWrapper = defaultWrapper) {
119
153
  },
120
154
  getBlobActivitiesCount(variables, requestHeaders, signal) {
121
155
  return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivitiesCount", "query", variables);
156
+ },
157
+ getPlacementGroupSlots(variables, requestHeaders, signal) {
158
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetPlacementGroupSlotsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getPlacementGroupSlots", "query", variables);
159
+ },
160
+ getPlacementGroupSlotsCount(variables, requestHeaders, signal) {
161
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetPlacementGroupSlotsCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getPlacementGroupSlotsCount", "query", variables);
162
+ },
163
+ getProcessorStatus(variables, requestHeaders, signal) {
164
+ return withWrapper((wrappedRequestHeaders) => client.request({ document: GetProcessorStatusDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getProcessorStatus", "query", variables);
122
165
  }
123
166
  };
124
167
  }
@@ -134,5 +177,8 @@ export {
134
177
  GetBlobActivitiesDocument,
135
178
  GetBlobsCountDocument,
136
179
  GetBlobActivitiesCountDocument,
180
+ GetPlacementGroupSlotsDocument,
181
+ GetPlacementGroupSlotsCountDocument,
182
+ GetProcessorStatusDocument,
137
183
  getSdk
138
184
  };
@@ -1,16 +1,39 @@
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-4ZOFT75Q.mjs";
7
7
 
8
- // src/node/clients/ShelbyMetadataClient.ts
8
+ // src/core/clients/ShelbyMetadataClient.ts
9
9
  import {
10
10
  AccountAddress,
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.quota.value,
20
+ stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
21
+ faulty: raw.faulty,
22
+ leaving: raw.leaving
23
+ };
24
+ case "Waitlisted":
25
+ return {
26
+ variant: "Waitlisted"
27
+ };
28
+ case "Frozen":
29
+ return {
30
+ variant: "Frozen",
31
+ frozenReason: raw.frozen_reason,
32
+ frozenFrom: raw.frozen_from,
33
+ frozenTill: raw.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
  });
@@ -9,6 +9,7 @@ var ReedSolomonErasureCodingProvider = class {
9
9
  const erasure_k = options?.erasure_k ?? DEFAULT_ERASURE_K;
10
10
  const erasure_n = options?.erasure_n ?? DEFAULT_ERASURE_N;
11
11
  const chunkSizeBytes = options?.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE_BYTES;
12
+ const enumIndex = -1;
12
13
  if (erasure_k <= 0)
13
14
  throw new Error("erasure_k (number of data chunks) must be > 0");
14
15
  if (erasure_n <= erasure_k)
@@ -19,7 +20,8 @@ var ReedSolomonErasureCodingProvider = class {
19
20
  this.config = {
20
21
  erasure_n,
21
22
  erasure_k,
22
- chunkSizeBytes
23
+ chunkSizeBytes,
24
+ enumIndex
23
25
  };
24
26
  }
25
27
  encode(data) {
@@ -1,12 +1,3 @@
1
- import {
2
- DEFAULT_CHUNK_SIZE_BYTES
3
- } from "./chunk-67F5YZ25.mjs";
4
- import {
5
- DEFAULT_ERASURE_D,
6
- DEFAULT_ERASURE_K,
7
- DEFAULT_ERASURE_N
8
- } from "./chunk-ZPW742E7.mjs";
9
-
10
1
  // src/core/erasure/clay-codes.ts
11
2
  import {
12
3
  createDecoder,
@@ -27,13 +18,7 @@ var ClayErasureCodingProvider = class _ClayErasureCodingProvider {
27
18
  /**
28
19
  * Static factory method to create an initialized ClayErasureCodingProvider
29
20
  */
30
- static async create(options) {
31
- const config = buildClayConfig({
32
- erasure_n: options?.erasure_n ?? DEFAULT_ERASURE_N,
33
- erasure_k: options?.erasure_k ?? DEFAULT_ERASURE_K,
34
- erasure_d: options?.erasure_d ?? DEFAULT_ERASURE_D,
35
- chunkSizeBytes: options?.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE_BYTES
36
- });
21
+ static async create(config) {
37
22
  const provider = new _ClayErasureCodingProvider(config);
38
23
  [provider.encoderCache, provider.decoderCache] = await Promise.all([
39
24
  createEncoder({
@@ -118,7 +103,7 @@ var ClayErasureCodingProvider = class _ClayErasureCodingProvider {
118
103
  }
119
104
  };
120
105
  function buildClayConfig(input) {
121
- const { erasure_n, erasure_k, erasure_d, chunkSizeBytes } = input;
106
+ const { erasure_n, erasure_k, erasure_d, chunkSizeBytes, enumIndex } = input;
122
107
  if (erasure_n <= 0)
123
108
  throw new Error("erasure_n (total number of chunks) must be > 0");
124
109
  if (erasure_k <= 0)
@@ -140,10 +125,12 @@ function buildClayConfig(input) {
140
125
  erasure_n,
141
126
  erasure_k,
142
127
  erasure_d,
143
- chunkSizeBytes
128
+ chunkSizeBytes,
129
+ enumIndex
144
130
  };
145
131
  }
146
132
 
147
133
  export {
148
- ClayErasureCodingProvider
134
+ ClayErasureCodingProvider,
135
+ buildClayConfig
149
136
  };
@@ -1,24 +1,48 @@
1
1
  import {
2
- getShelbyIndexerClient
3
- } from "./chunk-HFGEQP5N.mjs";
2
+ StaleChannelStateError
3
+ } from "./chunk-4MG4XGY4.mjs";
4
4
  import {
5
- BlobNameSchema
6
- } from "./chunk-GY5DCVVL.mjs";
5
+ getShelbyIndexerClient
6
+ } from "./chunk-CGYJLKBU.mjs";
7
7
  import {
8
8
  sleep
9
9
  } from "./chunk-I6NG5GNL.mjs";
10
10
  import {
11
- buildRequestUrl
11
+ StaleMicropaymentErrorResponseSchema,
12
+ StartMultipartUploadResponseSchema
13
+ } from "./chunk-IE6LYVIA.mjs";
14
+ import {
15
+ buildRequestUrl,
16
+ readInChunks
12
17
  } from "./chunk-4JZO2D7T.mjs";
13
18
  import {
14
19
  NetworkToShelbyRPCBaseUrl
15
- } from "./chunk-SEXQTDX6.mjs";
20
+ } from "./chunk-4ZOFT75Q.mjs";
21
+ import {
22
+ BlobNameSchema
23
+ } from "./chunk-Z4FZ7W6L.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
+ });
106
162
  }
163
+ if (uploadedBytes !== totalBytes) {
164
+ throw new Error(
165
+ `Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
166
+ );
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}`