@shelby-protocol/cli 0.0.12 → 0.0.14
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.
- package/bin/entry.js +1338 -926
- package/package.json +9 -7
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.
|
|
7
|
+
var version = "0.0.14";
|
|
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((resolve3) => setTimeout(resolve3, 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) {
|
|
@@ -3209,7 +3270,7 @@ var extras = {
|
|
|
3209
3270
|
gql["default"] = gql;
|
|
3210
3271
|
var lib_default = gql;
|
|
3211
3272
|
|
|
3212
|
-
// ../../packages/sdk/dist/chunk-
|
|
3273
|
+
// ../../packages/sdk/dist/chunk-WJKSPJSS.mjs
|
|
3213
3274
|
var GetBlobsDocument = lib_default`
|
|
3214
3275
|
query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
|
|
3215
3276
|
blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
|
|
@@ -3245,6 +3306,24 @@ var GetBlobActivitiesDocument = lib_default`
|
|
|
3245
3306
|
}
|
|
3246
3307
|
}
|
|
3247
3308
|
`;
|
|
3309
|
+
var GetBlobsCountDocument = lib_default`
|
|
3310
|
+
query getBlobsCount($where: blobs_bool_exp) {
|
|
3311
|
+
blobs_aggregate(where: $where) {
|
|
3312
|
+
aggregate {
|
|
3313
|
+
count
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
`;
|
|
3318
|
+
var GetBlobActivitiesCountDocument = lib_default`
|
|
3319
|
+
query getBlobActivitiesCount($where: blob_activities_bool_exp) {
|
|
3320
|
+
blob_activities_aggregate(where: $where) {
|
|
3321
|
+
aggregate {
|
|
3322
|
+
count
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
`;
|
|
3248
3327
|
var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
|
|
3249
3328
|
function getSdk(client, withWrapper = defaultWrapper) {
|
|
3250
3329
|
return {
|
|
@@ -3253,60 +3332,17 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
3253
3332
|
},
|
|
3254
3333
|
getBlobActivities(variables, requestHeaders, signal) {
|
|
3255
3334
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivities", "query", variables);
|
|
3335
|
+
},
|
|
3336
|
+
getBlobsCount(variables, requestHeaders, signal) {
|
|
3337
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobsCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobsCount", "query", variables);
|
|
3338
|
+
},
|
|
3339
|
+
getBlobActivitiesCount(variables, requestHeaders, signal) {
|
|
3340
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivitiesCount", "query", variables);
|
|
3256
3341
|
}
|
|
3257
3342
|
};
|
|
3258
3343
|
}
|
|
3259
3344
|
|
|
3260
|
-
// ../../packages/sdk/dist/chunk-
|
|
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
|
-
// ../../packages/sdk/dist/chunk-VPT45MTZ.mjs
|
|
3345
|
+
// ../../packages/sdk/dist/chunk-T6TVHFJO.mjs
|
|
3310
3346
|
import { Network as Network3 } from "@aptos-labs/ts-sdk";
|
|
3311
3347
|
|
|
3312
3348
|
// ../../node_modules/.pnpm/graphql-request@7.2.0_graphql@16.11.0/node_modules/graphql-request/build/legacy/classes/ClientError.js
|
|
@@ -3821,7 +3857,7 @@ var parseRequestArgs = (documentOrOptions, variables, requestHeaders) => {
|
|
|
3821
3857
|
};
|
|
3822
3858
|
};
|
|
3823
3859
|
|
|
3824
|
-
// ../../packages/sdk/dist/chunk-
|
|
3860
|
+
// ../../packages/sdk/dist/chunk-T6TVHFJO.mjs
|
|
3825
3861
|
function createShelbyIndexerClient(baseUrl, options) {
|
|
3826
3862
|
const graphqlClient = new GraphQLClient(baseUrl, options);
|
|
3827
3863
|
return getSdk(graphqlClient);
|
|
@@ -3854,14 +3890,8 @@ function getShelbyIndexerClient(config) {
|
|
|
3854
3890
|
});
|
|
3855
3891
|
}
|
|
3856
3892
|
|
|
3857
|
-
// ../../packages/sdk/dist/chunk-
|
|
3858
|
-
import {
|
|
3859
|
-
var createBlobKey = (params) => {
|
|
3860
|
-
return `@${AccountAddress.from(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
|
|
3861
|
-
};
|
|
3862
|
-
|
|
3863
|
-
// ../../packages/sdk/dist/chunk-KBUWZXFA.mjs
|
|
3864
|
-
import { Hex } from "@aptos-labs/ts-sdk";
|
|
3893
|
+
// ../../packages/sdk/dist/chunk-4JZO2D7T.mjs
|
|
3894
|
+
import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
|
|
3865
3895
|
async function* readInChunks(input, chunkSize) {
|
|
3866
3896
|
let idx = 0;
|
|
3867
3897
|
if (isReadableStream(input)) {
|
|
@@ -3921,7 +3951,7 @@ function zeroPadBytes(buffer, desiredLength) {
|
|
|
3921
3951
|
return paddedBuffer;
|
|
3922
3952
|
}
|
|
3923
3953
|
async function concatHashes(parts) {
|
|
3924
|
-
const chunks = parts.map((part) =>
|
|
3954
|
+
const chunks = parts.map((part) => Hex2.fromHexInput(part).toUint8Array());
|
|
3925
3955
|
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
3926
3956
|
const combined = new Uint8Array(totalLength);
|
|
3927
3957
|
let offset = 0;
|
|
@@ -3929,7 +3959,7 @@ async function concatHashes(parts) {
|
|
|
3929
3959
|
combined.set(chunk, offset);
|
|
3930
3960
|
offset += chunk.byteLength;
|
|
3931
3961
|
}
|
|
3932
|
-
return
|
|
3962
|
+
return Hex2.fromHexInput(
|
|
3933
3963
|
new Uint8Array(await crypto.subtle.digest("SHA-256", combined))
|
|
3934
3964
|
);
|
|
3935
3965
|
}
|
|
@@ -3945,6 +3975,10 @@ function buildRequestUrl(path6, baseUrl) {
|
|
|
3945
3975
|
const safePath = path6.replace(/^\/+/, "");
|
|
3946
3976
|
return new URL(safePath, safeBase);
|
|
3947
3977
|
}
|
|
3978
|
+
function getBlobNameSuffix(blobName) {
|
|
3979
|
+
const parts = blobName.split("/");
|
|
3980
|
+
return parts.slice(1).join("/") || "";
|
|
3981
|
+
}
|
|
3948
3982
|
|
|
3949
3983
|
// ../../packages/sdk/dist/chunk-ZPW742E7.mjs
|
|
3950
3984
|
var ERASURE_CODE_PARAMS = {
|
|
@@ -4125,143 +4159,420 @@ function buildClayConfig(input) {
|
|
|
4125
4159
|
};
|
|
4126
4160
|
}
|
|
4127
4161
|
|
|
4128
|
-
// ../../packages/sdk/dist/chunk-
|
|
4162
|
+
// ../../packages/sdk/dist/chunk-FIFKKWXV.mjs
|
|
4163
|
+
import { AccountAddress as AccountAddress2 } from "@aptos-labs/ts-sdk";
|
|
4129
4164
|
import { z } from "zod";
|
|
4130
|
-
var
|
|
4131
|
-
|
|
4132
|
-
|
|
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
|
-
}
|
|
4165
|
+
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("/"), {
|
|
4166
|
+
message: "Blob name cannot end with a slash"
|
|
4167
|
+
});
|
|
4249
4168
|
|
|
4250
|
-
// ../../packages/sdk/dist/chunk-
|
|
4251
|
-
import {
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
aptos;
|
|
4260
|
-
deployer;
|
|
4169
|
+
// ../../packages/sdk/dist/chunk-L7H6EKII.mjs
|
|
4170
|
+
import { AccountAddress as AccountAddress3 } from "@aptos-labs/ts-sdk";
|
|
4171
|
+
function encodeURIComponentKeepSlashes(str) {
|
|
4172
|
+
return encodeURIComponent(str).replace(/%2F/g, "/");
|
|
4173
|
+
}
|
|
4174
|
+
var ShelbyRPCClient = class {
|
|
4175
|
+
baseUrl;
|
|
4176
|
+
apiKey;
|
|
4177
|
+
rpcConfig;
|
|
4261
4178
|
indexer;
|
|
4262
4179
|
/**
|
|
4263
|
-
*
|
|
4264
|
-
*
|
|
4180
|
+
* Creates a new ShelbyRPCClient for interacting with Shelby RPC nodes.
|
|
4181
|
+
* This client handles blob storage operations including upload and download.
|
|
4182
|
+
*
|
|
4183
|
+
* @param config - The client configuration object.
|
|
4184
|
+
* @param config.network - The Shelby network to use.
|
|
4185
|
+
*
|
|
4186
|
+
* @example
|
|
4187
|
+
* ```typescript
|
|
4188
|
+
* const client = new ShelbyRPCClient({
|
|
4189
|
+
* network: Network.SHELBYNET,
|
|
4190
|
+
* apiKey: "AG-***",
|
|
4191
|
+
* });
|
|
4192
|
+
* ```
|
|
4193
|
+
*/
|
|
4194
|
+
constructor(config) {
|
|
4195
|
+
this.baseUrl = config.rpc?.baseUrl ?? NetworkToShelbyRPCBaseUrl.shelbynet;
|
|
4196
|
+
this.apiKey = config.apiKey ?? config.rpc?.apiKey;
|
|
4197
|
+
this.rpcConfig = config.rpc ?? {};
|
|
4198
|
+
this.indexer = getShelbyIndexerClient(config);
|
|
4199
|
+
}
|
|
4200
|
+
async #uploadPart(uploadId, partIdx, partData) {
|
|
4201
|
+
const nRetries = 5;
|
|
4202
|
+
for (let i = 0; i < nRetries; ++i) {
|
|
4203
|
+
const partResponse = await fetch(
|
|
4204
|
+
buildRequestUrl(
|
|
4205
|
+
`/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
|
|
4206
|
+
this.baseUrl
|
|
4207
|
+
),
|
|
4208
|
+
{
|
|
4209
|
+
method: "PUT",
|
|
4210
|
+
headers: {
|
|
4211
|
+
"Content-Type": "application/octet-stream",
|
|
4212
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
4213
|
+
},
|
|
4214
|
+
body: partData
|
|
4215
|
+
}
|
|
4216
|
+
);
|
|
4217
|
+
if (partResponse.ok) return;
|
|
4218
|
+
if (i < nRetries - 1) {
|
|
4219
|
+
const delay = 2 ** i * 100;
|
|
4220
|
+
await sleep(delay);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
throw new Error(`Failed to upload part ${partIdx}.`);
|
|
4224
|
+
}
|
|
4225
|
+
async #putBlobMultipart(account, blobName, blobData, partSize = 5 * 1024 * 1024) {
|
|
4226
|
+
const startResponse = await fetch(
|
|
4227
|
+
buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
|
|
4228
|
+
{
|
|
4229
|
+
method: "POST",
|
|
4230
|
+
headers: {
|
|
4231
|
+
"Content-Type": "application/json",
|
|
4232
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
4233
|
+
},
|
|
4234
|
+
body: JSON.stringify({
|
|
4235
|
+
rawAccount: account.toString(),
|
|
4236
|
+
rawBlobName: blobName,
|
|
4237
|
+
rawPartSize: partSize
|
|
4238
|
+
})
|
|
4239
|
+
}
|
|
4240
|
+
);
|
|
4241
|
+
if (!startResponse.ok) {
|
|
4242
|
+
let errorBodyText = "Could not read error body";
|
|
4243
|
+
try {
|
|
4244
|
+
errorBodyText = await startResponse.text();
|
|
4245
|
+
} catch (_e) {
|
|
4246
|
+
}
|
|
4247
|
+
throw new Error(
|
|
4248
|
+
`Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
const { uploadId } = await startResponse.json();
|
|
4252
|
+
const totalParts = Math.ceil(blobData.length / partSize);
|
|
4253
|
+
for (let partIdx = 0; partIdx < totalParts; partIdx++) {
|
|
4254
|
+
const start = partIdx * partSize;
|
|
4255
|
+
const end = Math.min(start + partSize, blobData.length);
|
|
4256
|
+
const partData = blobData.slice(start, end);
|
|
4257
|
+
await this.#uploadPart(uploadId, partIdx, partData);
|
|
4258
|
+
}
|
|
4259
|
+
const completeResponse = await fetch(
|
|
4260
|
+
buildRequestUrl(
|
|
4261
|
+
`/v1/multipart-uploads/${uploadId}/complete`,
|
|
4262
|
+
this.baseUrl
|
|
4263
|
+
),
|
|
4264
|
+
{
|
|
4265
|
+
method: "POST",
|
|
4266
|
+
headers: {
|
|
4267
|
+
"Content-Type": "application/json",
|
|
4268
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
);
|
|
4272
|
+
if (!completeResponse.ok) {
|
|
4273
|
+
let errorBodyText = "Could not read error body";
|
|
4274
|
+
try {
|
|
4275
|
+
errorBodyText = await completeResponse.text();
|
|
4276
|
+
} catch (_e) {
|
|
4277
|
+
}
|
|
4278
|
+
throw new Error(
|
|
4279
|
+
`Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
|
|
4280
|
+
);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* Uploads blob data to the Shelby RPC node for storage by storage providers.
|
|
4285
|
+
* This method should be called after blob commitments have been registered on the blockchain.
|
|
4286
|
+
* Uses multipart upload for efficient handling of large files.
|
|
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.blobData - The raw blob data as a Uint8Array.
|
|
4291
|
+
*
|
|
4292
|
+
* @example
|
|
4293
|
+
* ```typescript
|
|
4294
|
+
* const blobData = new TextEncoder().encode("Hello, world!");
|
|
4295
|
+
*
|
|
4296
|
+
* await client.putBlob({
|
|
4297
|
+
* account: AccountAddress.from("0x1"),
|
|
4298
|
+
* blobName: "greetings/hello.txt",
|
|
4299
|
+
* blobData
|
|
4300
|
+
* });
|
|
4301
|
+
* ```
|
|
4302
|
+
*/
|
|
4303
|
+
async putBlob(params) {
|
|
4304
|
+
BlobNameSchema.parse(params.blobName);
|
|
4305
|
+
await this.#putBlobMultipart(
|
|
4306
|
+
params.account,
|
|
4307
|
+
params.blobName,
|
|
4308
|
+
params.blobData
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
// FIXME make this possible to stream in put ^^^
|
|
4312
|
+
/**
|
|
4313
|
+
* Downloads a blob from the Shelby RPC node.
|
|
4314
|
+
* Returns a streaming response with validation to ensure data integrity.
|
|
4315
|
+
*
|
|
4316
|
+
* @param params.account - The account that owns the blob.
|
|
4317
|
+
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
4318
|
+
* @param params.range - Optional byte range for partial downloads.
|
|
4319
|
+
* @param params.range.start - Starting byte position (inclusive).
|
|
4320
|
+
* @param params.range.end - Ending byte position (inclusive, optional).
|
|
4321
|
+
*
|
|
4322
|
+
* @returns A ShelbyBlob object containing the account, name, readable stream, and content length.
|
|
4323
|
+
*
|
|
4324
|
+
* @throws Error if the download fails or content length doesn't match.
|
|
4325
|
+
*
|
|
4326
|
+
* @example
|
|
4327
|
+
* ```typescript
|
|
4328
|
+
* // Download entire blob
|
|
4329
|
+
* const blob = await client.getBlob({
|
|
4330
|
+
* account: AccountAddress.from("0x1"),
|
|
4331
|
+
* blobName: "documents/report.pdf"
|
|
4332
|
+
* });
|
|
4333
|
+
*
|
|
4334
|
+
* // Download partial content (bytes 100-199)
|
|
4335
|
+
* const partial = await client.getBlob({
|
|
4336
|
+
* account: AccountAddress.from("0x1"),
|
|
4337
|
+
* blobName: "large-file.bin",
|
|
4338
|
+
* range: { start: 100, end: 199 }
|
|
4339
|
+
* });
|
|
4340
|
+
* ```
|
|
4341
|
+
*/
|
|
4342
|
+
async getBlob(params) {
|
|
4343
|
+
BlobNameSchema.parse(params.blobName);
|
|
4344
|
+
const url = buildRequestUrl(
|
|
4345
|
+
`/v1/blobs/${params.account.toString()}/${encodeURIComponentKeepSlashes(
|
|
4346
|
+
params.blobName
|
|
4347
|
+
)}`,
|
|
4348
|
+
this.baseUrl
|
|
4349
|
+
);
|
|
4350
|
+
const requestInit = {};
|
|
4351
|
+
if (params.range !== void 0) {
|
|
4352
|
+
const headers = new Headers();
|
|
4353
|
+
const { start, end } = params.range;
|
|
4354
|
+
if (end === void 0) {
|
|
4355
|
+
headers.set("Range", `bytes=${start}-`);
|
|
4356
|
+
} else {
|
|
4357
|
+
if (end < start) {
|
|
4358
|
+
throw new Error("Range end cannot be less than start.");
|
|
4359
|
+
}
|
|
4360
|
+
headers.set("Range", `bytes=${start}-${end}`);
|
|
4361
|
+
}
|
|
4362
|
+
requestInit.headers = headers;
|
|
4363
|
+
}
|
|
4364
|
+
const response = await fetch(url, {
|
|
4365
|
+
...requestInit,
|
|
4366
|
+
headers: {
|
|
4367
|
+
...requestInit.headers,
|
|
4368
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
if (!response.ok) {
|
|
4372
|
+
throw new Error(
|
|
4373
|
+
`Failed to download blob: ${response.status} ${response.statusText}`
|
|
4374
|
+
);
|
|
4375
|
+
}
|
|
4376
|
+
if (!response.body) {
|
|
4377
|
+
throw new Error("Response body is null");
|
|
4378
|
+
}
|
|
4379
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
4380
|
+
if (contentLengthHeader === null) {
|
|
4381
|
+
throw new Error(
|
|
4382
|
+
"Response did not have content-length header, which is required"
|
|
4383
|
+
);
|
|
4384
|
+
}
|
|
4385
|
+
const expectedContentLength = Number.parseInt(contentLengthHeader, 10);
|
|
4386
|
+
if (Number.isNaN(expectedContentLength)) {
|
|
4387
|
+
throw new Error(
|
|
4388
|
+
`Invalid content-length header received: ${contentLengthHeader}`
|
|
4389
|
+
);
|
|
4390
|
+
}
|
|
4391
|
+
const validatingStream = new ReadableStream({
|
|
4392
|
+
start(controller) {
|
|
4393
|
+
const maybeReader = response.body?.getReader();
|
|
4394
|
+
if (!maybeReader) {
|
|
4395
|
+
controller.error(new Error("Response body reader is unavailable"));
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
const reader = maybeReader;
|
|
4399
|
+
let bytesReceived = 0;
|
|
4400
|
+
function pump() {
|
|
4401
|
+
return reader.read().then(({ done, value }) => {
|
|
4402
|
+
if (done) {
|
|
4403
|
+
if (bytesReceived !== expectedContentLength) {
|
|
4404
|
+
controller.error(
|
|
4405
|
+
new Error(
|
|
4406
|
+
`Downloaded data size (${bytesReceived} bytes) does not match content-length header (${expectedContentLength} bytes). This might indicate a partial or corrupted download.`
|
|
4407
|
+
)
|
|
4408
|
+
);
|
|
4409
|
+
return;
|
|
4410
|
+
}
|
|
4411
|
+
controller.close();
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
bytesReceived += value.byteLength;
|
|
4415
|
+
controller.enqueue(value);
|
|
4416
|
+
return pump();
|
|
4417
|
+
}).catch((error) => {
|
|
4418
|
+
controller.error(error);
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
return pump();
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
return {
|
|
4425
|
+
account: AccountAddress3.from(params.account),
|
|
4426
|
+
name: params.blobName,
|
|
4427
|
+
readable: validatingStream,
|
|
4428
|
+
contentLength: expectedContentLength
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
};
|
|
4432
|
+
|
|
4433
|
+
// ../../packages/sdk/dist/chunk-OTBLZL2S.mjs
|
|
4434
|
+
import { AccountAddress as AccountAddress4 } from "@aptos-labs/ts-sdk";
|
|
4435
|
+
var createBlobKey = (params) => {
|
|
4436
|
+
return `@${AccountAddress4.from(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
|
|
4437
|
+
};
|
|
4438
|
+
|
|
4439
|
+
// ../../packages/sdk/dist/chunk-PCNLFNAT.mjs
|
|
4440
|
+
import { z as z2 } from "zod";
|
|
4441
|
+
var ChunksetCommitmentSchema = z2.object({
|
|
4442
|
+
// Chunkset root (vector commitment of child chunks)
|
|
4443
|
+
chunkset_root: z2.string().nullable(),
|
|
4444
|
+
// the size is known statically from the current configuration
|
|
4445
|
+
chunk_commitments: z2.array(z2.string())
|
|
4446
|
+
}).refine(
|
|
4447
|
+
(data) => {
|
|
4448
|
+
return data.chunk_commitments.length === DEFAULT_ERASURE_K + DEFAULT_ERASURE_M;
|
|
4449
|
+
},
|
|
4450
|
+
{
|
|
4451
|
+
message: `Chunkset must have exactly ${DEFAULT_ERASURE_K + DEFAULT_ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${DEFAULT_ERASURE_K} + ${DEFAULT_ERASURE_M})`,
|
|
4452
|
+
path: ["chunk_commitments"]
|
|
4453
|
+
}
|
|
4454
|
+
);
|
|
4455
|
+
function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES) {
|
|
4456
|
+
if (chunksetSize <= 0) {
|
|
4457
|
+
throw new Error("chunksetSize must be positive");
|
|
4458
|
+
}
|
|
4459
|
+
if (rawSize === 0) return 1;
|
|
4460
|
+
return Math.ceil(rawSize / chunksetSize);
|
|
4461
|
+
}
|
|
4462
|
+
var BlobCommitmentsSchema = z2.object({
|
|
4463
|
+
schema_version: z2.string(),
|
|
4464
|
+
raw_data_size: z2.number(),
|
|
4465
|
+
// FIXME I am not sure about this being here, or if it should be somewhere else
|
|
4466
|
+
blob_merkle_root: z2.string(),
|
|
4467
|
+
chunkset_commitments: z2.array(ChunksetCommitmentSchema)
|
|
4468
|
+
}).refine(
|
|
4469
|
+
(data) => {
|
|
4470
|
+
return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
|
|
4471
|
+
},
|
|
4472
|
+
{
|
|
4473
|
+
message: "Total chunkset count mismatches with raw data size",
|
|
4474
|
+
// FIXME put more details in here
|
|
4475
|
+
path: ["chunkset_commitments"]
|
|
4476
|
+
}
|
|
4477
|
+
);
|
|
4478
|
+
async function generateChunksetCommitments(shouldPad, chunksetIdx, chunksetData, expectedChunksetSize, provider, onChunk) {
|
|
4479
|
+
const { erasure_n } = provider.config;
|
|
4480
|
+
const chunkCommitments = [];
|
|
4481
|
+
const chunksetPayload = shouldPad ? zeroPadBytes(chunksetData, expectedChunksetSize) : validatePrePaddedChunkset(
|
|
4482
|
+
chunksetData,
|
|
4483
|
+
expectedChunksetSize,
|
|
4484
|
+
chunksetIdx
|
|
4485
|
+
);
|
|
4486
|
+
const { chunks } = provider.encode(chunksetPayload);
|
|
4487
|
+
if (chunks.length !== erasure_n) {
|
|
4488
|
+
throw new Error(
|
|
4489
|
+
`Erasure provider produced ${chunks.length} chunks, expected ${erasure_n}.`
|
|
4490
|
+
);
|
|
4491
|
+
}
|
|
4492
|
+
let chunkIdx = 0;
|
|
4493
|
+
for (const chunkData of chunks) {
|
|
4494
|
+
if (onChunk !== void 0) {
|
|
4495
|
+
await onChunk(chunksetIdx, chunkIdx, chunkData);
|
|
4496
|
+
}
|
|
4497
|
+
const chunkHash = await concatHashes([chunkData]);
|
|
4498
|
+
chunkCommitments.push(chunkHash);
|
|
4499
|
+
chunkIdx += 1;
|
|
4500
|
+
}
|
|
4501
|
+
const h = await concatHashes(
|
|
4502
|
+
chunkCommitments.map((chunk) => chunk.toUint8Array())
|
|
4503
|
+
);
|
|
4504
|
+
const entry = {
|
|
4505
|
+
chunkset_root: h.toString(),
|
|
4506
|
+
chunk_commitments: chunkCommitments.map((chunk) => chunk.toString())
|
|
4507
|
+
};
|
|
4508
|
+
return { h, entry };
|
|
4509
|
+
}
|
|
4510
|
+
async function generateCommitments(provider, fullData, onChunk, options) {
|
|
4511
|
+
const expectedChunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES;
|
|
4512
|
+
const shouldPad = options?.pad ?? true;
|
|
4513
|
+
const chunksetCommitments = [];
|
|
4514
|
+
const chunksetCommitmentHashes = [];
|
|
4515
|
+
let rawDataSize = 0;
|
|
4516
|
+
const chunksetGen = readInChunks(fullData, expectedChunksetSize);
|
|
4517
|
+
for await (const [chunksetIdx, chunksetData] of chunksetGen) {
|
|
4518
|
+
rawDataSize += chunksetData.length;
|
|
4519
|
+
const { h, entry } = await generateChunksetCommitments(
|
|
4520
|
+
shouldPad,
|
|
4521
|
+
chunksetIdx,
|
|
4522
|
+
chunksetData,
|
|
4523
|
+
expectedChunksetSize,
|
|
4524
|
+
provider,
|
|
4525
|
+
onChunk
|
|
4526
|
+
);
|
|
4527
|
+
chunksetCommitments.push(entry);
|
|
4528
|
+
chunksetCommitmentHashes.push(h);
|
|
4529
|
+
}
|
|
4530
|
+
if (rawDataSize === 0) {
|
|
4531
|
+
const zeroChunkset = new Uint8Array(expectedChunksetSize);
|
|
4532
|
+
const { h, entry } = await generateChunksetCommitments(
|
|
4533
|
+
shouldPad,
|
|
4534
|
+
0,
|
|
4535
|
+
zeroChunkset,
|
|
4536
|
+
expectedChunksetSize,
|
|
4537
|
+
provider,
|
|
4538
|
+
onChunk
|
|
4539
|
+
);
|
|
4540
|
+
chunksetCommitments.push(entry);
|
|
4541
|
+
chunksetCommitmentHashes.push(h);
|
|
4542
|
+
}
|
|
4543
|
+
return {
|
|
4544
|
+
schema_version: "1.3",
|
|
4545
|
+
raw_data_size: rawDataSize,
|
|
4546
|
+
blob_merkle_root: (await concatHashes(
|
|
4547
|
+
chunksetCommitmentHashes.map((chunk) => chunk.toUint8Array())
|
|
4548
|
+
)).toString(),
|
|
4549
|
+
chunkset_commitments: chunksetCommitments
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
function validatePrePaddedChunkset(chunkset, expectedSize, chunksetIdx) {
|
|
4553
|
+
if (chunkset.byteLength !== expectedSize) {
|
|
4554
|
+
throw new Error(
|
|
4555
|
+
`Chunkset ${chunksetIdx} has size ${chunkset.byteLength} bytes but expected ${expectedSize} bytes. Enable padding or supply pre-padded data before calling generateCommitments.`
|
|
4556
|
+
);
|
|
4557
|
+
}
|
|
4558
|
+
return chunkset;
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
// ../../packages/sdk/dist/chunk-Y7KTNPPR.mjs
|
|
4562
|
+
import {
|
|
4563
|
+
AccountAddress as AccountAddress5,
|
|
4564
|
+
Aptos as Aptos2,
|
|
4565
|
+
AptosConfig as AptosConfig2,
|
|
4566
|
+
Hex as Hex3,
|
|
4567
|
+
MoveVector
|
|
4568
|
+
} from "@aptos-labs/ts-sdk";
|
|
4569
|
+
var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
4570
|
+
aptos;
|
|
4571
|
+
deployer;
|
|
4572
|
+
indexer;
|
|
4573
|
+
/**
|
|
4574
|
+
* The ShelbyBlobClient is used to interact with the Shelby contract on the Aptos blockchain. This
|
|
4575
|
+
* includes functions for registering blob commitments and retrieving blob metadata.
|
|
4265
4576
|
*
|
|
4266
4577
|
* @param config - The client configuration object.
|
|
4267
4578
|
* @param config.network - The Shelby network to use.
|
|
@@ -4288,8 +4599,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4288
4599
|
API_KEY: baseAptosConfig.clientConfig?.API_KEY ?? config.apiKey
|
|
4289
4600
|
}
|
|
4290
4601
|
});
|
|
4291
|
-
this.aptos = new
|
|
4292
|
-
this.deployer = config.deployer ??
|
|
4602
|
+
this.aptos = new Aptos2(aptosConfig);
|
|
4603
|
+
this.deployer = config.deployer ?? AccountAddress5.fromString(SHELBY_DEPLOYER);
|
|
4293
4604
|
this.indexer = getShelbyIndexerClient(config);
|
|
4294
4605
|
}
|
|
4295
4606
|
/**
|
|
@@ -4338,15 +4649,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4338
4649
|
);
|
|
4339
4650
|
}
|
|
4340
4651
|
return {
|
|
4341
|
-
blobMerkleRoot:
|
|
4652
|
+
blobMerkleRoot: Hex3.fromHexInput(
|
|
4342
4653
|
metadata.blob_commitment
|
|
4343
4654
|
).toUint8Array(),
|
|
4344
|
-
owner:
|
|
4655
|
+
owner: AccountAddress5.fromString(metadata.owner),
|
|
4345
4656
|
name: params.name,
|
|
4657
|
+
blobNameSuffix: getBlobNameSuffix(params.name),
|
|
4346
4658
|
size: Number(metadata.blob_size),
|
|
4347
4659
|
encoding,
|
|
4348
4660
|
expirationMicros: metadata.expiration_micros,
|
|
4349
|
-
sliceAddress:
|
|
4661
|
+
sliceAddress: AccountAddress5.fromString(metadata.slice.inner),
|
|
4350
4662
|
isWritten: metadata.is_written
|
|
4351
4663
|
};
|
|
4352
4664
|
} catch (error) {
|
|
@@ -4375,13 +4687,14 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4375
4687
|
* ```
|
|
4376
4688
|
*/
|
|
4377
4689
|
getAccountBlobs(params) {
|
|
4690
|
+
const { where, ...rest } = params;
|
|
4378
4691
|
return this.getBlobs({
|
|
4379
4692
|
where: {
|
|
4380
|
-
...
|
|
4381
|
-
owner: { _eq:
|
|
4693
|
+
...where ?? {},
|
|
4694
|
+
owner: { _eq: AccountAddress5.from(params.account).toString() }
|
|
4382
4695
|
},
|
|
4383
|
-
pagination:
|
|
4384
|
-
orderBy:
|
|
4696
|
+
pagination: rest.pagination,
|
|
4697
|
+
orderBy: rest.orderBy
|
|
4385
4698
|
});
|
|
4386
4699
|
}
|
|
4387
4700
|
/**
|
|
@@ -4403,17 +4716,24 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4403
4716
|
async getBlobs(params = {}) {
|
|
4404
4717
|
const { limit, offset } = params.pagination ?? {};
|
|
4405
4718
|
const { orderBy, where } = params;
|
|
4719
|
+
const currentMicros = Date.now() * 1e3;
|
|
4720
|
+
const defaultActiveFilter = {
|
|
4721
|
+
expires_at: { _gte: currentMicros },
|
|
4722
|
+
is_deleted: { _eq: "0" }
|
|
4723
|
+
};
|
|
4724
|
+
const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
|
|
4406
4725
|
const { blobs } = await this.indexer.getBlobs({
|
|
4407
|
-
where,
|
|
4726
|
+
where: finalWhere,
|
|
4408
4727
|
limit,
|
|
4409
4728
|
offset,
|
|
4410
4729
|
orderBy
|
|
4411
4730
|
});
|
|
4412
4731
|
return blobs.map(
|
|
4413
4732
|
(blob) => ({
|
|
4414
|
-
owner:
|
|
4733
|
+
owner: AccountAddress5.from(blob.owner),
|
|
4415
4734
|
name: blob.blob_name,
|
|
4416
|
-
|
|
4735
|
+
blobNameSuffix: getBlobNameSuffix(blob.blob_name),
|
|
4736
|
+
blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
|
|
4417
4737
|
size: Number(blob.size),
|
|
4418
4738
|
// TODO: Add encoding when supported in NCI
|
|
4419
4739
|
encoding: {
|
|
@@ -4422,7 +4742,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4422
4742
|
...ERASURE_CODE_AND_CHUNK_MAPPING.ClayCode_16Total_10Data_13Helper
|
|
4423
4743
|
},
|
|
4424
4744
|
expirationMicros: Number(blob.expires_at),
|
|
4425
|
-
sliceAddress:
|
|
4745
|
+
sliceAddress: AccountAddress5.from(blob.slice_address),
|
|
4426
4746
|
isWritten: Boolean(Number(blob.is_written))
|
|
4427
4747
|
})
|
|
4428
4748
|
);
|
|
@@ -4445,7 +4765,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4445
4765
|
return blob_activities.map(
|
|
4446
4766
|
(activity) => ({
|
|
4447
4767
|
blobName: activity.blob_name,
|
|
4448
|
-
accountAddress:
|
|
4768
|
+
accountAddress: AccountAddress5.from(
|
|
4449
4769
|
activity.blob_name.substring(1, 65)
|
|
4450
4770
|
),
|
|
4451
4771
|
type: activityTypeMapping[activity.event_type] ?? "unknown",
|
|
@@ -4458,28 +4778,36 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4458
4778
|
);
|
|
4459
4779
|
}
|
|
4460
4780
|
/**
|
|
4461
|
-
* Retrieves the
|
|
4462
|
-
* the commitment, the storage provider location, and the status of the chunk (stored or pending).
|
|
4781
|
+
* Retrieves the total number of blobs from the blockchain.
|
|
4463
4782
|
*
|
|
4464
|
-
* @
|
|
4465
|
-
*
|
|
4466
|
-
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
4467
|
-
* @param params.name - The name of the blob (e.g. "foo/bar")
|
|
4468
|
-
* @returns The chunks that make up the blob.
|
|
4783
|
+
* @param params.where (optional) - The where clause to filter the blobs by.
|
|
4784
|
+
* @returns The total number of blobs.
|
|
4469
4785
|
*
|
|
4470
4786
|
* @example
|
|
4471
4787
|
* ```typescript
|
|
4472
|
-
*
|
|
4473
|
-
*
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4788
|
+
* const count = await client.getBlobsCount();
|
|
4789
|
+
* ```
|
|
4790
|
+
*/
|
|
4791
|
+
async getBlobsCount(params) {
|
|
4792
|
+
const { where } = params;
|
|
4793
|
+
const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
|
|
4794
|
+
return blobs_aggregate?.aggregate?.count ?? 0;
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Retrieves the total number of blob activities from the blockchain.
|
|
4798
|
+
*
|
|
4799
|
+
* @param params.where (optional) - The where clause to filter the blob activities by.
|
|
4800
|
+
* @returns The total number of blob activities.
|
|
4477
4801
|
*
|
|
4478
|
-
*
|
|
4802
|
+
* @example
|
|
4803
|
+
* ```typescript
|
|
4804
|
+
* const count = await client.getBlobActivitiesCount();
|
|
4479
4805
|
* ```
|
|
4480
4806
|
*/
|
|
4481
|
-
|
|
4482
|
-
|
|
4807
|
+
async getBlobActivitiesCount(params) {
|
|
4808
|
+
const { where } = params;
|
|
4809
|
+
const { blob_activities_aggregate } = await this.indexer.getBlobActivitiesCount({ where });
|
|
4810
|
+
return blob_activities_aggregate?.aggregate?.count ?? 0;
|
|
4483
4811
|
}
|
|
4484
4812
|
/**
|
|
4485
4813
|
* Registers a blob on the blockchain by writing its merkle root and metadata.
|
|
@@ -4533,360 +4861,101 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4533
4861
|
}
|
|
4534
4862
|
/**
|
|
4535
4863
|
* 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
|
-
}
|
|
4736
|
-
}
|
|
4737
|
-
/**
|
|
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.
|
|
4864
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
4741
4865
|
*
|
|
4742
|
-
* @param params.
|
|
4743
|
-
* @param params.
|
|
4744
|
-
* @param params.
|
|
4866
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
4867
|
+
* @param params.account - The account that will own the blob.
|
|
4868
|
+
* @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
4869
|
+
* @param params.blobSize - The size of the blob in bytes.
|
|
4870
|
+
* @param params.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
|
|
4871
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
4872
|
+
* @param params.numChunksets - The total number of chunksets in the blob.
|
|
4745
4873
|
*
|
|
4746
|
-
* @
|
|
4747
|
-
* ```typescript
|
|
4748
|
-
* const blobData = new TextEncoder().encode("Hello, world!");
|
|
4874
|
+
* @returns An Aptos transaction payload data object for the register_blob Move function.
|
|
4749
4875
|
*
|
|
4750
|
-
*
|
|
4751
|
-
* account: AccountAddress.from("0x1"),
|
|
4752
|
-
* blobName: "greetings/hello.txt",
|
|
4753
|
-
* blobData
|
|
4754
|
-
* });
|
|
4755
|
-
* ```
|
|
4876
|
+
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
4756
4877
|
*/
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4878
|
+
static createRegisterBlobPayload(params) {
|
|
4879
|
+
return {
|
|
4880
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_blob`,
|
|
4881
|
+
functionArguments: [
|
|
4882
|
+
params.blobName,
|
|
4883
|
+
params.expirationMicros,
|
|
4884
|
+
MoveVector.U8(params.blobMerkleRoot),
|
|
4885
|
+
params.numChunksets,
|
|
4886
|
+
params.blobSize,
|
|
4887
|
+
// TODO
|
|
4888
|
+
0,
|
|
4889
|
+
// payment tier
|
|
4890
|
+
0
|
|
4891
|
+
// encoding
|
|
4892
|
+
]
|
|
4893
|
+
};
|
|
4764
4894
|
}
|
|
4765
|
-
// FIXME make this possible to stream in put ^^^
|
|
4766
4895
|
/**
|
|
4767
|
-
*
|
|
4768
|
-
*
|
|
4896
|
+
* Creates a transaction payload to register multiple blobs on the blockchain.
|
|
4897
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
4769
4898
|
*
|
|
4770
|
-
* @param params.
|
|
4771
|
-
* @param params.
|
|
4772
|
-
* @param params.
|
|
4773
|
-
* @param params.
|
|
4774
|
-
* @param params.
|
|
4899
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
4900
|
+
* @param params.account - The account that will own the blobs.
|
|
4901
|
+
* @param params.expirationMicros - The expiration time of the blobs in microseconds.
|
|
4902
|
+
* @param params.blobs - The blobs to register.
|
|
4903
|
+
* @param params.blobs.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
4904
|
+
* @param params.blobs.blobSize - The size of the blob in bytes.
|
|
4905
|
+
* @param params.blobs.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
|
|
4906
|
+
* @param params.blobs.numChunksets - The total number of chunksets in the blob.
|
|
4775
4907
|
*
|
|
4776
|
-
* @returns
|
|
4908
|
+
* @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
|
|
4777
4909
|
*
|
|
4778
|
-
* @
|
|
4910
|
+
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
4911
|
+
*/
|
|
4912
|
+
static createBatchRegisterBlobsPayload(params) {
|
|
4913
|
+
const blobNames = [];
|
|
4914
|
+
const blobMerkleRoots = [];
|
|
4915
|
+
const blobNumChunksets = [];
|
|
4916
|
+
const blobSizes = [];
|
|
4917
|
+
params.blobs.forEach((blob) => {
|
|
4918
|
+
blobNames.push(blob.blobName);
|
|
4919
|
+
blobMerkleRoots.push(MoveVector.U8(blob.blobMerkleRoot));
|
|
4920
|
+
blobNumChunksets.push(blob.numChunksets);
|
|
4921
|
+
blobSizes.push(blob.blobSize);
|
|
4922
|
+
});
|
|
4923
|
+
return {
|
|
4924
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::register_multiple_blobs`,
|
|
4925
|
+
functionArguments: [
|
|
4926
|
+
blobNames,
|
|
4927
|
+
params.expirationMicros,
|
|
4928
|
+
blobMerkleRoots,
|
|
4929
|
+
blobNumChunksets,
|
|
4930
|
+
blobSizes,
|
|
4931
|
+
// TODO
|
|
4932
|
+
0,
|
|
4933
|
+
0
|
|
4934
|
+
]
|
|
4935
|
+
};
|
|
4936
|
+
}
|
|
4937
|
+
/**
|
|
4938
|
+
* Creates a transaction payload to delete a blob on the blockchain.
|
|
4939
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
4779
4940
|
*
|
|
4780
|
-
* @
|
|
4781
|
-
*
|
|
4782
|
-
* // Download entire blob
|
|
4783
|
-
* const blob = await client.getBlob({
|
|
4784
|
-
* account: AccountAddress.from("0x1"),
|
|
4785
|
-
* blobName: "documents/report.pdf"
|
|
4786
|
-
* });
|
|
4941
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
4942
|
+
* @param params.blobNameSuffix - The blob name suffix (e.g. "bar.txt").
|
|
4787
4943
|
*
|
|
4788
|
-
*
|
|
4789
|
-
*
|
|
4790
|
-
*
|
|
4791
|
-
* blobName: "large-file.bin",
|
|
4792
|
-
* range: { start: 100, end: 199 }
|
|
4793
|
-
* });
|
|
4794
|
-
* ```
|
|
4944
|
+
* @returns An Aptos transaction payload data object for the delete_blob Move function.
|
|
4945
|
+
*
|
|
4946
|
+
* @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
|
|
4795
4947
|
*/
|
|
4796
|
-
|
|
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
|
-
}
|
|
4877
|
-
});
|
|
4948
|
+
static createDeleteBlobPayload(params) {
|
|
4878
4949
|
return {
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
readable: validatingStream,
|
|
4882
|
-
contentLength: expectedContentLength
|
|
4950
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::delete_blob`,
|
|
4951
|
+
functionArguments: [params.blobNameSuffix]
|
|
4883
4952
|
};
|
|
4884
4953
|
}
|
|
4885
4954
|
};
|
|
4886
4955
|
|
|
4887
|
-
// ../../packages/sdk/dist/chunk-
|
|
4956
|
+
// ../../packages/sdk/dist/chunk-TPGMXZRD.mjs
|
|
4888
4957
|
import {
|
|
4889
|
-
Aptos as
|
|
4958
|
+
Aptos as Aptos3
|
|
4890
4959
|
} from "@aptos-labs/ts-sdk";
|
|
4891
4960
|
var ShelbyClient = class {
|
|
4892
4961
|
/**
|
|
@@ -4943,7 +5012,7 @@ var ShelbyClient = class {
|
|
|
4943
5012
|
*/
|
|
4944
5013
|
constructor(config, provider) {
|
|
4945
5014
|
this.config = config;
|
|
4946
|
-
this.aptos = new
|
|
5015
|
+
this.aptos = new Aptos3(getAptosConfig(config));
|
|
4947
5016
|
this.coordination = new ShelbyBlobClient(config);
|
|
4948
5017
|
this.rpc = new ShelbyRPCClient(config);
|
|
4949
5018
|
this._provider = provider;
|
|
@@ -5039,16 +5108,29 @@ var ShelbyClient = class {
|
|
|
5039
5108
|
}
|
|
5040
5109
|
};
|
|
5041
5110
|
|
|
5042
|
-
// ../../packages/sdk/dist/chunk-
|
|
5111
|
+
// ../../packages/sdk/dist/chunk-2DD6Q5CK.mjs
|
|
5043
5112
|
var ShelbyNodeClient = class extends ShelbyClient {
|
|
5044
5113
|
};
|
|
5045
5114
|
|
|
5046
|
-
// ../../packages/sdk/dist/chunk-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
}
|
|
5115
|
+
// ../../packages/sdk/dist/chunk-7P6ASYW6.mjs
|
|
5116
|
+
var __defProp = Object.defineProperty;
|
|
5117
|
+
var __export = (target, all) => {
|
|
5118
|
+
for (var name in all)
|
|
5119
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5120
|
+
};
|
|
5121
|
+
|
|
5122
|
+
// ../../packages/sdk/dist/chunk-A4IG6GSE.mjs
|
|
5123
|
+
var testUtil_exports = {};
|
|
5124
|
+
__export(testUtil_exports, {
|
|
5125
|
+
makeChunk: () => makeChunk
|
|
5126
|
+
});
|
|
5127
|
+
function makeChunk(n) {
|
|
5128
|
+
const c = Buffer.alloc(n);
|
|
5129
|
+
for (let i = 0; i < n; ++i) {
|
|
5130
|
+
c[i] = i % 256;
|
|
5131
|
+
}
|
|
5132
|
+
return c;
|
|
5133
|
+
}
|
|
5052
5134
|
|
|
5053
5135
|
// ../../packages/sdk/dist/chunk-VRLIOKWG.mjs
|
|
5054
5136
|
import { Network as Network4 } from "@aptos-labs/ts-sdk";
|
|
@@ -5078,26 +5160,6 @@ function getShelbyAccountExplorerUrl(network, accountAddress) {
|
|
|
5078
5160
|
return `${baseUrl}/account/${accountAddress}`;
|
|
5079
5161
|
}
|
|
5080
5162
|
|
|
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
5163
|
// ../../packages/sdk/dist/chunk-C6RQ3AEU.mjs
|
|
5102
5164
|
import { Network as Network6 } from "@aptos-labs/ts-sdk";
|
|
5103
5165
|
function getAptosExplorerBaseUrl(network) {
|
|
@@ -5154,6 +5216,7 @@ var DEFAULT_CHUNK_SIZE_BYTES2 = 2 * 1024 * 1024;
|
|
|
5154
5216
|
|
|
5155
5217
|
// src/commands/account.tsx
|
|
5156
5218
|
import chalk from "chalk";
|
|
5219
|
+
import { filesize } from "filesize";
|
|
5157
5220
|
import { render } from "ink";
|
|
5158
5221
|
import { z as z9 } from "zod";
|
|
5159
5222
|
|
|
@@ -6266,6 +6329,9 @@ function getErasureCodingProvider() {
|
|
|
6266
6329
|
|
|
6267
6330
|
// src/utils/paths.ts
|
|
6268
6331
|
import path2 from "path";
|
|
6332
|
+
function endsWithDirectorySeparator(filePath) {
|
|
6333
|
+
return filePath.endsWith("/") || filePath.endsWith("\\") || filePath.endsWith(path2.sep);
|
|
6334
|
+
}
|
|
6269
6335
|
function normBlobName(pathModule, inputDirectoryName, filename, blobPrefix) {
|
|
6270
6336
|
const blobNameWithoutInputDir = pathModule.relative(
|
|
6271
6337
|
inputDirectoryName,
|
|
@@ -7029,10 +7095,10 @@ function askQuestion(question) {
|
|
|
7029
7095
|
input: process.stdin,
|
|
7030
7096
|
output: process.stdout
|
|
7031
7097
|
});
|
|
7032
|
-
return new Promise((
|
|
7098
|
+
return new Promise((resolve3) => {
|
|
7033
7099
|
rl.question(question, (answer) => {
|
|
7034
7100
|
rl.close();
|
|
7035
|
-
|
|
7101
|
+
resolve3(answer.trim());
|
|
7036
7102
|
});
|
|
7037
7103
|
});
|
|
7038
7104
|
}
|
|
@@ -7235,25 +7301,33 @@ function accountCommand(program) {
|
|
|
7235
7301
|
});
|
|
7236
7302
|
const activeAccount = getCurrentAccount(config, accountName).account;
|
|
7237
7303
|
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
7238
|
-
console.log(
|
|
7239
|
-
|
|
7304
|
+
console.log(`\u{1F50D} Retrieving blobs for ${accountName}`);
|
|
7305
|
+
console.log(`\u{1F464} Address: ${activeAccount.accountAddress.toString()}`);
|
|
7306
|
+
const shelbyExplorerUrl = getShelbyAccountExplorerUrl(
|
|
7307
|
+
shelbyConfig.network,
|
|
7308
|
+
activeAccount.accountAddress.toString()
|
|
7240
7309
|
);
|
|
7310
|
+
console.log(`\u{1F5C2}\uFE0F Shelby Explorer: ${shelbyExplorerUrl}`);
|
|
7241
7311
|
const blobs = await shelbyClient.coordination.getBlobs({
|
|
7242
7312
|
where: {
|
|
7243
7313
|
owner: { _eq: activeAccount.accountAddress.toString() }
|
|
7244
7314
|
}
|
|
7245
7315
|
});
|
|
7246
7316
|
console.log(
|
|
7247
|
-
|
|
7317
|
+
`
|
|
7318
|
+
\u2705 Retrieved ${blobs.length} blob${blobs.length === 1 ? "" : "s"}`
|
|
7248
7319
|
);
|
|
7320
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
7249
7321
|
if (blobs.length === 0) {
|
|
7250
7322
|
console.log("No blobs found.");
|
|
7323
|
+
console.log("\u2728 Done!");
|
|
7251
7324
|
return;
|
|
7252
7325
|
}
|
|
7326
|
+
console.log("\u{1F4E6} Stored Blobs");
|
|
7253
7327
|
const { table, wrapCell } = createResponsiveTable({
|
|
7254
7328
|
columns: [
|
|
7255
7329
|
{ header: "Name", width: 30, flex: true },
|
|
7256
|
-
{ header: "Size
|
|
7330
|
+
{ header: "Size", width: 12 },
|
|
7257
7331
|
{ header: "Expires", width: 25 }
|
|
7258
7332
|
],
|
|
7259
7333
|
padding: 12
|
|
@@ -7262,14 +7336,20 @@ function accountCommand(program) {
|
|
|
7262
7336
|
const expirationDate = new Date(blob.expirationMicros / 1e3);
|
|
7263
7337
|
const blobNameMatch = blob.name.match(/@[^/]+\/(.+)/);
|
|
7264
7338
|
const displayName = blobNameMatch ? blobNameMatch[1] : blob.name;
|
|
7265
|
-
const
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7339
|
+
const formattedSize = filesize(blob.size, { standard: "jedec" });
|
|
7340
|
+
const formattedDate = expirationDate.toLocaleDateString("en-US", {
|
|
7341
|
+
month: "short",
|
|
7342
|
+
day: "numeric",
|
|
7343
|
+
year: "numeric",
|
|
7344
|
+
hour: "numeric",
|
|
7345
|
+
minute: "2-digit",
|
|
7346
|
+
hour12: true
|
|
7347
|
+
});
|
|
7348
|
+
const row = [displayName, formattedSize, formattedDate];
|
|
7270
7349
|
table.push(row.map((cell, i) => wrapCell(cell, i)));
|
|
7271
7350
|
}
|
|
7272
7351
|
console.log(table.toString());
|
|
7352
|
+
console.log("\u2728 Done!");
|
|
7273
7353
|
} catch (error) {
|
|
7274
7354
|
let displayMessage;
|
|
7275
7355
|
if (error instanceof Error && error.message.includes("GraphQL Error (Code: 401)")) {
|
|
@@ -7776,52 +7856,99 @@ import { pipeline } from "stream/promises";
|
|
|
7776
7856
|
import ora from "ora";
|
|
7777
7857
|
import { z as z13 } from "zod";
|
|
7778
7858
|
var denormBlobName2 = (a, b, c) => denormBlobName(path3, a, b, c);
|
|
7779
|
-
var endsWithDirectorySeparator = (filePath) => {
|
|
7780
|
-
return filePath.endsWith("/") || filePath.endsWith("\\") || filePath.endsWith(path3.sep);
|
|
7781
|
-
};
|
|
7782
7859
|
var DownloadOptionsSchema = z13.object({
|
|
7783
|
-
|
|
7784
|
-
|
|
7860
|
+
source: z13.string({
|
|
7861
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing source blob name or prefix (first argument)\n\n\u{1F4A1} Usage:\n shelby download <source-blob-name> <destination-path> [options]\n\n\u{1F4DD} Examples:\n shelby download my-blob.txt ./myfile.txt\n shelby download my-folder/ ./my-folder/ -r\n"
|
|
7862
|
+
}).min(1, "Source blob name or directory prefix is required").describe("Blob name or directory prefix to download"),
|
|
7863
|
+
destination: z13.string({
|
|
7864
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing destination path (second argument)\n\n\u{1F4A1} Usage:\n shelby download <source-blob-name> <destination-path> [options]\n\n\u{1F4DD} Example:\n shelby download my-blob.txt ./myfile.txt\n"
|
|
7865
|
+
}).min(1, "`destination` must be a valid filepath").describe("Local path where to save the downloaded content"),
|
|
7785
7866
|
recursive: z13.boolean().default(false).describe("Download assuming canonical directory layout and recurse"),
|
|
7786
|
-
force: z13.boolean().default(false).describe("Overwrite the
|
|
7867
|
+
force: z13.boolean().default(false).describe("Overwrite the destination if it already exists")
|
|
7787
7868
|
}).refine(
|
|
7788
7869
|
(data) => {
|
|
7789
7870
|
if (data.recursive) {
|
|
7790
|
-
if (!data.
|
|
7871
|
+
if (!data.source.endsWith("/") || !endsWithDirectorySeparator(data.destination))
|
|
7791
7872
|
return false;
|
|
7792
7873
|
return true;
|
|
7793
7874
|
}
|
|
7794
7875
|
return true;
|
|
7795
7876
|
},
|
|
7796
|
-
{
|
|
7797
|
-
message:
|
|
7798
|
-
|
|
7877
|
+
(data) => ({
|
|
7878
|
+
message: `
|
|
7879
|
+
\u274C Invalid Download Options
|
|
7880
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7881
|
+
|
|
7882
|
+
\u26A0\uFE0F Recursive downloads require:
|
|
7883
|
+
- The source prefix must end with '/'
|
|
7884
|
+
- The destination path must end with '/' or '\\'
|
|
7885
|
+
|
|
7886
|
+
\u{1F9FE} Received:
|
|
7887
|
+
Source: ${data.source}
|
|
7888
|
+
Destination: ${data.destination}
|
|
7889
|
+
|
|
7890
|
+
\u{1F4A1} Tip: Add trailing slashes to both paths:
|
|
7891
|
+
\u{1F449} shelby download --recursive ${data.source.endsWith("/") ? data.source : `${data.source}/`} ${endsWithDirectorySeparator(data.destination) ? data.destination : `${data.destination}/`}
|
|
7892
|
+
`
|
|
7893
|
+
})
|
|
7799
7894
|
).refine(
|
|
7800
7895
|
(data) => {
|
|
7801
7896
|
if (!data.recursive) {
|
|
7802
|
-
if (!BlobNameSchema.safeParse(data.
|
|
7803
|
-
if (endsWithDirectorySeparator(data.
|
|
7897
|
+
if (!BlobNameSchema.safeParse(data.source).success) return false;
|
|
7898
|
+
if (endsWithDirectorySeparator(data.destination)) return false;
|
|
7804
7899
|
return true;
|
|
7805
7900
|
}
|
|
7806
7901
|
return true;
|
|
7807
7902
|
},
|
|
7808
|
-
{
|
|
7809
|
-
|
|
7903
|
+
(data) => {
|
|
7904
|
+
const sourceEndsWithSlash = data.source.endsWith("/") || data.source.endsWith("\\");
|
|
7905
|
+
const destEndsWithSlash = endsWithDirectorySeparator(data.destination);
|
|
7906
|
+
let tipMessage = "";
|
|
7907
|
+
if (sourceEndsWithSlash && destEndsWithSlash) {
|
|
7908
|
+
tipMessage = `\u{1F4A1} Tip: For downloading a directory, use the --recursive flag:
|
|
7909
|
+
\u{1F449} shelby download --recursive ${data.source} ${data.destination}`;
|
|
7910
|
+
} else if (sourceEndsWithSlash && !destEndsWithSlash) {
|
|
7911
|
+
tipMessage = `\u{1F4A1} Tip: For downloading a directory, both paths need trailing slashes:
|
|
7912
|
+
\u{1F449} shelby download --recursive ${data.source} ${data.destination}/`;
|
|
7913
|
+
} else if (!sourceEndsWithSlash && destEndsWithSlash) {
|
|
7914
|
+
tipMessage = `\u{1F4A1} Choose one of the following:
|
|
7915
|
+
|
|
7916
|
+
To download a single blob (remove trailing slash from destination):
|
|
7917
|
+
\u{1F449} shelby download ${data.source} ${data.destination.replace(/[\\/]+$/, "")}
|
|
7918
|
+
|
|
7919
|
+
To download a directory recursively (add trailing slash to source):
|
|
7920
|
+
\u{1F449} shelby download --recursive ${data.source}/ ${data.destination}`;
|
|
7921
|
+
}
|
|
7922
|
+
return {
|
|
7923
|
+
message: `
|
|
7924
|
+
\u274C Invalid Download Options
|
|
7925
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7926
|
+
|
|
7927
|
+
\u26A0\uFE0F Blob downloads (non-recursive) require:
|
|
7928
|
+
- The source blob name must NOT end with '/' or '\\'
|
|
7929
|
+
- The destination path must NOT end with '/' or '\\'
|
|
7930
|
+
|
|
7931
|
+
\u{1F9FE} Received:
|
|
7932
|
+
Source: ${data.source}
|
|
7933
|
+
Destination: ${data.destination}
|
|
7934
|
+
|
|
7935
|
+
` + tipMessage + "\n"
|
|
7936
|
+
};
|
|
7810
7937
|
}
|
|
7811
7938
|
);
|
|
7812
7939
|
async function validateOutput(options) {
|
|
7813
|
-
const parentDir = path3.dirname(options.
|
|
7940
|
+
const parentDir = path3.dirname(options.destination);
|
|
7814
7941
|
try {
|
|
7815
7942
|
const parentStats = await fs3.stat(parentDir);
|
|
7816
7943
|
if (!parentStats.isDirectory()) {
|
|
7817
7944
|
throw new Error(
|
|
7818
|
-
`Parent path '${parentDir}' exists but is not a directory. Cannot create
|
|
7945
|
+
`Parent path '${parentDir}' exists but is not a directory. Cannot create destination ${options.recursive ? "directory" : "file"} '${options.destination}'.`
|
|
7819
7946
|
);
|
|
7820
7947
|
}
|
|
7821
7948
|
} catch (error) {
|
|
7822
7949
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7823
7950
|
throw new Error(
|
|
7824
|
-
`Parent directory '${parentDir}' of
|
|
7951
|
+
`Parent directory '${parentDir}' of destination '${options.destination}' does not exist. Create it first or use a different destination path.`
|
|
7825
7952
|
);
|
|
7826
7953
|
}
|
|
7827
7954
|
throw error;
|
|
@@ -7829,7 +7956,7 @@ async function validateOutput(options) {
|
|
|
7829
7956
|
if (options.force) return;
|
|
7830
7957
|
let outputStats;
|
|
7831
7958
|
try {
|
|
7832
|
-
outputStats = await fs3.stat(options.
|
|
7959
|
+
outputStats = await fs3.stat(options.destination);
|
|
7833
7960
|
} catch (error) {
|
|
7834
7961
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7835
7962
|
return;
|
|
@@ -7839,61 +7966,70 @@ async function validateOutput(options) {
|
|
|
7839
7966
|
const isDirectory = outputStats.isDirectory();
|
|
7840
7967
|
if (!options.recursive && isDirectory) {
|
|
7841
7968
|
throw new Error(
|
|
7842
|
-
`
|
|
7969
|
+
`destination path '${options.destination}' is a directory but expected a file path to download a single blob (--recursive not provided).`
|
|
7843
7970
|
);
|
|
7844
7971
|
}
|
|
7845
7972
|
if (options.recursive && !isDirectory) {
|
|
7846
7973
|
throw new Error(
|
|
7847
|
-
`
|
|
7974
|
+
`destination path '${options.destination}' exists but is not a directory. Directory downloads require a directory path.`
|
|
7848
7975
|
);
|
|
7849
7976
|
}
|
|
7850
7977
|
if (options.recursive) {
|
|
7851
|
-
const entries = await fs3.readdir(options.
|
|
7978
|
+
const entries = await fs3.readdir(options.destination);
|
|
7852
7979
|
if (entries.length > 0) {
|
|
7853
7980
|
throw new Error(
|
|
7854
|
-
`Directory '${options.
|
|
7981
|
+
`Directory '${options.destination}' exists and is not empty. Use --force to overwrite or choose an empty directory.`
|
|
7855
7982
|
);
|
|
7856
7983
|
}
|
|
7857
7984
|
} else {
|
|
7858
7985
|
throw new Error(
|
|
7859
|
-
`File '${options.
|
|
7986
|
+
`File '${options.destination}' already exists. Use --force to overwrite.`
|
|
7860
7987
|
);
|
|
7861
7988
|
}
|
|
7862
7989
|
}
|
|
7863
7990
|
async function createFileList(options, nodeClient, account) {
|
|
7864
7991
|
const matchingBlobList = [];
|
|
7992
|
+
const stripAccountPrefix = (fullName) => {
|
|
7993
|
+
const blobNameMatch = fullName.match(/^@[^/]+\/(.+)$/);
|
|
7994
|
+
return blobNameMatch ? blobNameMatch[1] : fullName;
|
|
7995
|
+
};
|
|
7865
7996
|
if (options.recursive) {
|
|
7866
|
-
const
|
|
7997
|
+
const activeBlobs = await nodeClient.coordination.getAccountBlobs({
|
|
7867
7998
|
account
|
|
7868
7999
|
});
|
|
7869
|
-
for (const blobMd of
|
|
7870
|
-
const blobNameWithoutAccount = blobMd.name
|
|
7871
|
-
if (!blobNameWithoutAccount.startsWith(options.
|
|
8000
|
+
for (const blobMd of activeBlobs) {
|
|
8001
|
+
const blobNameWithoutAccount = stripAccountPrefix(blobMd.name);
|
|
8002
|
+
if (!blobNameWithoutAccount.startsWith(options.source)) continue;
|
|
7872
8003
|
matchingBlobList.push(blobMd);
|
|
7873
8004
|
}
|
|
7874
8005
|
if (matchingBlobList.length === 0) {
|
|
7875
8006
|
throw new Error(
|
|
7876
|
-
`No blobs matching prefix ${options.
|
|
8007
|
+
`No active blobs matching prefix '${options.source}' were found for account ${account.toString()}. They may have expired or been deleted.`
|
|
7877
8008
|
);
|
|
7878
8009
|
}
|
|
7879
8010
|
} else {
|
|
7880
|
-
const
|
|
7881
|
-
account
|
|
7882
|
-
name: options.src
|
|
8011
|
+
const activeBlobs = await nodeClient.coordination.getAccountBlobs({
|
|
8012
|
+
account
|
|
7883
8013
|
});
|
|
7884
|
-
|
|
7885
|
-
|
|
8014
|
+
const md = activeBlobs.find(
|
|
8015
|
+
(blob) => stripAccountPrefix(blob.name) === options.source
|
|
8016
|
+
);
|
|
8017
|
+
if (!md) {
|
|
8018
|
+
throw new Error(
|
|
8019
|
+
`No active blob named '${options.source}' was found for account ${account.toString()}. It may have expired or been deleted.
|
|
8020
|
+
`
|
|
8021
|
+
);
|
|
7886
8022
|
}
|
|
7887
8023
|
matchingBlobList.push(md);
|
|
7888
8024
|
}
|
|
7889
8025
|
const ret = [];
|
|
7890
8026
|
for (const blobMd of matchingBlobList) {
|
|
7891
|
-
const blobNameWithoutAccount = blobMd.name
|
|
8027
|
+
const blobNameWithoutAccount = stripAccountPrefix(blobMd.name);
|
|
7892
8028
|
const entry = {
|
|
7893
8029
|
filename: denormBlobName2(
|
|
7894
|
-
options.
|
|
8030
|
+
options.source,
|
|
7895
8031
|
blobNameWithoutAccount,
|
|
7896
|
-
options.
|
|
8032
|
+
options.destination
|
|
7897
8033
|
),
|
|
7898
8034
|
blobname: blobNameWithoutAccount,
|
|
7899
8035
|
// FIXME this is nasty
|
|
@@ -7935,96 +8071,170 @@ function createProgressTransform(totalBytes, reporter) {
|
|
|
7935
8071
|
});
|
|
7936
8072
|
}
|
|
7937
8073
|
function downloadCommand(program) {
|
|
7938
|
-
program.command("download
|
|
8074
|
+
program.command("download").description("Download a file or directory from Shelby RPC").argument("[source]", "Source blob name or prefix").argument("[destination]", "Destination path").option(
|
|
7939
8075
|
"-r, --recursive",
|
|
7940
8076
|
"Download assuming canonical directory layout using '/' as separators. Produces a directory."
|
|
7941
|
-
).option("-f, --force", "Overwrite the
|
|
7942
|
-
async (
|
|
7943
|
-
|
|
7944
|
-
...rawOptions,
|
|
7945
|
-
src,
|
|
7946
|
-
dst
|
|
7947
|
-
});
|
|
7948
|
-
options.dst = path3.resolve(options.dst);
|
|
7949
|
-
await validateOutput(options);
|
|
7950
|
-
if (options.recursive) {
|
|
7951
|
-
console.log(
|
|
7952
|
-
`Downloading directory: ${options.src} -> ${options.dst}`
|
|
7953
|
-
);
|
|
7954
|
-
} else {
|
|
7955
|
-
console.log(`Downloading blob: ${options.src} -> ${options.dst}`);
|
|
7956
|
-
}
|
|
7957
|
-
const configPath = program.opts().configFile;
|
|
7958
|
-
let config;
|
|
8077
|
+
).option("-f, --force", "Overwrite the destination").action(
|
|
8078
|
+
async (source, destination, rawOptions) => {
|
|
8079
|
+
let options;
|
|
7959
8080
|
try {
|
|
7960
|
-
|
|
8081
|
+
options = DownloadOptionsSchema.parse({
|
|
8082
|
+
...rawOptions,
|
|
8083
|
+
source,
|
|
8084
|
+
destination
|
|
8085
|
+
});
|
|
7961
8086
|
} catch (error) {
|
|
7962
|
-
|
|
8087
|
+
if (error instanceof z13.ZodError) {
|
|
8088
|
+
const firstIssue = error.issues[0];
|
|
8089
|
+
if (firstIssue) {
|
|
8090
|
+
console.log(firstIssue.message);
|
|
8091
|
+
} else {
|
|
8092
|
+
console.error("\u26A0\uFE0F Invalid download options provided");
|
|
8093
|
+
}
|
|
8094
|
+
} else {
|
|
8095
|
+
console.error(
|
|
8096
|
+
`\u26A0\uFE0F ${error instanceof Error ? error.message : String(error)}`
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
7963
8099
|
process.exit(1);
|
|
7964
8100
|
}
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
const activeAccount = getCurrentAccount(
|
|
7969
|
-
config,
|
|
7970
|
-
program.opts().account
|
|
7971
|
-
).account;
|
|
7972
|
-
const nodeClient = new ShelbyNodeClient(shelbyConfig);
|
|
7973
|
-
const fileList = await createFileList(
|
|
7974
|
-
options,
|
|
7975
|
-
nodeClient,
|
|
7976
|
-
activeAccount.accountAddress
|
|
7977
|
-
);
|
|
7978
|
-
for (const fileEntry of fileList) {
|
|
7979
|
-
console.log(
|
|
7980
|
-
`Downloading ${fileEntry.blobname} -> ${fileEntry.filename}`
|
|
7981
|
-
);
|
|
7982
|
-
}
|
|
7983
|
-
if (options.force) {
|
|
7984
|
-
console.log(`--force was set, so deleting ${options.dst}`);
|
|
7985
|
-
await fs3.rm(options.dst, { recursive: true, force: true });
|
|
7986
|
-
}
|
|
8101
|
+
options.destination = path3.resolve(options.destination);
|
|
8102
|
+
let config;
|
|
8103
|
+
let spinner;
|
|
7987
8104
|
const handleSigint = () => {
|
|
7988
|
-
spinner
|
|
8105
|
+
spinner?.fail("Quitting due to ctrl-C / SIGINT...");
|
|
7989
8106
|
process.exit(1);
|
|
7990
8107
|
};
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
}).start();
|
|
7997
|
-
await createOutputDirectories(fileList);
|
|
7998
|
-
let totalSize = 0;
|
|
7999
|
-
for (const fileEntry of fileList) totalSize += fileEntry.sizeBytes;
|
|
8000
|
-
const startTime = performance.now();
|
|
8001
|
-
let amountDownloaded = 0;
|
|
8002
|
-
const formatProgressPercent = () => (100 * (amountDownloaded / totalSize)).toFixed(2);
|
|
8003
|
-
const formatProgressRate = () => {
|
|
8004
|
-
const mib = amountDownloaded / 1024 / 1024;
|
|
8005
|
-
const sec = (performance.now() - startTime) / 1e3;
|
|
8006
|
-
return (mib / sec).toFixed(2);
|
|
8007
|
-
};
|
|
8008
|
-
for (const fileEntry of fileList) {
|
|
8009
|
-
const out = fsS.createWriteStream(fileEntry.filename);
|
|
8010
|
-
const { readable } = await nodeClient.rpc.getBlob({
|
|
8011
|
-
account: activeAccount.accountAddress,
|
|
8012
|
-
blobName: fileEntry.blobname
|
|
8108
|
+
try {
|
|
8109
|
+
const configPath = program.opts().configFile;
|
|
8110
|
+
config = loadConfig(configPath);
|
|
8111
|
+
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8112
|
+
context: program.opts().context
|
|
8013
8113
|
});
|
|
8014
|
-
const
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8114
|
+
const activeAccount = getCurrentAccount(
|
|
8115
|
+
config,
|
|
8116
|
+
program.opts().account
|
|
8117
|
+
).account;
|
|
8118
|
+
const nodeClient = new ShelbyNodeClient(shelbyConfig);
|
|
8119
|
+
const fileList = await createFileList(
|
|
8120
|
+
options,
|
|
8121
|
+
nodeClient,
|
|
8122
|
+
activeAccount.accountAddress
|
|
8123
|
+
);
|
|
8124
|
+
await validateOutput(options);
|
|
8125
|
+
console.log(`
|
|
8126
|
+
\u{1F4E5} Downloading Files (${fileList.length} total)`);
|
|
8127
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8128
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
8129
|
+
const fileEntry = fileList[i];
|
|
8130
|
+
const blobBaseName = path3.basename(fileEntry.blobname);
|
|
8131
|
+
console.log(`${i + 1}. ${blobBaseName} \u2192 ${fileEntry.filename}`);
|
|
8132
|
+
}
|
|
8133
|
+
console.log();
|
|
8134
|
+
if (options.force) {
|
|
8135
|
+
console.log(`--force was set, so deleting ${options.destination}`);
|
|
8136
|
+
await fs3.rm(options.destination, { recursive: true, force: true });
|
|
8137
|
+
}
|
|
8138
|
+
process.removeAllListeners("SIGINT");
|
|
8139
|
+
process.on("SIGINT", handleSigint);
|
|
8140
|
+
spinner = ora({
|
|
8141
|
+
text: "Creating destination directory tree...",
|
|
8142
|
+
discardStdin: false
|
|
8143
|
+
}).start();
|
|
8144
|
+
await createOutputDirectories(fileList);
|
|
8145
|
+
let totalSize = 0;
|
|
8146
|
+
for (const fileEntry of fileList) totalSize += fileEntry.sizeBytes;
|
|
8147
|
+
const startTime = performance.now();
|
|
8148
|
+
let amountDownloaded = 0;
|
|
8149
|
+
let filesDownloaded = 0;
|
|
8150
|
+
const isSingleFile = fileList.length === 1;
|
|
8151
|
+
const formatProgressPercent = () => totalSize > 0 ? (100 * (amountDownloaded / totalSize)).toFixed(2) : "0.00";
|
|
8152
|
+
for (const fileEntry of fileList) {
|
|
8153
|
+
const out = fsS.createWriteStream(fileEntry.filename);
|
|
8154
|
+
const fileName = path3.basename(fileEntry.blobname);
|
|
8155
|
+
const { readable } = await nodeClient.rpc.getBlob({
|
|
8156
|
+
account: activeAccount.accountAddress,
|
|
8157
|
+
blobName: fileEntry.blobname
|
|
8158
|
+
});
|
|
8159
|
+
const reporter = (filePercent, _rateMiB) => {
|
|
8160
|
+
if (spinner) {
|
|
8161
|
+
if (isSingleFile) {
|
|
8162
|
+
spinner.text = `Downloading ${fileName}... (${filePercent}%)`;
|
|
8163
|
+
} else {
|
|
8164
|
+
spinner.text = `Downloading ${fileName}... (${filesDownloaded}/${fileList.length} files, ${formatProgressPercent()}%)`;
|
|
8165
|
+
}
|
|
8166
|
+
}
|
|
8167
|
+
};
|
|
8168
|
+
const nodeStream = Readable2.fromWeb(readable);
|
|
8169
|
+
await pipeline(
|
|
8170
|
+
nodeStream,
|
|
8171
|
+
createProgressTransform(fileEntry.sizeBytes, reporter),
|
|
8172
|
+
out
|
|
8173
|
+
);
|
|
8174
|
+
amountDownloaded += fileEntry.sizeBytes;
|
|
8175
|
+
filesDownloaded++;
|
|
8176
|
+
}
|
|
8177
|
+
const totalTime = (performance.now() - startTime) / 1e3;
|
|
8178
|
+
spinner?.stop();
|
|
8179
|
+
console.log(
|
|
8180
|
+
`
|
|
8181
|
+
\u2705 All downloads complete \u2014 took ${totalTime.toFixed(1)} seconds`
|
|
8022
8182
|
);
|
|
8023
|
-
|
|
8183
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8184
|
+
console.log("\u2728 Done!");
|
|
8185
|
+
} catch (error) {
|
|
8186
|
+
spinner?.stop();
|
|
8187
|
+
console.log("\n\u274C Download Failed");
|
|
8188
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8189
|
+
if (options.recursive) {
|
|
8190
|
+
console.log(`\u{1F9FE} Directory: ${options.source}`);
|
|
8191
|
+
} else {
|
|
8192
|
+
console.log(`\u{1F9FE} File: ${options.source}`);
|
|
8193
|
+
}
|
|
8194
|
+
if (config) {
|
|
8195
|
+
const activeAccount = getCurrentAccount(
|
|
8196
|
+
config,
|
|
8197
|
+
program.opts().account
|
|
8198
|
+
).account;
|
|
8199
|
+
console.log(
|
|
8200
|
+
`\u{1F464} Account: ${activeAccount.accountAddress.toString()}`
|
|
8201
|
+
);
|
|
8202
|
+
}
|
|
8203
|
+
console.log();
|
|
8204
|
+
if (error instanceof Error) {
|
|
8205
|
+
if (error.message.includes("GraphQL Error (Code: 401)")) {
|
|
8206
|
+
const contextName = program.opts().context || config?.default_context;
|
|
8207
|
+
console.error("\u26A0\uFE0F Authentication required.");
|
|
8208
|
+
console.error(
|
|
8209
|
+
" Please add an indexer API key to your context configuration."
|
|
8210
|
+
);
|
|
8211
|
+
console.error();
|
|
8212
|
+
console.error("\u{1F4A1} Tip: Update your context with an API key:");
|
|
8213
|
+
console.error(
|
|
8214
|
+
` \u{1F449} shelby context update ${contextName ?? "default"} --indexer-api-key YOUR_KEY`
|
|
8215
|
+
);
|
|
8216
|
+
} else if (error.message.includes("No active blob") || error.message.includes("may have expired or been deleted")) {
|
|
8217
|
+
const blobName = options.source;
|
|
8218
|
+
console.error(
|
|
8219
|
+
`\u26A0\uFE0F No active blob${options.recursive ? "s" : ""} named '${blobName}' ${options.recursive ? "were" : "was"} found.`
|
|
8220
|
+
);
|
|
8221
|
+
console.error(" It may have expired or been deleted.");
|
|
8222
|
+
console.error();
|
|
8223
|
+
console.error(
|
|
8224
|
+
"\u{1F4A1} Tip: Try listing available blobs before downloading:"
|
|
8225
|
+
);
|
|
8226
|
+
console.error(" \u{1F449} shelby account blobs");
|
|
8227
|
+
} else {
|
|
8228
|
+
console.error(`\u26A0\uFE0F ${error.message}`);
|
|
8229
|
+
}
|
|
8230
|
+
} else {
|
|
8231
|
+
console.error(`\u26A0\uFE0F ${String(error)}`);
|
|
8232
|
+
}
|
|
8233
|
+
console.log("\n");
|
|
8234
|
+
process.exit(1);
|
|
8235
|
+
} finally {
|
|
8236
|
+
process.removeListener("SIGINT", handleSigint);
|
|
8024
8237
|
}
|
|
8025
|
-
const totalTime = (performance.now() - startTime) / 1e3;
|
|
8026
|
-
const totalRate = amountDownloaded / 1024 / 1024 / totalTime;
|
|
8027
|
-
spinner.succeed(`Final rate ${totalRate.toFixed(2)} MiB/s`);
|
|
8028
8238
|
}
|
|
8029
8239
|
);
|
|
8030
8240
|
}
|
|
@@ -8246,13 +8456,63 @@ import * as path5 from "path";
|
|
|
8246
8456
|
import { Aptos as Aptos5, AptosConfig as AptosConfig4 } from "@aptos-labs/ts-sdk";
|
|
8247
8457
|
import * as chrono from "chrono-node";
|
|
8248
8458
|
import { glob } from "glob";
|
|
8459
|
+
import ignore from "ignore";
|
|
8249
8460
|
import { Box as Box7, render as render4, Text as Text7 } from "ink";
|
|
8250
8461
|
import SelectInput4 from "ink-select-input";
|
|
8251
8462
|
import ora2 from "ora";
|
|
8252
8463
|
import { z as z15 } from "zod";
|
|
8464
|
+
|
|
8465
|
+
// src/utils/commander-helpers.ts
|
|
8466
|
+
function createExitOverrideHandler(commandName, requiredArgs, requiredOptions, exampleUsage, warningMessage) {
|
|
8467
|
+
return (err) => {
|
|
8468
|
+
if (err.code === "commander.helpDisplayed") {
|
|
8469
|
+
process.exit(0);
|
|
8470
|
+
}
|
|
8471
|
+
const args = process.argv.slice(process.argv.indexOf(commandName) + 1);
|
|
8472
|
+
const hasArguments = args.some((arg) => !arg.startsWith("-"));
|
|
8473
|
+
if (err.code === "commander.missingMandatoryOptionValue") {
|
|
8474
|
+
if (!hasArguments && requiredOptions) {
|
|
8475
|
+
console.error("\n\u274C Missing Required Arguments");
|
|
8476
|
+
console.error("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8477
|
+
const warning = warningMessage || "Missing required arguments or options";
|
|
8478
|
+
console.error(`\u26A0\uFE0F ${warning}
|
|
8479
|
+
`);
|
|
8480
|
+
console.error("\u{1F4A1} Usage:");
|
|
8481
|
+
console.error(
|
|
8482
|
+
` shelby ${commandName} ${requiredArgs} ${requiredOptions}
|
|
8483
|
+
`
|
|
8484
|
+
);
|
|
8485
|
+
if (exampleUsage) {
|
|
8486
|
+
console.error("\u{1F4DD} Examples:");
|
|
8487
|
+
console.error(` ${exampleUsage}`);
|
|
8488
|
+
}
|
|
8489
|
+
console.error("\n");
|
|
8490
|
+
} else {
|
|
8491
|
+
console.error(err.message);
|
|
8492
|
+
}
|
|
8493
|
+
process.exit(1);
|
|
8494
|
+
}
|
|
8495
|
+
if (err.code === "commander.optionMissingArgument") {
|
|
8496
|
+
console.error("\n\u274C Missing Option Value");
|
|
8497
|
+
console.error("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8498
|
+
const cleanMessage = err.message.replace(/^error:\s*/i, "");
|
|
8499
|
+
console.error(`\u26A0\uFE0F ${cleanMessage}
|
|
8500
|
+
`);
|
|
8501
|
+
if (exampleUsage) {
|
|
8502
|
+
console.error("\u{1F4A1} Example:");
|
|
8503
|
+
console.error(` ${exampleUsage}`);
|
|
8504
|
+
}
|
|
8505
|
+
console.error("\n");
|
|
8506
|
+
process.exit(1);
|
|
8507
|
+
}
|
|
8508
|
+
throw err;
|
|
8509
|
+
};
|
|
8510
|
+
}
|
|
8511
|
+
|
|
8512
|
+
// src/commands/upload.tsx
|
|
8253
8513
|
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8254
8514
|
var normBlobName2 = (i, f, b) => normBlobName(path5, i, f, b);
|
|
8255
|
-
var flexibleDateSchema = z15.string().transform((val) => {
|
|
8515
|
+
var flexibleDateSchema = z15.string().transform((val, ctx) => {
|
|
8256
8516
|
const now = /* @__PURE__ */ new Date();
|
|
8257
8517
|
let parsedDate = null;
|
|
8258
8518
|
if (/^\d+$/.test(val)) {
|
|
@@ -8274,77 +8534,207 @@ var flexibleDateSchema = z15.string().transform((val) => {
|
|
|
8274
8534
|
}
|
|
8275
8535
|
}
|
|
8276
8536
|
if (!parsedDate || Number.isNaN(parsedDate.getTime())) {
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8537
|
+
ctx.addIssue({
|
|
8538
|
+
code: z15.ZodIssueCode.custom,
|
|
8539
|
+
message: `
|
|
8540
|
+
\u274C Upload Failed
|
|
8541
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8542
|
+
|
|
8543
|
+
\u26A0\uFE0F Invalid date format: "${val}"
|
|
8544
|
+
|
|
8545
|
+
\u{1F4A1} Try formats like:
|
|
8546
|
+
\u2022 "tomorrow", "in 2 days", "next Friday"
|
|
8547
|
+
\u2022 "2025-12-31"
|
|
8548
|
+
\u2022 UNIX timestamp
|
|
8549
|
+
|
|
8550
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
8551
|
+
});
|
|
8552
|
+
return z15.NEVER;
|
|
8280
8553
|
}
|
|
8281
8554
|
if (parsedDate.getTime() <= now.getTime()) {
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8555
|
+
ctx.addIssue({
|
|
8556
|
+
code: z15.ZodIssueCode.custom,
|
|
8557
|
+
message: `
|
|
8558
|
+
\u274C Upload Failed
|
|
8559
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8560
|
+
|
|
8561
|
+
\u26A0\uFE0F Expiration date must be in the future
|
|
8562
|
+
|
|
8563
|
+
\u{1F4C5} "${val}" resolves to ${parsedDate.toLocaleString()}
|
|
8564
|
+
which is in the past.
|
|
8565
|
+
|
|
8566
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
8567
|
+
});
|
|
8568
|
+
return z15.NEVER;
|
|
8285
8569
|
}
|
|
8286
8570
|
return parsedDate;
|
|
8287
8571
|
});
|
|
8288
8572
|
var UploadOptionsSchema = z15.object({
|
|
8289
|
-
|
|
8290
|
-
|
|
8573
|
+
source: z15.string({
|
|
8574
|
+
required_error: '\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing source file or directory path (first argument)\n\n\u{1F4A1} Usage:\n shelby upload <source-file-or-directory> <destination-blob-name> [options]\n\n\u{1F4DD} Examples:\n shelby upload ./myfile.txt my-blob.txt -e tomorrow\n shelby upload ./my-folder/ my-folder/ -r -e "next week"\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
|
8575
|
+
}).min(1, "Source file or directory path is required"),
|
|
8576
|
+
destination: z15.string({
|
|
8577
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing destination blob name (second argument)\n\n\u{1F4A1} Usage:\n shelby upload <source-file-or-directory> <destination-blob-name> [options]\n\n\u{1F4DD} Example:\n shelby upload ./myfile.txt files/my-blob.txt -e tomorrow\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
8578
|
+
}).min(1, "Destination blob name is required"),
|
|
8291
8579
|
expiration: flexibleDateSchema,
|
|
8292
8580
|
recursive: z15.boolean().optional().default(false),
|
|
8293
8581
|
assumeYes: z15.boolean().optional().default(false),
|
|
8294
8582
|
outputCommitments: z15.string().optional()
|
|
8295
8583
|
}).superRefine(async (data, ctx) => {
|
|
8296
|
-
const stats = await fs5.stat(data.
|
|
8584
|
+
const stats = await fs5.stat(data.source);
|
|
8297
8585
|
if (!stats.isFile() && !stats.isDirectory()) {
|
|
8298
8586
|
ctx.addIssue({
|
|
8299
8587
|
code: z15.ZodIssueCode.custom,
|
|
8300
|
-
message: "
|
|
8301
|
-
path: ["
|
|
8588
|
+
message: "\n\u274C Upload Failed\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Source path must be a file or directory\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
8589
|
+
path: ["source"]
|
|
8302
8590
|
});
|
|
8303
8591
|
return;
|
|
8304
8592
|
}
|
|
8305
8593
|
if (stats.isDirectory()) {
|
|
8306
|
-
if (!data.
|
|
8594
|
+
if (!data.destination.endsWith("/")) {
|
|
8307
8595
|
ctx.addIssue({
|
|
8308
8596
|
code: z15.ZodIssueCode.custom,
|
|
8309
|
-
message:
|
|
8310
|
-
|
|
8597
|
+
message: `
|
|
8598
|
+
\u274C Upload Failed
|
|
8599
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8600
|
+
|
|
8601
|
+
\u26A0\uFE0F When source is a directory, destination must end with '/'
|
|
8602
|
+
|
|
8603
|
+
\u{1F4A1} Tip: When uploading a directory, add a trailing slash:
|
|
8604
|
+
\u{1F449} shelby upload -r ${data.source} ${data.destination}/
|
|
8605
|
+
|
|
8606
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
8607
|
+
path: ["destination"]
|
|
8311
8608
|
});
|
|
8312
8609
|
}
|
|
8313
8610
|
} else if (stats.isFile()) {
|
|
8314
|
-
const blobNameResult = BlobNameSchema.safeParse(data.
|
|
8611
|
+
const blobNameResult = BlobNameSchema.safeParse(data.destination);
|
|
8315
8612
|
if (!blobNameResult.success) {
|
|
8316
8613
|
ctx.addIssue({
|
|
8317
8614
|
code: z15.ZodIssueCode.custom,
|
|
8318
|
-
message:
|
|
8319
|
-
|
|
8615
|
+
message: `
|
|
8616
|
+
\u274C Upload Failed
|
|
8617
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8618
|
+
|
|
8619
|
+
\u26A0\uFE0F When source is a file, destination must be a valid blob name (cannot end with '/')
|
|
8620
|
+
|
|
8621
|
+
\u{1F4A1} Tip: When uploading a file, remove the trailing slash:
|
|
8622
|
+
\u{1F449} shelby upload ${data.source} ${data.destination.replace(/\/$/, "")}
|
|
8623
|
+
|
|
8624
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
8625
|
+
path: ["destination"]
|
|
8320
8626
|
});
|
|
8321
8627
|
}
|
|
8322
8628
|
}
|
|
8323
8629
|
});
|
|
8630
|
+
async function buildGitignoreMap(root) {
|
|
8631
|
+
const resolvedRoot = path5.resolve(root);
|
|
8632
|
+
const gitignoreMap = /* @__PURE__ */ new Map();
|
|
8633
|
+
const ensureMatcher = (dir) => {
|
|
8634
|
+
const existing = gitignoreMap.get(dir);
|
|
8635
|
+
if (existing) {
|
|
8636
|
+
return existing;
|
|
8637
|
+
}
|
|
8638
|
+
const instance = ignore();
|
|
8639
|
+
gitignoreMap.set(dir, instance);
|
|
8640
|
+
return instance;
|
|
8641
|
+
};
|
|
8642
|
+
ensureMatcher(resolvedRoot).add(".git/");
|
|
8643
|
+
const gitignoreFiles = await glob("**/.gitignore", {
|
|
8644
|
+
cwd: resolvedRoot,
|
|
8645
|
+
dot: true,
|
|
8646
|
+
absolute: true,
|
|
8647
|
+
nodir: true
|
|
8648
|
+
});
|
|
8649
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
8650
|
+
try {
|
|
8651
|
+
const contents = await fs5.readFile(gitignorePath, "utf8");
|
|
8652
|
+
const directory = path5.dirname(gitignorePath);
|
|
8653
|
+
ensureMatcher(directory).add(contents);
|
|
8654
|
+
} catch (error) {
|
|
8655
|
+
if (error instanceof Error && Object.hasOwn(error, "code") && // biome-ignore lint/suspicious/noExplicitAny: node fs error type
|
|
8656
|
+
error.code === "ENOENT") {
|
|
8657
|
+
continue;
|
|
8658
|
+
}
|
|
8659
|
+
throw error;
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
return gitignoreMap;
|
|
8663
|
+
}
|
|
8664
|
+
function collectDirectoriesFromRoot(root, filePath) {
|
|
8665
|
+
const directories = [];
|
|
8666
|
+
let current = path5.dirname(filePath);
|
|
8667
|
+
while (true) {
|
|
8668
|
+
directories.push(current);
|
|
8669
|
+
if (current === root) {
|
|
8670
|
+
break;
|
|
8671
|
+
}
|
|
8672
|
+
const parent = path5.dirname(current);
|
|
8673
|
+
if (parent === current) {
|
|
8674
|
+
break;
|
|
8675
|
+
}
|
|
8676
|
+
current = parent;
|
|
8677
|
+
}
|
|
8678
|
+
return directories.reverse();
|
|
8679
|
+
}
|
|
8680
|
+
function shouldIgnorePath(gitignoreMap, root, filePath) {
|
|
8681
|
+
const directories = collectDirectoriesFromRoot(root, filePath);
|
|
8682
|
+
let ignored = false;
|
|
8683
|
+
for (const directory of directories) {
|
|
8684
|
+
const matcher = gitignoreMap.get(directory);
|
|
8685
|
+
if (!matcher) {
|
|
8686
|
+
continue;
|
|
8687
|
+
}
|
|
8688
|
+
const relative2 = path5.relative(directory, filePath).split(path5.sep).join("/");
|
|
8689
|
+
const result = matcher.test(relative2);
|
|
8690
|
+
if (result.ignored) {
|
|
8691
|
+
ignored = true;
|
|
8692
|
+
}
|
|
8693
|
+
if (result.unignored) {
|
|
8694
|
+
ignored = false;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
return ignored;
|
|
8698
|
+
}
|
|
8324
8699
|
async function createFilelist(options) {
|
|
8325
|
-
const stats = await fs5.stat(options.
|
|
8700
|
+
const stats = await fs5.stat(options.source);
|
|
8326
8701
|
if (stats.isFile()) {
|
|
8327
|
-
const blobname = normBlobName2(
|
|
8702
|
+
const blobname = normBlobName2(
|
|
8703
|
+
options.source,
|
|
8704
|
+
options.source,
|
|
8705
|
+
options.destination
|
|
8706
|
+
);
|
|
8328
8707
|
const blobNameValidation = BlobNameSchema.safeParse(blobname);
|
|
8329
8708
|
if (!blobNameValidation.success) {
|
|
8330
8709
|
throw new Error(
|
|
8331
|
-
`File ${options.
|
|
8710
|
+
`File ${options.source} as blobname ${blobname} would form an invalid blob name: ${blobNameValidation.error.message}`
|
|
8332
8711
|
);
|
|
8333
8712
|
}
|
|
8334
|
-
return [{ filename: options.
|
|
8713
|
+
return [{ filename: options.source, blobname, sizeBytes: stats.size }];
|
|
8335
8714
|
}
|
|
8336
8715
|
if (!options.recursive) {
|
|
8337
8716
|
throw new Error(
|
|
8338
|
-
`${options.
|
|
8717
|
+
`${options.source} is a directory. Use --recursive to upload directories.`
|
|
8339
8718
|
);
|
|
8340
8719
|
}
|
|
8720
|
+
const sourceRoot = path5.resolve(options.source);
|
|
8721
|
+
const gitignoreMap = await buildGitignoreMap(sourceRoot);
|
|
8341
8722
|
const fileList = [];
|
|
8342
|
-
const result = await glob(
|
|
8343
|
-
|
|
8723
|
+
const result = await glob("**/*", {
|
|
8724
|
+
cwd: sourceRoot,
|
|
8725
|
+
absolute: true,
|
|
8726
|
+
nodir: true,
|
|
8727
|
+
dot: true
|
|
8344
8728
|
});
|
|
8345
8729
|
for await (const file of result) {
|
|
8730
|
+
if (shouldIgnorePath(gitignoreMap, sourceRoot, file)) {
|
|
8731
|
+
continue;
|
|
8732
|
+
}
|
|
8346
8733
|
const stats2 = await fs5.stat(file);
|
|
8347
|
-
|
|
8734
|
+
if (!stats2.isFile()) {
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
const blobname = normBlobName2(sourceRoot, file, options.destination);
|
|
8348
8738
|
const blobNameValidation = BlobNameSchema.safeParse(blobname);
|
|
8349
8739
|
if (!blobNameValidation.success) {
|
|
8350
8740
|
throw new Error(
|
|
@@ -8360,7 +8750,7 @@ async function createFilelist(options) {
|
|
|
8360
8750
|
return fileList;
|
|
8361
8751
|
}
|
|
8362
8752
|
function uploadCommand(program) {
|
|
8363
|
-
program.command("upload
|
|
8753
|
+
const uploadCmd = program.command("upload").description("Upload a file or directory to the shelby RPC in the config.").argument("[source]", "Source file or directory path").argument("[destination]", "Destination blob name").requiredOption(
|
|
8364
8754
|
"-e, --expiration <datetime>",
|
|
8365
8755
|
'Expiration date/time (required). Examples: "tomorrow", "in 2 days", "next Friday", "2025-12-31", UNIX timestamp'
|
|
8366
8756
|
).option("-r, --recursive", "If uploading a directory, recurse").option(
|
|
@@ -8369,205 +8759,227 @@ function uploadCommand(program) {
|
|
|
8369
8759
|
).option(
|
|
8370
8760
|
"--output-commitments <filename>",
|
|
8371
8761
|
"Location to store commitments computed as as part of the upload"
|
|
8372
|
-
).
|
|
8373
|
-
|
|
8374
|
-
try {
|
|
8375
|
-
validatedOptions = await UploadOptionsSchema.parseAsync({
|
|
8376
|
-
...options,
|
|
8377
|
-
src,
|
|
8378
|
-
dst
|
|
8379
|
-
});
|
|
8380
|
-
} catch (error) {
|
|
8381
|
-
const { displayMessage } = handleError(error);
|
|
8382
|
-
console.error(`\u274C ${displayMessage}`);
|
|
8383
|
-
process.exit(1);
|
|
8384
|
-
}
|
|
8385
|
-
const configPath = program.opts().configFile;
|
|
8386
|
-
let config;
|
|
8387
|
-
try {
|
|
8388
|
-
config = loadConfig(configPath);
|
|
8389
|
-
} catch (error) {
|
|
8390
|
-
console.error(`Error: ${error.message}`);
|
|
8391
|
-
process.exit(1);
|
|
8392
|
-
}
|
|
8393
|
-
const start = performance.now();
|
|
8394
|
-
const filelist = await createFilelist(validatedOptions);
|
|
8395
|
-
const timeToCreateFilelist = ((performance.now() - start) / 1e3).toFixed(
|
|
8396
|
-
5
|
|
8397
|
-
);
|
|
8398
|
-
console.log("\n\u{1F680} Upload Summary");
|
|
8399
|
-
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8400
|
-
if (filelist.length === 1) {
|
|
8401
|
-
console.log(`\u{1F4E6} File: ${filelist[0].filename}`);
|
|
8402
|
-
console.log(`\u{1F4C1} Blob Name: ${filelist[0].blobname}`);
|
|
8403
|
-
} else {
|
|
8404
|
-
console.log(`\u{1F4E6} Files: ${filelist.length} files`);
|
|
8762
|
+
).configureOutput({
|
|
8763
|
+
writeErr: (_str) => {
|
|
8405
8764
|
}
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
/* @__PURE__ */ jsx13(
|
|
8420
|
-
SelectInput4,
|
|
8421
|
-
{
|
|
8422
|
-
items: [
|
|
8423
|
-
{ label: "Yes", value: true },
|
|
8424
|
-
{ label: "No", value: false }
|
|
8425
|
-
],
|
|
8426
|
-
onSelect: (item) => {
|
|
8427
|
-
unmount();
|
|
8428
|
-
resolve2(item.value);
|
|
8429
|
-
}
|
|
8430
|
-
}
|
|
8431
|
-
)
|
|
8432
|
-
] })
|
|
8433
|
-
);
|
|
8434
|
-
});
|
|
8435
|
-
if (!shouldContinue) {
|
|
8436
|
-
console.log("Upload cancelled.");
|
|
8437
|
-
process.exit(0);
|
|
8438
|
-
}
|
|
8439
|
-
}
|
|
8440
|
-
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8441
|
-
context: program.opts().context
|
|
8442
|
-
});
|
|
8443
|
-
const activeAccount = getCurrentAccount(
|
|
8444
|
-
config,
|
|
8445
|
-
program.opts().account
|
|
8446
|
-
).account;
|
|
8447
|
-
const aptos = new Aptos5(new AptosConfig4(shelbyConfig.aptos));
|
|
8448
|
-
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
8449
|
-
const handleSigint = () => {
|
|
8450
|
-
spinner.fail("Quitting due to ctrl-C / SIGINT...");
|
|
8451
|
-
process.exit(1);
|
|
8452
|
-
};
|
|
8453
|
-
process.removeAllListeners("SIGINT");
|
|
8454
|
-
process.on("SIGINT", handleSigint);
|
|
8455
|
-
const expireUsec = validatedOptions.expiration.getTime() * 1e3;
|
|
8456
|
-
const expirationDate = new Date(expireUsec / 1e3);
|
|
8457
|
-
const formattedExpiration = expirationDate.toLocaleDateString("en-US", {
|
|
8458
|
-
year: "numeric",
|
|
8459
|
-
month: "short",
|
|
8460
|
-
day: "numeric",
|
|
8461
|
-
hour: "numeric",
|
|
8462
|
-
minute: "2-digit",
|
|
8463
|
-
second: "2-digit",
|
|
8464
|
-
hour12: true
|
|
8465
|
-
});
|
|
8466
|
-
console.log("");
|
|
8467
|
-
console.log(`\u{1F552} Expires: ${formattedExpiration}`);
|
|
8468
|
-
const spinner = ora2({
|
|
8469
|
-
text: "Uploading...",
|
|
8470
|
-
discardStdin: false
|
|
8471
|
-
}).start();
|
|
8472
|
-
const startTime = performance.now();
|
|
8473
|
-
let totalSize = 0;
|
|
8474
|
-
let amountUploaded = 0;
|
|
8475
|
-
const outputCommitments = {};
|
|
8476
|
-
let firstTransactionHash;
|
|
8477
|
-
const formatProgressPercent = () => (100 * (amountUploaded / totalSize)).toFixed(2);
|
|
8478
|
-
for (const entry of filelist) {
|
|
8479
|
-
totalSize += entry.sizeBytes;
|
|
8480
|
-
}
|
|
8481
|
-
let hasUploadedBlob = false;
|
|
8482
|
-
for (const entry of filelist) {
|
|
8483
|
-
const fileName = path5.basename(entry.filename);
|
|
8484
|
-
spinner.text = `\u{1F4D6} Reading ${fileName}... (${formatProgressPercent()}%)`;
|
|
8765
|
+
});
|
|
8766
|
+
uploadCmd.exitOverride(
|
|
8767
|
+
createExitOverrideHandler(
|
|
8768
|
+
"upload",
|
|
8769
|
+
"<source> <destination>",
|
|
8770
|
+
"-e, --expiration <datetime>",
|
|
8771
|
+
'shelby upload ./file.txt myblob -e "in 2 days"\n shelby upload ./my-folder/ my-folder/ -r -e tomorrow',
|
|
8772
|
+
"Missing source, destination, and expiration"
|
|
8773
|
+
)
|
|
8774
|
+
);
|
|
8775
|
+
uploadCmd.action(
|
|
8776
|
+
async (source, destination, options) => {
|
|
8777
|
+
let validatedOptions;
|
|
8485
8778
|
try {
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
);
|
|
8491
|
-
}
|
|
8492
|
-
spinner.text = `\u{1F517} Checking if blob exists... (${formatProgressPercent()}%)`;
|
|
8493
|
-
const existingBlobMetadata = await shelbyClient.coordination.getBlobMetadata({
|
|
8494
|
-
account: activeAccount.accountAddress,
|
|
8495
|
-
name: entry.blobname
|
|
8779
|
+
validatedOptions = await UploadOptionsSchema.parseAsync({
|
|
8780
|
+
...options,
|
|
8781
|
+
source,
|
|
8782
|
+
destination
|
|
8496
8783
|
});
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
)
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
}
|
|
8509
|
-
spinner.text = `\u{1F517} Registering ${fileName} on L1... (${formatProgressPercent()}%)`;
|
|
8510
|
-
const { transaction: pendingRegisterBlobTransaction } = await shelbyClient.coordination.registerBlob({
|
|
8511
|
-
account: activeAccount,
|
|
8512
|
-
blobName: entry.blobname,
|
|
8513
|
-
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
8514
|
-
size: blobData.length,
|
|
8515
|
-
expirationMicros: validatedOptions.expiration.getTime() * 1e3
|
|
8516
|
-
});
|
|
8517
|
-
const registerTransactionHash = pendingRegisterBlobTransaction.hash;
|
|
8518
|
-
if (!firstTransactionHash) {
|
|
8519
|
-
firstTransactionHash = registerTransactionHash;
|
|
8784
|
+
} catch (error) {
|
|
8785
|
+
if (error instanceof z15.ZodError) {
|
|
8786
|
+
const firstIssue = error.issues[0];
|
|
8787
|
+
if (firstIssue) {
|
|
8788
|
+
console.log(firstIssue.message);
|
|
8789
|
+
} else {
|
|
8790
|
+
console.error("\u26A0\uFE0F Invalid options provided");
|
|
8791
|
+
}
|
|
8792
|
+
} else {
|
|
8793
|
+
const { displayMessage } = handleError(error);
|
|
8794
|
+
console.error(`\u26A0\uFE0F ${displayMessage}`);
|
|
8520
8795
|
}
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
blobName: entry.blobname,
|
|
8528
|
-
blobData
|
|
8529
|
-
});
|
|
8530
|
-
hasUploadedBlob = true;
|
|
8531
|
-
amountUploaded += blobData.length;
|
|
8796
|
+
process.exit(1);
|
|
8797
|
+
}
|
|
8798
|
+
const configPath = program.opts().configFile;
|
|
8799
|
+
let config;
|
|
8800
|
+
try {
|
|
8801
|
+
config = loadConfig(configPath);
|
|
8532
8802
|
} catch (error) {
|
|
8533
|
-
|
|
8534
|
-
spinner.fail(displayMessage);
|
|
8803
|
+
console.error(`Error: ${error.message}`);
|
|
8535
8804
|
process.exit(1);
|
|
8536
8805
|
}
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
JSON.stringify(outputCommitments)
|
|
8806
|
+
const start = performance.now();
|
|
8807
|
+
const filelist = await createFilelist(validatedOptions);
|
|
8808
|
+
const timeToCreateFilelist = ((performance.now() - start) / 1e3).toFixed(
|
|
8809
|
+
5
|
|
8542
8810
|
);
|
|
8543
|
-
|
|
8544
|
-
|
|
8811
|
+
console.log("\n\u{1F680} Upload Summary");
|
|
8812
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8813
|
+
if (filelist.length === 1) {
|
|
8814
|
+
console.log(`\u{1F4E6} File: ${filelist[0].filename}`);
|
|
8815
|
+
console.log(`\u{1F4C1} Blob Name: ${filelist[0].blobname}`);
|
|
8816
|
+
} else {
|
|
8817
|
+
console.log(`\u{1F4E6} Files: ${filelist.length} files`);
|
|
8818
|
+
}
|
|
8819
|
+
console.log("");
|
|
8820
|
+
console.log(
|
|
8821
|
+
`\u{1F9EE} Filelist created (${filelist.length} ${filelist.length === 1 ? "entry" : "entries"})`
|
|
8822
|
+
);
|
|
8823
|
+
console.log(`\u23F1\uFE0F Took: ${timeToCreateFilelist}s`);
|
|
8824
|
+
if (validatedOptions.assumeYes) {
|
|
8825
|
+
console.log("\u2699\uFE0F Flag: --assume-yes (auto-confirmed)");
|
|
8826
|
+
}
|
|
8827
|
+
if (!validatedOptions.assumeYes) {
|
|
8828
|
+
const shouldContinue = await new Promise((resolve3) => {
|
|
8829
|
+
const { unmount } = render4(
|
|
8830
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
8831
|
+
/* @__PURE__ */ jsx13(Text7, { children: "Continue?" }),
|
|
8832
|
+
/* @__PURE__ */ jsx13(
|
|
8833
|
+
SelectInput4,
|
|
8834
|
+
{
|
|
8835
|
+
items: [
|
|
8836
|
+
{ label: "Yes", value: true },
|
|
8837
|
+
{ label: "No", value: false }
|
|
8838
|
+
],
|
|
8839
|
+
onSelect: (item) => {
|
|
8840
|
+
unmount();
|
|
8841
|
+
resolve3(item.value);
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
)
|
|
8845
|
+
] })
|
|
8846
|
+
);
|
|
8847
|
+
});
|
|
8848
|
+
if (!shouldContinue) {
|
|
8849
|
+
console.log("Upload cancelled.");
|
|
8850
|
+
process.exit(0);
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8853
|
+
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8854
|
+
context: program.opts().context
|
|
8855
|
+
});
|
|
8856
|
+
const activeAccount = getCurrentAccount(
|
|
8857
|
+
config,
|
|
8858
|
+
program.opts().account
|
|
8859
|
+
).account;
|
|
8860
|
+
const aptos = new Aptos5(new AptosConfig4(shelbyConfig.aptos));
|
|
8861
|
+
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
8862
|
+
const handleSigint = () => {
|
|
8863
|
+
spinner.fail("Quitting due to ctrl-C / SIGINT...");
|
|
8864
|
+
process.exit(1);
|
|
8865
|
+
};
|
|
8866
|
+
process.removeAllListeners("SIGINT");
|
|
8867
|
+
process.on("SIGINT", handleSigint);
|
|
8868
|
+
const expireUsec = validatedOptions.expiration.getTime() * 1e3;
|
|
8869
|
+
const expirationDate = new Date(expireUsec / 1e3);
|
|
8870
|
+
const formattedExpiration = expirationDate.toLocaleDateString("en-US", {
|
|
8871
|
+
year: "numeric",
|
|
8872
|
+
month: "short",
|
|
8873
|
+
day: "numeric",
|
|
8874
|
+
hour: "numeric",
|
|
8875
|
+
minute: "2-digit",
|
|
8876
|
+
second: "2-digit",
|
|
8877
|
+
hour12: true
|
|
8878
|
+
});
|
|
8879
|
+
console.log("");
|
|
8880
|
+
console.log(`\u{1F552} Expires: ${formattedExpiration}`);
|
|
8881
|
+
const spinner = ora2({
|
|
8882
|
+
text: "Uploading...",
|
|
8883
|
+
discardStdin: false
|
|
8884
|
+
}).start();
|
|
8885
|
+
const startTime = performance.now();
|
|
8886
|
+
let totalSize = 0;
|
|
8887
|
+
let amountUploaded = 0;
|
|
8888
|
+
const outputCommitments = {};
|
|
8889
|
+
let firstTransactionHash;
|
|
8890
|
+
for (const entry of filelist) {
|
|
8891
|
+
totalSize += entry.sizeBytes;
|
|
8892
|
+
}
|
|
8893
|
+
const isSingleFile = filelist.length === 1;
|
|
8894
|
+
const formatProgressPercent = () => totalSize > 0 ? (100 * (amountUploaded / totalSize)).toFixed(2) : "0.00";
|
|
8895
|
+
let filesProcessed = 0;
|
|
8896
|
+
for (const entry of filelist) {
|
|
8897
|
+
const fileName = path5.basename(entry.filename);
|
|
8898
|
+
const fileProgress = isSingleFile ? `(${formatProgressPercent()}%)` : `(${filesProcessed}/${filelist.length} files, ${formatProgressPercent()}%)`;
|
|
8899
|
+
spinner.text = `\u{1F4D6} Reading ${fileName}... ${fileProgress}`;
|
|
8900
|
+
try {
|
|
8901
|
+
const blobData = await fs5.readFile(entry.filename);
|
|
8902
|
+
if (blobData.length !== entry.sizeBytes) {
|
|
8903
|
+
throw new Error(
|
|
8904
|
+
`Size of file ${entry.filename} changed after initial scan. Original size was ${entry.sizeBytes} but it is now ${blobData.length}`
|
|
8905
|
+
);
|
|
8906
|
+
}
|
|
8907
|
+
spinner.text = `\u{1F517} Checking if blob exists... ${fileProgress}`;
|
|
8908
|
+
const existingBlobMetadata = await shelbyClient.coordination.getBlobMetadata({
|
|
8909
|
+
account: activeAccount.accountAddress,
|
|
8910
|
+
name: entry.blobname
|
|
8911
|
+
});
|
|
8912
|
+
if (existingBlobMetadata) {
|
|
8913
|
+
spinner.fail(
|
|
8914
|
+
`Blob '${entry.blobname}' already exists. Please use a different name or delete the existing blob first.`
|
|
8915
|
+
);
|
|
8916
|
+
process.exit(1);
|
|
8917
|
+
}
|
|
8918
|
+
spinner.text = `\u{1F517} Generating commitments for ${fileName}... ${fileProgress}`;
|
|
8919
|
+
const provider = await getErasureCodingProvider();
|
|
8920
|
+
const blobCommitments = await generateCommitments(provider, blobData);
|
|
8921
|
+
if (validatedOptions.outputCommitments) {
|
|
8922
|
+
outputCommitments[entry.filename] = blobCommitments;
|
|
8923
|
+
}
|
|
8924
|
+
spinner.text = `\u{1F517} Registering ${fileName} on L1... ${fileProgress}`;
|
|
8925
|
+
const { transaction: pendingRegisterBlobTransaction } = await shelbyClient.coordination.registerBlob({
|
|
8926
|
+
account: activeAccount,
|
|
8927
|
+
blobName: entry.blobname,
|
|
8928
|
+
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
8929
|
+
size: blobData.length,
|
|
8930
|
+
expirationMicros: validatedOptions.expiration.getTime() * 1e3
|
|
8931
|
+
});
|
|
8932
|
+
const registerTransactionHash = pendingRegisterBlobTransaction.hash;
|
|
8933
|
+
if (!firstTransactionHash) {
|
|
8934
|
+
firstTransactionHash = registerTransactionHash;
|
|
8935
|
+
}
|
|
8936
|
+
await aptos.waitForTransaction({
|
|
8937
|
+
transactionHash: pendingRegisterBlobTransaction.hash
|
|
8938
|
+
});
|
|
8939
|
+
spinner.text = `\u{1F4E4} Uploading ${fileName} to Shelby RPC... ${fileProgress}`;
|
|
8940
|
+
await shelbyClient.rpc.putBlob({
|
|
8941
|
+
account: activeAccount.accountAddress,
|
|
8942
|
+
blobName: entry.blobname,
|
|
8943
|
+
blobData
|
|
8944
|
+
});
|
|
8945
|
+
amountUploaded += blobData.length;
|
|
8946
|
+
filesProcessed++;
|
|
8947
|
+
} catch (error) {
|
|
8948
|
+
const { displayMessage } = handleError(error);
|
|
8949
|
+
spinner.fail(displayMessage);
|
|
8950
|
+
process.exit(1);
|
|
8951
|
+
}
|
|
8952
|
+
}
|
|
8953
|
+
if (validatedOptions.outputCommitments) {
|
|
8954
|
+
await fs5.writeFile(
|
|
8955
|
+
validatedOptions.outputCommitments,
|
|
8956
|
+
JSON.stringify(outputCommitments)
|
|
8957
|
+
);
|
|
8958
|
+
}
|
|
8545
8959
|
const elapsedSec = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
8546
8960
|
spinner.succeed(`Upload complete \u2014 took ${elapsedSec}s`);
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8961
|
+
console.log("");
|
|
8962
|
+
if (firstTransactionHash) {
|
|
8963
|
+
const explorerUrl = getAptosTransactionExplorerUrl(
|
|
8964
|
+
shelbyConfig.network,
|
|
8965
|
+
firstTransactionHash
|
|
8966
|
+
);
|
|
8967
|
+
console.log("\u{1F310} Aptos Explorer:");
|
|
8968
|
+
console.log(` ${explorerUrl}`);
|
|
8969
|
+
console.log("");
|
|
8970
|
+
}
|
|
8971
|
+
const shelbyExplorerUrl = getShelbyAccountExplorerUrl(
|
|
8553
8972
|
shelbyConfig.network,
|
|
8554
|
-
|
|
8973
|
+
activeAccount.accountAddress.toString()
|
|
8555
8974
|
);
|
|
8556
|
-
console.log("\u{
|
|
8557
|
-
console.log(` ${
|
|
8975
|
+
console.log("\u{1F5C2}\uFE0F Shelby Explorer:");
|
|
8976
|
+
console.log(` ${shelbyExplorerUrl}`);
|
|
8977
|
+
console.log("");
|
|
8978
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8979
|
+
console.log("\u2728 Done!");
|
|
8558
8980
|
console.log("");
|
|
8559
8981
|
}
|
|
8560
|
-
|
|
8561
|
-
shelbyConfig.network,
|
|
8562
|
-
activeAccount.accountAddress.toString()
|
|
8563
|
-
);
|
|
8564
|
-
console.log("\u{1F5C2}\uFE0F Shelby Explorer:");
|
|
8565
|
-
console.log(` ${shelbyExplorerUrl}`);
|
|
8566
|
-
console.log("");
|
|
8567
|
-
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8568
|
-
console.log("\u2728 Done!");
|
|
8569
|
-
console.log("");
|
|
8570
|
-
});
|
|
8982
|
+
);
|
|
8571
8983
|
}
|
|
8572
8984
|
|
|
8573
8985
|
// src/cli.tsx
|