@shelby-protocol/cli 0.0.12 → 0.0.13

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 (2) hide show
  1. package/bin/entry.js +602 -602
  2. package/package.json +3 -3
package/bin/entry.js CHANGED
@@ -4,12 +4,73 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // package.json
7
- var version = "0.0.12";
7
+ var version = "0.0.13";
8
8
 
9
9
  // src/commands/account.tsx
10
10
  import readline from "readline";
11
11
  import { Account as Account3, AptosApiError as AptosApiError2, Ed25519PrivateKey as Ed25519PrivateKey4 } from "@aptos-labs/ts-sdk";
12
12
 
13
+ // ../../packages/sdk/dist/chunk-RBFWGDMY.mjs
14
+ import { AptosConfig, Network } from "@aptos-labs/ts-sdk";
15
+ var getAptosConfig = (config) => {
16
+ if (config.aptos) {
17
+ return new AptosConfig(config.aptos);
18
+ }
19
+ let aptosConfig;
20
+ switch (config.network) {
21
+ case "local":
22
+ aptosConfig = new AptosConfig({
23
+ network: Network.LOCAL
24
+ });
25
+ break;
26
+ case "shelbynet":
27
+ aptosConfig = new AptosConfig({
28
+ network: Network.SHELBYNET,
29
+ clientConfig: {
30
+ API_KEY: config.apiKey
31
+ }
32
+ });
33
+ break;
34
+ default:
35
+ throw new Error(`Unsupported network: ${config.network}`);
36
+ }
37
+ return aptosConfig;
38
+ };
39
+
40
+ // ../../packages/sdk/dist/chunk-SEXQTDX6.mjs
41
+ import { Network as Network2 } from "@aptos-labs/ts-sdk";
42
+ var NetworkToShelbyRPCBaseUrl = {
43
+ [Network2.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
44
+ [Network2.DEVNET]: void 0,
45
+ [Network2.TESTNET]: void 0,
46
+ [Network2.MAINNET]: void 0,
47
+ [Network2.LOCAL]: void 0,
48
+ [Network2.CUSTOM]: void 0
49
+ };
50
+ var NetworkToShelbyBlobIndexerBaseUrl = {
51
+ [Network2.SHELBYNET]: "https://api.shelbynet.aptoslabs.com/nocode/v1/public/cmforrguw0042s601fn71f9l2/v1/graphql",
52
+ [Network2.DEVNET]: void 0,
53
+ [Network2.TESTNET]: void 0,
54
+ [Network2.MAINNET]: void 0,
55
+ [Network2.LOCAL]: void 0,
56
+ [Network2.CUSTOM]: void 0
57
+ };
58
+ var SHELBY_DEPLOYER = "0xc63d6a5efb0080a6029403131715bd4971e1149f7cc099aac69bb0069b3ddbf5";
59
+ var SHELBYUSD_TOKEN_NAME = "ShelbyUSD";
60
+ var SHELBYUSD_FA_METADATA_ADDRESS = "0x1b18363a9f1fe5e6ebf247daba5cc1c18052bb232efdc4c50f556053922d98e1";
61
+
62
+ // ../../packages/sdk/dist/chunk-CTGCK3H2.mjs
63
+ import {
64
+ AccountAddress,
65
+ Aptos,
66
+ Hex
67
+ } from "@aptos-labs/ts-sdk";
68
+
69
+ // ../../packages/sdk/dist/chunk-I6NG5GNL.mjs
70
+ function sleep(ms) {
71
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
72
+ }
73
+
13
74
  // ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
14
75
  var __assign = function() {
15
76
  __assign = Object.assign || function __assign2(t) {
@@ -3257,55 +3318,6 @@ function getSdk(client, withWrapper = defaultWrapper) {
3257
3318
  };
3258
3319
  }
3259
3320
 
3260
- // ../../packages/sdk/dist/chunk-RBFWGDMY.mjs
3261
- import { AptosConfig, Network } from "@aptos-labs/ts-sdk";
3262
- var getAptosConfig = (config) => {
3263
- if (config.aptos) {
3264
- return new AptosConfig(config.aptos);
3265
- }
3266
- let aptosConfig;
3267
- switch (config.network) {
3268
- case "local":
3269
- aptosConfig = new AptosConfig({
3270
- network: Network.LOCAL
3271
- });
3272
- break;
3273
- case "shelbynet":
3274
- aptosConfig = new AptosConfig({
3275
- network: Network.SHELBYNET,
3276
- clientConfig: {
3277
- API_KEY: config.apiKey
3278
- }
3279
- });
3280
- break;
3281
- default:
3282
- throw new Error(`Unsupported network: ${config.network}`);
3283
- }
3284
- return aptosConfig;
3285
- };
3286
-
3287
- // ../../packages/sdk/dist/chunk-SEXQTDX6.mjs
3288
- import { Network as Network2 } from "@aptos-labs/ts-sdk";
3289
- var NetworkToShelbyRPCBaseUrl = {
3290
- [Network2.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
3291
- [Network2.DEVNET]: void 0,
3292
- [Network2.TESTNET]: void 0,
3293
- [Network2.MAINNET]: void 0,
3294
- [Network2.LOCAL]: void 0,
3295
- [Network2.CUSTOM]: void 0
3296
- };
3297
- var NetworkToShelbyBlobIndexerBaseUrl = {
3298
- [Network2.SHELBYNET]: "https://api.shelbynet.aptoslabs.com/nocode/v1/public/cmforrguw0042s601fn71f9l2/v1/graphql",
3299
- [Network2.DEVNET]: void 0,
3300
- [Network2.TESTNET]: void 0,
3301
- [Network2.MAINNET]: void 0,
3302
- [Network2.LOCAL]: void 0,
3303
- [Network2.CUSTOM]: void 0
3304
- };
3305
- var SHELBY_DEPLOYER = "0xc63d6a5efb0080a6029403131715bd4971e1149f7cc099aac69bb0069b3ddbf5";
3306
- var SHELBYUSD_TOKEN_NAME = "ShelbyUSD";
3307
- var SHELBYUSD_FA_METADATA_ADDRESS = "0x1b18363a9f1fe5e6ebf247daba5cc1c18052bb232efdc4c50f556053922d98e1";
3308
-
3309
3321
  // ../../packages/sdk/dist/chunk-VPT45MTZ.mjs
3310
3322
  import { Network as Network3 } from "@aptos-labs/ts-sdk";
3311
3323
 
@@ -3854,14 +3866,8 @@ function getShelbyIndexerClient(config) {
3854
3866
  });
3855
3867
  }
3856
3868
 
3857
- // ../../packages/sdk/dist/chunk-OTBLZL2S.mjs
3858
- import { AccountAddress } from "@aptos-labs/ts-sdk";
3859
- var createBlobKey = (params) => {
3860
- return `@${AccountAddress.from(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
3861
- };
3862
-
3863
3869
  // ../../packages/sdk/dist/chunk-KBUWZXFA.mjs
3864
- import { Hex } from "@aptos-labs/ts-sdk";
3870
+ import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
3865
3871
  async function* readInChunks(input, chunkSize) {
3866
3872
  let idx = 0;
3867
3873
  if (isReadableStream(input)) {
@@ -3921,7 +3927,7 @@ function zeroPadBytes(buffer, desiredLength) {
3921
3927
  return paddedBuffer;
3922
3928
  }
3923
3929
  async function concatHashes(parts) {
3924
- const chunks = parts.map((part) => Hex.fromHexInput(part).toUint8Array());
3930
+ const chunks = parts.map((part) => Hex2.fromHexInput(part).toUint8Array());
3925
3931
  const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
3926
3932
  const combined = new Uint8Array(totalLength);
3927
3933
  let offset = 0;
@@ -3929,7 +3935,7 @@ async function concatHashes(parts) {
3929
3935
  combined.set(chunk, offset);
3930
3936
  offset += chunk.byteLength;
3931
3937
  }
3932
- return Hex.fromHexInput(
3938
+ return Hex2.fromHexInput(
3933
3939
  new Uint8Array(await crypto.subtle.digest("SHA-256", combined))
3934
3940
  );
3935
3941
  }
@@ -4125,158 +4131,435 @@ function buildClayConfig(input) {
4125
4131
  };
4126
4132
  }
4127
4133
 
4128
- // ../../packages/sdk/dist/chunk-LTV26KU4.mjs
4134
+ // ../../packages/sdk/dist/chunk-FIFKKWXV.mjs
4135
+ import { AccountAddress as AccountAddress2 } from "@aptos-labs/ts-sdk";
4129
4136
  import { z } from "zod";
4130
- var ChunksetCommitmentSchema = z.object({
4131
- // Chunkset root (vector commitment of child chunks)
4132
- chunkset_root: z.string().nullable(),
4133
- // the size is known statically from the current configuration
4134
- chunk_commitments: z.array(z.string())
4135
- }).refine(
4136
- (data) => {
4137
- return data.chunk_commitments.length === DEFAULT_ERASURE_K + DEFAULT_ERASURE_M;
4138
- },
4139
- {
4140
- message: `Chunkset must have exactly ${DEFAULT_ERASURE_K + DEFAULT_ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${DEFAULT_ERASURE_K} + ${DEFAULT_ERASURE_M})`,
4141
- path: ["chunk_commitments"]
4142
- }
4143
- );
4144
- function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES) {
4145
- if (chunksetSize <= 0) {
4146
- throw new Error("chunksetSize must be positive");
4147
- }
4148
- if (rawSize === 0) return 1;
4149
- return Math.ceil(rawSize / chunksetSize);
4150
- }
4151
- var BlobCommitmentsSchema = z.object({
4152
- schema_version: z.string(),
4153
- raw_data_size: z.number(),
4154
- // FIXME I am not sure about this being here, or if it should be somewhere else
4155
- blob_merkle_root: z.string(),
4156
- chunkset_commitments: z.array(ChunksetCommitmentSchema)
4157
- }).refine(
4158
- (data) => {
4159
- return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
4160
- },
4161
- {
4162
- message: "Total chunkset count mismatches with raw data size",
4163
- // FIXME put more details in here
4164
- path: ["chunkset_commitments"]
4165
- }
4166
- );
4167
- async function generateChunksetCommitments(shouldPad, chunksetIdx, chunksetData, expectedChunksetSize, provider, onChunk) {
4168
- const { erasure_n } = provider.config;
4169
- const chunkCommitments = [];
4170
- const chunksetPayload = shouldPad ? zeroPadBytes(chunksetData, expectedChunksetSize) : validatePrePaddedChunkset(
4171
- chunksetData,
4172
- expectedChunksetSize,
4173
- chunksetIdx
4174
- );
4175
- const { chunks } = provider.encode(chunksetPayload);
4176
- if (chunks.length !== erasure_n) {
4177
- throw new Error(
4178
- `Erasure provider produced ${chunks.length} chunks, expected ${erasure_n}.`
4179
- );
4180
- }
4181
- let chunkIdx = 0;
4182
- for (const chunkData of chunks) {
4183
- if (onChunk !== void 0) {
4184
- await onChunk(chunksetIdx, chunkIdx, chunkData);
4185
- }
4186
- const chunkHash = await concatHashes([chunkData]);
4187
- chunkCommitments.push(chunkHash);
4188
- chunkIdx += 1;
4189
- }
4190
- const h = await concatHashes(
4191
- chunkCommitments.map((chunk) => chunk.toUint8Array())
4192
- );
4193
- const entry = {
4194
- chunkset_root: h.toString(),
4195
- chunk_commitments: chunkCommitments.map((chunk) => chunk.toString())
4196
- };
4197
- return { h, entry };
4198
- }
4199
- async function generateCommitments(provider, fullData, onChunk, options) {
4200
- const expectedChunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES;
4201
- const shouldPad = options?.pad ?? true;
4202
- const chunksetCommitments = [];
4203
- const chunksetCommitmentHashes = [];
4204
- let rawDataSize = 0;
4205
- const chunksetGen = readInChunks(fullData, expectedChunksetSize);
4206
- for await (const [chunksetIdx, chunksetData] of chunksetGen) {
4207
- rawDataSize += chunksetData.length;
4208
- const { h, entry } = await generateChunksetCommitments(
4209
- shouldPad,
4210
- chunksetIdx,
4211
- chunksetData,
4212
- expectedChunksetSize,
4213
- provider,
4214
- onChunk
4215
- );
4216
- chunksetCommitments.push(entry);
4217
- chunksetCommitmentHashes.push(h);
4218
- }
4219
- if (rawDataSize === 0) {
4220
- const zeroChunkset = new Uint8Array(expectedChunksetSize);
4221
- const { h, entry } = await generateChunksetCommitments(
4222
- shouldPad,
4223
- 0,
4224
- zeroChunkset,
4225
- expectedChunksetSize,
4226
- provider,
4227
- onChunk
4228
- );
4229
- chunksetCommitments.push(entry);
4230
- chunksetCommitmentHashes.push(h);
4231
- }
4232
- return {
4233
- schema_version: "1.3",
4234
- raw_data_size: rawDataSize,
4235
- blob_merkle_root: (await concatHashes(
4236
- chunksetCommitmentHashes.map((chunk) => chunk.toUint8Array())
4237
- )).toString(),
4238
- chunkset_commitments: chunksetCommitments
4239
- };
4240
- }
4241
- function validatePrePaddedChunkset(chunkset, expectedSize, chunksetIdx) {
4242
- if (chunkset.byteLength !== expectedSize) {
4243
- throw new Error(
4244
- `Chunkset ${chunksetIdx} has size ${chunkset.byteLength} bytes but expected ${expectedSize} bytes. Enable padding or supply pre-padded data before calling generateCommitments.`
4245
- );
4246
- }
4247
- return chunkset;
4248
- }
4137
+ var BlobNameSchema = z.string().min(1, "Blob name path parameter cannot be empty.").max(1024, "Blob name cannot exceed 1024 characters.").refine((name) => !name.endsWith("/"), {
4138
+ message: "Blob name cannot end with a slash"
4139
+ });
4249
4140
 
4250
- // ../../packages/sdk/dist/chunk-3QINXXV6.mjs
4251
- import {
4252
- AccountAddress as AccountAddress2,
4253
- Aptos,
4254
- AptosConfig as AptosConfig2,
4255
- Hex as Hex2,
4256
- MoveVector
4257
- } from "@aptos-labs/ts-sdk";
4258
- var ShelbyBlobClient = class _ShelbyBlobClient {
4259
- aptos;
4260
- deployer;
4141
+ // ../../packages/sdk/dist/chunk-TEDFWEY6.mjs
4142
+ import { AccountAddress as AccountAddress3 } from "@aptos-labs/ts-sdk";
4143
+ function encodeURIComponentKeepSlashes(str) {
4144
+ return encodeURIComponent(str).replace(/%2F/g, "/");
4145
+ }
4146
+ var ShelbyRPCClient = class {
4147
+ baseUrl;
4148
+ apiKey;
4149
+ rpcConfig;
4261
4150
  indexer;
4262
4151
  /**
4263
- * The ShelbyBlobClient is used to interact with the Shelby contract on the Aptos blockchain. This
4264
- * includes functions for registering blob commitments and retrieving blob metadata.
4152
+ * Creates a new ShelbyRPCClient for interacting with Shelby RPC nodes.
4153
+ * This client handles blob storage operations including upload and download.
4265
4154
  *
4266
4155
  * @param config - The client configuration object.
4267
4156
  * @param config.network - The Shelby network to use.
4268
4157
  *
4269
4158
  * @example
4270
4159
  * ```typescript
4271
- * const blobClient = new ShelbyBlobClient({
4272
- * aptos: {
4273
- * network: Network.SHELBYNET,
4274
- * clientConfig: {
4275
- * API_KEY: "AG-***",
4276
- * },
4277
- * },
4278
- * });
4279
- * ```
4160
+ * const client = new ShelbyRPCClient({
4161
+ * network: Network.SHELBYNET,
4162
+ * apiKey: "AG-***",
4163
+ * });
4164
+ * ```
4165
+ */
4166
+ constructor(config) {
4167
+ this.baseUrl = config.rpc?.baseUrl ?? NetworkToShelbyRPCBaseUrl.shelbynet;
4168
+ this.apiKey = config.apiKey ?? config.rpc?.apiKey;
4169
+ this.rpcConfig = config.rpc ?? {};
4170
+ this.indexer = getShelbyIndexerClient(config);
4171
+ }
4172
+ async #uploadPart(uploadId, partIdx, partData) {
4173
+ const nRetries = 5;
4174
+ for (let i = 0; i < nRetries; ++i) {
4175
+ const partResponse = await fetch(
4176
+ buildRequestUrl(
4177
+ `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
4178
+ this.baseUrl
4179
+ ),
4180
+ {
4181
+ method: "PUT",
4182
+ headers: {
4183
+ "Content-Type": "application/octet-stream",
4184
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4185
+ },
4186
+ body: partData
4187
+ }
4188
+ );
4189
+ if (partResponse.ok) return;
4190
+ if (i < nRetries - 1) {
4191
+ const delay = 2 ** i * 100;
4192
+ await sleep(delay);
4193
+ }
4194
+ }
4195
+ throw new Error(`Failed to upload part ${partIdx}.`);
4196
+ }
4197
+ async #putBlobMultipart(account, blobName, blobData, partSize = 5 * 1024 * 1024) {
4198
+ const startResponse = await fetch(
4199
+ buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
4200
+ {
4201
+ method: "POST",
4202
+ headers: {
4203
+ "Content-Type": "application/json",
4204
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4205
+ },
4206
+ body: JSON.stringify({
4207
+ rawAccount: account.toString(),
4208
+ rawBlobName: blobName,
4209
+ rawPartSize: partSize
4210
+ })
4211
+ }
4212
+ );
4213
+ if (!startResponse.ok) {
4214
+ let errorBodyText = "Could not read error body";
4215
+ try {
4216
+ errorBodyText = await startResponse.text();
4217
+ } catch (_e) {
4218
+ }
4219
+ throw new Error(
4220
+ `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
4221
+ );
4222
+ }
4223
+ const { uploadId } = await startResponse.json();
4224
+ const totalParts = Math.ceil(blobData.length / partSize);
4225
+ for (let partIdx = 0; partIdx < totalParts; partIdx++) {
4226
+ const start = partIdx * partSize;
4227
+ const end = Math.min(start + partSize, blobData.length);
4228
+ const partData = blobData.slice(start, end);
4229
+ await this.#uploadPart(uploadId, partIdx, partData);
4230
+ }
4231
+ const completeResponse = await fetch(
4232
+ buildRequestUrl(
4233
+ `/v1/multipart-uploads/${uploadId}/complete`,
4234
+ this.baseUrl
4235
+ ),
4236
+ {
4237
+ method: "POST",
4238
+ headers: {
4239
+ "Content-Type": "application/json",
4240
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4241
+ }
4242
+ }
4243
+ );
4244
+ if (!completeResponse.ok) {
4245
+ let errorBodyText = "Could not read error body";
4246
+ try {
4247
+ errorBodyText = await completeResponse.text();
4248
+ } catch (_e) {
4249
+ }
4250
+ throw new Error(
4251
+ `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
4252
+ );
4253
+ }
4254
+ }
4255
+ /**
4256
+ * Uploads blob data to the Shelby RPC node for storage by storage providers.
4257
+ * This method should be called after blob commitments have been registered on the blockchain.
4258
+ * Uses multipart upload for efficient handling of large files.
4259
+ *
4260
+ * @param params.account - The account that owns the blob.
4261
+ * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
4262
+ * @param params.blobData - The raw blob data as a Uint8Array.
4263
+ *
4264
+ * @example
4265
+ * ```typescript
4266
+ * const blobData = new TextEncoder().encode("Hello, world!");
4267
+ *
4268
+ * await client.putBlob({
4269
+ * account: AccountAddress.from("0x1"),
4270
+ * blobName: "greetings/hello.txt",
4271
+ * blobData
4272
+ * });
4273
+ * ```
4274
+ */
4275
+ async putBlob(params) {
4276
+ BlobNameSchema.parse(params.blobName);
4277
+ await this.#putBlobMultipart(
4278
+ params.account,
4279
+ params.blobName,
4280
+ params.blobData
4281
+ );
4282
+ }
4283
+ // FIXME make this possible to stream in put ^^^
4284
+ /**
4285
+ * Downloads a blob from the Shelby RPC node.
4286
+ * Returns a streaming response with validation to ensure data integrity.
4287
+ *
4288
+ * @param params.account - The account that owns the blob.
4289
+ * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
4290
+ * @param params.range - Optional byte range for partial downloads.
4291
+ * @param params.range.start - Starting byte position (inclusive).
4292
+ * @param params.range.end - Ending byte position (inclusive, optional).
4293
+ *
4294
+ * @returns A ShelbyBlob object containing the account, name, readable stream, and content length.
4295
+ *
4296
+ * @throws Error if the download fails or content length doesn't match.
4297
+ *
4298
+ * @example
4299
+ * ```typescript
4300
+ * // Download entire blob
4301
+ * const blob = await client.getBlob({
4302
+ * account: AccountAddress.from("0x1"),
4303
+ * blobName: "documents/report.pdf"
4304
+ * });
4305
+ *
4306
+ * // Download partial content (bytes 100-199)
4307
+ * const partial = await client.getBlob({
4308
+ * account: AccountAddress.from("0x1"),
4309
+ * blobName: "large-file.bin",
4310
+ * range: { start: 100, end: 199 }
4311
+ * });
4312
+ * ```
4313
+ */
4314
+ async getBlob(params) {
4315
+ BlobNameSchema.parse(params.blobName);
4316
+ const url = buildRequestUrl(
4317
+ `/v1/blobs/${params.account.toString()}/${encodeURIComponentKeepSlashes(
4318
+ params.blobName
4319
+ )}`,
4320
+ this.baseUrl
4321
+ );
4322
+ const requestInit = {};
4323
+ if (params.range !== void 0) {
4324
+ const headers = new Headers();
4325
+ const { start, end } = params.range;
4326
+ if (end === void 0) {
4327
+ headers.set("Range", `bytes=${start}-`);
4328
+ } else {
4329
+ if (end < start) {
4330
+ throw new Error("Range end cannot be less than start.");
4331
+ }
4332
+ headers.set("Range", `bytes=${start}-${end}`);
4333
+ }
4334
+ requestInit.headers = headers;
4335
+ }
4336
+ const response = await fetch(url, {
4337
+ ...requestInit,
4338
+ headers: {
4339
+ ...requestInit.headers,
4340
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4341
+ }
4342
+ });
4343
+ if (!response.ok) {
4344
+ throw new Error(
4345
+ `Failed to download blob: ${response.status} ${response.statusText}`
4346
+ );
4347
+ }
4348
+ if (!response.body) {
4349
+ throw new Error("Response body is null");
4350
+ }
4351
+ const contentLengthHeader = response.headers.get("content-length");
4352
+ if (contentLengthHeader === null) {
4353
+ throw new Error(
4354
+ "Response did not have content-length header, which is required"
4355
+ );
4356
+ }
4357
+ const expectedContentLength = Number.parseInt(contentLengthHeader, 10);
4358
+ if (Number.isNaN(expectedContentLength)) {
4359
+ throw new Error(
4360
+ `Invalid content-length header received: ${contentLengthHeader}`
4361
+ );
4362
+ }
4363
+ const validatingStream = new ReadableStream({
4364
+ start(controller) {
4365
+ const maybeReader = response.body?.getReader();
4366
+ if (!maybeReader) {
4367
+ controller.error(new Error("Response body reader is unavailable"));
4368
+ return;
4369
+ }
4370
+ const reader = maybeReader;
4371
+ let bytesReceived = 0;
4372
+ function pump() {
4373
+ return reader.read().then(({ done, value }) => {
4374
+ if (done) {
4375
+ if (bytesReceived !== expectedContentLength) {
4376
+ controller.error(
4377
+ new Error(
4378
+ `Downloaded data size (${bytesReceived} bytes) does not match content-length header (${expectedContentLength} bytes). This might indicate a partial or corrupted download.`
4379
+ )
4380
+ );
4381
+ return;
4382
+ }
4383
+ controller.close();
4384
+ return;
4385
+ }
4386
+ bytesReceived += value.byteLength;
4387
+ controller.enqueue(value);
4388
+ return pump();
4389
+ }).catch((error) => {
4390
+ controller.error(error);
4391
+ });
4392
+ }
4393
+ return pump();
4394
+ }
4395
+ });
4396
+ return {
4397
+ account: AccountAddress3.from(params.account),
4398
+ name: params.blobName,
4399
+ readable: validatingStream,
4400
+ contentLength: expectedContentLength
4401
+ };
4402
+ }
4403
+ };
4404
+
4405
+ // ../../packages/sdk/dist/chunk-OTBLZL2S.mjs
4406
+ import { AccountAddress as AccountAddress4 } from "@aptos-labs/ts-sdk";
4407
+ var createBlobKey = (params) => {
4408
+ return `@${AccountAddress4.from(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
4409
+ };
4410
+
4411
+ // ../../packages/sdk/dist/chunk-LTV26KU4.mjs
4412
+ import { z as z2 } from "zod";
4413
+ var ChunksetCommitmentSchema = z2.object({
4414
+ // Chunkset root (vector commitment of child chunks)
4415
+ chunkset_root: z2.string().nullable(),
4416
+ // the size is known statically from the current configuration
4417
+ chunk_commitments: z2.array(z2.string())
4418
+ }).refine(
4419
+ (data) => {
4420
+ return data.chunk_commitments.length === DEFAULT_ERASURE_K + DEFAULT_ERASURE_M;
4421
+ },
4422
+ {
4423
+ message: `Chunkset must have exactly ${DEFAULT_ERASURE_K + DEFAULT_ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${DEFAULT_ERASURE_K} + ${DEFAULT_ERASURE_M})`,
4424
+ path: ["chunk_commitments"]
4425
+ }
4426
+ );
4427
+ function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES) {
4428
+ if (chunksetSize <= 0) {
4429
+ throw new Error("chunksetSize must be positive");
4430
+ }
4431
+ if (rawSize === 0) return 1;
4432
+ return Math.ceil(rawSize / chunksetSize);
4433
+ }
4434
+ var BlobCommitmentsSchema = z2.object({
4435
+ schema_version: z2.string(),
4436
+ raw_data_size: z2.number(),
4437
+ // FIXME I am not sure about this being here, or if it should be somewhere else
4438
+ blob_merkle_root: z2.string(),
4439
+ chunkset_commitments: z2.array(ChunksetCommitmentSchema)
4440
+ }).refine(
4441
+ (data) => {
4442
+ return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
4443
+ },
4444
+ {
4445
+ message: "Total chunkset count mismatches with raw data size",
4446
+ // FIXME put more details in here
4447
+ path: ["chunkset_commitments"]
4448
+ }
4449
+ );
4450
+ async function generateChunksetCommitments(shouldPad, chunksetIdx, chunksetData, expectedChunksetSize, provider, onChunk) {
4451
+ const { erasure_n } = provider.config;
4452
+ const chunkCommitments = [];
4453
+ const chunksetPayload = shouldPad ? zeroPadBytes(chunksetData, expectedChunksetSize) : validatePrePaddedChunkset(
4454
+ chunksetData,
4455
+ expectedChunksetSize,
4456
+ chunksetIdx
4457
+ );
4458
+ const { chunks } = provider.encode(chunksetPayload);
4459
+ if (chunks.length !== erasure_n) {
4460
+ throw new Error(
4461
+ `Erasure provider produced ${chunks.length} chunks, expected ${erasure_n}.`
4462
+ );
4463
+ }
4464
+ let chunkIdx = 0;
4465
+ for (const chunkData of chunks) {
4466
+ if (onChunk !== void 0) {
4467
+ await onChunk(chunksetIdx, chunkIdx, chunkData);
4468
+ }
4469
+ const chunkHash = await concatHashes([chunkData]);
4470
+ chunkCommitments.push(chunkHash);
4471
+ chunkIdx += 1;
4472
+ }
4473
+ const h = await concatHashes(
4474
+ chunkCommitments.map((chunk) => chunk.toUint8Array())
4475
+ );
4476
+ const entry = {
4477
+ chunkset_root: h.toString(),
4478
+ chunk_commitments: chunkCommitments.map((chunk) => chunk.toString())
4479
+ };
4480
+ return { h, entry };
4481
+ }
4482
+ async function generateCommitments(provider, fullData, onChunk, options) {
4483
+ const expectedChunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES;
4484
+ const shouldPad = options?.pad ?? true;
4485
+ const chunksetCommitments = [];
4486
+ const chunksetCommitmentHashes = [];
4487
+ let rawDataSize = 0;
4488
+ const chunksetGen = readInChunks(fullData, expectedChunksetSize);
4489
+ for await (const [chunksetIdx, chunksetData] of chunksetGen) {
4490
+ rawDataSize += chunksetData.length;
4491
+ const { h, entry } = await generateChunksetCommitments(
4492
+ shouldPad,
4493
+ chunksetIdx,
4494
+ chunksetData,
4495
+ expectedChunksetSize,
4496
+ provider,
4497
+ onChunk
4498
+ );
4499
+ chunksetCommitments.push(entry);
4500
+ chunksetCommitmentHashes.push(h);
4501
+ }
4502
+ if (rawDataSize === 0) {
4503
+ const zeroChunkset = new Uint8Array(expectedChunksetSize);
4504
+ const { h, entry } = await generateChunksetCommitments(
4505
+ shouldPad,
4506
+ 0,
4507
+ zeroChunkset,
4508
+ expectedChunksetSize,
4509
+ provider,
4510
+ onChunk
4511
+ );
4512
+ chunksetCommitments.push(entry);
4513
+ chunksetCommitmentHashes.push(h);
4514
+ }
4515
+ return {
4516
+ schema_version: "1.3",
4517
+ raw_data_size: rawDataSize,
4518
+ blob_merkle_root: (await concatHashes(
4519
+ chunksetCommitmentHashes.map((chunk) => chunk.toUint8Array())
4520
+ )).toString(),
4521
+ chunkset_commitments: chunksetCommitments
4522
+ };
4523
+ }
4524
+ function validatePrePaddedChunkset(chunkset, expectedSize, chunksetIdx) {
4525
+ if (chunkset.byteLength !== expectedSize) {
4526
+ throw new Error(
4527
+ `Chunkset ${chunksetIdx} has size ${chunkset.byteLength} bytes but expected ${expectedSize} bytes. Enable padding or supply pre-padded data before calling generateCommitments.`
4528
+ );
4529
+ }
4530
+ return chunkset;
4531
+ }
4532
+
4533
+ // ../../packages/sdk/dist/chunk-MEINKPI3.mjs
4534
+ import {
4535
+ AccountAddress as AccountAddress5,
4536
+ Aptos as Aptos2,
4537
+ AptosConfig as AptosConfig2,
4538
+ Hex as Hex3,
4539
+ MoveVector
4540
+ } from "@aptos-labs/ts-sdk";
4541
+ var ShelbyBlobClient = class _ShelbyBlobClient {
4542
+ aptos;
4543
+ deployer;
4544
+ indexer;
4545
+ /**
4546
+ * The ShelbyBlobClient is used to interact with the Shelby contract on the Aptos blockchain. This
4547
+ * includes functions for registering blob commitments and retrieving blob metadata.
4548
+ *
4549
+ * @param config - The client configuration object.
4550
+ * @param config.network - The Shelby network to use.
4551
+ *
4552
+ * @example
4553
+ * ```typescript
4554
+ * const blobClient = new ShelbyBlobClient({
4555
+ * aptos: {
4556
+ * network: Network.SHELBYNET,
4557
+ * clientConfig: {
4558
+ * API_KEY: "AG-***",
4559
+ * },
4560
+ * },
4561
+ * });
4562
+ * ```
4280
4563
  */
4281
4564
  constructor(config) {
4282
4565
  const baseAptosConfig = getAptosConfig(config);
@@ -4288,8 +4571,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4288
4571
  API_KEY: baseAptosConfig.clientConfig?.API_KEY ?? config.apiKey
4289
4572
  }
4290
4573
  });
4291
- this.aptos = new Aptos(aptosConfig);
4292
- this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
4574
+ this.aptos = new Aptos2(aptosConfig);
4575
+ this.deployer = config.deployer ?? AccountAddress5.fromString(SHELBY_DEPLOYER);
4293
4576
  this.indexer = getShelbyIndexerClient(config);
4294
4577
  }
4295
4578
  /**
@@ -4338,15 +4621,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4338
4621
  );
4339
4622
  }
4340
4623
  return {
4341
- blobMerkleRoot: Hex2.fromHexInput(
4624
+ blobMerkleRoot: Hex3.fromHexInput(
4342
4625
  metadata.blob_commitment
4343
4626
  ).toUint8Array(),
4344
- owner: AccountAddress2.fromString(metadata.owner),
4627
+ owner: AccountAddress5.fromString(metadata.owner),
4345
4628
  name: params.name,
4346
4629
  size: Number(metadata.blob_size),
4347
4630
  encoding,
4348
4631
  expirationMicros: metadata.expiration_micros,
4349
- sliceAddress: AccountAddress2.fromString(metadata.slice.inner),
4632
+ sliceAddress: AccountAddress5.fromString(metadata.slice.inner),
4350
4633
  isWritten: metadata.is_written
4351
4634
  };
4352
4635
  } catch (error) {
@@ -4378,7 +4661,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4378
4661
  return this.getBlobs({
4379
4662
  where: {
4380
4663
  ...params.where,
4381
- owner: { _eq: AccountAddress2.from(params.account).toString() }
4664
+ owner: { _eq: AccountAddress5.from(params.account).toString() }
4382
4665
  },
4383
4666
  pagination: params.pagination,
4384
4667
  orderBy: params.orderBy
@@ -4411,9 +4694,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4411
4694
  });
4412
4695
  return blobs.map(
4413
4696
  (blob) => ({
4414
- owner: AccountAddress2.from(blob.owner),
4697
+ owner: AccountAddress5.from(blob.owner),
4415
4698
  name: blob.blob_name,
4416
- blobMerkleRoot: Hex2.fromHexInput(blob.blob_commitment).toUint8Array(),
4699
+ blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
4417
4700
  size: Number(blob.size),
4418
4701
  // TODO: Add encoding when supported in NCI
4419
4702
  encoding: {
@@ -4422,7 +4705,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4422
4705
  ...ERASURE_CODE_AND_CHUNK_MAPPING.ClayCode_16Total_10Data_13Helper
4423
4706
  },
4424
4707
  expirationMicros: Number(blob.expires_at),
4425
- sliceAddress: AccountAddress2.from(blob.slice_address),
4708
+ sliceAddress: AccountAddress5.from(blob.slice_address),
4426
4709
  isWritten: Boolean(Number(blob.is_written))
4427
4710
  })
4428
4711
  );
@@ -4445,7 +4728,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4445
4728
  return blob_activities.map(
4446
4729
  (activity) => ({
4447
4730
  blobName: activity.blob_name,
4448
- accountAddress: AccountAddress2.from(
4731
+ accountAddress: AccountAddress5.from(
4449
4732
  activity.blob_name.substring(1, 65)
4450
4733
  ),
4451
4734
  type: activityTypeMapping[activity.event_type] ?? "unknown",
@@ -4513,380 +4796,104 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
4513
4796
  const chunksetSize = params.options?.chunksetSizeBytes ?? DEFAULT_CHUNKSET_SIZE_BYTES;
4514
4797
  const transaction = await this.aptos.transaction.build.simple({
4515
4798
  ...params.options?.build,
4516
- data: _ShelbyBlobClient.createRegisterBlobPayload({
4517
- deployer: this.deployer,
4518
- account: params.account.accountAddress,
4519
- blobName: params.blobName,
4520
- blobSize: params.size,
4521
- blobMerkleRoot: params.blobMerkleRoot,
4522
- numChunksets: expectedTotalChunksets(params.size, chunksetSize),
4523
- expirationMicros: params.expirationMicros
4524
- }),
4525
- sender: params.account.accountAddress
4526
- });
4527
- return {
4528
- transaction: await this.aptos.signAndSubmitTransaction({
4529
- signer: params.account,
4530
- transaction
4531
- })
4532
- };
4533
- }
4534
- /**
4535
- * Creates a transaction payload to register a blob on the blockchain.
4536
- * This is a static helper method for constructing the Move function call payload.
4537
- *
4538
- * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
4539
- * @param params.account - The account that will own the blob.
4540
- * @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
4541
- * @param params.blobSize - The size of the blob in bytes.
4542
- * @param params.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
4543
- * @param params.expirationMicros - The expiration time of the blob in microseconds.
4544
- * @param params.numChunksets - The total number of chunksets in the blob.
4545
- *
4546
- * @returns An Aptos transaction payload data object for the register_blob Move function.
4547
- *
4548
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
4549
- */
4550
- static createRegisterBlobPayload(params) {
4551
- return {
4552
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_blob`,
4553
- functionArguments: [
4554
- params.blobName,
4555
- params.expirationMicros,
4556
- MoveVector.U8(params.blobMerkleRoot),
4557
- params.numChunksets,
4558
- params.blobSize,
4559
- // TODO
4560
- 0,
4561
- // payment tier
4562
- 0
4563
- // encoding
4564
- ]
4565
- };
4566
- }
4567
- /**
4568
- * Creates a transaction payload to register multiple blobs on the blockchain.
4569
- * This is a static helper method for constructing the Move function call payload.
4570
- *
4571
- * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
4572
- * @param params.account - The account that will own the blobs.
4573
- * @param params.expirationMicros - The expiration time of the blobs in microseconds.
4574
- * @param params.blobs - The blobs to register.
4575
- * @param params.blobs.blobName - The name/path of the blob (e.g. "foo/bar.txt").
4576
- * @param params.blobs.blobSize - The size of the blob in bytes.
4577
- * @param params.blobs.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
4578
- * @param params.blobs.numChunksets - The total number of chunksets in the blob.
4579
- *
4580
- * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
4581
- *
4582
- * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
4583
- */
4584
- static createBatchRegisterBlobsPayload(params) {
4585
- const blobNames = [];
4586
- const blobMerkleRoots = [];
4587
- const blobNumChunksets = [];
4588
- const blobSizes = [];
4589
- params.blobs.forEach((blob) => {
4590
- blobNames.push(blob.blobName);
4591
- blobMerkleRoots.push(MoveVector.U8(blob.blobMerkleRoot));
4592
- blobNumChunksets.push(blob.numChunksets);
4593
- blobSizes.push(blob.blobSize);
4594
- });
4595
- return {
4596
- function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_multiple_blobs`,
4597
- functionArguments: [
4598
- blobNames,
4599
- params.expirationMicros,
4600
- blobMerkleRoots,
4601
- blobNumChunksets,
4602
- blobSizes,
4603
- // TODO
4604
- 0,
4605
- 0
4606
- ]
4607
- };
4608
- }
4609
- };
4610
-
4611
- // ../../packages/sdk/dist/chunk-FIFKKWXV.mjs
4612
- import { AccountAddress as AccountAddress3 } from "@aptos-labs/ts-sdk";
4613
- import { z as z2 } from "zod";
4614
- var BlobNameSchema = z2.string().min(1, "Blob name path parameter cannot be empty.").max(1024, "Blob name cannot exceed 1024 characters.").refine((name) => !name.endsWith("/"), {
4615
- message: "Blob name cannot end with a slash"
4616
- });
4617
-
4618
- // ../../packages/sdk/dist/chunk-I6NG5GNL.mjs
4619
- function sleep(ms) {
4620
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4621
- }
4622
-
4623
- // ../../packages/sdk/dist/chunk-GKRHKR3J.mjs
4624
- import { AccountAddress as AccountAddress4 } from "@aptos-labs/ts-sdk";
4625
- function encodeURIComponentKeepSlashes(str) {
4626
- return encodeURIComponent(str).replace(/%2F/g, "/");
4627
- }
4628
- var ShelbyRPCClient = class {
4629
- baseUrl;
4630
- apiKey;
4631
- rpcConfig;
4632
- indexer;
4633
- /**
4634
- * Creates a new ShelbyRPCClient for interacting with Shelby RPC nodes.
4635
- * This client handles blob storage operations including upload and download.
4636
- *
4637
- * @param config - The client configuration object.
4638
- * @param config.network - The Shelby network to use.
4639
- *
4640
- * @example
4641
- * ```typescript
4642
- * const client = new ShelbyRPCClient({
4643
- * network: Network.SHELBYNET,
4644
- * apiKey: "AG-***",
4645
- * });
4646
- * ```
4647
- */
4648
- constructor(config) {
4649
- this.baseUrl = config.rpc?.baseUrl ?? NetworkToShelbyRPCBaseUrl.shelbynet;
4650
- this.apiKey = config.apiKey ?? config.rpc?.apiKey;
4651
- this.rpcConfig = config.rpc ?? {};
4652
- this.indexer = getShelbyIndexerClient(config);
4653
- }
4654
- async #uploadPart(uploadId, partIdx, partData) {
4655
- const nRetries = 5;
4656
- for (let i = 0; i < nRetries; ++i) {
4657
- const partResponse = await fetch(
4658
- buildRequestUrl(
4659
- `/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
4660
- this.baseUrl
4661
- ),
4662
- {
4663
- method: "PUT",
4664
- headers: {
4665
- "Content-Type": "application/octet-stream",
4666
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4667
- },
4668
- body: partData
4669
- }
4670
- );
4671
- if (partResponse.ok) return;
4672
- if (i < nRetries - 1) {
4673
- const delay = 2 ** i * 100;
4674
- await sleep(delay);
4675
- }
4676
- }
4677
- throw new Error(`Failed to upload part ${partIdx}.`);
4678
- }
4679
- async #putBlobMultipart(account, blobName, blobData, partSize = 5 * 1024 * 1024) {
4680
- const startResponse = await fetch(
4681
- buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
4682
- {
4683
- method: "POST",
4684
- headers: {
4685
- "Content-Type": "application/json",
4686
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4687
- },
4688
- body: JSON.stringify({
4689
- rawAccount: account.toString(),
4690
- rawBlobName: blobName,
4691
- rawPartSize: partSize
4692
- })
4693
- }
4694
- );
4695
- if (!startResponse.ok) {
4696
- let errorBodyText = "Could not read error body";
4697
- try {
4698
- errorBodyText = await startResponse.text();
4699
- } catch (_e) {
4700
- }
4701
- throw new Error(
4702
- `Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
4703
- );
4704
- }
4705
- const { uploadId } = await startResponse.json();
4706
- const totalParts = Math.ceil(blobData.length / partSize);
4707
- for (let partIdx = 0; partIdx < totalParts; partIdx++) {
4708
- const start = partIdx * partSize;
4709
- const end = Math.min(start + partSize, blobData.length);
4710
- const partData = blobData.slice(start, end);
4711
- await this.#uploadPart(uploadId, partIdx, partData);
4712
- }
4713
- const completeResponse = await fetch(
4714
- buildRequestUrl(
4715
- `/v1/multipart-uploads/${uploadId}/complete`,
4716
- this.baseUrl
4717
- ),
4718
- {
4719
- method: "POST",
4720
- headers: {
4721
- "Content-Type": "application/json",
4722
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4723
- }
4724
- }
4725
- );
4726
- if (!completeResponse.ok) {
4727
- let errorBodyText = "Could not read error body";
4728
- try {
4729
- errorBodyText = await completeResponse.text();
4730
- } catch (_e) {
4731
- }
4732
- throw new Error(
4733
- `Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
4734
- );
4735
- }
4799
+ data: _ShelbyBlobClient.createRegisterBlobPayload({
4800
+ deployer: this.deployer,
4801
+ account: params.account.accountAddress,
4802
+ blobName: params.blobName,
4803
+ blobSize: params.size,
4804
+ blobMerkleRoot: params.blobMerkleRoot,
4805
+ numChunksets: expectedTotalChunksets(params.size, chunksetSize),
4806
+ expirationMicros: params.expirationMicros
4807
+ }),
4808
+ sender: params.account.accountAddress
4809
+ });
4810
+ return {
4811
+ transaction: await this.aptos.signAndSubmitTransaction({
4812
+ signer: params.account,
4813
+ transaction
4814
+ })
4815
+ };
4736
4816
  }
4737
4817
  /**
4738
- * Uploads blob data to the Shelby RPC node for storage by storage providers.
4739
- * This method should be called after blob commitments have been registered on the blockchain.
4740
- * Uses multipart upload for efficient handling of large files.
4818
+ * Creates a transaction payload to register a blob on the blockchain.
4819
+ * This is a static helper method for constructing the Move function call payload.
4741
4820
  *
4742
- * @param params.account - The account that owns the blob.
4743
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
4744
- * @param params.blobData - The raw blob data as a Uint8Array.
4821
+ * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
4822
+ * @param params.account - The account that will own the blob.
4823
+ * @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
4824
+ * @param params.blobSize - The size of the blob in bytes.
4825
+ * @param params.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
4826
+ * @param params.expirationMicros - The expiration time of the blob in microseconds.
4827
+ * @param params.numChunksets - The total number of chunksets in the blob.
4745
4828
  *
4746
- * @example
4747
- * ```typescript
4748
- * const blobData = new TextEncoder().encode("Hello, world!");
4829
+ * @returns An Aptos transaction payload data object for the register_blob Move function.
4749
4830
  *
4750
- * await client.putBlob({
4751
- * account: AccountAddress.from("0x1"),
4752
- * blobName: "greetings/hello.txt",
4753
- * blobData
4754
- * });
4755
- * ```
4831
+ * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
4756
4832
  */
4757
- async putBlob(params) {
4758
- BlobNameSchema.parse(params.blobName);
4759
- await this.#putBlobMultipart(
4760
- params.account,
4761
- params.blobName,
4762
- params.blobData
4763
- );
4833
+ static createRegisterBlobPayload(params) {
4834
+ return {
4835
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_blob`,
4836
+ functionArguments: [
4837
+ params.blobName,
4838
+ params.expirationMicros,
4839
+ MoveVector.U8(params.blobMerkleRoot),
4840
+ params.numChunksets,
4841
+ params.blobSize,
4842
+ // TODO
4843
+ 0,
4844
+ // payment tier
4845
+ 0
4846
+ // encoding
4847
+ ]
4848
+ };
4764
4849
  }
4765
- // FIXME make this possible to stream in put ^^^
4766
4850
  /**
4767
- * Downloads a blob from the Shelby RPC node.
4768
- * Returns a streaming response with validation to ensure data integrity.
4769
- *
4770
- * @param params.account - The account that owns the blob.
4771
- * @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
4772
- * @param params.range - Optional byte range for partial downloads.
4773
- * @param params.range.start - Starting byte position (inclusive).
4774
- * @param params.range.end - Ending byte position (inclusive, optional).
4775
- *
4776
- * @returns A ShelbyBlob object containing the account, name, readable stream, and content length.
4851
+ * Creates a transaction payload to register multiple blobs on the blockchain.
4852
+ * This is a static helper method for constructing the Move function call payload.
4777
4853
  *
4778
- * @throws Error if the download fails or content length doesn't match.
4854
+ * @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
4855
+ * @param params.account - The account that will own the blobs.
4856
+ * @param params.expirationMicros - The expiration time of the blobs in microseconds.
4857
+ * @param params.blobs - The blobs to register.
4858
+ * @param params.blobs.blobName - The name/path of the blob (e.g. "foo/bar.txt").
4859
+ * @param params.blobs.blobSize - The size of the blob in bytes.
4860
+ * @param params.blobs.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
4861
+ * @param params.blobs.numChunksets - The total number of chunksets in the blob.
4779
4862
  *
4780
- * @example
4781
- * ```typescript
4782
- * // Download entire blob
4783
- * const blob = await client.getBlob({
4784
- * account: AccountAddress.from("0x1"),
4785
- * blobName: "documents/report.pdf"
4786
- * });
4863
+ * @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
4787
4864
  *
4788
- * // Download partial content (bytes 100-199)
4789
- * const partial = await client.getBlob({
4790
- * account: AccountAddress.from("0x1"),
4791
- * blobName: "large-file.bin",
4792
- * range: { start: 100, end: 199 }
4793
- * });
4794
- * ```
4865
+ * @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
4795
4866
  */
4796
- async getBlob(params) {
4797
- BlobNameSchema.parse(params.blobName);
4798
- const url = buildRequestUrl(
4799
- `/v1/blobs/${params.account.toString()}/${encodeURIComponentKeepSlashes(
4800
- params.blobName
4801
- )}`,
4802
- this.baseUrl
4803
- );
4804
- const requestInit = {};
4805
- if (params.range !== void 0) {
4806
- const headers = new Headers();
4807
- const { start, end } = params.range;
4808
- if (end === void 0) {
4809
- headers.set("Range", `bytes=${start}-`);
4810
- } else {
4811
- if (end < start) {
4812
- throw new Error("Range end cannot be less than start.");
4813
- }
4814
- headers.set("Range", `bytes=${start}-${end}`);
4815
- }
4816
- requestInit.headers = headers;
4817
- }
4818
- const response = await fetch(url, {
4819
- ...requestInit,
4820
- headers: {
4821
- ...requestInit.headers,
4822
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
4823
- }
4824
- });
4825
- if (!response.ok) {
4826
- throw new Error(
4827
- `Failed to download blob: ${response.status} ${response.statusText}`
4828
- );
4829
- }
4830
- if (!response.body) {
4831
- throw new Error("Response body is null");
4832
- }
4833
- const contentLengthHeader = response.headers.get("content-length");
4834
- if (contentLengthHeader === null) {
4835
- throw new Error(
4836
- "Response did not have content-length header, which is required"
4837
- );
4838
- }
4839
- const expectedContentLength = Number.parseInt(contentLengthHeader, 10);
4840
- if (Number.isNaN(expectedContentLength)) {
4841
- throw new Error(
4842
- `Invalid content-length header received: ${contentLengthHeader}`
4843
- );
4844
- }
4845
- const validatingStream = new ReadableStream({
4846
- start(controller) {
4847
- const maybeReader = response.body?.getReader();
4848
- if (!maybeReader) {
4849
- controller.error(new Error("Response body reader is unavailable"));
4850
- return;
4851
- }
4852
- const reader = maybeReader;
4853
- let bytesReceived = 0;
4854
- function pump() {
4855
- return reader.read().then(({ done, value }) => {
4856
- if (done) {
4857
- if (bytesReceived !== expectedContentLength) {
4858
- controller.error(
4859
- new Error(
4860
- `Downloaded data size (${bytesReceived} bytes) does not match content-length header (${expectedContentLength} bytes). This might indicate a partial or corrupted download.`
4861
- )
4862
- );
4863
- return;
4864
- }
4865
- controller.close();
4866
- return;
4867
- }
4868
- bytesReceived += value.byteLength;
4869
- controller.enqueue(value);
4870
- return pump();
4871
- }).catch((error) => {
4872
- controller.error(error);
4873
- });
4874
- }
4875
- return pump();
4876
- }
4867
+ static createBatchRegisterBlobsPayload(params) {
4868
+ const blobNames = [];
4869
+ const blobMerkleRoots = [];
4870
+ const blobNumChunksets = [];
4871
+ const blobSizes = [];
4872
+ params.blobs.forEach((blob) => {
4873
+ blobNames.push(blob.blobName);
4874
+ blobMerkleRoots.push(MoveVector.U8(blob.blobMerkleRoot));
4875
+ blobNumChunksets.push(blob.numChunksets);
4876
+ blobSizes.push(blob.blobSize);
4877
4877
  });
4878
4878
  return {
4879
- account: AccountAddress4.from(params.account),
4880
- name: params.blobName,
4881
- readable: validatingStream,
4882
- contentLength: expectedContentLength
4879
+ function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_multiple_blobs`,
4880
+ functionArguments: [
4881
+ blobNames,
4882
+ params.expirationMicros,
4883
+ blobMerkleRoots,
4884
+ blobNumChunksets,
4885
+ blobSizes,
4886
+ // TODO
4887
+ 0,
4888
+ 0
4889
+ ]
4883
4890
  };
4884
4891
  }
4885
4892
  };
4886
4893
 
4887
- // ../../packages/sdk/dist/chunk-4W3FFVXZ.mjs
4894
+ // ../../packages/sdk/dist/chunk-2Y3ZLUYT.mjs
4888
4895
  import {
4889
- Aptos as Aptos2
4896
+ Aptos as Aptos3
4890
4897
  } from "@aptos-labs/ts-sdk";
4891
4898
  var ShelbyClient = class {
4892
4899
  /**
@@ -4943,7 +4950,7 @@ var ShelbyClient = class {
4943
4950
  */
4944
4951
  constructor(config, provider) {
4945
4952
  this.config = config;
4946
- this.aptos = new Aptos2(getAptosConfig(config));
4953
+ this.aptos = new Aptos3(getAptosConfig(config));
4947
4954
  this.coordination = new ShelbyBlobClient(config);
4948
4955
  this.rpc = new ShelbyRPCClient(config);
4949
4956
  this._provider = provider;
@@ -5039,16 +5046,29 @@ var ShelbyClient = class {
5039
5046
  }
5040
5047
  };
5041
5048
 
5042
- // ../../packages/sdk/dist/chunk-MYN7KW2X.mjs
5049
+ // ../../packages/sdk/dist/chunk-X56YUBBI.mjs
5043
5050
  var ShelbyNodeClient = class extends ShelbyClient {
5044
5051
  };
5045
5052
 
5046
- // ../../packages/sdk/dist/chunk-CTGCK3H2.mjs
5047
- import {
5048
- AccountAddress as AccountAddress5,
5049
- Aptos as Aptos3,
5050
- Hex as Hex3
5051
- } from "@aptos-labs/ts-sdk";
5053
+ // ../../packages/sdk/dist/chunk-7P6ASYW6.mjs
5054
+ var __defProp = Object.defineProperty;
5055
+ var __export = (target, all) => {
5056
+ for (var name in all)
5057
+ __defProp(target, name, { get: all[name], enumerable: true });
5058
+ };
5059
+
5060
+ // ../../packages/sdk/dist/chunk-A4IG6GSE.mjs
5061
+ var testUtil_exports = {};
5062
+ __export(testUtil_exports, {
5063
+ makeChunk: () => makeChunk
5064
+ });
5065
+ function makeChunk(n) {
5066
+ const c = Buffer.alloc(n);
5067
+ for (let i = 0; i < n; ++i) {
5068
+ c[i] = i % 256;
5069
+ }
5070
+ return c;
5071
+ }
5052
5072
 
5053
5073
  // ../../packages/sdk/dist/chunk-VRLIOKWG.mjs
5054
5074
  import { Network as Network4 } from "@aptos-labs/ts-sdk";
@@ -5078,26 +5098,6 @@ function getShelbyAccountExplorerUrl(network, accountAddress) {
5078
5098
  return `${baseUrl}/account/${accountAddress}`;
5079
5099
  }
5080
5100
 
5081
- // ../../packages/sdk/dist/chunk-7P6ASYW6.mjs
5082
- var __defProp = Object.defineProperty;
5083
- var __export = (target, all) => {
5084
- for (var name in all)
5085
- __defProp(target, name, { get: all[name], enumerable: true });
5086
- };
5087
-
5088
- // ../../packages/sdk/dist/chunk-A4IG6GSE.mjs
5089
- var testUtil_exports = {};
5090
- __export(testUtil_exports, {
5091
- makeChunk: () => makeChunk
5092
- });
5093
- function makeChunk(n) {
5094
- const c = Buffer.alloc(n);
5095
- for (let i = 0; i < n; ++i) {
5096
- c[i] = i % 256;
5097
- }
5098
- return c;
5099
- }
5100
-
5101
5101
  // ../../packages/sdk/dist/chunk-C6RQ3AEU.mjs
5102
5102
  import { Network as Network6 } from "@aptos-labs/ts-sdk";
5103
5103
  function getAptosExplorerBaseUrl(network) {
@@ -7888,15 +7888,15 @@ async function createFileList(options, nodeClient, account) {
7888
7888
  }
7889
7889
  const ret = [];
7890
7890
  for (const blobMd of matchingBlobList) {
7891
- const blobNameWithoutAccount = blobMd.name.slice(1 + 64 + 1);
7891
+ let blobname;
7892
+ if (options.recursive) {
7893
+ blobname = blobMd.name.slice(1 + 64 + 1);
7894
+ } else {
7895
+ blobname = blobMd.name;
7896
+ }
7892
7897
  const entry = {
7893
- filename: denormBlobName2(
7894
- options.src,
7895
- blobNameWithoutAccount,
7896
- options.dst
7897
- ),
7898
- blobname: blobNameWithoutAccount,
7899
- // FIXME this is nasty
7898
+ filename: denormBlobName2(options.src, blobname, options.dst),
7899
+ blobname,
7900
7900
  sizeBytes: blobMd.size
7901
7901
  };
7902
7902
  ret.push(entry);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shelby-protocol/cli",
3
3
  "type": "module",
4
- "version": "0.0.12",
4
+ "version": "0.0.13",
5
5
  "private": false,
6
6
  "bin": {
7
7
  "shelby": "bin/entry.js"
@@ -39,8 +39,8 @@
39
39
  "wrap-ansi": "^9.0.2",
40
40
  "yaml": "^2.7.1",
41
41
  "zod": "^3.22.4",
42
- "@shelby-protocol/sdk": "0.0.4",
43
- "@shelby-protocol/clay-codes": "0.0.1"
42
+ "@shelby-protocol/clay-codes": "0.0.1",
43
+ "@shelby-protocol/sdk": "0.0.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/fs-extra": "^11.0.4",