@shelby-protocol/sdk 0.2.4 → 0.3.1
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/dist/browser/index.d.ts +13 -4
- package/dist/browser/index.mjs +3719 -140
- package/dist/{clay-codes-DHP-bYcP.d.ts → clay-codes-DdXABBDx.d.ts} +0 -7
- package/dist/core/aptos-explorer.mjs +46 -5
- package/dist/core/blobs.d.ts +1 -1
- package/dist/core/blobs.mjs +12 -4
- package/dist/core/chunk.mjs +58 -9
- package/dist/core/clients/ShelbyBlobClient.d.ts +9 -44
- package/dist/core/clients/ShelbyBlobClient.mjs +1084 -17
- package/dist/core/clients/ShelbyClient.d.ts +12 -2
- package/dist/core/clients/ShelbyClient.mjs +2915 -23
- package/dist/core/clients/ShelbyClientConfig.d.ts +8 -0
- package/dist/core/clients/ShelbyClientConfig.mjs +0 -1
- package/dist/core/clients/ShelbyMetadataClient.d.ts +0 -18
- package/dist/core/clients/ShelbyMetadataClient.mjs +306 -6
- package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +7 -49
- package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +839 -13
- package/dist/core/clients/ShelbyPlacementGroupClient.mjs +307 -8
- package/dist/core/clients/ShelbyRPCClient.d.ts +107 -4
- package/dist/core/clients/ShelbyRPCClient.mjs +1261 -12
- package/dist/core/clients/index.d.ts +3 -2
- package/dist/core/clients/index.mjs +3421 -40
- package/dist/core/clients/utils.mjs +65 -6
- package/dist/core/commitments.d.ts +3 -12
- package/dist/core/commitments.mjs +270 -11
- package/dist/core/constants.d.ts +4 -4
- package/dist/core/constants.mjs +39 -16
- package/dist/core/erasure/clay-codes.d.ts +1 -1
- package/dist/core/erasure/clay-codes.mjs +129 -4
- package/dist/core/erasure/constants.mjs +33 -10
- package/dist/core/erasure/default.d.ts +1 -1
- package/dist/core/erasure/default.mjs +191 -9
- package/dist/core/erasure/index.d.ts +1 -1
- package/dist/core/erasure/index.mjs +266 -23
- package/dist/core/erasure/provider.d.ts +1 -1
- package/dist/core/erasure/reed-solomon.d.ts +1 -1
- package/dist/core/erasure/reed-solomon.mjs +68 -4
- package/dist/core/erasure/utils.d.ts +1 -1
- package/dist/core/erasure/utils.mjs +0 -2
- package/dist/core/errors.mjs +29 -8
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.mjs +3713 -140
- package/dist/core/layout.d.ts +1 -1
- package/dist/core/layout.mjs +37 -5
- package/dist/core/networks.mjs +10 -5
- package/dist/core/operations/generated/sdk.d.ts +9 -0
- package/dist/core/operations/generated/sdk.mjs +183 -18
- package/dist/core/operations/index.mjs +284 -24
- package/dist/core/promises.mjs +4 -4
- package/dist/core/rpc-responses.d.ts +38 -1
- package/dist/core/rpc-responses.mjs +31 -8
- package/dist/core/shelby-explorer.mjs +31 -6
- package/dist/core/strings.mjs +0 -2
- package/dist/core/types/blobs.d.ts +1 -1
- package/dist/core/types/blobs.mjs +0 -1
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/index.mjs +230 -9
- package/dist/core/types/payments.d.ts +6 -6
- package/dist/core/types/payments.mjs +230 -5
- package/dist/core/types/placement_groups.mjs +0 -1
- package/dist/core/types/storage_providers.mjs +0 -1
- package/dist/core/utils.mjs +94 -8
- package/dist/node/clients/ShelbyNodeClient.d.ts +2 -1
- package/dist/node/clients/ShelbyNodeClient.mjs +2919 -24
- package/dist/node/clients/index.d.ts +2 -1
- package/dist/node/clients/index.mjs +2920 -26
- package/dist/node/index.d.ts +4 -4
- package/dist/node/index.mjs +3736 -147
- package/dist/node/parallel/commitment_worker.d.ts +9 -0
- package/dist/node/parallel/commitment_worker.mjs +298 -0
- package/dist/node/parallel/commitment_worker_pool.d.ts +35 -0
- package/dist/node/parallel/commitment_worker_pool.mjs +286 -0
- package/dist/node/parallel/default_commitment_worker_pool.d.ts +24 -0
- package/dist/node/parallel/default_commitment_worker_pool.mjs +302 -0
- package/dist/node/parallel/index.d.ts +11 -0
- package/dist/node/parallel/index.mjs +563 -0
- package/dist/node/parallel/parallel_commitments.d.ts +32 -0
- package/dist/node/parallel/parallel_commitments.mjs +561 -0
- package/dist/node/parallel/worker_pool.d.ts +74 -0
- package/dist/node/parallel/worker_pool.mjs +248 -0
- package/dist/node/testUtil.mjs +8 -4
- package/package.json +9 -6
- package/dist/chunk-3NRBHSMQ.mjs +0 -12
- package/dist/chunk-3PCG7PNP.mjs +0 -375
- package/dist/chunk-7OV5ZYW6.mjs +0 -55
- package/dist/chunk-7P6ASYW6.mjs +0 -9
- package/dist/chunk-A4IG6GSE.mjs +0 -21
- package/dist/chunk-AD2G3QYD.mjs +0 -0
- package/dist/chunk-AEDVYYGD.mjs +0 -11
- package/dist/chunk-AUQDI5BS.mjs +0 -43
- package/dist/chunk-BDSW5PHM.mjs +0 -37
- package/dist/chunk-BKL7NCUB.mjs +0 -412
- package/dist/chunk-BUWAD67L.mjs +0 -505
- package/dist/chunk-BXEVML7N.mjs +0 -169
- package/dist/chunk-C6RQ3AEU.mjs +0 -51
- package/dist/chunk-CQ6QPIZK.mjs +0 -37
- package/dist/chunk-D6GQHO6G.mjs +0 -15
- package/dist/chunk-EM67QTMR.mjs +0 -0
- package/dist/chunk-I6NG5GNL.mjs +0 -8
- package/dist/chunk-IE6LYVIA.mjs +0 -26
- package/dist/chunk-IKRWBDG5.mjs +0 -42
- package/dist/chunk-JTXYKO3U.mjs +0 -38
- package/dist/chunk-KYQRWJ3U.mjs +0 -49
- package/dist/chunk-MB7C7VQF.mjs +0 -0
- package/dist/chunk-MQUVYMNQ.mjs +0 -0
- package/dist/chunk-NHWWORCH.mjs +0 -72
- package/dist/chunk-NI6XBZLI.mjs +0 -83
- package/dist/chunk-OGKZ575S.mjs +0 -136
- package/dist/chunk-QQ57OGQ2.mjs +0 -0
- package/dist/chunk-SSE4MFNN.mjs +0 -278
- package/dist/chunk-TET3DJEO.mjs +0 -199
- package/dist/chunk-TVLV7C74.mjs +0 -207
- package/dist/chunk-UEZNZBJO.mjs +0 -104
- package/dist/chunk-WJTVYLKW.mjs +0 -782
- package/dist/chunk-WTICJPDB.mjs +0 -0
- package/dist/chunk-XNEIWM4O.mjs +0 -0
- package/dist/chunk-XRLHLAOW.mjs +0 -51
- package/dist/chunk-Z4FZ7W6L.mjs +0 -39
- package/dist/chunk-Z7RFCADT.mjs +0 -0
- package/dist/chunk-ZHXCVRZX.mjs +0 -0
- package/dist/core/operations/generated/types.d.ts +0 -908
- package/dist/core/operations/generated/types.mjs +0 -63
- package/dist/core/types/encodings.d.ts +0 -11
- package/dist/core/types/encodings.mjs +0 -0
package/dist/node/index.mjs
CHANGED
|
@@ -1,158 +1,3745 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import "
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
import {
|
|
56
|
-
Blob_Activities_Select_Column,
|
|
57
|
-
Blobs_Select_Column,
|
|
58
|
-
Cursor_Ordering,
|
|
59
|
-
GetBlobActivitiesCountDocument,
|
|
60
|
-
GetBlobActivitiesDocument,
|
|
61
|
-
GetBlobsCountDocument,
|
|
62
|
-
GetBlobsDocument,
|
|
63
|
-
GetPlacementGroupSlotsCountDocument,
|
|
64
|
-
GetPlacementGroupSlotsDocument,
|
|
65
|
-
GetProcessorStatusDocument,
|
|
66
|
-
GetTotalBlobsSizeDocument,
|
|
67
|
-
Order_By,
|
|
68
|
-
Placement_Group_Slots_Select_Column,
|
|
69
|
-
Processor_Status_Select_Column,
|
|
70
|
-
getSdk
|
|
71
|
-
} from "../chunk-TET3DJEO.mjs";
|
|
72
|
-
import "../chunk-IKRWBDG5.mjs";
|
|
73
|
-
import {
|
|
74
|
-
isShelbyNetwork,
|
|
75
|
-
shelbyNetworks
|
|
76
|
-
} from "../chunk-D6GQHO6G.mjs";
|
|
77
|
-
import "../chunk-I6NG5GNL.mjs";
|
|
78
|
-
import {
|
|
79
|
-
CompleteMultipartUploadResponseSchema,
|
|
80
|
-
RPCErrorResponseSchema,
|
|
81
|
-
StaleMicropaymentErrorResponseSchema,
|
|
82
|
-
StartMultipartUploadResponseSchema,
|
|
83
|
-
UploadPartResponseSchema
|
|
84
|
-
} from "../chunk-IE6LYVIA.mjs";
|
|
85
|
-
import {
|
|
86
|
-
getShelbyAccountBlobsExplorerUrl,
|
|
87
|
-
getShelbyAccountExplorerUrl,
|
|
88
|
-
getShelbyBlobExplorerUrl
|
|
89
|
-
} from "../chunk-BDSW5PHM.mjs";
|
|
90
|
-
import {
|
|
91
|
-
getAptosAccountExplorerUrl,
|
|
92
|
-
getAptosTransactionExplorerUrl
|
|
93
|
-
} from "../chunk-C6RQ3AEU.mjs";
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/core/aptos-explorer.ts
|
|
8
|
+
import { Network } from "@aptos-labs/ts-sdk";
|
|
9
|
+
function getAptosExplorerBaseUrl(network) {
|
|
10
|
+
const normalizedNetwork = network.toLowerCase();
|
|
11
|
+
switch (normalizedNetwork) {
|
|
12
|
+
case Network.MAINNET.toLowerCase():
|
|
13
|
+
return "https://explorer.aptoslabs.com";
|
|
14
|
+
case Network.TESTNET.toLowerCase():
|
|
15
|
+
return "https://explorer.aptoslabs.com";
|
|
16
|
+
case Network.DEVNET.toLowerCase():
|
|
17
|
+
return "https://explorer.aptoslabs.com";
|
|
18
|
+
case Network.LOCAL.toLowerCase():
|
|
19
|
+
return "https://explorer.aptoslabs.com";
|
|
20
|
+
case Network.SHELBYNET.toLowerCase():
|
|
21
|
+
return "https://explorer.aptoslabs.com";
|
|
22
|
+
default:
|
|
23
|
+
return "https://explorer.aptoslabs.com";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getAptosExplorerNetworkParam(network) {
|
|
27
|
+
const normalizedNetwork = network.toLowerCase();
|
|
28
|
+
switch (normalizedNetwork) {
|
|
29
|
+
case Network.MAINNET.toLowerCase():
|
|
30
|
+
return "mainnet";
|
|
31
|
+
case Network.TESTNET.toLowerCase():
|
|
32
|
+
return "testnet";
|
|
33
|
+
case Network.DEVNET.toLowerCase():
|
|
34
|
+
return "devnet";
|
|
35
|
+
case Network.LOCAL.toLowerCase():
|
|
36
|
+
return "local";
|
|
37
|
+
case Network.SHELBYNET.toLowerCase():
|
|
38
|
+
return "shelbynet";
|
|
39
|
+
default:
|
|
40
|
+
return normalizedNetwork;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getAptosTransactionExplorerUrl(network, transactionHash) {
|
|
44
|
+
const baseUrl = getAptosExplorerBaseUrl(network);
|
|
45
|
+
const networkParam = getAptosExplorerNetworkParam(network);
|
|
46
|
+
return `${baseUrl}/txn/${transactionHash}?network=${networkParam}`;
|
|
47
|
+
}
|
|
48
|
+
function getAptosAccountExplorerUrl(network, accountAddress) {
|
|
49
|
+
const baseUrl = getAptosExplorerBaseUrl(network);
|
|
50
|
+
const networkParam = getAptosExplorerNetworkParam(network);
|
|
51
|
+
return `${baseUrl}/account/${accountAddress}?network=${networkParam}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/core/utils.ts
|
|
94
55
|
import {
|
|
95
|
-
|
|
96
|
-
|
|
56
|
+
AccountAddress,
|
|
57
|
+
Hex
|
|
58
|
+
} from "@aptos-labs/ts-sdk";
|
|
59
|
+
async function* readInChunks(input, chunkSize) {
|
|
60
|
+
let idx = 0;
|
|
61
|
+
if (isReadableStream(input)) {
|
|
62
|
+
const reader = input.getReader();
|
|
63
|
+
let buffer = new Uint8Array(chunkSize);
|
|
64
|
+
let bufferWriteOffset = 0;
|
|
65
|
+
try {
|
|
66
|
+
while (true) {
|
|
67
|
+
const { value, done } = await reader.read();
|
|
68
|
+
if (done) break;
|
|
69
|
+
if (value === void 0) continue;
|
|
70
|
+
let srcOffset = 0;
|
|
71
|
+
while (srcOffset < value.length) {
|
|
72
|
+
const remainingCapacity = chunkSize - bufferWriteOffset;
|
|
73
|
+
const bytesToCopy = Math.min(
|
|
74
|
+
remainingCapacity,
|
|
75
|
+
value.length - srcOffset
|
|
76
|
+
);
|
|
77
|
+
buffer.set(
|
|
78
|
+
value.subarray(srcOffset, srcOffset + bytesToCopy),
|
|
79
|
+
bufferWriteOffset
|
|
80
|
+
);
|
|
81
|
+
bufferWriteOffset += bytesToCopy;
|
|
82
|
+
srcOffset += bytesToCopy;
|
|
83
|
+
if (bufferWriteOffset >= chunkSize) {
|
|
84
|
+
yield [idx++, buffer];
|
|
85
|
+
buffer = new Uint8Array(chunkSize);
|
|
86
|
+
bufferWriteOffset = 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
reader.releaseLock();
|
|
92
|
+
}
|
|
93
|
+
if (bufferWriteOffset > 0) {
|
|
94
|
+
yield [idx++, buffer.subarray(0, bufferWriteOffset)];
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const view = toUint8Array(input);
|
|
99
|
+
for (let offset = 0; offset < view.byteLength; offset += chunkSize) {
|
|
100
|
+
yield [
|
|
101
|
+
idx++,
|
|
102
|
+
view.subarray(offset, Math.min(offset + chunkSize, view.byteLength))
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function zeroPadBytes(buffer, desiredLength) {
|
|
107
|
+
if (buffer.byteLength === desiredLength) {
|
|
108
|
+
return buffer;
|
|
109
|
+
}
|
|
110
|
+
if (buffer.byteLength > desiredLength) {
|
|
111
|
+
return buffer.subarray(0, desiredLength);
|
|
112
|
+
}
|
|
113
|
+
const paddedBuffer = new Uint8Array(desiredLength);
|
|
114
|
+
paddedBuffer.set(buffer);
|
|
115
|
+
return paddedBuffer;
|
|
116
|
+
}
|
|
117
|
+
async function concatHashes(parts) {
|
|
118
|
+
const chunks = parts.map((part) => Hex.fromHexInput(part).toUint8Array());
|
|
119
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
120
|
+
const combined = new Uint8Array(totalLength);
|
|
121
|
+
let offset = 0;
|
|
122
|
+
for (const chunk of chunks) {
|
|
123
|
+
combined.set(chunk, offset);
|
|
124
|
+
offset += chunk.byteLength;
|
|
125
|
+
}
|
|
126
|
+
return Hex.fromHexInput(
|
|
127
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", combined))
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function isReadableStream(value) {
|
|
131
|
+
return typeof value === "object" && value !== null && "getReader" in value && typeof value.getReader === "function";
|
|
132
|
+
}
|
|
133
|
+
function toUint8Array(view) {
|
|
134
|
+
return view instanceof Uint8Array ? view : new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
135
|
+
}
|
|
136
|
+
function buildRequestUrl(path, baseUrl) {
|
|
137
|
+
const baseHasSlash = baseUrl.endsWith("/");
|
|
138
|
+
const safeBase = baseHasSlash ? baseUrl : `${baseUrl}/`;
|
|
139
|
+
const safePath = path.replace(/^\/+/, "");
|
|
140
|
+
return new URL(safePath, safeBase);
|
|
141
|
+
}
|
|
142
|
+
function getBlobNameSuffix(blobName) {
|
|
143
|
+
const parts = blobName.split("/");
|
|
144
|
+
return parts.slice(1).join("/") || "";
|
|
145
|
+
}
|
|
146
|
+
function normalizeAddress(address) {
|
|
147
|
+
return AccountAddress.from(address, { maxMissingChars: 63 });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/core/blobs.ts
|
|
151
|
+
var createBlobKey = (params) => {
|
|
152
|
+
return `@${normalizeAddress(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/core/erasure/constants.ts
|
|
156
|
+
var ErasureCodingScheme = /* @__PURE__ */ ((ErasureCodingScheme2) => {
|
|
157
|
+
ErasureCodingScheme2["ClayCode_16Total_10Data_13Helper"] = "ClayCode_16Total_10Data_13Helper";
|
|
158
|
+
ErasureCodingScheme2["ClayCode_4Total_2Data_3Helper"] = "ClayCode_4Total_2Data_3Helper";
|
|
159
|
+
return ErasureCodingScheme2;
|
|
160
|
+
})(ErasureCodingScheme || {});
|
|
161
|
+
var ERASURE_CODE_PARAMS = {
|
|
162
|
+
["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */]: {
|
|
163
|
+
// total chunks (data + parity)
|
|
164
|
+
erasure_n: 16,
|
|
165
|
+
// data chunks
|
|
166
|
+
erasure_k: 10,
|
|
167
|
+
// helper nodes
|
|
168
|
+
erasure_d: 13,
|
|
169
|
+
// enum index
|
|
170
|
+
enumIndex: 0
|
|
171
|
+
},
|
|
172
|
+
["ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */]: {
|
|
173
|
+
// total chunks (data + parity)
|
|
174
|
+
erasure_n: 4,
|
|
175
|
+
// data chunks
|
|
176
|
+
erasure_k: 2,
|
|
177
|
+
// helper nodes
|
|
178
|
+
erasure_d: 3,
|
|
179
|
+
// enum index
|
|
180
|
+
enumIndex: 1
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
|
|
184
|
+
var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
|
|
185
|
+
var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
|
|
186
|
+
var DEFAULT_ERASURE_M = DEFAULT_ERASURE_N - DEFAULT_ERASURE_K;
|
|
187
|
+
var DEFAULT_SAMPLE_SIZE = 1024;
|
|
188
|
+
|
|
189
|
+
// src/core/chunk.ts
|
|
190
|
+
var ChunkSizeScheme = /* @__PURE__ */ ((ChunkSizeScheme2) => {
|
|
191
|
+
ChunkSizeScheme2["ChunkSet10MiB_Chunk1MiB"] = "ChunkSet10MiB_Chunk1MiB";
|
|
192
|
+
ChunkSizeScheme2["ChunkSet2MiB_Chunk1MiB"] = "ChunkSet2MiB_Chunk1MiB";
|
|
193
|
+
return ChunkSizeScheme2;
|
|
194
|
+
})(ChunkSizeScheme || {});
|
|
195
|
+
var CHUNK_SIZE_PARAMS = {
|
|
196
|
+
["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */]: {
|
|
197
|
+
// 1MiB
|
|
198
|
+
chunkSizeBytes: 1 * 1024 * 1024,
|
|
199
|
+
// 10MiB
|
|
200
|
+
chunksetSizeBytes: 10 * 1024 * 1024
|
|
201
|
+
},
|
|
202
|
+
["ChunkSet2MiB_Chunk1MiB" /* ChunkSet2MiB_Chunk1MiB */]: {
|
|
203
|
+
// 1MiB
|
|
204
|
+
chunkSizeBytes: 1 * 1024 * 1024,
|
|
205
|
+
// 2MiB
|
|
206
|
+
chunksetSizeBytes: 2 * 1024 * 1024
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var DEFAULT_CHUNK_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */].chunkSizeBytes;
|
|
210
|
+
var DEFAULT_CHUNKSET_SIZE_BYTES = CHUNK_SIZE_PARAMS["ChunkSet10MiB_Chunk1MiB" /* ChunkSet10MiB_Chunk1MiB */].chunksetSizeBytes;
|
|
211
|
+
var ERASURE_CODE_AND_CHUNK_MAPPING = {
|
|
212
|
+
["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */]: {
|
|
213
|
+
...CHUNK_SIZE_PARAMS.ChunkSet10MiB_Chunk1MiB
|
|
214
|
+
},
|
|
215
|
+
["ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */]: {
|
|
216
|
+
...CHUNK_SIZE_PARAMS.ChunkSet2MiB_Chunk1MiB
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/core/clients/ShelbyBlobClient.ts
|
|
97
221
|
import {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
222
|
+
AccountAddress as AccountAddress2,
|
|
223
|
+
Aptos,
|
|
224
|
+
Hex as Hex3,
|
|
225
|
+
U32
|
|
226
|
+
} from "@aptos-labs/ts-sdk";
|
|
227
|
+
|
|
228
|
+
// src/core/commitments.ts
|
|
229
|
+
import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
|
|
230
|
+
import { z } from "zod";
|
|
231
|
+
var COMMITMENT_SCHEMA_VERSION = "1.3";
|
|
232
|
+
var ChunksetCommitmentSchema = z.object({
|
|
233
|
+
// Chunkset root (vector commitment of child chunks)
|
|
234
|
+
chunkset_root: z.string(),
|
|
235
|
+
// the size is known statically from the current configuration
|
|
236
|
+
chunk_commitments: z.array(z.string())
|
|
237
|
+
}).refine(
|
|
238
|
+
(data) => {
|
|
239
|
+
return data.chunk_commitments.length === DEFAULT_ERASURE_K + DEFAULT_ERASURE_M;
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
message: `Chunkset must have exactly ${DEFAULT_ERASURE_K + DEFAULT_ERASURE_M} chunks (ERASURE_K + ERASURE_M = ${DEFAULT_ERASURE_K} + ${DEFAULT_ERASURE_M})`,
|
|
243
|
+
path: ["chunk_commitments"]
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
function expectedTotalChunksets(rawSize, chunksetSize = DEFAULT_CHUNKSET_SIZE_BYTES) {
|
|
247
|
+
if (chunksetSize <= 0) {
|
|
248
|
+
throw new Error("chunksetSize must be positive");
|
|
249
|
+
}
|
|
250
|
+
if (rawSize === 0) return 1;
|
|
251
|
+
return Math.ceil(rawSize / chunksetSize);
|
|
252
|
+
}
|
|
253
|
+
var BlobCommitmentsSchema = z.object({
|
|
254
|
+
schema_version: z.string(),
|
|
255
|
+
raw_data_size: z.number(),
|
|
256
|
+
// FIXME I am not sure about this being here, or if it should be somewhere else
|
|
257
|
+
blob_merkle_root: z.string(),
|
|
258
|
+
chunkset_commitments: z.array(ChunksetCommitmentSchema)
|
|
259
|
+
}).refine(
|
|
260
|
+
(data) => {
|
|
261
|
+
return expectedTotalChunksets(data.raw_data_size) === data.chunkset_commitments.length;
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
message: "Total chunkset count mismatches with raw data size",
|
|
265
|
+
// FIXME put more details in here
|
|
266
|
+
path: ["chunkset_commitments"]
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
async function generateMerkleRoot(leafHashes) {
|
|
270
|
+
if (!leafHashes.length) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"An empty array cannot be used to construct a Merkle tree."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const zeroArray = new Uint8Array(leafHashes[0].toUint8Array().length);
|
|
276
|
+
const zeroBytes = Hex2.fromHexInput(zeroArray);
|
|
277
|
+
let currentLeaves = leafHashes;
|
|
278
|
+
while (currentLeaves.length > 1) {
|
|
279
|
+
if (currentLeaves.length % 2 !== 0) {
|
|
280
|
+
currentLeaves.push(zeroBytes);
|
|
281
|
+
}
|
|
282
|
+
const nextLeaves = [];
|
|
283
|
+
for (let i = 0; i < currentLeaves.length; i += 2) {
|
|
284
|
+
nextLeaves.push(
|
|
285
|
+
await concatHashes([
|
|
286
|
+
currentLeaves[i].toUint8Array(),
|
|
287
|
+
currentLeaves[i + 1].toUint8Array()
|
|
288
|
+
])
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
currentLeaves = nextLeaves;
|
|
292
|
+
}
|
|
293
|
+
return currentLeaves[0];
|
|
294
|
+
}
|
|
295
|
+
async function generateChunksetCommitments(chunksetIdx, chunksetData, expectedChunksetSize, provider, onChunk) {
|
|
296
|
+
const { erasure_n } = provider.config;
|
|
297
|
+
const chunksetPayload = zeroPadBytes(chunksetData, expectedChunksetSize);
|
|
298
|
+
const { chunks } = provider.encode(chunksetPayload);
|
|
299
|
+
if (chunks.length !== erasure_n) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Erasure provider produced ${chunks.length} chunks, expected ${erasure_n}.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const chunkRoots = provider.getChunkMerkleRoots();
|
|
305
|
+
let chunkIdx = 0;
|
|
306
|
+
for (const chunkData of chunks) {
|
|
307
|
+
if (onChunk !== void 0) {
|
|
308
|
+
await onChunk(chunksetIdx, chunkIdx, chunkData);
|
|
309
|
+
}
|
|
310
|
+
chunkIdx += 1;
|
|
311
|
+
}
|
|
312
|
+
const a = await generateMerkleRoot(
|
|
313
|
+
chunkRoots.map((a2) => Hex2.fromHexInput(a2))
|
|
314
|
+
);
|
|
315
|
+
const entry = {
|
|
316
|
+
chunkset_root: a.toString(),
|
|
317
|
+
chunk_commitments: chunkRoots.map(
|
|
318
|
+
(chunk) => Hex2.fromHexInput(chunk).toString()
|
|
319
|
+
)
|
|
320
|
+
};
|
|
321
|
+
return { h: a, entry };
|
|
322
|
+
}
|
|
323
|
+
async function generateCommitments(provider, fullData, onChunk) {
|
|
324
|
+
const expectedChunksetSize = provider.config.chunkSizeBytes * provider.config.erasure_k;
|
|
325
|
+
const chunksetCommitments = [];
|
|
326
|
+
const chunksetCommitmentHashes = [];
|
|
327
|
+
let rawDataSize = 0;
|
|
328
|
+
const chunksetGen = readInChunks(fullData, expectedChunksetSize);
|
|
329
|
+
for await (const [chunksetIdx, chunksetData] of chunksetGen) {
|
|
330
|
+
rawDataSize += chunksetData.length;
|
|
331
|
+
const { h, entry } = await generateChunksetCommitments(
|
|
332
|
+
chunksetIdx,
|
|
333
|
+
chunksetData,
|
|
334
|
+
expectedChunksetSize,
|
|
335
|
+
provider,
|
|
336
|
+
onChunk
|
|
337
|
+
);
|
|
338
|
+
chunksetCommitments.push(entry);
|
|
339
|
+
chunksetCommitmentHashes.push(h);
|
|
340
|
+
}
|
|
341
|
+
if (rawDataSize === 0) {
|
|
342
|
+
const zeroChunkset = new Uint8Array(expectedChunksetSize);
|
|
343
|
+
const { h, entry } = await generateChunksetCommitments(
|
|
344
|
+
0,
|
|
345
|
+
zeroChunkset,
|
|
346
|
+
expectedChunksetSize,
|
|
347
|
+
provider,
|
|
348
|
+
onChunk
|
|
349
|
+
);
|
|
350
|
+
chunksetCommitments.push(entry);
|
|
351
|
+
chunksetCommitmentHashes.push(h);
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
schema_version: COMMITMENT_SCHEMA_VERSION,
|
|
355
|
+
raw_data_size: rawDataSize,
|
|
356
|
+
blob_merkle_root: (await generateMerkleRoot(chunksetCommitmentHashes)).toString(),
|
|
357
|
+
chunkset_commitments: chunksetCommitments
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/core/constants.ts
|
|
362
|
+
import { Network as Network2 } from "@aptos-labs/ts-sdk";
|
|
363
|
+
var DEFAULT_PROJECT_NAME = "shelby";
|
|
364
|
+
var DEFAULT_PROJECT_DESCRIPTION = "High performance, decentralized storage";
|
|
365
|
+
var NetworkToShelbyRPCBaseUrl = {
|
|
366
|
+
[Network2.SHELBYNET]: "https://api.shelbynet.shelby.xyz/shelby",
|
|
367
|
+
[Network2.NETNA]: void 0,
|
|
368
|
+
[Network2.DEVNET]: void 0,
|
|
369
|
+
[Network2.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
|
|
370
|
+
[Network2.MAINNET]: void 0,
|
|
371
|
+
[Network2.LOCAL]: "http://localhost:9090",
|
|
372
|
+
[Network2.CUSTOM]: void 0
|
|
373
|
+
};
|
|
374
|
+
var NetworkToShelbyBlobIndexerBaseUrl = {
|
|
375
|
+
[Network2.SHELBYNET]: "https://api.shelbynet.aptoslabs.com/nocode/v1/public/alias/shelby/shelbynet/v1/graphql",
|
|
376
|
+
[Network2.NETNA]: void 0,
|
|
377
|
+
[Network2.DEVNET]: void 0,
|
|
378
|
+
[Network2.TESTNET]: "https://api.testnet.aptoslabs.com/nocode/v1/public/alias/shelby/testnet/v1/graphql",
|
|
379
|
+
[Network2.MAINNET]: void 0,
|
|
380
|
+
[Network2.LOCAL]: "http://localhost:8091/v1/graphql",
|
|
381
|
+
[Network2.CUSTOM]: void 0
|
|
382
|
+
};
|
|
383
|
+
var NetworkToGasStationBaseUrl = {
|
|
384
|
+
[Network2.SHELBYNET]: "https://api.shelbynet.shelby.xyz/gs/v1",
|
|
385
|
+
[Network2.NETNA]: void 0,
|
|
386
|
+
[Network2.DEVNET]: void 0,
|
|
387
|
+
[Network2.TESTNET]: "https://api.testnet.shelby.xyz/gs/v1",
|
|
388
|
+
[Network2.MAINNET]: void 0,
|
|
389
|
+
[Network2.LOCAL]: void 0,
|
|
390
|
+
[Network2.CUSTOM]: void 0
|
|
391
|
+
};
|
|
392
|
+
var SHELBY_DEPLOYER = "0x85fdb9a176ab8ef1d9d9c1b60d60b3924f0800ac1de1cc2085fb0b8bb4988e6a";
|
|
393
|
+
var MICROPAYMENTS_DEPLOYER = "0x1ae7275148bf6ef742b658fd9cbcc2e094201606f4a7bc707bab0201da8043ee";
|
|
394
|
+
var TOKEN_DEPLOYER = "0x33009e852be7f93762dd0bf303383c2cb2c5cab7a30d8238ca5f9f177ae75124";
|
|
395
|
+
var TOKEN_OBJECT_ADDRESS = "0x249f5c642a63885ff88a5113b3ba0079840af5a1357706f8c7f3bfc5dd12511f";
|
|
396
|
+
var SHELBYUSD_TOKEN_ADDRESS = TOKEN_OBJECT_ADDRESS;
|
|
397
|
+
var SHELBYUSD_TOKEN_MODULE = "shelby_usd";
|
|
398
|
+
var SHELBYUSD_TOKEN_NAME = "ShelbyUSD";
|
|
399
|
+
var SHELBYUSD_FA_METADATA_ADDRESS = "0x1b18363a9f1fe5e6ebf247daba5cc1c18052bb232efdc4c50f556053922d98e1";
|
|
400
|
+
|
|
401
|
+
// src/core/erasure/clay-codes.ts
|
|
105
402
|
import {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
403
|
+
createDecoder,
|
|
404
|
+
createEncoder
|
|
405
|
+
} from "@shelby-protocol/clay-codes";
|
|
406
|
+
function getTotalChunks(config) {
|
|
407
|
+
return config.erasure_n;
|
|
408
|
+
}
|
|
409
|
+
var ClayErasureCodingProvider = class _ClayErasureCodingProvider {
|
|
410
|
+
config;
|
|
411
|
+
encoderCache;
|
|
412
|
+
decoderCache;
|
|
413
|
+
lastFunction;
|
|
414
|
+
constructor(config) {
|
|
415
|
+
this.config = config;
|
|
416
|
+
this.lastFunction = "none" /* NONE */;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Static factory method to create an initialized ClayErasureCodingProvider
|
|
420
|
+
*/
|
|
421
|
+
static async create(config) {
|
|
422
|
+
const provider = new _ClayErasureCodingProvider(config);
|
|
423
|
+
[provider.encoderCache, provider.decoderCache] = await Promise.all([
|
|
424
|
+
createEncoder({
|
|
425
|
+
n: getTotalChunks(config),
|
|
426
|
+
k: config.erasure_k,
|
|
427
|
+
d: config.erasure_d,
|
|
428
|
+
chunkSizeBytes: config.chunkSizeBytes
|
|
429
|
+
}),
|
|
430
|
+
createDecoder({
|
|
431
|
+
n: getTotalChunks(config),
|
|
432
|
+
k: config.erasure_k,
|
|
433
|
+
d: config.erasure_d,
|
|
434
|
+
chunkSizeBytes: config.chunkSizeBytes,
|
|
435
|
+
erasedChunkIndexes: []
|
|
436
|
+
})
|
|
437
|
+
]);
|
|
438
|
+
return provider;
|
|
439
|
+
}
|
|
440
|
+
encode(data) {
|
|
441
|
+
const { erasure_k, chunkSizeBytes } = this.config;
|
|
442
|
+
if (!this.encoderCache) {
|
|
443
|
+
throw new Error("Encoder cache is missing");
|
|
444
|
+
}
|
|
445
|
+
const systematicCapacity = erasure_k * chunkSizeBytes;
|
|
446
|
+
if (data.byteLength > systematicCapacity) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Data size ${data.byteLength} bytes exceeds systematic capacity ${systematicCapacity} bytes (erasure_k=${erasure_k} * chunkSizeBytes=${chunkSizeBytes}).`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (this.requiresPadding(data.length)) {
|
|
452
|
+
throw new Error(
|
|
453
|
+
`Data size ${data.length} bytes does not match systematic capacity ${systematicCapacity} bytes (erasure_k=${erasure_k} * chunkSizeBytes=${chunkSizeBytes}). Provide pre-padded data before encoding.`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
this.lastFunction = "encoded" /* ENCODED */;
|
|
457
|
+
return this.encoderCache.erasureCode(data);
|
|
458
|
+
}
|
|
459
|
+
decode(available, config) {
|
|
460
|
+
if (!this.decoderCache) {
|
|
461
|
+
throw new Error("Decoder cache is missing");
|
|
462
|
+
}
|
|
463
|
+
const { erasure_k, chunkSizeBytes } = this.config;
|
|
464
|
+
if (available.length < erasure_k) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Insufficient available chunks: need at least ${erasure_k}, received ${available.length}.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
for (let i = 0; i < available.length; i++) {
|
|
470
|
+
const chunk = available[i];
|
|
471
|
+
if (chunk.length !== chunkSizeBytes) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Chunk ${i} size ${chunk.length} bytes does not match expected chunkSizeBytes ${chunkSizeBytes}.`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
this.lastFunction = "decoded" /* DECODED */;
|
|
478
|
+
return this.decoderCache.decode(available, config);
|
|
479
|
+
}
|
|
480
|
+
getChunkMerkleRoots() {
|
|
481
|
+
if (this.decoderCache && this.lastFunction === "decoded" /* DECODED */)
|
|
482
|
+
return this.decoderCache.getChunkMerkleRoots();
|
|
483
|
+
if (this.encoderCache && this.lastFunction === "encoded" /* ENCODED */)
|
|
484
|
+
return this.encoderCache.getChunkMerkleRoots();
|
|
485
|
+
throw new Error(
|
|
486
|
+
"You must call encode or decode before calling getChunkMerkleRoots"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Determines if data can be erasure coded as-is or requires padding.
|
|
491
|
+
*
|
|
492
|
+
* Data can be erasure coded without padding if its size exactly matches
|
|
493
|
+
* the total systematic data capacity (k * chunkSizeBytes).
|
|
494
|
+
*
|
|
495
|
+
* @param dataSize - Size of the data in bytes
|
|
496
|
+
* @returns true if data needs padding, false if it can be coded as-is
|
|
497
|
+
*/
|
|
498
|
+
requiresPadding(dataSize) {
|
|
499
|
+
const { erasure_k, chunkSizeBytes } = this.config;
|
|
500
|
+
const systematicCapacity = erasure_k * chunkSizeBytes;
|
|
501
|
+
return dataSize !== systematicCapacity;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/core/erasure/default.ts
|
|
506
|
+
var defaultProviderPromise;
|
|
507
|
+
function createDefaultErasureCodingProvider() {
|
|
508
|
+
if (!defaultProviderPromise) {
|
|
509
|
+
defaultProviderPromise = ClayErasureCodingProvider.create(
|
|
510
|
+
defaultErasureCodingConfig()
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
return defaultProviderPromise;
|
|
514
|
+
}
|
|
515
|
+
function defaultErasureCodingConfig() {
|
|
516
|
+
const encoding = Number(process.env.SHELBY_ENCODING) || 0;
|
|
517
|
+
if (encoding === 0) {
|
|
518
|
+
return erasureCodingConfig16Total10Data13Helper();
|
|
519
|
+
}
|
|
520
|
+
return erasureCodingConfig4Total2Data3Helper();
|
|
521
|
+
}
|
|
522
|
+
function erasureCodingConfig16Total10Data13Helper() {
|
|
523
|
+
return erasureCodingConfig(
|
|
524
|
+
"ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
function erasureCodingConfig4Total2Data3Helper() {
|
|
528
|
+
return erasureCodingConfig("ClayCode_4Total_2Data_3Helper" /* ClayCode_4Total_2Data_3Helper */);
|
|
529
|
+
}
|
|
530
|
+
function erasureCodingConfig(encodingScheme) {
|
|
531
|
+
return {
|
|
532
|
+
erasure_n: ERASURE_CODE_PARAMS[encodingScheme].erasure_n,
|
|
533
|
+
erasure_k: ERASURE_CODE_PARAMS[encodingScheme].erasure_k,
|
|
534
|
+
erasure_d: ERASURE_CODE_PARAMS[encodingScheme].erasure_d,
|
|
535
|
+
chunkSizeBytes: DEFAULT_CHUNK_SIZE_BYTES,
|
|
536
|
+
enumIndex: ERASURE_CODE_PARAMS[encodingScheme].enumIndex
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/core/erasure/reed-solomon.ts
|
|
541
|
+
import { createWasmReedSolomonBinding } from "@shelby-protocol/reed-solomon";
|
|
542
|
+
var DEFAULT_ERASURE_K2 = 10;
|
|
543
|
+
var DEFAULT_ERASURE_N2 = 16;
|
|
544
|
+
var DEFAULT_CHUNK_SIZE_BYTES2 = 2 * 1024 * 1024;
|
|
545
|
+
var ReedSolomonErasureCodingProvider = class {
|
|
546
|
+
config;
|
|
547
|
+
constructor(options) {
|
|
548
|
+
const erasure_k = options?.erasure_k ?? DEFAULT_ERASURE_K2;
|
|
549
|
+
const erasure_n = options?.erasure_n ?? DEFAULT_ERASURE_N2;
|
|
550
|
+
const chunkSizeBytes = options?.chunkSizeBytes ?? DEFAULT_CHUNK_SIZE_BYTES2;
|
|
551
|
+
const enumIndex = -1;
|
|
552
|
+
if (erasure_k <= 0)
|
|
553
|
+
throw new Error("erasure_k (number of data chunks) must be > 0");
|
|
554
|
+
if (erasure_n <= erasure_k)
|
|
555
|
+
throw new Error(
|
|
556
|
+
`erasure_n (${erasure_n}) must be > erasure_k (${erasure_k})`
|
|
557
|
+
);
|
|
558
|
+
if (chunkSizeBytes <= 0) throw new Error("chunkSizeBytes must be > 0");
|
|
559
|
+
this.config = {
|
|
560
|
+
erasure_n,
|
|
561
|
+
erasure_k,
|
|
562
|
+
chunkSizeBytes,
|
|
563
|
+
enumIndex
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
encode(data) {
|
|
567
|
+
const { erasure_k, erasure_n, chunkSizeBytes } = this.config;
|
|
568
|
+
const erasure_m = erasure_n - erasure_k;
|
|
569
|
+
const systematicCapacity = erasure_k * chunkSizeBytes;
|
|
570
|
+
if (data.byteLength > systematicCapacity) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Data size ${data.byteLength} bytes exceeds systematic capacity ${systematicCapacity} bytes (erasure_k=${erasure_k} * chunkSizeBytes=${chunkSizeBytes}).`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (data.byteLength !== systematicCapacity) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`Data size ${data.byteLength} bytes does not match systematic capacity ${systematicCapacity} bytes (erasure_k=${erasure_k} * chunkSizeBytes=${chunkSizeBytes}). Provide pre-padded data before encoding.`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const reedSolomon = createWasmReedSolomonBinding();
|
|
581
|
+
const shards = new Uint8Array(
|
|
582
|
+
systematicCapacity + erasure_m * chunkSizeBytes
|
|
583
|
+
);
|
|
584
|
+
shards.set(data, 0);
|
|
585
|
+
reedSolomon.encode(shards, erasure_k, erasure_m);
|
|
586
|
+
const chunks = new Array(erasure_n);
|
|
587
|
+
for (let idx = 0; idx < erasure_n; idx++) {
|
|
588
|
+
const start = idx * chunkSizeBytes;
|
|
589
|
+
chunks[idx] = shards.slice(start, start + chunkSizeBytes);
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
chunks,
|
|
593
|
+
systematic: chunks.slice(0, erasure_k),
|
|
594
|
+
parity: chunks.slice(erasure_k)
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
decode(_available, _config) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
"ReedSolomonErasureCodingProvider does not implement decode"
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
getChunkMerkleRoots() {
|
|
603
|
+
throw new Error(
|
|
604
|
+
"ReedSolomonErasureCodingProvider does not implement getChunkMerkleRoots"
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/core/operations/index.ts
|
|
610
|
+
import { Network as Network3 } from "@aptos-labs/ts-sdk";
|
|
611
|
+
import { GraphQLClient } from "graphql-request";
|
|
612
|
+
|
|
613
|
+
// src/core/clients/utils.ts
|
|
614
|
+
import { AptosConfig } from "@aptos-labs/ts-sdk";
|
|
615
|
+
var getAptosConfig = (config) => {
|
|
616
|
+
const baseFaucetConfig = config.faucet?.authToken ? { AUTH_TOKEN: config.faucet.authToken } : void 0;
|
|
617
|
+
if (config.aptos) {
|
|
618
|
+
return new AptosConfig({
|
|
619
|
+
// Spread user's aptos config first
|
|
620
|
+
...config.aptos,
|
|
621
|
+
// Network from top-level is authoritative - cannot be overridden
|
|
622
|
+
network: config.network,
|
|
623
|
+
// Merge clientConfig with API_KEY default
|
|
624
|
+
clientConfig: {
|
|
625
|
+
API_KEY: config.apiKey,
|
|
626
|
+
...config.aptos.clientConfig
|
|
627
|
+
},
|
|
628
|
+
// Merge faucetConfig: aptos config first, then base authToken (authoritative)
|
|
629
|
+
faucetConfig: {
|
|
630
|
+
...config.aptos.faucetConfig,
|
|
631
|
+
...baseFaucetConfig
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return new AptosConfig({
|
|
636
|
+
network: config.network,
|
|
637
|
+
clientConfig: {
|
|
638
|
+
API_KEY: config.apiKey
|
|
639
|
+
},
|
|
640
|
+
faucetConfig: baseFaucetConfig
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
var getShelbyRPCBaseUrl = (config) => {
|
|
644
|
+
return config.rpc?.baseUrl ?? NetworkToShelbyRPCBaseUrl[config.network] ?? NetworkToShelbyRPCBaseUrl.testnet;
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/core/operations/generated/sdk.ts
|
|
648
|
+
import gql from "graphql-tag";
|
|
649
|
+
var Blob_Activities_Select_Column = /* @__PURE__ */ ((Blob_Activities_Select_Column2) => {
|
|
650
|
+
Blob_Activities_Select_Column2["BlobName"] = "blob_name";
|
|
651
|
+
Blob_Activities_Select_Column2["EventIndex"] = "event_index";
|
|
652
|
+
Blob_Activities_Select_Column2["EventType"] = "event_type";
|
|
653
|
+
Blob_Activities_Select_Column2["Owner"] = "owner";
|
|
654
|
+
Blob_Activities_Select_Column2["Timestamp"] = "timestamp";
|
|
655
|
+
Blob_Activities_Select_Column2["TransactionHash"] = "transaction_hash";
|
|
656
|
+
Blob_Activities_Select_Column2["TransactionVersion"] = "transaction_version";
|
|
657
|
+
return Blob_Activities_Select_Column2;
|
|
658
|
+
})(Blob_Activities_Select_Column || {});
|
|
659
|
+
var Blobs_Select_Column = /* @__PURE__ */ ((Blobs_Select_Column2) => {
|
|
660
|
+
Blobs_Select_Column2["BlobCommitment"] = "blob_commitment";
|
|
661
|
+
Blobs_Select_Column2["BlobName"] = "blob_name";
|
|
662
|
+
Blobs_Select_Column2["CreatedAt"] = "created_at";
|
|
663
|
+
Blobs_Select_Column2["ExpiresAt"] = "expires_at";
|
|
664
|
+
Blobs_Select_Column2["IsDeleted"] = "is_deleted";
|
|
665
|
+
Blobs_Select_Column2["IsWritten"] = "is_written";
|
|
666
|
+
Blobs_Select_Column2["NumChunksets"] = "num_chunksets";
|
|
667
|
+
Blobs_Select_Column2["Owner"] = "owner";
|
|
668
|
+
Blobs_Select_Column2["PlacementGroup"] = "placement_group";
|
|
669
|
+
Blobs_Select_Column2["Size"] = "size";
|
|
670
|
+
Blobs_Select_Column2["SliceAddress"] = "slice_address";
|
|
671
|
+
Blobs_Select_Column2["UpdatedAt"] = "updated_at";
|
|
672
|
+
return Blobs_Select_Column2;
|
|
673
|
+
})(Blobs_Select_Column || {});
|
|
674
|
+
var Cursor_Ordering = /* @__PURE__ */ ((Cursor_Ordering2) => {
|
|
675
|
+
Cursor_Ordering2["Asc"] = "ASC";
|
|
676
|
+
Cursor_Ordering2["Desc"] = "DESC";
|
|
677
|
+
return Cursor_Ordering2;
|
|
678
|
+
})(Cursor_Ordering || {});
|
|
679
|
+
var Order_By = /* @__PURE__ */ ((Order_By2) => {
|
|
680
|
+
Order_By2["Asc"] = "asc";
|
|
681
|
+
Order_By2["AscNullsFirst"] = "asc_nulls_first";
|
|
682
|
+
Order_By2["AscNullsLast"] = "asc_nulls_last";
|
|
683
|
+
Order_By2["Desc"] = "desc";
|
|
684
|
+
Order_By2["DescNullsFirst"] = "desc_nulls_first";
|
|
685
|
+
Order_By2["DescNullsLast"] = "desc_nulls_last";
|
|
686
|
+
return Order_By2;
|
|
687
|
+
})(Order_By || {});
|
|
688
|
+
var Placement_Group_Slots_Select_Column = /* @__PURE__ */ ((Placement_Group_Slots_Select_Column2) => {
|
|
689
|
+
Placement_Group_Slots_Select_Column2["PlacementGroup"] = "placement_group";
|
|
690
|
+
Placement_Group_Slots_Select_Column2["SlotIndex"] = "slot_index";
|
|
691
|
+
Placement_Group_Slots_Select_Column2["Status"] = "status";
|
|
692
|
+
Placement_Group_Slots_Select_Column2["StorageProvider"] = "storage_provider";
|
|
693
|
+
Placement_Group_Slots_Select_Column2["UpdatedAt"] = "updated_at";
|
|
694
|
+
return Placement_Group_Slots_Select_Column2;
|
|
695
|
+
})(Placement_Group_Slots_Select_Column || {});
|
|
696
|
+
var Processor_Status_Select_Column = /* @__PURE__ */ ((Processor_Status_Select_Column2) => {
|
|
697
|
+
Processor_Status_Select_Column2["LastSuccessVersion"] = "last_success_version";
|
|
698
|
+
Processor_Status_Select_Column2["LastTransactionTimestamp"] = "last_transaction_timestamp";
|
|
699
|
+
Processor_Status_Select_Column2["LastUpdated"] = "last_updated";
|
|
700
|
+
return Processor_Status_Select_Column2;
|
|
701
|
+
})(Processor_Status_Select_Column || {});
|
|
702
|
+
var GetBlobsDocument = gql`
|
|
703
|
+
query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
|
|
704
|
+
blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
|
|
705
|
+
owner
|
|
706
|
+
blob_commitment
|
|
707
|
+
blob_name
|
|
708
|
+
created_at
|
|
709
|
+
expires_at
|
|
710
|
+
num_chunksets
|
|
711
|
+
is_deleted
|
|
712
|
+
is_written
|
|
713
|
+
placement_group
|
|
714
|
+
size
|
|
715
|
+
updated_at
|
|
716
|
+
slice_address
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
`;
|
|
720
|
+
var GetBlobActivitiesDocument = gql`
|
|
721
|
+
query getBlobActivities($where: blob_activities_bool_exp, $orderBy: [blob_activities_order_by!], $limit: Int, $offset: Int) {
|
|
722
|
+
blob_activities(
|
|
723
|
+
where: $where
|
|
724
|
+
order_by: $orderBy
|
|
725
|
+
limit: $limit
|
|
726
|
+
offset: $offset
|
|
727
|
+
) {
|
|
728
|
+
blob_name
|
|
729
|
+
event_index
|
|
730
|
+
event_type
|
|
731
|
+
transaction_hash
|
|
732
|
+
transaction_version
|
|
733
|
+
timestamp
|
|
734
|
+
owner
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
`;
|
|
738
|
+
var GetBlobsCountDocument = gql`
|
|
739
|
+
query getBlobsCount($where: blobs_bool_exp) {
|
|
740
|
+
blobs_aggregate(where: $where) {
|
|
741
|
+
aggregate {
|
|
742
|
+
count
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
`;
|
|
747
|
+
var GetBlobActivitiesCountDocument = gql`
|
|
748
|
+
query getBlobActivitiesCount($where: blob_activities_bool_exp) {
|
|
749
|
+
blob_activities_aggregate(where: $where) {
|
|
750
|
+
aggregate {
|
|
751
|
+
count
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
`;
|
|
756
|
+
var GetTotalBlobsSizeDocument = gql`
|
|
757
|
+
query getTotalBlobsSize($where: blobs_bool_exp) {
|
|
758
|
+
blobs_aggregate(where: $where) {
|
|
759
|
+
aggregate {
|
|
760
|
+
sum {
|
|
761
|
+
size
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
`;
|
|
767
|
+
var GetPlacementGroupSlotsDocument = gql`
|
|
768
|
+
query getPlacementGroupSlots($where: placement_group_slots_bool_exp, $orderBy: [placement_group_slots_order_by!], $limit: Int, $offset: Int) {
|
|
769
|
+
placement_group_slots(
|
|
770
|
+
where: $where
|
|
771
|
+
order_by: $orderBy
|
|
772
|
+
limit: $limit
|
|
773
|
+
offset: $offset
|
|
774
|
+
) {
|
|
775
|
+
placement_group
|
|
776
|
+
slot_index
|
|
777
|
+
storage_provider
|
|
778
|
+
status
|
|
779
|
+
updated_at
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
`;
|
|
783
|
+
var GetPlacementGroupSlotsCountDocument = gql`
|
|
784
|
+
query getPlacementGroupSlotsCount($where: placement_group_slots_bool_exp) {
|
|
785
|
+
placement_group_slots_aggregate(where: $where) {
|
|
786
|
+
aggregate {
|
|
787
|
+
count
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
`;
|
|
792
|
+
var GetProcessorStatusDocument = gql`
|
|
793
|
+
query getProcessorStatus {
|
|
794
|
+
processor_status {
|
|
795
|
+
last_success_version
|
|
796
|
+
last_transaction_timestamp
|
|
797
|
+
last_updated
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
`;
|
|
801
|
+
var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
|
|
802
|
+
function getSdk(client, withWrapper = defaultWrapper) {
|
|
803
|
+
return {
|
|
804
|
+
getBlobs(variables, requestHeaders, signal) {
|
|
805
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobs", "query", variables);
|
|
806
|
+
},
|
|
807
|
+
getBlobActivities(variables, requestHeaders, signal) {
|
|
808
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivities", "query", variables);
|
|
809
|
+
},
|
|
810
|
+
getBlobsCount(variables, requestHeaders, signal) {
|
|
811
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobsCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobsCount", "query", variables);
|
|
812
|
+
},
|
|
813
|
+
getBlobActivitiesCount(variables, requestHeaders, signal) {
|
|
814
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivitiesCount", "query", variables);
|
|
815
|
+
},
|
|
816
|
+
getTotalBlobsSize(variables, requestHeaders, signal) {
|
|
817
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetTotalBlobsSizeDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getTotalBlobsSize", "query", variables);
|
|
818
|
+
},
|
|
819
|
+
getPlacementGroupSlots(variables, requestHeaders, signal) {
|
|
820
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetPlacementGroupSlotsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getPlacementGroupSlots", "query", variables);
|
|
821
|
+
},
|
|
822
|
+
getPlacementGroupSlotsCount(variables, requestHeaders, signal) {
|
|
823
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetPlacementGroupSlotsCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getPlacementGroupSlotsCount", "query", variables);
|
|
824
|
+
},
|
|
825
|
+
getProcessorStatus(variables, requestHeaders, signal) {
|
|
826
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetProcessorStatusDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getProcessorStatus", "query", variables);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/core/operations/index.ts
|
|
832
|
+
function createShelbyIndexerClient(baseUrl, options) {
|
|
833
|
+
const graphqlClient = new GraphQLClient(baseUrl, options);
|
|
834
|
+
return getSdk(graphqlClient);
|
|
835
|
+
}
|
|
836
|
+
function getShelbyIndexerClient(config) {
|
|
837
|
+
const aptosConfig = getAptosConfig(config);
|
|
838
|
+
let { apiKey: indexerApiKey, baseUrl } = config.indexer ?? {};
|
|
839
|
+
if (!baseUrl) {
|
|
840
|
+
switch (aptosConfig?.network) {
|
|
841
|
+
// TODO: Add endpoints for core networks
|
|
842
|
+
case Network3.MAINNET:
|
|
843
|
+
case Network3.TESTNET:
|
|
844
|
+
case Network3.DEVNET:
|
|
845
|
+
case Network3.SHELBYNET:
|
|
846
|
+
case Network3.LOCAL:
|
|
847
|
+
baseUrl = NetworkToShelbyBlobIndexerBaseUrl[aptosConfig.network];
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (baseUrl === void 0) {
|
|
852
|
+
throw new Error(
|
|
853
|
+
"The ShelbyClientConfig does not support the Shelby indexer. Please configure the endpoint in the ShelbyClientConfig.indexer.endpoint or provide a supported Aptos network."
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
const apiKey = indexerApiKey ?? config.apiKey;
|
|
857
|
+
return createShelbyIndexerClient(baseUrl, {
|
|
858
|
+
headers: {
|
|
859
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
860
|
+
"x-aptos-client": "shelby-ts-sdk"
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/core/clients/ShelbyBlobClient.ts
|
|
866
|
+
var MissingTransactionSubmitterError = class extends Error {
|
|
867
|
+
constructor() {
|
|
868
|
+
super(
|
|
869
|
+
"USD sponsorship (usdSponsor) requires a transaction submitter to be configured. The sponsored transaction is a multi-agent transaction that must be co-signed by the sponsor. Configure a transaction submitter via aptos.pluginSettings.TRANSACTION_SUBMITTER that routes transactions to a service (like a gas station) capable of providing the sponsor's signature."
|
|
870
|
+
);
|
|
871
|
+
this.name = "MissingTransactionSubmitterError";
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
875
|
+
aptos;
|
|
876
|
+
deployer;
|
|
877
|
+
indexer;
|
|
878
|
+
defaultOptions;
|
|
879
|
+
orderless;
|
|
880
|
+
/**
|
|
881
|
+
* The ShelbyBlobClient is used to interact with the Shelby contract on the Aptos blockchain. This
|
|
882
|
+
* includes functions for registering blob commitments and retrieving blob metadata.
|
|
883
|
+
*
|
|
884
|
+
* @param config - The client configuration object.
|
|
885
|
+
* @param config.network - The Shelby network to use.
|
|
886
|
+
* @param defaultOptions - Optional default options for blob operations.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* const blobClient = new ShelbyBlobClient({
|
|
891
|
+
* aptos: {
|
|
892
|
+
* network: Network.SHELBYNET,
|
|
893
|
+
* clientConfig: {
|
|
894
|
+
* API_KEY: "AG-***",
|
|
895
|
+
* },
|
|
896
|
+
* },
|
|
897
|
+
* });
|
|
898
|
+
* ```
|
|
899
|
+
*
|
|
900
|
+
* @example
|
|
901
|
+
* ```typescript
|
|
902
|
+
* // With default options for USD sponsorship
|
|
903
|
+
* const blobClient = new ShelbyBlobClient(
|
|
904
|
+
* {
|
|
905
|
+
* network: Network.SHELBYNET,
|
|
906
|
+
* aptos: {
|
|
907
|
+
* pluginSettings: {
|
|
908
|
+
* TRANSACTION_SUBMITTER: myGasStationSubmitter,
|
|
909
|
+
* },
|
|
910
|
+
* },
|
|
911
|
+
* },
|
|
912
|
+
* {
|
|
913
|
+
* usdSponsor: { feePayerAddress: sponsorAddress },
|
|
914
|
+
* }
|
|
915
|
+
* );
|
|
916
|
+
* ```
|
|
917
|
+
*/
|
|
918
|
+
constructor(config, defaultOptions) {
|
|
919
|
+
this.aptos = new Aptos(getAptosConfig(config));
|
|
920
|
+
this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
|
|
921
|
+
this.indexer = getShelbyIndexerClient(config);
|
|
922
|
+
this.defaultOptions = defaultOptions ?? {};
|
|
923
|
+
this.orderless = config.orderless ?? false;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Merges method-level options with default options, giving precedence to method-level values.
|
|
927
|
+
*/
|
|
928
|
+
mergeOptions(options) {
|
|
929
|
+
return {
|
|
930
|
+
build: options?.build ?? this.defaultOptions.build,
|
|
931
|
+
submit: options?.submit ?? this.defaultOptions.submit,
|
|
932
|
+
usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
|
|
933
|
+
chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Validates that if USD sponsorship is requested, a transaction submitter is
|
|
938
|
+
* configured as well. Checks both the client-level and method-level transaction
|
|
939
|
+
* submitter configurations. Throws MissingTransactionSubmitterError if usdSponsor is
|
|
940
|
+
* provided without a transaction submitter.
|
|
941
|
+
*/
|
|
942
|
+
validateUsdSponsorConfig(usdSponsor, submit) {
|
|
943
|
+
if (!usdSponsor) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const hasClientSubmitter = this.aptos.config.getTransactionSubmitter();
|
|
947
|
+
const hasMethodSubmitter = submit?.transactionSubmitter !== void 0 && submit?.transactionSubmitter !== null;
|
|
948
|
+
if (!hasClientSubmitter && !hasMethodSubmitter) {
|
|
949
|
+
throw new MissingTransactionSubmitterError();
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Merges orderless replay protection into transaction options when
|
|
954
|
+
* `config.orderless` is enabled. If the caller already supplies options
|
|
955
|
+
* (e.g. custom gas limits), those are preserved and the nonce is injected
|
|
956
|
+
* alongside them. When orderless mode is off, user options are returned as-is.
|
|
957
|
+
*/
|
|
958
|
+
orderlessTxOptions(userOptions) {
|
|
959
|
+
if (!this.orderless) return userOptions;
|
|
960
|
+
return {
|
|
961
|
+
replayProtectionNonce: crypto.getRandomValues(new Uint32Array(1))[0],
|
|
962
|
+
...userOptions
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Retrieves the blob metadata from the blockchain. If it does not exist,
|
|
967
|
+
* returns `undefined`.
|
|
968
|
+
*
|
|
969
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
970
|
+
* @param params.name - The name of the blob (e.g. "foo/bar")
|
|
971
|
+
* @returns The blob metadata.
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* ```typescript
|
|
975
|
+
* const metadata = await client.getBlobMetadata({
|
|
976
|
+
* account: AccountAddress.fromString("0x1"),
|
|
977
|
+
* name: "foo/bar.txt",
|
|
978
|
+
* });
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
async getBlobMetadata(params) {
|
|
982
|
+
try {
|
|
983
|
+
const rawMetadata = await this.aptos.view({
|
|
984
|
+
payload: {
|
|
985
|
+
function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
|
|
986
|
+
functionArguments: [
|
|
987
|
+
createBlobKey({
|
|
988
|
+
account: params.account,
|
|
989
|
+
blobName: params.name
|
|
990
|
+
})
|
|
991
|
+
]
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
if (!rawMetadata?.[0]?.vec?.[0]) {
|
|
995
|
+
return void 0;
|
|
996
|
+
}
|
|
997
|
+
const metadata = rawMetadata[0].vec[0];
|
|
998
|
+
let encoding;
|
|
999
|
+
if (metadata.encoding.__variant__ === "ClayCode_16Total_10Data_13Helper") {
|
|
1000
|
+
encoding = {
|
|
1001
|
+
variant: "clay",
|
|
1002
|
+
...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
|
|
1003
|
+
...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
|
|
1004
|
+
};
|
|
1005
|
+
} else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
|
|
1006
|
+
encoding = {
|
|
1007
|
+
variant: "clay",
|
|
1008
|
+
...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
|
|
1009
|
+
...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
|
|
1010
|
+
};
|
|
1011
|
+
} else {
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
"Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
blobMerkleRoot: Hex3.fromHexInput(
|
|
1018
|
+
metadata.blob_commitment
|
|
1019
|
+
).toUint8Array(),
|
|
1020
|
+
owner: normalizeAddress(metadata.owner),
|
|
1021
|
+
name: params.name,
|
|
1022
|
+
blobNameSuffix: getBlobNameSuffix(params.name),
|
|
1023
|
+
size: Number(metadata.blob_size),
|
|
1024
|
+
encoding,
|
|
1025
|
+
expirationMicros: Number(metadata.expiration_micros),
|
|
1026
|
+
creationMicros: Number(metadata.creation_micros),
|
|
1027
|
+
sliceAddress: normalizeAddress(metadata.slice.inner),
|
|
1028
|
+
isWritten: metadata.is_written
|
|
1029
|
+
};
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
1032
|
+
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
throw error;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Retrieves all the blobs and their metadata for an account from the
|
|
1040
|
+
* blockchain.
|
|
1041
|
+
*
|
|
1042
|
+
* @param params.account - The account namespace the blobs are stored in (e.g. "0x1")
|
|
1043
|
+
* @param params.pagination (optional) - The pagination options.
|
|
1044
|
+
* @param params.orderBy (optional) - The order by clause to sort the blobs by.
|
|
1045
|
+
* @returns The blob metadata for all the blobs for the account.
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```typescript
|
|
1049
|
+
* // BlobMetadata[]
|
|
1050
|
+
* const blobs = await client.getAccountBlobs({
|
|
1051
|
+
* account: AccountAddress.fromString("0x1"),
|
|
1052
|
+
* });
|
|
1053
|
+
* ```
|
|
1054
|
+
*/
|
|
1055
|
+
getAccountBlobs(params) {
|
|
1056
|
+
const { where, ...rest } = params;
|
|
1057
|
+
return this.getBlobs({
|
|
1058
|
+
where: {
|
|
1059
|
+
...where,
|
|
1060
|
+
owner: { _eq: normalizeAddress(params.account).toString() }
|
|
1061
|
+
},
|
|
1062
|
+
pagination: rest.pagination,
|
|
1063
|
+
orderBy: rest.orderBy
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Retrieves blobs and their metadata from the blockchain.
|
|
1068
|
+
*
|
|
1069
|
+
* @param params.where (optional) - The where clause to filter the blobs by.
|
|
1070
|
+
* @param params.pagination (optional) - The pagination options.
|
|
1071
|
+
* @param params.orderBy (optional) - The order by clause to sort the blobs by.
|
|
1072
|
+
* @returns The blob metadata for all the blobs that match the where clause.
|
|
1073
|
+
*
|
|
1074
|
+
* @example
|
|
1075
|
+
* ```typescript
|
|
1076
|
+
* // BlobMetadata[]
|
|
1077
|
+
* const blobs = await client.getBlobs({
|
|
1078
|
+
* where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
|
|
1079
|
+
* });
|
|
1080
|
+
* ```
|
|
1081
|
+
*/
|
|
1082
|
+
async getBlobs(params = {}) {
|
|
1083
|
+
const { limit, offset } = params.pagination ?? {};
|
|
1084
|
+
const { orderBy, where } = params;
|
|
1085
|
+
const currentMicros = String(Date.now() * 1e3);
|
|
1086
|
+
const defaultActiveFilter = {
|
|
1087
|
+
expires_at: { _gte: currentMicros },
|
|
1088
|
+
is_deleted: { _eq: "0" }
|
|
1089
|
+
};
|
|
1090
|
+
const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
|
|
1091
|
+
const { blobs } = await this.indexer.getBlobs({
|
|
1092
|
+
where: finalWhere,
|
|
1093
|
+
limit,
|
|
1094
|
+
offset,
|
|
1095
|
+
orderBy
|
|
1096
|
+
});
|
|
1097
|
+
return blobs.map(
|
|
1098
|
+
(blob) => ({
|
|
1099
|
+
owner: normalizeAddress(blob.owner),
|
|
1100
|
+
name: blob.blob_name,
|
|
1101
|
+
blobNameSuffix: getBlobNameSuffix(blob.blob_name),
|
|
1102
|
+
blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
|
|
1103
|
+
size: Number(blob.size),
|
|
1104
|
+
// TODO: Add encoding when supported in NCI
|
|
1105
|
+
encoding: {
|
|
1106
|
+
variant: "clay",
|
|
1107
|
+
...ERASURE_CODE_PARAMS.ClayCode_16Total_10Data_13Helper,
|
|
1108
|
+
...ERASURE_CODE_AND_CHUNK_MAPPING.ClayCode_16Total_10Data_13Helper
|
|
1109
|
+
},
|
|
1110
|
+
expirationMicros: Number(blob.expires_at),
|
|
1111
|
+
creationMicros: Number(blob.created_at),
|
|
1112
|
+
sliceAddress: normalizeAddress(blob.slice_address),
|
|
1113
|
+
isWritten: Boolean(Number(blob.is_written)),
|
|
1114
|
+
isDeleted: Boolean(Number(blob.is_deleted))
|
|
1115
|
+
})
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
async getBlobActivities(params) {
|
|
1119
|
+
const { limit, offset } = params.pagination ?? {};
|
|
1120
|
+
const { orderBy, where } = params;
|
|
1121
|
+
const { blob_activities } = await this.indexer.getBlobActivities({
|
|
1122
|
+
where,
|
|
1123
|
+
limit,
|
|
1124
|
+
offset,
|
|
1125
|
+
orderBy
|
|
1126
|
+
});
|
|
1127
|
+
const activityTypeMapping = {
|
|
1128
|
+
[`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
|
|
1129
|
+
[`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
|
|
1130
|
+
[`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
|
|
1131
|
+
[`${this.deployer.toStringLong()}::blob_metadata::BlobWrittenEvent`]: "write_blob"
|
|
1132
|
+
};
|
|
1133
|
+
return blob_activities.map(
|
|
1134
|
+
(activity) => ({
|
|
1135
|
+
blobName: activity.blob_name,
|
|
1136
|
+
accountAddress: normalizeAddress(activity.blob_name.substring(1, 65)),
|
|
1137
|
+
type: activityTypeMapping[activity.event_type] ?? "unknown",
|
|
1138
|
+
eventType: activity.event_type,
|
|
1139
|
+
eventIndex: Number(activity.event_index),
|
|
1140
|
+
transactionHash: activity.transaction_hash,
|
|
1141
|
+
// Using Number here in JS is technically not okay because txn version can be
|
|
1142
|
+
// as large as u64::MAX, but it should be fine for a long while.
|
|
1143
|
+
transactionVersion: Number(activity.transaction_version),
|
|
1144
|
+
timestamp: `${activity.timestamp}Z`
|
|
1145
|
+
})
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Retrieves the total number of blobs from the blockchain.
|
|
1150
|
+
*
|
|
1151
|
+
* @param params.where (optional) - The where clause to filter the blobs by.
|
|
1152
|
+
* @returns The total number of blobs.
|
|
1153
|
+
*
|
|
1154
|
+
* @example
|
|
1155
|
+
* ```typescript
|
|
1156
|
+
* const count = await client.getBlobsCount();
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
1159
|
+
async getBlobsCount(params) {
|
|
1160
|
+
const { where } = params;
|
|
1161
|
+
const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
|
|
1162
|
+
return blobs_aggregate?.aggregate?.count ?? 0;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Retrieves the total size of blobs from the blockchain.
|
|
1166
|
+
*
|
|
1167
|
+
* @param params.where (optional) - The where clause to filter the blobs by.
|
|
1168
|
+
* @returns The total size of blobs in bytes.
|
|
1169
|
+
*
|
|
1170
|
+
* @example
|
|
1171
|
+
* ```typescript
|
|
1172
|
+
* const size = await client.getTotalBlobsSize();
|
|
1173
|
+
* ```
|
|
1174
|
+
*/
|
|
1175
|
+
async getTotalBlobsSize(params = {}) {
|
|
1176
|
+
const { where } = params;
|
|
1177
|
+
const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({ where });
|
|
1178
|
+
return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Retrieves the total number of blob activities from the blockchain.
|
|
1182
|
+
*
|
|
1183
|
+
* @param params.where (optional) - The where clause to filter the blob activities by.
|
|
1184
|
+
* @returns The total number of blob activities.
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* ```typescript
|
|
1188
|
+
* const count = await client.getBlobActivitiesCount();
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
1191
|
+
async getBlobActivitiesCount(params) {
|
|
1192
|
+
const { where } = params;
|
|
1193
|
+
const { blob_activities_aggregate } = await this.indexer.getBlobActivitiesCount({ where });
|
|
1194
|
+
return blob_activities_aggregate?.aggregate?.count ?? 0;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Registers a blob on the blockchain by writing its merkle root and metadata.
|
|
1198
|
+
*
|
|
1199
|
+
* @param params.account - The account that is signing and paying for the transaction.
|
|
1200
|
+
* @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
1201
|
+
* @param params.blobMerkleRoot - The merkle root of the blob commitments.
|
|
1202
|
+
* @param params.size - The size of the blob in bytes.
|
|
1203
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
1204
|
+
* @param params.options - Optional transaction building options.
|
|
1205
|
+
* @param params.options.chunksetSizeBytes - Custom chunkset size (defaults to DEFAULT_CHUNKSET_SIZE_BYTES).
|
|
1206
|
+
* @param params.options.build - Additional Aptos transaction building options.
|
|
1207
|
+
*
|
|
1208
|
+
* @returns An object containing the pending transaction.
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```typescript
|
|
1212
|
+
* const provider = await ClayErasureCodingProvider.create();
|
|
1213
|
+
* const blobCommitments = await generateCommitments(provider, data);
|
|
1214
|
+
*
|
|
1215
|
+
* const { transaction } = await client.registerBlob({
|
|
1216
|
+
* account: signer,
|
|
1217
|
+
* blobName: "foo/bar.txt",
|
|
1218
|
+
* blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
1219
|
+
* size: data.length,
|
|
1220
|
+
* expirationMicros: Date.now() * 1000 + 3600_000_000, // 1 hour from now in microseconds
|
|
1221
|
+
* });
|
|
1222
|
+
* ```
|
|
1223
|
+
*/
|
|
1224
|
+
async registerBlob(params) {
|
|
1225
|
+
const options = this.mergeOptions(params.options);
|
|
1226
|
+
this.validateUsdSponsorConfig(options.usdSponsor, options.submit);
|
|
1227
|
+
const config = params.config ?? defaultErasureCodingConfig();
|
|
1228
|
+
const chunksetSize = config.chunkSizeBytes * config.erasure_k;
|
|
1229
|
+
const buildArgs = {
|
|
1230
|
+
...options.build,
|
|
1231
|
+
options: this.orderlessTxOptions(options.build?.options),
|
|
1232
|
+
data: _ShelbyBlobClient.createRegisterBlobPayload({
|
|
1233
|
+
deployer: this.deployer,
|
|
1234
|
+
account: params.account.accountAddress,
|
|
1235
|
+
blobName: params.blobName,
|
|
1236
|
+
blobSize: params.size,
|
|
1237
|
+
blobMerkleRoot: params.blobMerkleRoot,
|
|
1238
|
+
numChunksets: expectedTotalChunksets(params.size, chunksetSize),
|
|
1239
|
+
expirationMicros: params.expirationMicros,
|
|
1240
|
+
useSponsoredUsdVariant: options.usdSponsor !== void 0,
|
|
1241
|
+
encoding: config.enumIndex
|
|
1242
|
+
}),
|
|
1243
|
+
sender: params.account.accountAddress
|
|
1244
|
+
};
|
|
1245
|
+
const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
|
|
1246
|
+
...buildArgs,
|
|
1247
|
+
secondarySignerAddresses: [options.usdSponsor.feePayerAddress]
|
|
1248
|
+
}) : await this.aptos.transaction.build.simple(buildArgs);
|
|
1249
|
+
return {
|
|
1250
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
1251
|
+
signer: params.account,
|
|
1252
|
+
transaction,
|
|
1253
|
+
...options.submit
|
|
1254
|
+
})
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Deletes a blob on the blockchain.
|
|
1259
|
+
*
|
|
1260
|
+
* @param params.account - The account that is signing and paying for the transaction.
|
|
1261
|
+
* @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
1262
|
+
* @param params.options - Optional transaction building options.
|
|
1263
|
+
*
|
|
1264
|
+
* @returns An object containing the pending transaction.
|
|
1265
|
+
*
|
|
1266
|
+
* @example
|
|
1267
|
+
* ```typescript
|
|
1268
|
+
*
|
|
1269
|
+
* const { transaction } = await client.deleteBlob({
|
|
1270
|
+
* account: signer,
|
|
1271
|
+
* blobName: "foo/bar.txt",
|
|
1272
|
+
* });
|
|
1273
|
+
* ```
|
|
1274
|
+
*/
|
|
1275
|
+
async deleteBlob(params) {
|
|
1276
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
1277
|
+
options: this.orderlessTxOptions(params.options),
|
|
1278
|
+
data: _ShelbyBlobClient.createDeleteBlobPayload({
|
|
1279
|
+
deployer: this.deployer,
|
|
1280
|
+
blobName: params.blobName
|
|
1281
|
+
}),
|
|
1282
|
+
sender: params.account.accountAddress
|
|
1283
|
+
});
|
|
1284
|
+
return {
|
|
1285
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
1286
|
+
signer: params.account,
|
|
1287
|
+
transaction
|
|
1288
|
+
})
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Deletes multiple blobs on the blockchain in a single atomic transaction.
|
|
1293
|
+
*
|
|
1294
|
+
* **Note:** This function requires the `delete_multiple_blobs` entry function
|
|
1295
|
+
* which will be deployed to the smart contract on 2026-02-04. Using this
|
|
1296
|
+
* function before that date will result in a transaction failure.
|
|
1297
|
+
*
|
|
1298
|
+
* This operation is atomic: if any blob deletion fails (e.g., blob not found),
|
|
1299
|
+
* the entire transaction fails and no blobs are deleted.
|
|
1300
|
+
*
|
|
1301
|
+
* @param params.account - The account that is signing and paying for the transaction.
|
|
1302
|
+
* @param params.blobNames - Array of blob name suffixes without the account address prefix
|
|
1303
|
+
* (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
|
|
1304
|
+
* prefix is automatically derived from the signer.
|
|
1305
|
+
* @param params.options - Optional transaction building options.
|
|
1306
|
+
*
|
|
1307
|
+
* @returns An object containing the pending transaction.
|
|
1308
|
+
*
|
|
1309
|
+
* @example
|
|
1310
|
+
* ```typescript
|
|
1311
|
+
*
|
|
1312
|
+
* const { transaction } = await client.deleteMultipleBlobs({
|
|
1313
|
+
* account: signer,
|
|
1314
|
+
* blobNames: ["foo/bar.txt", "baz.txt"],
|
|
1315
|
+
* });
|
|
1316
|
+
* ```
|
|
1317
|
+
*/
|
|
1318
|
+
async deleteMultipleBlobs(params) {
|
|
1319
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
1320
|
+
options: this.orderlessTxOptions(params.options),
|
|
1321
|
+
data: _ShelbyBlobClient.createDeleteMultipleBlobsPayload({
|
|
1322
|
+
deployer: this.deployer,
|
|
1323
|
+
blobNames: params.blobNames
|
|
1324
|
+
}),
|
|
1325
|
+
sender: params.account.accountAddress
|
|
1326
|
+
});
|
|
1327
|
+
return {
|
|
1328
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
1329
|
+
signer: params.account,
|
|
1330
|
+
transaction
|
|
1331
|
+
})
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Registers multiple blobs on the blockchain by writing their merkle roots and metadata.
|
|
1336
|
+
*
|
|
1337
|
+
* @param params.account - The account that is signing and paying for the transaction.
|
|
1338
|
+
* @param params.expirationMicros - The expiration time of the blobs in microseconds.
|
|
1339
|
+
* @param params.blobs - The blobs to register.
|
|
1340
|
+
* @param params.blobs.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
1341
|
+
* @param params.blobs.blobSize - The size of the blob in bytes.
|
|
1342
|
+
* @param params.blobs.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
|
|
1343
|
+
* @param params.options - Optional transaction building options.
|
|
1344
|
+
* @param params.options.chunksetSizeBytes - Custom chunkset size (defaults to DEFAULT_CHUNKSET_SIZE_BYTES).
|
|
1345
|
+
* @param params.options.build - Additional Aptos transaction building options.
|
|
1346
|
+
*
|
|
1347
|
+
* @returns An object containing the pending transaction.
|
|
1348
|
+
*
|
|
1349
|
+
* @example
|
|
1350
|
+
* ```typescript
|
|
1351
|
+
* const provider = await ClayErasureCodingProvider.create();
|
|
1352
|
+
* const blobCommitments = await generateCommitments(provider, data);
|
|
1353
|
+
*
|
|
1354
|
+
* const { transaction } = await client.batchRegisterBlobs({
|
|
1355
|
+
* account: signer,
|
|
1356
|
+
* expirationMicros: Date.now() * 1000 + 3600_000_000, // 1 hour from now in microseconds
|
|
1357
|
+
* blobs: [
|
|
1358
|
+
* {
|
|
1359
|
+
* blobName: "foo/bar.txt",
|
|
1360
|
+
* blobSize: data.length,
|
|
1361
|
+
* blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
1362
|
+
* },
|
|
1363
|
+
* ],
|
|
1364
|
+
* });
|
|
1365
|
+
* ```
|
|
1366
|
+
*/
|
|
1367
|
+
async batchRegisterBlobs(params) {
|
|
1368
|
+
const options = this.mergeOptions(params.options);
|
|
1369
|
+
this.validateUsdSponsorConfig(options.usdSponsor, options.submit);
|
|
1370
|
+
const config = params.config ?? defaultErasureCodingConfig();
|
|
1371
|
+
const chunksetSize = config.chunkSizeBytes * config.erasure_k;
|
|
1372
|
+
const buildArgs = {
|
|
1373
|
+
...options.build,
|
|
1374
|
+
options: this.orderlessTxOptions(options.build?.options),
|
|
1375
|
+
sender: params.account.accountAddress,
|
|
1376
|
+
data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
|
|
1377
|
+
deployer: this.deployer,
|
|
1378
|
+
account: params.account.accountAddress,
|
|
1379
|
+
expirationMicros: params.expirationMicros,
|
|
1380
|
+
blobs: params.blobs.map((blob) => ({
|
|
1381
|
+
blobName: blob.blobName,
|
|
1382
|
+
blobSize: blob.blobSize,
|
|
1383
|
+
blobMerkleRoot: blob.blobMerkleRoot,
|
|
1384
|
+
numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
|
|
1385
|
+
})),
|
|
1386
|
+
useSponsoredUsdVariant: options.usdSponsor !== void 0,
|
|
1387
|
+
encoding: config.enumIndex
|
|
1388
|
+
})
|
|
1389
|
+
};
|
|
1390
|
+
const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
|
|
1391
|
+
...buildArgs,
|
|
1392
|
+
secondarySignerAddresses: [options.usdSponsor.feePayerAddress]
|
|
1393
|
+
}) : await this.aptos.transaction.build.simple(buildArgs);
|
|
1394
|
+
return {
|
|
1395
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
1396
|
+
signer: params.account,
|
|
1397
|
+
transaction,
|
|
1398
|
+
...options.submit
|
|
1399
|
+
})
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Creates a transaction payload to register a blob on the blockchain.
|
|
1404
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
1405
|
+
*
|
|
1406
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
1407
|
+
* @param params.account - The account that will own the blob.
|
|
1408
|
+
* @param params.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
1409
|
+
* @param params.blobSize - The size of the blob in bytes.
|
|
1410
|
+
* @param params.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
|
|
1411
|
+
* @param params.expirationMicros - The expiration time of the blob in microseconds.
|
|
1412
|
+
* @param params.numChunksets - The total number of chunksets in the blob.
|
|
1413
|
+
*
|
|
1414
|
+
* @returns An Aptos transaction payload data object for the register_blob Move function.
|
|
1415
|
+
*
|
|
1416
|
+
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
1417
|
+
*/
|
|
1418
|
+
static createRegisterBlobPayload(params) {
|
|
1419
|
+
const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
|
|
1420
|
+
return {
|
|
1421
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
|
|
1422
|
+
functionArguments: [
|
|
1423
|
+
params.blobName,
|
|
1424
|
+
params.expirationMicros,
|
|
1425
|
+
Hex3.fromHexString(params.blobMerkleRoot).toUint8Array(),
|
|
1426
|
+
params.numChunksets,
|
|
1427
|
+
params.blobSize,
|
|
1428
|
+
// TODO
|
|
1429
|
+
0,
|
|
1430
|
+
// payment tier
|
|
1431
|
+
params.encoding
|
|
1432
|
+
]
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Creates a transaction payload to register multiple blobs on the blockchain.
|
|
1437
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
1438
|
+
*
|
|
1439
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
1440
|
+
* @param params.account - The account that will own the blobs.
|
|
1441
|
+
* @param params.expirationMicros - The expiration time of the blobs in microseconds.
|
|
1442
|
+
* @param params.blobs - The blobs to register.
|
|
1443
|
+
* @param params.blobs.blobName - The name/path of the blob (e.g. "foo/bar.txt").
|
|
1444
|
+
* @param params.blobs.blobSize - The size of the blob in bytes.
|
|
1445
|
+
* @param params.blobs.blobMerkleRoot - The merkle root of the blob commitments as a hex string.
|
|
1446
|
+
* @param params.blobs.numChunksets - The total number of chunksets in the blob.
|
|
1447
|
+
*
|
|
1448
|
+
* @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
|
|
1449
|
+
*
|
|
1450
|
+
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
1451
|
+
*/
|
|
1452
|
+
static createBatchRegisterBlobsPayload(params) {
|
|
1453
|
+
const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
|
|
1454
|
+
const blobNames = [];
|
|
1455
|
+
const blobMerkleRoots = [];
|
|
1456
|
+
const blobNumChunksets = [];
|
|
1457
|
+
const blobSizes = [];
|
|
1458
|
+
params.blobs.forEach((blob) => {
|
|
1459
|
+
blobNames.push(blob.blobName);
|
|
1460
|
+
blobMerkleRoots.push(
|
|
1461
|
+
Hex3.fromHexString(blob.blobMerkleRoot).toUint8Array()
|
|
1462
|
+
);
|
|
1463
|
+
blobNumChunksets.push(blob.numChunksets);
|
|
1464
|
+
blobSizes.push(blob.blobSize);
|
|
1465
|
+
});
|
|
1466
|
+
return {
|
|
1467
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
|
|
1468
|
+
functionArguments: [
|
|
1469
|
+
blobNames,
|
|
1470
|
+
params.expirationMicros,
|
|
1471
|
+
blobMerkleRoots,
|
|
1472
|
+
blobNumChunksets,
|
|
1473
|
+
blobSizes,
|
|
1474
|
+
// TODO
|
|
1475
|
+
0,
|
|
1476
|
+
// payment tier
|
|
1477
|
+
params.encoding
|
|
1478
|
+
]
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Creates a transaction payload to delete a blob on the blockchain.
|
|
1483
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
1484
|
+
*
|
|
1485
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
1486
|
+
* @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
|
|
1487
|
+
*
|
|
1488
|
+
* @returns An Aptos transaction payload data object for the delete_blob Move function.
|
|
1489
|
+
*
|
|
1490
|
+
* @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
|
|
1491
|
+
*/
|
|
1492
|
+
static createDeleteBlobPayload(params) {
|
|
1493
|
+
return {
|
|
1494
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_blob`,
|
|
1495
|
+
functionArguments: [params.blobName]
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Creates a transaction payload to delete multiple blobs on the blockchain.
|
|
1500
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
1501
|
+
*
|
|
1502
|
+
* **Note:** This function requires the `delete_multiple_blobs` entry function
|
|
1503
|
+
* which will be deployed to the smart contract on 2026-02-04. Using this
|
|
1504
|
+
* function before that date will result in a transaction failure.
|
|
1505
|
+
*
|
|
1506
|
+
* This operation is atomic: if any blob deletion fails (e.g., blob not found),
|
|
1507
|
+
* the entire transaction fails and no blobs are deleted.
|
|
1508
|
+
*
|
|
1509
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
1510
|
+
* @param params.blobNames - Array of blob name suffixes without the account address prefix
|
|
1511
|
+
* (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
|
|
1512
|
+
* prefix is automatically derived from the transaction sender.
|
|
1513
|
+
*
|
|
1514
|
+
* @returns An Aptos transaction payload data object for the delete_multiple_blobs Move function.
|
|
1515
|
+
*
|
|
1516
|
+
* @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
|
|
1517
|
+
*/
|
|
1518
|
+
static createDeleteMultipleBlobsPayload(params) {
|
|
1519
|
+
return {
|
|
1520
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_blobs`,
|
|
1521
|
+
functionArguments: [params.blobNames]
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
static createBlobAcknowledgementsPayload(params) {
|
|
1525
|
+
const ackBitMask = params.storageProviderAcks.reduce(
|
|
1526
|
+
(acc, ack) => acc | 1 << ack.slot,
|
|
1527
|
+
0
|
|
1528
|
+
);
|
|
1529
|
+
return {
|
|
1530
|
+
function: `${SHELBY_DEPLOYER}::blob_metadata::add_blob_acknowledgements`,
|
|
1531
|
+
functionArguments: [
|
|
1532
|
+
createBlobKey({
|
|
1533
|
+
account: params.blobOwner,
|
|
1534
|
+
blobName: params.blobName
|
|
1535
|
+
}),
|
|
1536
|
+
params.creationMicros,
|
|
1537
|
+
new U32(Number(ackBitMask)),
|
|
1538
|
+
params.storageProviderAcks.map((ack) => ack.signature)
|
|
1539
|
+
]
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
async addBlobAcknowledgements(params) {
|
|
1543
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
1544
|
+
...params.options?.build,
|
|
1545
|
+
options: this.orderlessTxOptions(params.options?.build?.options),
|
|
1546
|
+
data: _ShelbyBlobClient.createBlobAcknowledgementsPayload({
|
|
1547
|
+
blobOwner: params.blobOwner,
|
|
1548
|
+
blobName: params.blobName,
|
|
1549
|
+
creationMicros: params.creationMicros,
|
|
1550
|
+
storageProviderAcks: params.storageProviderAcks
|
|
1551
|
+
}),
|
|
1552
|
+
sender: params.account.accountAddress
|
|
1553
|
+
});
|
|
1554
|
+
return {
|
|
1555
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
1556
|
+
signer: params.account,
|
|
1557
|
+
transaction
|
|
1558
|
+
})
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
// src/core/clients/ShelbyClient.ts
|
|
113
1564
|
import {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1565
|
+
Aptos as Aptos3,
|
|
1566
|
+
DEFAULT_TXN_TIMEOUT_SEC,
|
|
1567
|
+
Network as Network4,
|
|
1568
|
+
TransactionResponseType
|
|
1569
|
+
} from "@aptos-labs/ts-sdk";
|
|
1570
|
+
import pLimit from "p-limit";
|
|
1571
|
+
|
|
1572
|
+
// src/core/clients/ShelbyMetadataClient.ts
|
|
120
1573
|
import {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
1574
|
+
AccountAddress as AccountAddress3,
|
|
1575
|
+
Aptos as Aptos2,
|
|
1576
|
+
Hex as Hex4
|
|
1577
|
+
} from "@aptos-labs/ts-sdk";
|
|
1578
|
+
function parseStorageProviderState(raw) {
|
|
1579
|
+
switch (raw.__variant__) {
|
|
1580
|
+
case "Active":
|
|
1581
|
+
return {
|
|
1582
|
+
variant: "Active",
|
|
1583
|
+
quota: raw.quota.value,
|
|
1584
|
+
stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
|
|
1585
|
+
faulty: raw.faulty,
|
|
1586
|
+
leaving: raw.leaving
|
|
1587
|
+
};
|
|
1588
|
+
case "Waitlisted":
|
|
1589
|
+
return {
|
|
1590
|
+
variant: "Waitlisted"
|
|
1591
|
+
};
|
|
1592
|
+
case "Frozen":
|
|
1593
|
+
return {
|
|
1594
|
+
variant: "Frozen",
|
|
1595
|
+
frozenReason: raw.frozen_reason,
|
|
1596
|
+
frozenFrom: raw.frozen_from,
|
|
1597
|
+
frozenTill: raw.frozen_till
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
var ShelbyMetadataClient = class {
|
|
1602
|
+
aptos;
|
|
1603
|
+
deployer;
|
|
1604
|
+
/**
|
|
1605
|
+
* The ShelbyMetadataClient is used to interact with the Shelby contract on the Aptos blockchain. This
|
|
1606
|
+
* includes functions like gathering basic details about the Shelby system, including storage provider information.
|
|
1607
|
+
*
|
|
1608
|
+
* @param config.aptos.config - The Aptos config.
|
|
1609
|
+
* @param config.shelbyDeployer - The deployer account address of the Shelby contract. If not provided, the default deployer address will be used.
|
|
1610
|
+
*
|
|
1611
|
+
* @example
|
|
1612
|
+
* ```typescript
|
|
1613
|
+
* const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
|
|
1614
|
+
* const metadataClient = new ShelbyMetadataClient({ aptos });
|
|
1615
|
+
* ```
|
|
1616
|
+
*/
|
|
1617
|
+
constructor(config) {
|
|
1618
|
+
this.aptos = new Aptos2(getAptosConfig(config));
|
|
1619
|
+
this.deployer = config.deployer ?? AccountAddress3.fromString(SHELBY_DEPLOYER);
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Retrieves storage provider list from the blockchain.
|
|
1623
|
+
*
|
|
1624
|
+
* @returns A list of storage providers, or empty array if none exist.
|
|
1625
|
+
*
|
|
1626
|
+
* @example
|
|
1627
|
+
* ```typescript
|
|
1628
|
+
* const spList = await client.getStorageProviders();
|
|
1629
|
+
* ```
|
|
1630
|
+
*/
|
|
1631
|
+
async getStorageProviders() {
|
|
1632
|
+
try {
|
|
1633
|
+
const rawMetadata = await this.aptos.view({
|
|
1634
|
+
payload: {
|
|
1635
|
+
function: `${this.deployer.toString()}::storage_provider_registry::get_all_storage_providers`,
|
|
1636
|
+
functionArguments: []
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
const metadata = rawMetadata[0];
|
|
1640
|
+
return metadata.map((provider) => ({
|
|
1641
|
+
address: normalizeAddress(provider.address),
|
|
1642
|
+
ipAddress: provider.ip_address,
|
|
1643
|
+
port: provider.port,
|
|
1644
|
+
blsPublicKey: Hex4.fromHexInput(provider.bls_public_key).toUint8Array(),
|
|
1645
|
+
availabilityZone: provider.availability_zone,
|
|
1646
|
+
state: parseStorageProviderState(provider.state)
|
|
1647
|
+
}));
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
1650
|
+
error.message?.includes("sub_status: Some(404)")) {
|
|
1651
|
+
return [];
|
|
1652
|
+
}
|
|
1653
|
+
throw error;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Retrieves the list of placement group addresses.
|
|
1658
|
+
*
|
|
1659
|
+
* @returns The placement group address list, or an empty array if none exist.
|
|
1660
|
+
*
|
|
1661
|
+
* @example
|
|
1662
|
+
* ```typescript
|
|
1663
|
+
* const pgList = await client.getPlacementGroupAddresses();
|
|
1664
|
+
* ```
|
|
1665
|
+
*/
|
|
1666
|
+
async getPlacementGroupAddresses() {
|
|
1667
|
+
try {
|
|
1668
|
+
const pgSizeMetadata = await this.aptos.view({
|
|
1669
|
+
payload: {
|
|
1670
|
+
function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
|
|
1671
|
+
functionArguments: []
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
|
|
1675
|
+
const addressMetadataArray = await this.aptos.view({
|
|
1676
|
+
payload: {
|
|
1677
|
+
function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
|
|
1678
|
+
functionArguments: [0, finalPlacementGroupIndex]
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
const metadata = addressMetadataArray[0];
|
|
1682
|
+
return metadata.map((pg) => normalizeAddress(pg));
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
1685
|
+
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("E_PLACEMENT_GROUP_NOT_FOUND"))) {
|
|
1686
|
+
return [];
|
|
1687
|
+
}
|
|
1688
|
+
throw error;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Retrieves the list of slice addresses.
|
|
1693
|
+
*
|
|
1694
|
+
* @returns The slice group list, or an empty array if none exist.
|
|
1695
|
+
*
|
|
1696
|
+
* @example
|
|
1697
|
+
* ```typescript
|
|
1698
|
+
* const pgList = await client.getSliceAddresses();
|
|
1699
|
+
* ```
|
|
1700
|
+
*/
|
|
1701
|
+
async getSliceAddresses() {
|
|
1702
|
+
try {
|
|
1703
|
+
const sliceSizeMetadata = await this.aptos.view({
|
|
1704
|
+
payload: {
|
|
1705
|
+
function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
|
|
1706
|
+
functionArguments: []
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
const finalSliceIndex = sliceSizeMetadata[0] - 1;
|
|
1710
|
+
const addressMetadataArray = await this.aptos.view({
|
|
1711
|
+
payload: {
|
|
1712
|
+
function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
|
|
1713
|
+
functionArguments: [0, finalSliceIndex]
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
const metadata = addressMetadataArray[0];
|
|
1717
|
+
return metadata.map((slice) => normalizeAddress(slice));
|
|
1718
|
+
} catch (error) {
|
|
1719
|
+
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
1720
|
+
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("E_SLICE_NOT_FOUND"))) {
|
|
1721
|
+
return [];
|
|
1722
|
+
}
|
|
1723
|
+
throw error;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Gets the placement group address for a slice.
|
|
1728
|
+
*
|
|
1729
|
+
* @param sliceAddress - The address of the slice account.
|
|
1730
|
+
* @returns The placement group address as a string.
|
|
1731
|
+
*/
|
|
1732
|
+
async getPlacementGroupAddressForSlice(sliceAddress) {
|
|
1733
|
+
const sliceMetadata = await this.aptos.view({
|
|
1734
|
+
payload: {
|
|
1735
|
+
function: `${this.deployer.toString()}::slice::get_slice_info`,
|
|
1736
|
+
functionArguments: [sliceAddress.toString()]
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
return sliceMetadata[0].placement_group_assignments[0].placement_group_address;
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Retrieves the designated storage providers for a slice.
|
|
1743
|
+
*
|
|
1744
|
+
* Designated SPs are those appointed to store data for their slots:
|
|
1745
|
+
* - Active: Currently serving data
|
|
1746
|
+
* - Receiving: Receiving data during slot transfer
|
|
1747
|
+
* - Repairing: Repairing data after crash or failed transfer
|
|
1748
|
+
* - Reconstructing: Reconstructing data if the repair fails
|
|
1749
|
+
*
|
|
1750
|
+
* @param params.account - The address of the slice account.
|
|
1751
|
+
* @returns An array where result[i] is the designated SP for slot i, or null if no SP is designated.
|
|
1752
|
+
*
|
|
1753
|
+
* @example
|
|
1754
|
+
* ```typescript
|
|
1755
|
+
* const providers = await client.getDesignatedStorageProvidersForSlice({ account: sliceAddress });
|
|
1756
|
+
* ```
|
|
1757
|
+
*/
|
|
1758
|
+
async getDesignatedStorageProvidersForSlice(params) {
|
|
1759
|
+
const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
|
|
1760
|
+
params.account
|
|
1761
|
+
);
|
|
1762
|
+
const rawMetadata = await this.aptos.view({
|
|
1763
|
+
payload: {
|
|
1764
|
+
function: `${this.deployer.toString()}::placement_group::get_designated_storage_providers`,
|
|
1765
|
+
functionArguments: [placementGroupAddress]
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
const providers = rawMetadata[0];
|
|
1769
|
+
return providers.map(
|
|
1770
|
+
(opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Retrieves the serving storage providers for a slice.
|
|
1775
|
+
*
|
|
1776
|
+
* Serving SPs are those that can respond to read requests. The serving logic is:
|
|
1777
|
+
* - If an Active SP exists for a slot: Only the Active SP is serving
|
|
1778
|
+
* - If no Active SP (transition in progress): Both Designated and Vacating SPs serve
|
|
1779
|
+
*
|
|
1780
|
+
* Each slot may have multiple serving SPs during transitions.
|
|
1781
|
+
*
|
|
1782
|
+
* @param params.account - The address of the slice account.
|
|
1783
|
+
* @returns An array where result[i] contains the serving SPs for slot i.
|
|
1784
|
+
*
|
|
1785
|
+
* @example
|
|
1786
|
+
* ```typescript
|
|
1787
|
+
* const providers = await client.getServingStorageProvidersForSlice({ account: sliceAddress });
|
|
1788
|
+
* ```
|
|
1789
|
+
*/
|
|
1790
|
+
async getServingStorageProvidersForSlice(params) {
|
|
1791
|
+
const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
|
|
1792
|
+
params.account
|
|
1793
|
+
);
|
|
1794
|
+
const rawMetadata = await this.aptos.view({
|
|
1795
|
+
payload: {
|
|
1796
|
+
function: `${this.deployer.toString()}::placement_group::get_serving_storage_providers`,
|
|
1797
|
+
functionArguments: [placementGroupAddress]
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
const providers = rawMetadata[0];
|
|
1801
|
+
return providers.map(
|
|
1802
|
+
(slotProviders) => slotProviders.map((addr) => normalizeAddress(addr))
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
// src/core/clients/ShelbyRPCClient.ts
|
|
129
1808
|
import {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1809
|
+
Hex as Hex6
|
|
1810
|
+
} from "@aptos-labs/ts-sdk";
|
|
1811
|
+
|
|
1812
|
+
// src/core/layout.ts
|
|
1813
|
+
import { z as z2 } from "zod";
|
|
1814
|
+
var BlobNameSchema = z2.string().min(1, "Blob name path parameter cannot be empty.").max(
|
|
1815
|
+
190,
|
|
1816
|
+
"Blob name suffix cannot exceed 190 characters (on-chain full key limit is 256 bytes)."
|
|
1817
|
+
).refine((name) => !name.endsWith("/"), {
|
|
1818
|
+
message: "Blob name cannot end with a slash"
|
|
1819
|
+
});
|
|
1820
|
+
var ChunkKey = class {
|
|
1821
|
+
constructor(account, blobName, chunksetIdx, chunkIdx, config) {
|
|
1822
|
+
this.account = account;
|
|
1823
|
+
this.blobName = blobName;
|
|
1824
|
+
this.chunksetIdx = chunksetIdx;
|
|
1825
|
+
this.chunkIdx = chunkIdx;
|
|
1826
|
+
this.config = config;
|
|
1827
|
+
if (chunkIdx >= config.erasure_n) {
|
|
1828
|
+
throw new Error(
|
|
1829
|
+
`Cannot create a chunk with idx ${chunkIdx}. M+K=${config.erasure_n}`
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
key() {
|
|
1834
|
+
return `${this.account.toString()}.${this.blobName}.${this.chunksetIdx}.${this.chunkIdx}`;
|
|
1835
|
+
}
|
|
1836
|
+
// Returns the range in the blob that this key represents, or "parity" if this is parity chunk.
|
|
1837
|
+
range() {
|
|
1838
|
+
if (this.chunkIdx >= this.config.erasure_k) {
|
|
1839
|
+
return "parity";
|
|
1840
|
+
}
|
|
1841
|
+
const chunksetStart = this.chunksetIdx * this.config.chunkSizeBytes * this.config.erasure_k;
|
|
1842
|
+
const chunkStart = chunksetStart + this.chunkIdx * this.config.chunkSizeBytes;
|
|
1843
|
+
return {
|
|
1844
|
+
start: chunkStart,
|
|
1845
|
+
end: chunkStart + this.config.chunkSizeBytes
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
// src/core/promises.ts
|
|
1851
|
+
function sleep(ms) {
|
|
1852
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// src/core/rpc-responses.ts
|
|
1856
|
+
import { z as z3 } from "zod";
|
|
1857
|
+
var ChallengeResponseSchema = z3.object({
|
|
1858
|
+
challenge: z3.string(),
|
|
1859
|
+
expiresAt: z3.number()
|
|
1860
|
+
});
|
|
1861
|
+
var MultipartUploadStatusResponseSchema = z3.object({
|
|
1862
|
+
uploadId: z3.string(),
|
|
1863
|
+
completedParts: z3.array(z3.number()),
|
|
1864
|
+
partSize: z3.number(),
|
|
1865
|
+
nParts: z3.number(),
|
|
1866
|
+
uploadedBytes: z3.number()
|
|
1867
|
+
});
|
|
1868
|
+
var StartMultipartUploadResponseSchema = z3.object({
|
|
1869
|
+
uploadId: z3.string()
|
|
1870
|
+
});
|
|
1871
|
+
var UploadPartResponseSchema = z3.object({
|
|
1872
|
+
success: z3.literal(true)
|
|
1873
|
+
});
|
|
1874
|
+
var CompleteMultipartUploadResponseSchema = z3.object({
|
|
1875
|
+
success: z3.literal(true)
|
|
1876
|
+
});
|
|
1877
|
+
var RPCErrorResponseSchema = z3.object({
|
|
1878
|
+
error: z3.string()
|
|
1879
|
+
});
|
|
1880
|
+
var StaleMicropaymentErrorResponseSchema = z3.object({
|
|
1881
|
+
error: z3.string().optional(),
|
|
1882
|
+
storedMicropayment: z3.string().optional()
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
// src/core/types/payments.ts
|
|
144
1886
|
import {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1887
|
+
AccountAddress as AccountAddress4,
|
|
1888
|
+
Deserializer,
|
|
1889
|
+
Hex as Hex5,
|
|
1890
|
+
Serializer
|
|
1891
|
+
} from "@aptos-labs/ts-sdk";
|
|
1892
|
+
var MICROPAYMENTS_MODULE_NAME = "micropayments";
|
|
1893
|
+
var WITHDRAW_APPROVAL_STRUCT_NAME = "WithdrawApproval";
|
|
1894
|
+
function serializeTypeInfo(serializer, moduleAddress, moduleName, structName) {
|
|
1895
|
+
moduleAddress.serialize(serializer);
|
|
1896
|
+
const moduleNameBytes = new TextEncoder().encode(moduleName);
|
|
1897
|
+
serializer.serializeBytes(moduleNameBytes);
|
|
1898
|
+
const structNameBytes = new TextEncoder().encode(structName);
|
|
1899
|
+
serializer.serializeBytes(structNameBytes);
|
|
1900
|
+
}
|
|
1901
|
+
var StaleChannelStateError = class _StaleChannelStateError extends Error {
|
|
1902
|
+
/**
|
|
1903
|
+
* The last valid micropayment stored by the server.
|
|
1904
|
+
* Clients can use this to reset their local channel state.
|
|
1905
|
+
*/
|
|
1906
|
+
storedMicropayment;
|
|
1907
|
+
constructor(storedMicropayment, message) {
|
|
1908
|
+
super(
|
|
1909
|
+
message ?? "Client has stale channel state. Use the returned micropayment to reset local state."
|
|
1910
|
+
);
|
|
1911
|
+
this.name = "StaleChannelStateError";
|
|
1912
|
+
this.storedMicropayment = storedMicropayment;
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Returns the stored micropayment as a base64-encoded string.
|
|
1916
|
+
*/
|
|
1917
|
+
toBase64() {
|
|
1918
|
+
const bytes = this.storedMicropayment.bcsToBytes();
|
|
1919
|
+
const binaryString = Array.from(
|
|
1920
|
+
bytes,
|
|
1921
|
+
(byte) => String.fromCharCode(byte)
|
|
1922
|
+
).join("");
|
|
1923
|
+
return btoa(binaryString);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Creates a StaleChannelStateError from a base64-encoded micropayment string.
|
|
1927
|
+
*/
|
|
1928
|
+
static fromBase64(base64, message) {
|
|
1929
|
+
const binaryString = atob(base64);
|
|
1930
|
+
const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
1931
|
+
const micropayment = SenderBuiltMicropayment.deserialize(bytes);
|
|
1932
|
+
return new _StaleChannelStateError(micropayment, message);
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
var SenderBuiltMicropayment = class _SenderBuiltMicropayment {
|
|
1936
|
+
/**
|
|
1937
|
+
* The sender's address (owner of the payment channel).
|
|
1938
|
+
*/
|
|
1939
|
+
sender;
|
|
1940
|
+
/**
|
|
1941
|
+
* The receiver's address (beneficiary of the withdrawal).
|
|
1942
|
+
*/
|
|
1943
|
+
receiver;
|
|
1944
|
+
/**
|
|
1945
|
+
* The unique ID of the payment channel.
|
|
1946
|
+
*/
|
|
1947
|
+
paymentChannelId;
|
|
1948
|
+
/**
|
|
1949
|
+
* The cumulative amount the receiver is authorized to withdraw.
|
|
1950
|
+
*/
|
|
1951
|
+
amount;
|
|
1952
|
+
/**
|
|
1953
|
+
* The fungible asset metadata address.
|
|
1954
|
+
*/
|
|
1955
|
+
fungibleAssetAddress;
|
|
1956
|
+
/**
|
|
1957
|
+
* Monotonically increasing sequence number for replay protection.
|
|
1958
|
+
*/
|
|
1959
|
+
sequenceNumber;
|
|
1960
|
+
/**
|
|
1961
|
+
* The sender's Ed25519 public key (32 bytes).
|
|
1962
|
+
* Used by the receiver to verify the signature before submitting.
|
|
1963
|
+
*/
|
|
1964
|
+
publicKey;
|
|
1965
|
+
/**
|
|
1966
|
+
* The Ed25519 signature of the SignedMessage<WithdrawApproval> struct.
|
|
1967
|
+
* The SignedMessage includes TypeInfo for domain separation.
|
|
1968
|
+
*/
|
|
1969
|
+
signature;
|
|
1970
|
+
/**
|
|
1971
|
+
* The deployer address of the micropayments module.
|
|
1972
|
+
* This is needed to reconstruct the TypeInfo for signature verification.
|
|
1973
|
+
*/
|
|
1974
|
+
deployer;
|
|
1975
|
+
constructor(sender, receiver, paymentChannelId, amount, fungibleAssetAddress, sequenceNumber, publicKey, signature, deployer) {
|
|
1976
|
+
this.sender = sender;
|
|
1977
|
+
this.receiver = receiver;
|
|
1978
|
+
this.paymentChannelId = paymentChannelId;
|
|
1979
|
+
this.amount = amount;
|
|
1980
|
+
this.fungibleAssetAddress = fungibleAssetAddress;
|
|
1981
|
+
this.sequenceNumber = sequenceNumber;
|
|
1982
|
+
this.publicKey = publicKey;
|
|
1983
|
+
this.signature = signature;
|
|
1984
|
+
this.deployer = deployer ?? AccountAddress4.fromString(MICROPAYMENTS_DEPLOYER);
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Creates the BCS-serialized message that was signed.
|
|
1988
|
+
* This is a SignedMessage<WithdrawApproval> which includes:
|
|
1989
|
+
* 1. TypeInfo (module_address, module_name, struct_name)
|
|
1990
|
+
* 2. WithdrawApproval struct fields
|
|
1991
|
+
*
|
|
1992
|
+
* This format is used with signature_verify_strict_t for domain separation.
|
|
1993
|
+
*/
|
|
1994
|
+
getSignedMessage() {
|
|
1995
|
+
return _SenderBuiltMicropayment.buildSignedMessage({
|
|
1996
|
+
deployer: this.deployer,
|
|
1997
|
+
sender: this.sender,
|
|
1998
|
+
receiver: this.receiver,
|
|
1999
|
+
fungibleAssetAddress: this.fungibleAssetAddress,
|
|
2000
|
+
amount: this.amount,
|
|
2001
|
+
paymentChannelId: this.paymentChannelId,
|
|
2002
|
+
sequenceNumber: this.sequenceNumber
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Static helper to build the SignedMessage<WithdrawApproval> bytes from raw parameters.
|
|
2007
|
+
* This can be used to create the message for signing without constructing the full object.
|
|
2008
|
+
*
|
|
2009
|
+
* @param params - The withdrawal approval parameters.
|
|
2010
|
+
* @returns The BCS-serialized SignedMessage<WithdrawApproval> bytes.
|
|
2011
|
+
*/
|
|
2012
|
+
static buildSignedMessage(params) {
|
|
2013
|
+
const serializer = new Serializer();
|
|
2014
|
+
serializeTypeInfo(
|
|
2015
|
+
serializer,
|
|
2016
|
+
params.deployer,
|
|
2017
|
+
MICROPAYMENTS_MODULE_NAME,
|
|
2018
|
+
WITHDRAW_APPROVAL_STRUCT_NAME
|
|
2019
|
+
);
|
|
2020
|
+
params.sender.serialize(serializer);
|
|
2021
|
+
params.receiver.serialize(serializer);
|
|
2022
|
+
params.fungibleAssetAddress.serialize(serializer);
|
|
2023
|
+
serializer.serializeU64(params.amount);
|
|
2024
|
+
serializer.serializeU64(params.paymentChannelId);
|
|
2025
|
+
serializer.serializeU64(params.sequenceNumber);
|
|
2026
|
+
return serializer.toUint8Array();
|
|
2027
|
+
}
|
|
2028
|
+
serialize(serializer) {
|
|
2029
|
+
this.sender.serialize(serializer);
|
|
2030
|
+
this.receiver.serialize(serializer);
|
|
2031
|
+
this.fungibleAssetAddress.serialize(serializer);
|
|
2032
|
+
serializer.serializeU64(this.amount);
|
|
2033
|
+
serializer.serializeU64(this.paymentChannelId);
|
|
2034
|
+
serializer.serializeU64(this.sequenceNumber);
|
|
2035
|
+
serializer.serializeBytes(this.publicKey);
|
|
2036
|
+
serializer.serializeBytes(this.signature);
|
|
2037
|
+
this.deployer.serialize(serializer);
|
|
2038
|
+
}
|
|
2039
|
+
bcsToBytes() {
|
|
2040
|
+
const serializer = new Serializer();
|
|
2041
|
+
this.serialize(serializer);
|
|
2042
|
+
return serializer.toUint8Array();
|
|
2043
|
+
}
|
|
2044
|
+
bcsToHex() {
|
|
2045
|
+
return Hex5.fromHexInput(this.bcsToBytes());
|
|
2046
|
+
}
|
|
2047
|
+
toStringWithoutPrefix() {
|
|
2048
|
+
return this.bcsToHex().toStringWithoutPrefix();
|
|
2049
|
+
}
|
|
2050
|
+
toString() {
|
|
2051
|
+
return this.bcsToHex().toString();
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Deserializes a SenderBuiltMicropayment from BCS bytes.
|
|
2055
|
+
* @param bytes - The bytes to deserialize from (Uint8Array or hex string).
|
|
2056
|
+
* @returns A new SenderBuiltMicropayment instance.
|
|
2057
|
+
*/
|
|
2058
|
+
static deserialize(bytes) {
|
|
2059
|
+
const bytesArray = typeof bytes === "string" ? Hex5.fromHexInput(bytes).toUint8Array() : bytes;
|
|
2060
|
+
const deserializer = new Deserializer(bytesArray);
|
|
2061
|
+
const sender = AccountAddress4.deserialize(deserializer);
|
|
2062
|
+
const receiver = AccountAddress4.deserialize(deserializer);
|
|
2063
|
+
const fungibleAssetAddress = AccountAddress4.deserialize(deserializer);
|
|
2064
|
+
const amount = deserializer.deserializeU64();
|
|
2065
|
+
const paymentChannelId = deserializer.deserializeU64();
|
|
2066
|
+
const sequenceNumber = deserializer.deserializeU64();
|
|
2067
|
+
const publicKey = deserializer.deserializeBytes();
|
|
2068
|
+
const signature = deserializer.deserializeBytes();
|
|
2069
|
+
const deployer = AccountAddress4.deserialize(deserializer);
|
|
2070
|
+
return new _SenderBuiltMicropayment(
|
|
2071
|
+
sender,
|
|
2072
|
+
receiver,
|
|
2073
|
+
paymentChannelId,
|
|
2074
|
+
amount,
|
|
2075
|
+
fungibleAssetAddress,
|
|
2076
|
+
sequenceNumber,
|
|
2077
|
+
publicKey,
|
|
2078
|
+
signature,
|
|
2079
|
+
deployer
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
// src/core/clients/ShelbyRPCClient.ts
|
|
2085
|
+
var MICROPAYMENT_HEADER = "X-Shelby-Micropayment";
|
|
2086
|
+
var BLOB_OWNER_CHALLENGE_HEADER = "X-Shelby-Challenge";
|
|
2087
|
+
var BLOB_OWNER_SIGNATURE_HEADER = "X-Shelby-Signature";
|
|
2088
|
+
var BLOB_OWNER_PUBLIC_KEY_HEADER = "X-Shelby-Public-Key";
|
|
2089
|
+
var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
|
|
2090
|
+
var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
|
|
2091
|
+
var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
|
|
2092
|
+
var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
|
|
2093
|
+
function buildAuthHeaders(auth) {
|
|
2094
|
+
const signatureBase64 = btoa(
|
|
2095
|
+
Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
|
|
2096
|
+
);
|
|
2097
|
+
const publicKeyHex = Hex6.fromHexInput(auth.publicKey).toString();
|
|
2098
|
+
const headers = {
|
|
2099
|
+
[BLOB_OWNER_CHALLENGE_HEADER]: auth.challenge,
|
|
2100
|
+
[BLOB_OWNER_SIGNATURE_HEADER]: signatureBase64,
|
|
2101
|
+
[BLOB_OWNER_PUBLIC_KEY_HEADER]: publicKeyHex
|
|
2102
|
+
};
|
|
2103
|
+
if (auth.authScheme === "derivable") {
|
|
2104
|
+
headers[BLOB_OWNER_AUTH_SCHEME_HEADER] = auth.authScheme;
|
|
2105
|
+
headers[BLOB_OWNER_IDENTITY_HEADER] = auth.identity;
|
|
2106
|
+
headers[BLOB_OWNER_DOMAIN_HEADER] = auth.domain;
|
|
2107
|
+
headers[BLOB_OWNER_AUTH_FUNCTION_HEADER] = auth.authFunction;
|
|
2108
|
+
} else if (auth.authScheme) {
|
|
2109
|
+
headers[BLOB_OWNER_AUTH_SCHEME_HEADER] = auth.authScheme;
|
|
2110
|
+
}
|
|
2111
|
+
return headers;
|
|
2112
|
+
}
|
|
2113
|
+
function encodeURIComponentKeepSlashes(str) {
|
|
2114
|
+
return encodeURIComponent(str).replace(/%2F/g, "/");
|
|
2115
|
+
}
|
|
2116
|
+
function validateTotalBytes(totalBytes) {
|
|
2117
|
+
if (!Number.isInteger(totalBytes) || totalBytes < 0) {
|
|
2118
|
+
throw new Error("totalBytes must be a non-negative integer");
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
function isRetryableStatus(status) {
|
|
2122
|
+
return status === 408 || status === 429 || status >= 500;
|
|
2123
|
+
}
|
|
2124
|
+
function getErrorCode(error) {
|
|
2125
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
|
|
2126
|
+
return error.code;
|
|
2127
|
+
}
|
|
2128
|
+
if (error instanceof Error) {
|
|
2129
|
+
const match = error.message.match(/\bE[A-Z0-9_]+\b/);
|
|
2130
|
+
return match?.[0];
|
|
2131
|
+
}
|
|
2132
|
+
return void 0;
|
|
2133
|
+
}
|
|
2134
|
+
var ShelbyRPCClient = class {
|
|
2135
|
+
baseUrl;
|
|
2136
|
+
apiKey;
|
|
2137
|
+
rpcConfig;
|
|
2138
|
+
indexer;
|
|
2139
|
+
#signChallengeOverride;
|
|
2140
|
+
/**
|
|
2141
|
+
* Creates a new ShelbyRPCClient for interacting with Shelby RPC nodes.
|
|
2142
|
+
* This client handles blob storage operations including upload and download.
|
|
2143
|
+
*
|
|
2144
|
+
* @param config - The client configuration object.
|
|
2145
|
+
* @param config.network - The Shelby network to use.
|
|
2146
|
+
* @param options.signChallengeHandler - Optional override for challenge
|
|
2147
|
+
* signing. When set, `putBlobResumable` uses this instead of the built-in
|
|
2148
|
+
* `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
|
|
2149
|
+
*
|
|
2150
|
+
* @example
|
|
2151
|
+
* ```typescript
|
|
2152
|
+
* const client = new ShelbyRPCClient({
|
|
2153
|
+
* network: Network.SHELBYNET,
|
|
2154
|
+
* apiKey: "AG-***",
|
|
2155
|
+
* });
|
|
2156
|
+
* ```
|
|
2157
|
+
*/
|
|
2158
|
+
constructor(config, options) {
|
|
2159
|
+
this.baseUrl = getShelbyRPCBaseUrl(config);
|
|
2160
|
+
this.apiKey = config.apiKey ?? config.rpc?.apiKey;
|
|
2161
|
+
this.rpcConfig = config.rpc ?? {};
|
|
2162
|
+
this.indexer = getShelbyIndexerClient(config);
|
|
2163
|
+
this.#signChallengeOverride = options?.signChallengeHandler;
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Request an authentication challenge for the given account.
|
|
2167
|
+
* The challenge must be signed and included in subsequent authenticated requests.
|
|
2168
|
+
*
|
|
2169
|
+
* @param account - The Aptos account address to authenticate as.
|
|
2170
|
+
* @returns The challenge string and expiration timestamp.
|
|
2171
|
+
*
|
|
2172
|
+
* @example
|
|
2173
|
+
* ```typescript
|
|
2174
|
+
* const { challenge, expiresAt } = await client.getChallenge(account.accountAddress);
|
|
2175
|
+
* const auth = client.signChallenge(account, challenge);
|
|
2176
|
+
* ```
|
|
2177
|
+
*/
|
|
2178
|
+
async getChallenge(account) {
|
|
2179
|
+
const response = await fetch(
|
|
2180
|
+
buildRequestUrl("/v1/auth/challenge", this.baseUrl),
|
|
2181
|
+
{
|
|
2182
|
+
method: "POST",
|
|
2183
|
+
headers: {
|
|
2184
|
+
"Content-Type": "application/json",
|
|
2185
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2186
|
+
},
|
|
2187
|
+
body: JSON.stringify({ account: account.toString() })
|
|
2188
|
+
}
|
|
2189
|
+
);
|
|
2190
|
+
if (!response.ok) {
|
|
2191
|
+
const errorBody = await response.text().catch(() => "");
|
|
2192
|
+
throw new Error(
|
|
2193
|
+
`Failed to get challenge: status ${response.status}, body: ${errorBody}`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
return ChallengeResponseSchema.parse(await response.json());
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Check if there's an existing multipart upload for a blob.
|
|
2200
|
+
* Returns the upload status including which parts have been uploaded.
|
|
2201
|
+
*
|
|
2202
|
+
* @param account - The account that owns the blob.
|
|
2203
|
+
* @param blobName - The name of the blob.
|
|
2204
|
+
* @returns The upload status, or undefined if no pending upload exists.
|
|
2205
|
+
*
|
|
2206
|
+
* @example
|
|
2207
|
+
* ```typescript
|
|
2208
|
+
* const status = await client.getMultipartUploadStatus(account, "myblob.txt");
|
|
2209
|
+
* if (status) {
|
|
2210
|
+
* console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
|
|
2211
|
+
* }
|
|
2212
|
+
* ```
|
|
2213
|
+
*/
|
|
2214
|
+
async getMultipartUploadStatus(account, blobName) {
|
|
2215
|
+
const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
|
|
2216
|
+
url.searchParams.set("account", account.toString());
|
|
2217
|
+
url.searchParams.set("blobName", blobName);
|
|
2218
|
+
const response = await fetch(url.toString(), {
|
|
2219
|
+
method: "GET",
|
|
2220
|
+
headers: {
|
|
2221
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
if (response.status === 404) {
|
|
2225
|
+
return void 0;
|
|
2226
|
+
}
|
|
2227
|
+
if (!response.ok) {
|
|
2228
|
+
const errorBody = await response.text().catch(() => "");
|
|
2229
|
+
throw new Error(
|
|
2230
|
+
`Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
return MultipartUploadStatusResponseSchema.parse(await response.json());
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Sign a challenge using the given account and return auth credentials.
|
|
2237
|
+
*
|
|
2238
|
+
* @param account - The Aptos account to sign with.
|
|
2239
|
+
* @param challenge - The hex-encoded challenge string from getChallenge().
|
|
2240
|
+
* @returns BlobOwnerAuth credentials for authenticated requests.
|
|
2241
|
+
*/
|
|
2242
|
+
signChallenge(account, challenge) {
|
|
2243
|
+
const challengeBytes = Hex6.fromHexInput(challenge).toUint8Array();
|
|
2244
|
+
const signature = account.sign(challengeBytes);
|
|
2245
|
+
return {
|
|
2246
|
+
challenge,
|
|
2247
|
+
signature: signature.toUint8Array(),
|
|
2248
|
+
publicKey: account.publicKey.toUint8Array()
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
async #uploadPart(uploadId, partIdx, partData) {
|
|
2252
|
+
const nRetries = 5;
|
|
2253
|
+
let lastResponse;
|
|
2254
|
+
let lastError;
|
|
2255
|
+
let attempts = 0;
|
|
2256
|
+
const partUrl = buildRequestUrl(
|
|
2257
|
+
`/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
|
|
2258
|
+
this.baseUrl
|
|
2259
|
+
);
|
|
2260
|
+
for (let i = 0; i < nRetries; ++i) {
|
|
2261
|
+
attempts++;
|
|
2262
|
+
try {
|
|
2263
|
+
lastResponse = await fetch(partUrl, {
|
|
2264
|
+
method: "PUT",
|
|
2265
|
+
headers: {
|
|
2266
|
+
"Content-Type": "application/octet-stream",
|
|
2267
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2268
|
+
},
|
|
2269
|
+
body: partData
|
|
2270
|
+
});
|
|
2271
|
+
lastError = void 0;
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
lastError = error;
|
|
2274
|
+
if (i < nRetries - 1) {
|
|
2275
|
+
const delay = 2 ** i * 100;
|
|
2276
|
+
await sleep(delay);
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
break;
|
|
2280
|
+
}
|
|
2281
|
+
if (lastResponse.ok) return;
|
|
2282
|
+
if (!isRetryableStatus(lastResponse.status)) {
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
2285
|
+
if (i < nRetries - 1) {
|
|
2286
|
+
const delay = 2 ** i * 100;
|
|
2287
|
+
await sleep(delay);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
if (lastError !== void 0) {
|
|
2291
|
+
const errorCode = getErrorCode(lastError);
|
|
2292
|
+
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2293
|
+
throw new Error(
|
|
2294
|
+
`Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. The connection to the Shelby RPC endpoint was interrupted while sending data${errorCode ? ` (${errorCode})` : ""}. Endpoint: ${partUrl.toString()}, partBytes: ${partData.length}. Last error: ${errorMessage}`,
|
|
2295
|
+
{ cause: lastError }
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
const errorBody = await lastResponse?.text().catch(() => "");
|
|
2299
|
+
throw new Error(
|
|
2300
|
+
`Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
|
|
2304
|
+
validateTotalBytes(totalBytes);
|
|
2305
|
+
const startResponse = await fetch(
|
|
2306
|
+
buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
|
|
2307
|
+
{
|
|
2308
|
+
method: "POST",
|
|
2309
|
+
headers: {
|
|
2310
|
+
"Content-Type": "application/json",
|
|
2311
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2312
|
+
},
|
|
2313
|
+
body: JSON.stringify({
|
|
2314
|
+
rawAccount: account.toString(),
|
|
2315
|
+
rawBlobName: blobName,
|
|
2316
|
+
rawPartSize: partSize
|
|
2317
|
+
})
|
|
2318
|
+
}
|
|
2319
|
+
);
|
|
2320
|
+
if (!startResponse.ok) {
|
|
2321
|
+
let errorBodyText = "Could not read error body";
|
|
2322
|
+
try {
|
|
2323
|
+
errorBodyText = await startResponse.text();
|
|
2324
|
+
} catch (_e) {
|
|
2325
|
+
}
|
|
2326
|
+
throw new Error(
|
|
2327
|
+
`Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
const { uploadId } = StartMultipartUploadResponseSchema.parse(
|
|
2331
|
+
await startResponse.json()
|
|
2332
|
+
);
|
|
2333
|
+
const totalParts = Math.ceil(totalBytes / partSize);
|
|
2334
|
+
let uploadedBytes = 0;
|
|
2335
|
+
for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
|
|
2336
|
+
await this.#uploadPart(uploadId, partIdx, partData);
|
|
2337
|
+
uploadedBytes += partData.length;
|
|
2338
|
+
onProgress?.({
|
|
2339
|
+
phase: "uploading",
|
|
2340
|
+
partIdx,
|
|
2341
|
+
totalParts,
|
|
2342
|
+
partBytes: partData.length,
|
|
2343
|
+
uploadedBytes,
|
|
2344
|
+
totalBytes
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
if (uploadedBytes !== totalBytes) {
|
|
2348
|
+
throw new Error(
|
|
2349
|
+
`Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
|
|
2353
|
+
onProgress?.({
|
|
2354
|
+
phase: "finalizing",
|
|
2355
|
+
partIdx: finalPartIdx,
|
|
2356
|
+
totalParts,
|
|
2357
|
+
// no part uploaded in this phase
|
|
2358
|
+
partBytes: 0,
|
|
2359
|
+
uploadedBytes: totalBytes,
|
|
2360
|
+
totalBytes
|
|
2361
|
+
});
|
|
2362
|
+
const completeResponse = await fetch(
|
|
2363
|
+
buildRequestUrl(
|
|
2364
|
+
`/v1/multipart-uploads/${uploadId}/complete`,
|
|
2365
|
+
this.baseUrl
|
|
2366
|
+
),
|
|
2367
|
+
{
|
|
2368
|
+
method: "POST",
|
|
2369
|
+
headers: {
|
|
2370
|
+
"Content-Type": "application/json",
|
|
2371
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
);
|
|
2375
|
+
if (!completeResponse.ok) {
|
|
2376
|
+
let errorBodyText = "Could not read error body";
|
|
2377
|
+
try {
|
|
2378
|
+
errorBodyText = await completeResponse.text();
|
|
2379
|
+
} catch (_e) {
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error(
|
|
2382
|
+
`Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Uploads blob data to the Shelby RPC node for storage by storage providers.
|
|
2388
|
+
* This method should be called after blob commitments have been registered on the blockchain.
|
|
2389
|
+
* Uses multipart upload for efficient handling of large files.
|
|
2390
|
+
*
|
|
2391
|
+
* @param params.account - The account that owns the blob.
|
|
2392
|
+
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2393
|
+
* @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
|
|
2394
|
+
* @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
|
|
2395
|
+
*
|
|
2396
|
+
* @example
|
|
2397
|
+
* ```typescript
|
|
2398
|
+
* const blobData = new TextEncoder().encode("Hello, world!");
|
|
2399
|
+
*
|
|
2400
|
+
* await client.putBlob({
|
|
2401
|
+
* account: AccountAddress.from("0x1"),
|
|
2402
|
+
* blobName: "greetings/hello.txt",
|
|
2403
|
+
* blobData,
|
|
2404
|
+
* });
|
|
2405
|
+
* ```
|
|
2406
|
+
*/
|
|
2407
|
+
async putBlob(params) {
|
|
2408
|
+
BlobNameSchema.parse(params.blobName);
|
|
2409
|
+
let totalBytes;
|
|
2410
|
+
if (params.blobData instanceof Uint8Array) {
|
|
2411
|
+
totalBytes = params.totalBytes ?? params.blobData.length;
|
|
2412
|
+
if (totalBytes !== params.blobData.length) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
"totalBytes must match blobData.length when blobData is a Uint8Array"
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
} else {
|
|
2418
|
+
if (params.totalBytes === void 0) {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
"totalBytes is required when blobData is a ReadableStream"
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
totalBytes = params.totalBytes;
|
|
2424
|
+
}
|
|
2425
|
+
validateTotalBytes(totalBytes);
|
|
2426
|
+
await this.#putBlobMultipart(
|
|
2427
|
+
params.account,
|
|
2428
|
+
params.blobName,
|
|
2429
|
+
params.blobData,
|
|
2430
|
+
totalBytes,
|
|
2431
|
+
void 0,
|
|
2432
|
+
params.onProgress
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Upload a part with authentication headers.
|
|
2437
|
+
*/
|
|
2438
|
+
async #uploadPartWithAuth(uploadId, partIdx, partData, auth) {
|
|
2439
|
+
const nRetries = 5;
|
|
2440
|
+
let lastResponse;
|
|
2441
|
+
let lastError;
|
|
2442
|
+
let attempts = 0;
|
|
2443
|
+
const partUrl = buildRequestUrl(
|
|
2444
|
+
`/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
|
|
2445
|
+
this.baseUrl
|
|
2446
|
+
);
|
|
2447
|
+
const authHeaders = buildAuthHeaders(auth);
|
|
2448
|
+
for (let i = 0; i < nRetries; ++i) {
|
|
2449
|
+
attempts++;
|
|
2450
|
+
try {
|
|
2451
|
+
lastResponse = await fetch(partUrl, {
|
|
2452
|
+
method: "PUT",
|
|
2453
|
+
headers: {
|
|
2454
|
+
"Content-Type": "application/octet-stream",
|
|
2455
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
2456
|
+
...authHeaders
|
|
2457
|
+
},
|
|
2458
|
+
body: partData
|
|
2459
|
+
});
|
|
2460
|
+
lastError = void 0;
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
lastError = error;
|
|
2463
|
+
if (i < nRetries - 1) {
|
|
2464
|
+
const delay = 2 ** i * 100;
|
|
2465
|
+
await sleep(delay);
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
break;
|
|
2469
|
+
}
|
|
2470
|
+
if (lastResponse.ok) return;
|
|
2471
|
+
if (!isRetryableStatus(lastResponse.status)) {
|
|
2472
|
+
break;
|
|
2473
|
+
}
|
|
2474
|
+
if (i < nRetries - 1) {
|
|
2475
|
+
const delay = 2 ** i * 100;
|
|
2476
|
+
await sleep(delay);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (lastError !== void 0) {
|
|
2480
|
+
const errorCode = getErrorCode(lastError);
|
|
2481
|
+
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2482
|
+
throw new Error(
|
|
2483
|
+
`Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
|
|
2484
|
+
{ cause: lastError }
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
const errorBody = await lastResponse?.text().catch(() => "");
|
|
2488
|
+
throw new Error(
|
|
2489
|
+
`Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Uploads blob data to the Shelby RPC node with authentication and resume support.
|
|
2494
|
+
* This method authenticates using challenge-response and can resume interrupted uploads.
|
|
2495
|
+
*
|
|
2496
|
+
* @param params.account - The Aptos Account (with signing capability) that owns the blob.
|
|
2497
|
+
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2498
|
+
* @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
|
|
2499
|
+
* @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
|
|
2500
|
+
* @param params.onProgress - Optional callback for upload progress.
|
|
2501
|
+
*
|
|
2502
|
+
* @example
|
|
2503
|
+
* ```typescript
|
|
2504
|
+
* await client.putBlobResumable({
|
|
2505
|
+
* account: myAccount, // Aptos Account with signing capability
|
|
2506
|
+
* blobName: "documents/report.pdf",
|
|
2507
|
+
* blobData: fileData,
|
|
2508
|
+
* totalBytes: fileData.length,
|
|
2509
|
+
* });
|
|
2510
|
+
* ```
|
|
2511
|
+
*/
|
|
2512
|
+
async putBlobResumable(params) {
|
|
2513
|
+
BlobNameSchema.parse(params.blobName);
|
|
2514
|
+
let totalBytes;
|
|
2515
|
+
if (params.blobData instanceof Uint8Array) {
|
|
2516
|
+
totalBytes = params.totalBytes ?? params.blobData.length;
|
|
2517
|
+
if (totalBytes !== params.blobData.length) {
|
|
2518
|
+
throw new Error(
|
|
2519
|
+
"totalBytes must match blobData.length when blobData is a Uint8Array"
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
} else {
|
|
2523
|
+
if (params.totalBytes === void 0) {
|
|
2524
|
+
throw new Error(
|
|
2525
|
+
"totalBytes is required when blobData is a ReadableStream"
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
totalBytes = params.totalBytes;
|
|
2529
|
+
}
|
|
2530
|
+
validateTotalBytes(totalBytes);
|
|
2531
|
+
const partSize = params.partSize ?? 5 * 1024 * 1024;
|
|
2532
|
+
const { challenge } = await this.getChallenge(
|
|
2533
|
+
params.account.accountAddress
|
|
2534
|
+
);
|
|
2535
|
+
const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
|
|
2536
|
+
const auth = signFn(params.account, challenge);
|
|
2537
|
+
const authHeaders = buildAuthHeaders(auth);
|
|
2538
|
+
const existingUpload = await this.getMultipartUploadStatus(
|
|
2539
|
+
params.account.accountAddress,
|
|
2540
|
+
params.blobName
|
|
2541
|
+
);
|
|
2542
|
+
let uploadId;
|
|
2543
|
+
let completedPartsSet;
|
|
2544
|
+
let totalParts;
|
|
2545
|
+
if (existingUpload) {
|
|
2546
|
+
uploadId = existingUpload.uploadId;
|
|
2547
|
+
completedPartsSet = new Set(existingUpload.completedParts);
|
|
2548
|
+
totalParts = existingUpload.nParts;
|
|
2549
|
+
if (existingUpload.partSize !== partSize) {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
`Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
} else {
|
|
2555
|
+
const startResponse = await fetch(
|
|
2556
|
+
buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
|
|
2557
|
+
{
|
|
2558
|
+
method: "POST",
|
|
2559
|
+
headers: {
|
|
2560
|
+
"Content-Type": "application/json",
|
|
2561
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
2562
|
+
...authHeaders
|
|
2563
|
+
},
|
|
2564
|
+
body: JSON.stringify({
|
|
2565
|
+
rawAccount: params.account.accountAddress.toString(),
|
|
2566
|
+
rawBlobName: params.blobName,
|
|
2567
|
+
rawPartSize: partSize
|
|
2568
|
+
})
|
|
2569
|
+
}
|
|
2570
|
+
);
|
|
2571
|
+
if (!startResponse.ok) {
|
|
2572
|
+
const errorBodyText = await startResponse.text().catch(() => "");
|
|
2573
|
+
throw new Error(
|
|
2574
|
+
`Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
const parsed = StartMultipartUploadResponseSchema.parse(
|
|
2578
|
+
await startResponse.json()
|
|
2579
|
+
);
|
|
2580
|
+
uploadId = parsed.uploadId;
|
|
2581
|
+
completedPartsSet = /* @__PURE__ */ new Set();
|
|
2582
|
+
totalParts = Math.ceil(totalBytes / partSize);
|
|
2583
|
+
}
|
|
2584
|
+
let uploadedBytes = 0;
|
|
2585
|
+
let partIdx = 0;
|
|
2586
|
+
for await (const [idx, partData] of readInChunks(
|
|
2587
|
+
params.blobData,
|
|
2588
|
+
partSize
|
|
2589
|
+
)) {
|
|
2590
|
+
partIdx = idx;
|
|
2591
|
+
if (completedPartsSet.has(partIdx)) {
|
|
2592
|
+
uploadedBytes += partData.length;
|
|
2593
|
+
continue;
|
|
2594
|
+
}
|
|
2595
|
+
await this.#uploadPartWithAuth(uploadId, partIdx, partData, auth);
|
|
2596
|
+
uploadedBytes += partData.length;
|
|
2597
|
+
params.onProgress?.({
|
|
2598
|
+
phase: "uploading",
|
|
2599
|
+
partIdx,
|
|
2600
|
+
totalParts,
|
|
2601
|
+
partBytes: partData.length,
|
|
2602
|
+
uploadedBytes,
|
|
2603
|
+
totalBytes
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
|
|
2607
|
+
params.onProgress?.({
|
|
2608
|
+
phase: "finalizing",
|
|
2609
|
+
partIdx: finalPartIdx,
|
|
2610
|
+
totalParts,
|
|
2611
|
+
partBytes: 0,
|
|
2612
|
+
uploadedBytes: totalBytes,
|
|
2613
|
+
totalBytes
|
|
2614
|
+
});
|
|
2615
|
+
const completeResponse = await fetch(
|
|
2616
|
+
buildRequestUrl(
|
|
2617
|
+
`/v1/multipart-uploads/${uploadId}/complete`,
|
|
2618
|
+
this.baseUrl
|
|
2619
|
+
),
|
|
2620
|
+
{
|
|
2621
|
+
method: "POST",
|
|
2622
|
+
headers: {
|
|
2623
|
+
"Content-Type": "application/json",
|
|
2624
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
2625
|
+
...authHeaders
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
);
|
|
2629
|
+
if (!completeResponse.ok) {
|
|
2630
|
+
const errorBodyText = await completeResponse.text().catch(() => "");
|
|
2631
|
+
throw new Error(
|
|
2632
|
+
`Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
|
|
2633
|
+
);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Downloads a blob from the Shelby RPC node.
|
|
2638
|
+
* Returns a streaming response with validation to ensure data integrity.
|
|
2639
|
+
*
|
|
2640
|
+
* @param params.account - The account that owns the blob.
|
|
2641
|
+
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2642
|
+
* @param params.range - Optional byte range for partial downloads.
|
|
2643
|
+
* @param params.range.start - Starting byte position (inclusive).
|
|
2644
|
+
* @param params.range.end - Ending byte position (inclusive, optional).
|
|
2645
|
+
* @param params.micropayment - Optional micropayment to attach to the request.
|
|
2646
|
+
*
|
|
2647
|
+
* @returns A ShelbyBlob object containing the account, name, readable stream, and content length.
|
|
2648
|
+
*
|
|
2649
|
+
* @throws Error if the download fails or content length doesn't match.
|
|
2650
|
+
* @throws StaleChannelStateError if the micropayment is stale (server has newer state).
|
|
2651
|
+
*
|
|
2652
|
+
* @example
|
|
2653
|
+
* ```typescript
|
|
2654
|
+
* // Download entire blob
|
|
2655
|
+
* const blob = await client.getBlob({
|
|
2656
|
+
* account: AccountAddress.from("0x1"),
|
|
2657
|
+
* blobName: "documents/report.pdf"
|
|
2658
|
+
* });
|
|
2659
|
+
*
|
|
2660
|
+
* // Download partial content (bytes 100-199)
|
|
2661
|
+
* const partial = await client.getBlob({
|
|
2662
|
+
* account: AccountAddress.from("0x1"),
|
|
2663
|
+
* blobName: "large-file.bin",
|
|
2664
|
+
* range: { start: 100, end: 199 }
|
|
2665
|
+
* });
|
|
2666
|
+
*
|
|
2667
|
+
* // Download with micropayment
|
|
2668
|
+
* const blob = await client.getBlob({
|
|
2669
|
+
* account: AccountAddress.from("0x1"),
|
|
2670
|
+
* blobName: "documents/report.pdf",
|
|
2671
|
+
* micropayment: senderBuiltMicropayment
|
|
2672
|
+
* });
|
|
2673
|
+
* ```
|
|
2674
|
+
*/
|
|
2675
|
+
async getBlob(params) {
|
|
2676
|
+
BlobNameSchema.parse(params.blobName);
|
|
2677
|
+
const url = buildRequestUrl(
|
|
2678
|
+
`/v1/blobs/${params.account.toString()}/${encodeURIComponentKeepSlashes(
|
|
2679
|
+
params.blobName
|
|
2680
|
+
)}`,
|
|
2681
|
+
this.baseUrl
|
|
2682
|
+
);
|
|
2683
|
+
const headers = new Headers();
|
|
2684
|
+
if (params.range !== void 0) {
|
|
2685
|
+
const { start, end } = params.range;
|
|
2686
|
+
if (end === void 0) {
|
|
2687
|
+
headers.set("Range", `bytes=${start}-`);
|
|
2688
|
+
} else {
|
|
2689
|
+
if (end < start) {
|
|
2690
|
+
throw new Error("Range end cannot be less than start.");
|
|
2691
|
+
}
|
|
2692
|
+
headers.set("Range", `bytes=${start}-${end}`);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
if (this.apiKey) {
|
|
2696
|
+
headers.set("Authorization", `Bearer ${this.apiKey}`);
|
|
2697
|
+
}
|
|
2698
|
+
if (params.micropayment) {
|
|
2699
|
+
const bytes = params.micropayment.bcsToBytes();
|
|
2700
|
+
const binaryString = Array.from(
|
|
2701
|
+
bytes,
|
|
2702
|
+
(byte) => String.fromCharCode(byte)
|
|
2703
|
+
).join("");
|
|
2704
|
+
headers.set(MICROPAYMENT_HEADER, btoa(binaryString));
|
|
2705
|
+
}
|
|
2706
|
+
const response = await fetch(url, { headers });
|
|
2707
|
+
if (response.status === 409) {
|
|
2708
|
+
let json;
|
|
2709
|
+
try {
|
|
2710
|
+
json = await response.json();
|
|
2711
|
+
} catch {
|
|
2712
|
+
throw new Error(
|
|
2713
|
+
`Failed to download blob: ${response.status} ${response.statusText}`
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
const parseResult = StaleMicropaymentErrorResponseSchema.safeParse(json);
|
|
2717
|
+
if (!parseResult.success) {
|
|
2718
|
+
throw new Error(
|
|
2719
|
+
`Failed to download blob: ${response.status} ${response.statusText}`
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
const errorBody = parseResult.data;
|
|
2723
|
+
if (errorBody.storedMicropayment) {
|
|
2724
|
+
throw StaleChannelStateError.fromBase64(
|
|
2725
|
+
errorBody.storedMicropayment,
|
|
2726
|
+
errorBody.error
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
throw new Error(
|
|
2730
|
+
errorBody.error ?? `Failed to download blob: ${response.status} ${response.statusText}`
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
if (!response.ok) {
|
|
2734
|
+
throw new Error(
|
|
2735
|
+
`Failed to download blob: ${response.status} ${response.statusText}`
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
if (!response.body) {
|
|
2739
|
+
throw new Error("Response body is null");
|
|
2740
|
+
}
|
|
2741
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
2742
|
+
if (contentLengthHeader === null) {
|
|
2743
|
+
throw new Error(
|
|
2744
|
+
"Response did not have content-length header, which is required"
|
|
2745
|
+
);
|
|
2746
|
+
}
|
|
2747
|
+
const expectedContentLength = Number.parseInt(contentLengthHeader, 10);
|
|
2748
|
+
if (Number.isNaN(expectedContentLength)) {
|
|
2749
|
+
throw new Error(
|
|
2750
|
+
`Invalid content-length header received: ${contentLengthHeader}`
|
|
2751
|
+
);
|
|
2752
|
+
}
|
|
2753
|
+
const validatingStream = new ReadableStream({
|
|
2754
|
+
start(controller) {
|
|
2755
|
+
const maybeReader = response.body?.getReader();
|
|
2756
|
+
if (!maybeReader) {
|
|
2757
|
+
controller.error(new Error("Response body reader is unavailable"));
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const reader = maybeReader;
|
|
2761
|
+
let bytesReceived = 0;
|
|
2762
|
+
function pump() {
|
|
2763
|
+
return reader.read().then(({ done, value }) => {
|
|
2764
|
+
if (done) {
|
|
2765
|
+
if (bytesReceived !== expectedContentLength) {
|
|
2766
|
+
controller.error(
|
|
2767
|
+
new Error(
|
|
2768
|
+
`Downloaded data size (${bytesReceived} bytes) does not match content-length header (${expectedContentLength} bytes). This might indicate a partial or corrupted download.`
|
|
2769
|
+
)
|
|
2770
|
+
);
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
controller.close();
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
bytesReceived += value.byteLength;
|
|
2777
|
+
controller.enqueue(value);
|
|
2778
|
+
return pump();
|
|
2779
|
+
}).catch((error) => {
|
|
2780
|
+
controller.error(error);
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
return pump();
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
return {
|
|
2787
|
+
account: normalizeAddress(params.account),
|
|
2788
|
+
name: params.blobName,
|
|
2789
|
+
readable: validatingStream,
|
|
2790
|
+
contentLength: expectedContentLength
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
|
|
2795
|
+
// src/core/clients/ShelbyClient.ts
|
|
2796
|
+
var ShelbyClient = class {
|
|
2797
|
+
/**
|
|
2798
|
+
* The coordination client is used to interact with the Aptos blockchain which handles the commitments
|
|
2799
|
+
* and metadata for blobs.
|
|
2800
|
+
*/
|
|
2801
|
+
coordination;
|
|
2802
|
+
/**
|
|
2803
|
+
* The metadata client is used for protocol-level metadata queries.
|
|
2804
|
+
*/
|
|
2805
|
+
metadata;
|
|
2806
|
+
/**
|
|
2807
|
+
* The RPC client is used to interact with the Shelby RPC node which can be responsible for storing,
|
|
2808
|
+
* confirming, and retrieving blobs from the storage layer.
|
|
2809
|
+
*
|
|
2810
|
+
* If not provided, the default RPC client will be created.
|
|
2811
|
+
*/
|
|
2812
|
+
rpc;
|
|
2813
|
+
/**
|
|
2814
|
+
* The configuration for the Shelby client.
|
|
2815
|
+
*/
|
|
2816
|
+
config;
|
|
2817
|
+
/**
|
|
2818
|
+
* The Aptos client.
|
|
2819
|
+
*
|
|
2820
|
+
* If not provided, a default Aptos client will be created.
|
|
2821
|
+
*/
|
|
2822
|
+
aptos;
|
|
2823
|
+
/**
|
|
2824
|
+
* The erasure coding provider used for encoding/decoding operations.
|
|
2825
|
+
* Lazily initialized on first use if not provided.
|
|
2826
|
+
*/
|
|
2827
|
+
_provider;
|
|
2828
|
+
/**
|
|
2829
|
+
* Creates a new ShelbyClient instance for interacting with the Shelby Protocol.
|
|
2830
|
+
* This client combines blockchain operations (via coordination) and storage operations (via RPC).
|
|
2831
|
+
*
|
|
2832
|
+
* @param config - The client configuration object.
|
|
2833
|
+
* @param config.aptos.config - The Aptos network configuration.
|
|
2834
|
+
* @param config.shelby.rpc.baseUrl - The base URL of the Shelby RPC node (optional, defaults to devnet).
|
|
2835
|
+
* @param config.shelby.indexer - The indexer configuration for GraphQL queries.
|
|
2836
|
+
* @param provider - Optional erasure coding provider for encoding/decoding operations.
|
|
2837
|
+
* If not provided, a ClayErasureCodingProvider will be created on first use.
|
|
2838
|
+
* Pass a shared provider to reuse across multiple clients.
|
|
2839
|
+
*
|
|
2840
|
+
* @example
|
|
2841
|
+
* ```typescript
|
|
2842
|
+
* // Basic usage (provider created automatically)
|
|
2843
|
+
* const client = new ShelbyClient({
|
|
2844
|
+
* network: Network.SHELBYNET,
|
|
2845
|
+
* });
|
|
2846
|
+
*
|
|
2847
|
+
* // Advanced: Share provider across multiple clients
|
|
2848
|
+
* const provider = await ClayErasureCodingProvider.create();
|
|
2849
|
+
* const mainnetClient = new ShelbyClient(mainnetConfig, provider);
|
|
2850
|
+
* const devnetClient = new ShelbyClient(devnetConfig, provider);
|
|
2851
|
+
* ```
|
|
2852
|
+
*/
|
|
2853
|
+
constructor(config, provider) {
|
|
2854
|
+
this.config = config;
|
|
2855
|
+
this.aptos = new Aptos3(getAptosConfig(config));
|
|
2856
|
+
this.coordination = new ShelbyBlobClient(config);
|
|
2857
|
+
this.metadata = new ShelbyMetadataClient(config);
|
|
2858
|
+
this.rpc = new ShelbyRPCClient(config, {
|
|
2859
|
+
signChallengeHandler: (account, challenge) => this.signChallenge(account, challenge)
|
|
2860
|
+
});
|
|
2861
|
+
this._provider = provider;
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Sign an authentication challenge for blob owner verification.
|
|
2865
|
+
*
|
|
2866
|
+
* The default implementation delegates to `ShelbyRPCClient.signChallenge`
|
|
2867
|
+
* which works for standard Ed25519 accounts. Kits that use derived /
|
|
2868
|
+
* abstracted accounts (Solana, Ethereum) should override this method to
|
|
2869
|
+
* provide the correct public key bytes and derivation metadata.
|
|
2870
|
+
*/
|
|
2871
|
+
signChallenge(account, challenge) {
|
|
2872
|
+
return this.rpc.signChallenge(account, challenge);
|
|
2873
|
+
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Get the erasure coding provider, creating it if necessary.
|
|
2876
|
+
* This allows lazy initialization for users who don't provide a provider.
|
|
2877
|
+
*/
|
|
2878
|
+
async getProvider() {
|
|
2879
|
+
if (!this._provider) {
|
|
2880
|
+
this._provider = await ClayErasureCodingProvider.create(
|
|
2881
|
+
defaultErasureCodingConfig()
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
return this._provider;
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* The base URL for the Shelby RPC node.
|
|
2888
|
+
*/
|
|
2889
|
+
get baseUrl() {
|
|
2890
|
+
return getShelbyRPCBaseUrl(this.config);
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Uploads a blob to the Shelby network.
|
|
2894
|
+
* This method handles the complete upload flow including commitment generation,
|
|
2895
|
+
* blockchain registration, and storage upload.
|
|
2896
|
+
*
|
|
2897
|
+
* Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
|
|
2898
|
+
* For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
|
|
2899
|
+
* using `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
|
|
2900
|
+
* with a `ReadableStream`.
|
|
2901
|
+
*
|
|
2902
|
+
* @param params.blobData - The raw data to upload as a Uint8Array.
|
|
2903
|
+
* @param params.signer - The account that signs and pays for the transaction.
|
|
2904
|
+
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2905
|
+
* @param params.expirationMicros - The expiration time in microseconds since Unix epoch.
|
|
2906
|
+
* @param params.options - Optional upload configuration.
|
|
2907
|
+
* @param params.options.chunksetSizeBytes - Custom chunkset size.
|
|
2908
|
+
* @param params.options.build - Additional Aptos transaction options.
|
|
2909
|
+
*
|
|
2910
|
+
* @returns The transaction and generated blob commitments (when implemented).
|
|
2911
|
+
*
|
|
2912
|
+
* @example
|
|
2913
|
+
* ```typescript
|
|
2914
|
+
* await client.upload({
|
|
2915
|
+
* blobData: Buffer.from("Hello, World!"),
|
|
2916
|
+
* signer: account,
|
|
2917
|
+
* blobName: "hello.txt",
|
|
2918
|
+
* expirationMicros: Date.now() * 1000 + 86400_000_000 // 24 hours from now
|
|
2919
|
+
* });
|
|
2920
|
+
* ```
|
|
2921
|
+
*/
|
|
2922
|
+
async upload(params) {
|
|
2923
|
+
const existingBlobMetadata = await this.coordination.getBlobMetadata({
|
|
2924
|
+
account: params.signer.accountAddress,
|
|
2925
|
+
name: params.blobName
|
|
2926
|
+
});
|
|
2927
|
+
if (!existingBlobMetadata) {
|
|
2928
|
+
const provider = await this.getProvider();
|
|
2929
|
+
const blobCommitments = await generateCommitments(
|
|
2930
|
+
provider,
|
|
2931
|
+
params.blobData
|
|
2932
|
+
);
|
|
2933
|
+
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
|
|
2934
|
+
account: params.signer,
|
|
2935
|
+
blobName: params.blobName,
|
|
2936
|
+
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
2937
|
+
size: params.blobData.length,
|
|
2938
|
+
expirationMicros: params.expirationMicros,
|
|
2939
|
+
config: provider.config,
|
|
2940
|
+
options: params.options
|
|
2941
|
+
});
|
|
2942
|
+
await this.coordination.aptos.waitForTransaction({
|
|
2943
|
+
transactionHash: pendingRegisterBlobTransaction.hash
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
await this.rpc.putBlobResumable({
|
|
2947
|
+
account: params.signer,
|
|
2948
|
+
blobName: params.blobName,
|
|
2949
|
+
blobData: params.blobData
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Uploads a batch of blobs to the Shelby network.
|
|
2954
|
+
* This method handles the complete upload flow including commitment generation,
|
|
2955
|
+
* blockchain registration, and storage upload.
|
|
2956
|
+
*
|
|
2957
|
+
* Note: This method accepts only `Uint8Array` and buffers each blob in memory.
|
|
2958
|
+
* For streaming uploads of large files, orchestrate the steps manually using
|
|
2959
|
+
* `generateCommitments()`, `coordination.registerBlob()`, and `rpc.putBlob()`
|
|
2960
|
+
* with a `ReadableStream`.
|
|
2961
|
+
*
|
|
2962
|
+
* @param params.blobs - The blobs to upload.
|
|
2963
|
+
* @param params.blobs.blobData - The raw data to upload as a Uint8Array.
|
|
2964
|
+
* @param params.blobs.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2965
|
+
* @param params.expirationMicros - The expiration time in microseconds since Unix epoch.
|
|
2966
|
+
* @param params.signer - The account that signs and pays for the transaction.
|
|
2967
|
+
* @param params.options - Optional upload configuration.
|
|
2968
|
+
* @param params.options.chunksetSizeBytes - Custom chunkset size.
|
|
2969
|
+
* @param params.options.build - Additional Aptos transaction options.
|
|
2970
|
+
*
|
|
2971
|
+
* @returns The transaction and generated blob commitments (when implemented).
|
|
2972
|
+
*
|
|
2973
|
+
* @example
|
|
2974
|
+
* ```typescript
|
|
2975
|
+
* await client.batchUpload({
|
|
2976
|
+
* blobs: [
|
|
2977
|
+
* { blobData: Buffer.from("Hello, World!"), blobName: "hello.txt" },
|
|
2978
|
+
* { blobData: Buffer.from("Hello, World 2!"), blobName: "hello2.txt" },
|
|
2979
|
+
* ],
|
|
2980
|
+
* expirationMicros: Date.now() * 1000 + 86400_000_000 // 24 hours from now
|
|
2981
|
+
* });
|
|
2982
|
+
* ```
|
|
2983
|
+
*/
|
|
2984
|
+
async batchUpload(params) {
|
|
2985
|
+
const existingBlobs = await this.coordination.getBlobs({
|
|
2986
|
+
where: {
|
|
2987
|
+
blob_name: {
|
|
2988
|
+
_in: params.blobs.map(
|
|
2989
|
+
(blob) => createBlobKey({
|
|
2990
|
+
account: params.signer.accountAddress,
|
|
2991
|
+
blobName: blob.blobName
|
|
2992
|
+
})
|
|
2993
|
+
)
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
const blobsToRegister = params.blobs.filter(
|
|
2998
|
+
(blob) => !existingBlobs.some(
|
|
2999
|
+
(existingBlob) => existingBlob.name === createBlobKey({
|
|
3000
|
+
account: params.signer.accountAddress,
|
|
3001
|
+
blobName: blob.blobName
|
|
3002
|
+
})
|
|
3003
|
+
)
|
|
3004
|
+
);
|
|
3005
|
+
if (blobsToRegister.length > 0) {
|
|
3006
|
+
const provider = await this.getProvider();
|
|
3007
|
+
const blobCommitments = await Promise.all(
|
|
3008
|
+
blobsToRegister.map(
|
|
3009
|
+
async (blob) => generateCommitments(provider, blob.blobData)
|
|
3010
|
+
)
|
|
3011
|
+
);
|
|
3012
|
+
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
|
|
3013
|
+
account: params.signer,
|
|
3014
|
+
expirationMicros: params.expirationMicros,
|
|
3015
|
+
blobs: blobsToRegister.map((blob, index) => ({
|
|
3016
|
+
blobName: blob.blobName,
|
|
3017
|
+
blobSize: blob.blobData.length,
|
|
3018
|
+
blobMerkleRoot: blobCommitments[index].blob_merkle_root
|
|
3019
|
+
})),
|
|
3020
|
+
config: provider.config,
|
|
3021
|
+
options: params.options
|
|
3022
|
+
});
|
|
3023
|
+
await this.coordination.aptos.waitForTransaction({
|
|
3024
|
+
transactionHash: pendingRegisterBlobTransaction.hash,
|
|
3025
|
+
options: { waitForIndexer: true }
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
const limit = pLimit(3);
|
|
3029
|
+
await Promise.all(
|
|
3030
|
+
params.blobs.map(
|
|
3031
|
+
(blob) => limit(
|
|
3032
|
+
() => this.rpc.putBlobResumable({
|
|
3033
|
+
account: params.signer,
|
|
3034
|
+
blobName: blob.blobName,
|
|
3035
|
+
blobData: blob.blobData
|
|
3036
|
+
})
|
|
3037
|
+
)
|
|
3038
|
+
)
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Downloads a blob from the Shelby RPC node.
|
|
3043
|
+
*
|
|
3044
|
+
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
3045
|
+
* @param params.blobName - The name of the blob (e.g. "foo/bar")
|
|
3046
|
+
* @param params.range - The range of the blob to download.
|
|
3047
|
+
*
|
|
3048
|
+
* @returns A `ShelbyBlob` object containing the blob data.
|
|
3049
|
+
*
|
|
3050
|
+
* @example
|
|
3051
|
+
* ```typescript
|
|
3052
|
+
* const blob = await client.download({
|
|
3053
|
+
* account,
|
|
3054
|
+
* blobName: "foo/bar.txt",
|
|
3055
|
+
* });
|
|
3056
|
+
* ```
|
|
3057
|
+
*/
|
|
3058
|
+
async download(params) {
|
|
3059
|
+
return await this.rpc.getBlob(params);
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
*
|
|
3063
|
+
* Funds an account with ShelbyUSD tokens.
|
|
3064
|
+
*
|
|
3065
|
+
* @param params.address - The address to fund.
|
|
3066
|
+
* @param params.amount - The amount to fund.
|
|
3067
|
+
* @returns The transaction hash of the funded account.
|
|
3068
|
+
*
|
|
3069
|
+
* @example
|
|
3070
|
+
* ```typescript
|
|
3071
|
+
* const hash = await client.fundAccountWithShelbyUSD({
|
|
3072
|
+
* address: "0x1",
|
|
3073
|
+
* amount: 100000000,
|
|
3074
|
+
* });
|
|
3075
|
+
* ```
|
|
3076
|
+
*/
|
|
3077
|
+
async fundAccountWithShelbyUSD(params) {
|
|
3078
|
+
if (this.config.network === Network4.TESTNET) {
|
|
3079
|
+
throw new Error(
|
|
3080
|
+
`ShelbyUSD cannot be minted programmatically on ${Network4.TESTNET}. Please use the faucet at https://docs.shelby.xyz/apis/faucet/shelbyusd instead.`
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
const { address, amount } = params;
|
|
3084
|
+
try {
|
|
3085
|
+
const faucet = this.config.faucet?.baseUrl ?? "https://faucet.shelbynet.shelby.xyz/fund?asset=shelbyusd";
|
|
3086
|
+
const authToken = this.config.faucet?.authToken;
|
|
3087
|
+
const response = await fetch(`${faucet}`, {
|
|
3088
|
+
method: "POST",
|
|
3089
|
+
body: JSON.stringify({
|
|
3090
|
+
address: normalizeAddress(address).toString(),
|
|
3091
|
+
amount
|
|
3092
|
+
}),
|
|
3093
|
+
headers: {
|
|
3094
|
+
"Content-Type": "application/json",
|
|
3095
|
+
...authToken ? { Authorization: `Bearer ${authToken}` } : {}
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
if (!response.ok) {
|
|
3099
|
+
const errorBody = await response.text();
|
|
3100
|
+
throw new Error(`Failed to fund account: ${errorBody}`);
|
|
3101
|
+
}
|
|
3102
|
+
const json = await response.json();
|
|
3103
|
+
const res = await this.aptos.waitForTransaction({
|
|
3104
|
+
transactionHash: json.txn_hashes[0],
|
|
3105
|
+
options: {
|
|
3106
|
+
timeoutSecs: DEFAULT_TXN_TIMEOUT_SEC,
|
|
3107
|
+
checkSuccess: true
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
if (res.type === TransactionResponseType.User) {
|
|
3111
|
+
return res.hash;
|
|
3112
|
+
}
|
|
3113
|
+
throw new Error(
|
|
3114
|
+
`Unexpected transaction received for fund account: ${res.type}`
|
|
3115
|
+
);
|
|
3116
|
+
} catch (error) {
|
|
3117
|
+
throw new Error(`Failed to fund account: ${error}`);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Fund an account with APT tokens
|
|
3122
|
+
*
|
|
3123
|
+
* @param params.address - The address to fund
|
|
3124
|
+
* @param params.amount - The amount to fund
|
|
3125
|
+
* @returns The transaction hash of the funded account
|
|
3126
|
+
*
|
|
3127
|
+
* @example
|
|
3128
|
+
* ```typescript
|
|
3129
|
+
* const hash = await client.fundAccountWithAPT({
|
|
3130
|
+
* address: "0x1",
|
|
3131
|
+
* amount: 100000000,
|
|
3132
|
+
* });
|
|
3133
|
+
* ```
|
|
3134
|
+
*/
|
|
3135
|
+
async fundAccountWithAPT(params) {
|
|
3136
|
+
const transaction = await this.aptos.fundAccount({
|
|
3137
|
+
accountAddress: params.address,
|
|
3138
|
+
amount: params.amount
|
|
3139
|
+
});
|
|
3140
|
+
return transaction.hash;
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
|
|
3144
|
+
// src/core/clients/ShelbyMicropaymentChannelClient.ts
|
|
151
3145
|
import {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
3146
|
+
AccountAddress as AccountAddress5,
|
|
3147
|
+
Aptos as Aptos4,
|
|
3148
|
+
MoveVector
|
|
3149
|
+
} from "@aptos-labs/ts-sdk";
|
|
3150
|
+
var ShelbyMicropaymentChannelClient = class _ShelbyMicropaymentChannelClient {
|
|
3151
|
+
aptos;
|
|
3152
|
+
deployer;
|
|
3153
|
+
indexer;
|
|
3154
|
+
/**
|
|
3155
|
+
* The ShelbyMicropaymentChannelClient is used to interact with the Micropayment contract on the Aptos blockchain. This
|
|
3156
|
+
* includes functions for the full lifecycle of micropayment channels: creation, withdrawals, expiration extensions, etc.
|
|
3157
|
+
*
|
|
3158
|
+
* @param config - The client configuration object.
|
|
3159
|
+
* @param config.network - The Shelby network to use.
|
|
3160
|
+
*
|
|
3161
|
+
* @example
|
|
3162
|
+
* ```typescript
|
|
3163
|
+
* const client = new ShelbyMicropaymentChannelClient({
|
|
3164
|
+
* aptos: {
|
|
3165
|
+
* network: Network.SHELBYNET,
|
|
3166
|
+
* clientConfig: {
|
|
3167
|
+
* API_KEY: "AG-***",
|
|
3168
|
+
* },
|
|
3169
|
+
* },
|
|
3170
|
+
* });
|
|
3171
|
+
* ```
|
|
3172
|
+
*/
|
|
3173
|
+
constructor(config) {
|
|
3174
|
+
this.aptos = new Aptos4(getAptosConfig(config));
|
|
3175
|
+
this.deployer = config.deployer ?? AccountAddress5.fromString(MICROPAYMENTS_DEPLOYER);
|
|
3176
|
+
this.indexer = getShelbyIndexerClient(config);
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3179
|
+
* Calls initialize function so caller can thereafter create micropayment channels.
|
|
3180
|
+
*
|
|
3181
|
+
* @param params.sender - The account that will be sending funds to various receivers in micropayment channels.
|
|
3182
|
+
* @param params.options - Optional transaction building options.
|
|
3183
|
+
*
|
|
3184
|
+
* @returns An object containing the pending transaction.
|
|
3185
|
+
*
|
|
3186
|
+
* @example
|
|
3187
|
+
* ```typescript
|
|
3188
|
+
*
|
|
3189
|
+
* const { transaction } = await client.initializePaymentChannels({
|
|
3190
|
+
* sender: sender,
|
|
3191
|
+
* });
|
|
3192
|
+
* ```
|
|
3193
|
+
*/
|
|
3194
|
+
async initializePaymentChannels(params) {
|
|
3195
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3196
|
+
options: params.options,
|
|
3197
|
+
data: {
|
|
3198
|
+
function: `${(this.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::initialize_payment_channels`,
|
|
3199
|
+
functionArguments: []
|
|
3200
|
+
},
|
|
3201
|
+
sender: params.sender.accountAddress
|
|
3202
|
+
});
|
|
3203
|
+
return {
|
|
3204
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3205
|
+
signer: params.sender,
|
|
3206
|
+
transaction
|
|
3207
|
+
})
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Creates a micropayment channel to start a channel bweteen a sender and a receiver for a specific asset.
|
|
3212
|
+
*
|
|
3213
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
3214
|
+
* @param params.sender - The account that will be sending funds.
|
|
3215
|
+
* @param params.receiver - The account that will be receiving funds.
|
|
3216
|
+
* @param params.expirationMicros - The expiration of the micropayment channel.
|
|
3217
|
+
* @param params.depositAmount - The amount of the asset locked to the channel.
|
|
3218
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3219
|
+
* @param params.options - Optional transaction building options.
|
|
3220
|
+
*
|
|
3221
|
+
* @returns An object containing the pending transaction.
|
|
3222
|
+
*
|
|
3223
|
+
* @example
|
|
3224
|
+
* ```typescript
|
|
3225
|
+
*
|
|
3226
|
+
* const { transaction } = await client.createChannel({
|
|
3227
|
+
* sender: sender,
|
|
3228
|
+
* receiver: receiver,
|
|
3229
|
+
* expirationMicros: Date.now() * 1000 + 86400_000_000, // 1 day from now in microseconds
|
|
3230
|
+
* depositAmount: 0.001,
|
|
3231
|
+
* fungibleAssetAddress: fungibleAssetAddress,
|
|
3232
|
+
* });
|
|
3233
|
+
* ```
|
|
3234
|
+
*/
|
|
3235
|
+
async createChannel(params) {
|
|
3236
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3237
|
+
options: params.options,
|
|
3238
|
+
data: _ShelbyMicropaymentChannelClient.makeCreateMicropaymentChannelPayload(
|
|
3239
|
+
{
|
|
3240
|
+
deployer: this.deployer,
|
|
3241
|
+
receiver: params.receiver,
|
|
3242
|
+
expirationMicros: params.expirationMicros,
|
|
3243
|
+
depositAmount: params.depositAmount,
|
|
3244
|
+
fungibleAssetAddress: params.fungibleAssetAddress,
|
|
3245
|
+
publicKey: params.publicKey ?? params.sender.publicKey.toUint8Array()
|
|
3246
|
+
}
|
|
3247
|
+
),
|
|
3248
|
+
sender: params.sender.accountAddress
|
|
3249
|
+
});
|
|
3250
|
+
return {
|
|
3251
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3252
|
+
signer: params.sender,
|
|
3253
|
+
transaction
|
|
3254
|
+
})
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* Creates a micropayment channel creation payload to start a channel between a sender and a receiver for a specific asset.
|
|
3259
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
3260
|
+
*
|
|
3261
|
+
* @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
|
|
3262
|
+
* @param params.sender - The account that will be sending funds.
|
|
3263
|
+
* @param params.receiver - The account that will be receiving funds.
|
|
3264
|
+
* @param params.expirationMicros - The expiration of the micropayment channel.
|
|
3265
|
+
* @param params.depositAmount - The amount of the asset locked to the channel.
|
|
3266
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3267
|
+
* @param params.publicKey - The Ed25519 public key bytes for the channel.
|
|
3268
|
+
*
|
|
3269
|
+
* @returns An Aptos transaction payload data object for the create_channel Move function.
|
|
3270
|
+
*/
|
|
3271
|
+
static makeCreateMicropaymentChannelPayload(params) {
|
|
3272
|
+
return {
|
|
3273
|
+
function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::create_channel`,
|
|
3274
|
+
functionArguments: [
|
|
3275
|
+
params.receiver,
|
|
3276
|
+
params.expirationMicros,
|
|
3277
|
+
params.depositAmount,
|
|
3278
|
+
params.fungibleAssetAddress,
|
|
3279
|
+
MoveVector.U8(params.publicKey)
|
|
3280
|
+
]
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
/**
|
|
3284
|
+
* Creates a micropayment that can be sent to a receiver.
|
|
3285
|
+
* The sender signs a WithdrawApproval message that authorizes the receiver
|
|
3286
|
+
* to withdraw funds from the micropayment channel.
|
|
3287
|
+
*
|
|
3288
|
+
* @param params.sender - The account that owns the payment channel and signs the approval.
|
|
3289
|
+
* @param params.receiver - The account address that will receive funds.
|
|
3290
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3291
|
+
* @param params.amount - The cumulative amount to authorize (not incremental).
|
|
3292
|
+
* @param params.paymentChannelId - The id of the payment channel.
|
|
3293
|
+
* @param params.sequenceNumber - The sequence number of the micropayment. Must be >= channel's next_withdrawn_sequence_number.
|
|
3294
|
+
*
|
|
3295
|
+
* @returns A SenderBuiltMicropayment containing the signed approval.
|
|
3296
|
+
*
|
|
3297
|
+
* @example
|
|
3298
|
+
* ```typescript
|
|
3299
|
+
* const senderBuiltMicropayment = client.createMicropayment({
|
|
3300
|
+
* sender: senderAccount,
|
|
3301
|
+
* receiver: receiverAddress,
|
|
3302
|
+
* fungibleAssetAddress: fungibleAssetAddress,
|
|
3303
|
+
* amount: 100,
|
|
3304
|
+
* paymentChannelId: channelId,
|
|
3305
|
+
* sequenceNumber: 1,
|
|
3306
|
+
* });
|
|
3307
|
+
* ```
|
|
3308
|
+
*/
|
|
3309
|
+
createMicropayment(params) {
|
|
3310
|
+
const message = SenderBuiltMicropayment.buildSignedMessage({
|
|
3311
|
+
deployer: this.deployer,
|
|
3312
|
+
sender: params.sender.accountAddress,
|
|
3313
|
+
receiver: params.receiver,
|
|
3314
|
+
fungibleAssetAddress: params.fungibleAssetAddress,
|
|
3315
|
+
amount: params.amount,
|
|
3316
|
+
paymentChannelId: params.paymentChannelId,
|
|
3317
|
+
sequenceNumber: params.sequenceNumber
|
|
3318
|
+
});
|
|
3319
|
+
const signature = params.sender.sign(message);
|
|
3320
|
+
return new SenderBuiltMicropayment(
|
|
3321
|
+
params.sender.accountAddress,
|
|
3322
|
+
params.receiver,
|
|
3323
|
+
BigInt(params.paymentChannelId),
|
|
3324
|
+
BigInt(params.amount),
|
|
3325
|
+
params.fungibleAssetAddress,
|
|
3326
|
+
BigInt(params.sequenceNumber),
|
|
3327
|
+
params.sender.publicKey.toUint8Array(),
|
|
3328
|
+
signature.toUint8Array(),
|
|
3329
|
+
this.deployer
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Creates a static payload for the receiver_withdraw Move function.
|
|
3334
|
+
* This is a helper method for constructing the transaction payload.
|
|
3335
|
+
*
|
|
3336
|
+
* @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
|
|
3337
|
+
* @param params.sender - The sender's account address.
|
|
3338
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3339
|
+
* @param params.amount - The cumulative amount authorized to withdraw.
|
|
3340
|
+
* @param params.paymentChannelId - The payment channel id.
|
|
3341
|
+
* @param params.sequenceNumber - The sequence number for this withdrawal.
|
|
3342
|
+
* @param params.signature - The Ed25519 signature bytes.
|
|
3343
|
+
*
|
|
3344
|
+
* @returns An Aptos transaction payload data object for the receiver_withdraw Move function.
|
|
3345
|
+
*/
|
|
3346
|
+
static createMicropaymentTransactionPayload(params) {
|
|
3347
|
+
return {
|
|
3348
|
+
function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::receiver_withdraw`,
|
|
3349
|
+
functionArguments: [
|
|
3350
|
+
params.sender,
|
|
3351
|
+
params.fungibleAssetAddress,
|
|
3352
|
+
params.amount,
|
|
3353
|
+
params.paymentChannelId,
|
|
3354
|
+
params.sequenceNumber,
|
|
3355
|
+
MoveVector.U8(params.signature)
|
|
3356
|
+
]
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* Withdraws funds from a micropayment channel as the receiver.
|
|
3361
|
+
* The receiver submits the sender's signed approval to claim funds.
|
|
3362
|
+
*
|
|
3363
|
+
* @param params.receiver - The receiver account that will sign and submit the transaction.
|
|
3364
|
+
* @param params.micropayment - The sender's signed micropayment approval.
|
|
3365
|
+
* @param params.options - Optional transaction generation options.
|
|
3366
|
+
*
|
|
3367
|
+
* @returns An object containing the pending transaction.
|
|
3368
|
+
*
|
|
3369
|
+
* @example
|
|
3370
|
+
* ```typescript
|
|
3371
|
+
* const { transaction } = await client.receiverWithdraw({
|
|
3372
|
+
* receiver: receiverAccount,
|
|
3373
|
+
* micropayment: senderBuiltMicropayment,
|
|
3374
|
+
* });
|
|
3375
|
+
* ```
|
|
3376
|
+
*/
|
|
3377
|
+
async receiverWithdraw(params) {
|
|
3378
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3379
|
+
options: params.options,
|
|
3380
|
+
data: _ShelbyMicropaymentChannelClient.createMicropaymentTransactionPayload(
|
|
3381
|
+
{
|
|
3382
|
+
deployer: this.deployer,
|
|
3383
|
+
sender: params.micropayment.sender,
|
|
3384
|
+
fungibleAssetAddress: params.micropayment.fungibleAssetAddress,
|
|
3385
|
+
amount: params.micropayment.amount,
|
|
3386
|
+
paymentChannelId: params.micropayment.paymentChannelId,
|
|
3387
|
+
sequenceNumber: params.micropayment.sequenceNumber,
|
|
3388
|
+
signature: params.micropayment.signature
|
|
3389
|
+
}
|
|
3390
|
+
),
|
|
3391
|
+
sender: params.receiver.accountAddress
|
|
3392
|
+
});
|
|
3393
|
+
return {
|
|
3394
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3395
|
+
signer: params.receiver,
|
|
3396
|
+
transaction
|
|
3397
|
+
})
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Extends the expiration time of a micropayment channel.
|
|
3402
|
+
*
|
|
3403
|
+
* @param params.sender - The owner of the account.
|
|
3404
|
+
* @param params.receiver - The account address of the receiver.
|
|
3405
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3406
|
+
* @param params.newExpirationMicros - The new expiration time in microseconds.
|
|
3407
|
+
* @param params.options - Optional transaction generation options.
|
|
3408
|
+
*
|
|
3409
|
+
* @returns An object containing the pending transaction.
|
|
3410
|
+
*
|
|
3411
|
+
* @example
|
|
3412
|
+
* ```typescript
|
|
3413
|
+
* const { transaction } = await client.extendExpirationTime({
|
|
3414
|
+
* sender: sender,
|
|
3415
|
+
* receiver: receiverAddress,
|
|
3416
|
+
* fungibleAssetAddress: fungibleAssetAddress,
|
|
3417
|
+
* newExpirationMicros: Date.now() * 1000 + 172800_000_000, // 2 days from now
|
|
3418
|
+
* });
|
|
3419
|
+
* ```
|
|
3420
|
+
*/
|
|
3421
|
+
async extendExpirationTime(params) {
|
|
3422
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3423
|
+
options: params.options,
|
|
3424
|
+
data: _ShelbyMicropaymentChannelClient.makeExtendExpirationTimePayload({
|
|
3425
|
+
deployer: this.deployer,
|
|
3426
|
+
receiver: params.receiver,
|
|
3427
|
+
fungibleAssetAddress: params.fungibleAssetAddress,
|
|
3428
|
+
newExpirationMicros: params.newExpirationMicros
|
|
3429
|
+
}),
|
|
3430
|
+
sender: params.sender.accountAddress
|
|
3431
|
+
});
|
|
3432
|
+
return {
|
|
3433
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3434
|
+
signer: params.sender,
|
|
3435
|
+
transaction
|
|
3436
|
+
})
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Creates a payload to extend the expiration time on a micropayment channel.
|
|
3441
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
3442
|
+
*
|
|
3443
|
+
* @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
|
|
3444
|
+
* @param params.receiver - The account that will be receiving funds.
|
|
3445
|
+
* @param params.expirationMicros - The new expiration of the micropayment channel.
|
|
3446
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3447
|
+
*
|
|
3448
|
+
* @returns An Aptos transaction payload data object for the extend_expiration_time Move function.
|
|
3449
|
+
*/
|
|
3450
|
+
static makeExtendExpirationTimePayload(params) {
|
|
3451
|
+
return {
|
|
3452
|
+
function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::extend_expiration_time`,
|
|
3453
|
+
functionArguments: [
|
|
3454
|
+
params.receiver,
|
|
3455
|
+
params.fungibleAssetAddress,
|
|
3456
|
+
params.newExpirationMicros
|
|
3457
|
+
]
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Increases the deposit amount locked in a micropayment channel.
|
|
3462
|
+
*
|
|
3463
|
+
* @param params.sender - The account that is increasing the deposit.
|
|
3464
|
+
* @param params.receiver - The account address of the receiver.
|
|
3465
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3466
|
+
* @param params.additionalAmount - The additional amount to deposit.
|
|
3467
|
+
* @param params.options - Optional transaction generation options.
|
|
3468
|
+
*
|
|
3469
|
+
* @returns An object containing the pending transaction.
|
|
3470
|
+
*
|
|
3471
|
+
* @example
|
|
3472
|
+
* ```typescript
|
|
3473
|
+
* const { transaction } = await client.increaseDepositAmount({
|
|
3474
|
+
* sender: sender,
|
|
3475
|
+
* receiver: receiverAddress,
|
|
3476
|
+
* fungibleAssetAddress: fungibleAssetAddress,
|
|
3477
|
+
* additionalAmount: 500,
|
|
3478
|
+
* });
|
|
3479
|
+
* ```
|
|
3480
|
+
*/
|
|
3481
|
+
async increaseDepositAmount(params) {
|
|
3482
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3483
|
+
options: params.options,
|
|
3484
|
+
data: _ShelbyMicropaymentChannelClient.makeIncreaseDepositAmountPayload({
|
|
3485
|
+
deployer: this.deployer,
|
|
3486
|
+
receiver: params.receiver,
|
|
3487
|
+
fungibleAssetAddress: params.fungibleAssetAddress,
|
|
3488
|
+
additionalAmount: params.additionalAmount
|
|
3489
|
+
}),
|
|
3490
|
+
sender: params.sender.accountAddress
|
|
3491
|
+
});
|
|
3492
|
+
return {
|
|
3493
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3494
|
+
signer: params.sender,
|
|
3495
|
+
transaction
|
|
3496
|
+
})
|
|
3497
|
+
};
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Creates a payload to increase the deposit amount on a micropayment channel.
|
|
3501
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
3502
|
+
*
|
|
3503
|
+
* @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
|
|
3504
|
+
* @param params.receiver - The account that will be receiving funds.
|
|
3505
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3506
|
+
* @param params.additionalAmount - The amount of the fungible asset to add to the channel's balance.
|
|
3507
|
+
*
|
|
3508
|
+
* @returns An Aptos transaction payload data object for the extend_expiration_time Move function.
|
|
3509
|
+
*/
|
|
3510
|
+
static makeIncreaseDepositAmountPayload(params) {
|
|
3511
|
+
return {
|
|
3512
|
+
function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::increase_deposit_amount`,
|
|
3513
|
+
functionArguments: [
|
|
3514
|
+
params.receiver,
|
|
3515
|
+
params.fungibleAssetAddress,
|
|
3516
|
+
params.additionalAmount
|
|
3517
|
+
]
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
/**
|
|
3521
|
+
* Retrieves the vector of channels owned by the sender, optionally
|
|
3522
|
+
* filtered by the receiver.
|
|
3523
|
+
*
|
|
3524
|
+
* @param params.sender - The account namespace owning the micropayment channels.
|
|
3525
|
+
* @param params.receiver? - An optional receiver account address.
|
|
3526
|
+
* @returns A vector of ChannelInfo objects.
|
|
3527
|
+
*
|
|
3528
|
+
* @example
|
|
3529
|
+
* ```typescript
|
|
3530
|
+
* const channelInfoVec = await client.getChannelInfo({
|
|
3531
|
+
* sender: AccountAddress.fromString("0x1"),
|
|
3532
|
+
* receiver: AccountAddress.fromString("0x2"),
|
|
3533
|
+
* });
|
|
3534
|
+
* ```
|
|
3535
|
+
*/
|
|
3536
|
+
async getChannelInfo(params) {
|
|
3537
|
+
const functionName = params.receiver ? "get_all_channels_from_sender_to_receiver" : "get_all_channels_from_sender";
|
|
3538
|
+
const functionArguments = [params.sender];
|
|
3539
|
+
if (params.receiver) {
|
|
3540
|
+
functionArguments.push(params.receiver);
|
|
3541
|
+
}
|
|
3542
|
+
const rawArray = await this.aptos.view({
|
|
3543
|
+
payload: {
|
|
3544
|
+
function: `${this.deployer.toString()}::micropayments::${functionName}`,
|
|
3545
|
+
functionArguments
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
const channelInfoArray = rawArray[0];
|
|
3549
|
+
return channelInfoArray.map(
|
|
3550
|
+
(channelInfo) => {
|
|
3551
|
+
const publicKeyHex = channelInfo.public_key.startsWith("0x") ? channelInfo.public_key.slice(2) : channelInfo.public_key;
|
|
3552
|
+
const publicKey = new Uint8Array(
|
|
3553
|
+
publicKeyHex.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? []
|
|
3554
|
+
);
|
|
3555
|
+
return {
|
|
3556
|
+
sender: params.sender,
|
|
3557
|
+
receiver: params.receiver,
|
|
3558
|
+
fungibleAssetAddress: normalizeAddress(
|
|
3559
|
+
channelInfo.asset_metadata.inner
|
|
3560
|
+
),
|
|
3561
|
+
balance: BigInt(channelInfo.balance),
|
|
3562
|
+
creationMicros: BigInt(channelInfo.creation_micros),
|
|
3563
|
+
expirationMicros: BigInt(channelInfo.expiration_micros),
|
|
3564
|
+
nextWithdrawnSequenceNumber: BigInt(
|
|
3565
|
+
channelInfo.next_withdrawn_sequence_number
|
|
3566
|
+
),
|
|
3567
|
+
paymentChannelId: BigInt(channelInfo.payment_channel_id),
|
|
3568
|
+
receiverWithdrawnAmount: BigInt(
|
|
3569
|
+
channelInfo.receiver_withdrawn_amount
|
|
3570
|
+
),
|
|
3571
|
+
publicKey
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
|
|
3578
|
+
// src/core/clients/ShelbyPlacementGroupClient.ts
|
|
3579
|
+
var ShelbyPlacementGroupClient = class {
|
|
3580
|
+
indexer;
|
|
3581
|
+
/**
|
|
3582
|
+
* Creates a new ShelbyPlacementGroupClient.
|
|
3583
|
+
*
|
|
3584
|
+
* @param config - The client configuration object.
|
|
3585
|
+
*
|
|
3586
|
+
* @example
|
|
3587
|
+
* ```typescript
|
|
3588
|
+
* const pgClient = new ShelbyPlacementGroupClient({
|
|
3589
|
+
* network: Network.SHELBYNET,
|
|
3590
|
+
* });
|
|
3591
|
+
* ```
|
|
3592
|
+
*/
|
|
3593
|
+
constructor(config) {
|
|
3594
|
+
this.indexer = getShelbyIndexerClient(config);
|
|
3595
|
+
}
|
|
3596
|
+
/**
|
|
3597
|
+
* Retrieves placement group slots from the indexer.
|
|
3598
|
+
*
|
|
3599
|
+
* @param params.where (optional) - The where clause to filter slots by.
|
|
3600
|
+
* @param params.pagination (optional) - The pagination options.
|
|
3601
|
+
* @param params.orderBy (optional) - The order by clause to sort slots by.
|
|
3602
|
+
* @returns The placement group slots that match the filter.
|
|
3603
|
+
*
|
|
3604
|
+
* @example
|
|
3605
|
+
* ```typescript
|
|
3606
|
+
* // Get all active slots
|
|
3607
|
+
* const slots = await client.getPlacementGroupSlots({
|
|
3608
|
+
* where: { status: { _eq: "active" } },
|
|
3609
|
+
* });
|
|
3610
|
+
* ```
|
|
3611
|
+
*/
|
|
3612
|
+
async getPlacementGroupSlots(params) {
|
|
3613
|
+
const { limit, offset } = params.pagination ?? {};
|
|
3614
|
+
const { orderBy, where } = params;
|
|
3615
|
+
const { placement_group_slots } = await this.indexer.getPlacementGroupSlots(
|
|
3616
|
+
{
|
|
3617
|
+
where,
|
|
3618
|
+
limit,
|
|
3619
|
+
offset,
|
|
3620
|
+
orderBy
|
|
3621
|
+
}
|
|
3622
|
+
);
|
|
3623
|
+
return placement_group_slots.map(
|
|
3624
|
+
(slot) => ({
|
|
3625
|
+
placementGroup: slot.placement_group,
|
|
3626
|
+
slotIndex: Number(slot.slot_index),
|
|
3627
|
+
storageProvider: slot.storage_provider,
|
|
3628
|
+
status: slot.status,
|
|
3629
|
+
updatedAt: Number(slot.updated_at)
|
|
3630
|
+
})
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Retrieves the total count of placement group slots from the indexer.
|
|
3635
|
+
*
|
|
3636
|
+
* @param params.where (optional) - The where clause to filter slots by.
|
|
3637
|
+
* @returns The count of placement group slots that match the filter.
|
|
3638
|
+
*
|
|
3639
|
+
* @example
|
|
3640
|
+
* ```typescript
|
|
3641
|
+
* // Get count of active slots
|
|
3642
|
+
* const count = await client.getPlacementGroupSlotsCount({
|
|
3643
|
+
* where: { status: { _eq: "active" } },
|
|
3644
|
+
* });
|
|
3645
|
+
* ```
|
|
3646
|
+
*/
|
|
3647
|
+
async getPlacementGroupSlotsCount(params) {
|
|
3648
|
+
const { where } = params;
|
|
3649
|
+
const { placement_group_slots_aggregate } = await this.indexer.getPlacementGroupSlotsCount({ where });
|
|
3650
|
+
return placement_group_slots_aggregate?.aggregate?.count ?? 0;
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
|
|
3654
|
+
// src/core/errors.ts
|
|
3655
|
+
var ShelbyErrorCodes = {
|
|
3656
|
+
// blob_metadata.move errors
|
|
3657
|
+
E_BLOB_NOT_FOUND: "E_BLOB_NOT_FOUND",
|
|
3658
|
+
E_INVALID_EXPIRATION_TIME: "E_INVALID_EXPIRATION_TIME",
|
|
3659
|
+
E_TEST_ENCODING_NOT_ALLOWED: "E_TEST_ENCODING_NOT_ALLOWED",
|
|
3660
|
+
E_BLOB_NOT_EXPIRED: "E_BLOB_NOT_EXPIRED",
|
|
3661
|
+
E_INVALID_CHUNKSET_COUNT: "E_INVALID_CHUNKSET_COUNT",
|
|
3662
|
+
E_NOT_BLOB_OWNER: "E_NOT_BLOB_OWNER",
|
|
3663
|
+
E_NOT_ADMIN: "E_NOT_ADMIN",
|
|
3664
|
+
E_BLOB_EXPIRED: "E_BLOB_EXPIRED",
|
|
3665
|
+
E_BLOB_NAME_TOO_LONG: "E_BLOB_NAME_TOO_LONG",
|
|
3666
|
+
E_INVALID_PAYMENT_EPOCHS: "E_INVALID_PAYMENT_EPOCHS",
|
|
3667
|
+
E_NO_SLICES_AVAILABLE: "E_NO_SLICES_AVAILABLE",
|
|
3668
|
+
// Common Aptos/Move errors
|
|
3669
|
+
EALREADY_EXISTS: "EALREADY_EXISTS"
|
|
3670
|
+
};
|
|
3671
|
+
function isBlobAlreadyExistsError(errorMessage) {
|
|
3672
|
+
return errorMessage.includes(ShelbyErrorCodes.EALREADY_EXISTS) || errorMessage.includes("already exists");
|
|
3673
|
+
}
|
|
3674
|
+
function isAccessDeniedError(errorMessage) {
|
|
3675
|
+
return errorMessage.includes(ShelbyErrorCodes.E_NOT_BLOB_OWNER) || errorMessage.includes(ShelbyErrorCodes.E_NOT_ADMIN) || errorMessage.includes("ENOT_AUTHORIZED") || errorMessage.includes("not authorized") || errorMessage.includes("permission denied");
|
|
3676
|
+
}
|
|
3677
|
+
function isBlobNotFoundError(errorMessage) {
|
|
3678
|
+
return errorMessage.includes(ShelbyErrorCodes.E_BLOB_NOT_FOUND);
|
|
3679
|
+
}
|
|
3680
|
+
function isBlobExpiredError(errorMessage) {
|
|
3681
|
+
return errorMessage.includes(ShelbyErrorCodes.E_BLOB_EXPIRED);
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
// src/core/networks.ts
|
|
3685
|
+
import { Network as Network5 } from "@aptos-labs/ts-sdk";
|
|
3686
|
+
var shelbyNetworks = [
|
|
3687
|
+
Network5.LOCAL,
|
|
3688
|
+
Network5.TESTNET,
|
|
3689
|
+
Network5.SHELBYNET
|
|
3690
|
+
];
|
|
3691
|
+
var isShelbyNetwork = (network) => {
|
|
3692
|
+
return shelbyNetworks.includes(network.toLowerCase());
|
|
3693
|
+
};
|
|
3694
|
+
|
|
3695
|
+
// src/core/shelby-explorer.ts
|
|
3696
|
+
import { Network as Network6 } from "@aptos-labs/ts-sdk";
|
|
3697
|
+
function getShelbyExplorerBaseUrl(network) {
|
|
3698
|
+
const normalizedNetwork = network.toLowerCase();
|
|
3699
|
+
switch (normalizedNetwork) {
|
|
3700
|
+
case Network6.MAINNET.toLowerCase():
|
|
3701
|
+
return "https://explorer.shelby.xyz/mainnet";
|
|
3702
|
+
case Network6.TESTNET.toLowerCase():
|
|
3703
|
+
return "https://explorer.shelby.xyz/testnet";
|
|
3704
|
+
case Network6.DEVNET.toLowerCase():
|
|
3705
|
+
return "https://explorer.shelby.xyz/devnet";
|
|
3706
|
+
case Network6.LOCAL.toLowerCase():
|
|
3707
|
+
return "https://explorer.shelby.xyz/local";
|
|
3708
|
+
case Network6.SHELBYNET.toLowerCase():
|
|
3709
|
+
return "https://explorer.shelby.xyz/shelbynet";
|
|
3710
|
+
default:
|
|
3711
|
+
return `https://explorer.shelby.xyz/${normalizedNetwork}`;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
function getShelbyAccountBlobsExplorerUrl(network, accountAddress) {
|
|
3715
|
+
const baseUrl = getShelbyExplorerBaseUrl(network);
|
|
3716
|
+
return `${baseUrl}/account/${accountAddress}/blobs`;
|
|
3717
|
+
}
|
|
3718
|
+
function getShelbyBlobExplorerUrl(network, accountAddress, blobName) {
|
|
3719
|
+
const baseUrl = getShelbyExplorerBaseUrl(network);
|
|
3720
|
+
return `${baseUrl}/account/${accountAddress}/blob/${encodeURIComponent(blobName)}`;
|
|
3721
|
+
}
|
|
3722
|
+
function getShelbyAccountExplorerUrl(network, accountAddress) {
|
|
3723
|
+
const baseUrl = getShelbyExplorerBaseUrl(network);
|
|
3724
|
+
return `${baseUrl}/account/${accountAddress}`;
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
// src/node/clients/ShelbyNodeClient.ts
|
|
3728
|
+
var ShelbyNodeClient = class extends ShelbyClient {
|
|
3729
|
+
};
|
|
3730
|
+
|
|
3731
|
+
// src/node/testUtil.ts
|
|
3732
|
+
var testUtil_exports = {};
|
|
3733
|
+
__export(testUtil_exports, {
|
|
3734
|
+
makeChunk: () => makeChunk
|
|
3735
|
+
});
|
|
3736
|
+
function makeChunk(n) {
|
|
3737
|
+
const c = Buffer.alloc(n);
|
|
3738
|
+
for (let i = 0; i < n; ++i) {
|
|
3739
|
+
c[i] = i % 256;
|
|
3740
|
+
}
|
|
3741
|
+
return c;
|
|
3742
|
+
}
|
|
156
3743
|
export {
|
|
157
3744
|
BlobCommitmentsSchema,
|
|
158
3745
|
BlobNameSchema,
|
|
@@ -160,6 +3747,7 @@ export {
|
|
|
160
3747
|
Blobs_Select_Column,
|
|
161
3748
|
CHUNK_SIZE_PARAMS,
|
|
162
3749
|
COMMITMENT_SCHEMA_VERSION,
|
|
3750
|
+
ChallengeResponseSchema,
|
|
163
3751
|
ChunkKey,
|
|
164
3752
|
ChunkSizeScheme,
|
|
165
3753
|
ChunksetCommitmentSchema,
|
|
@@ -188,6 +3776,7 @@ export {
|
|
|
188
3776
|
GetTotalBlobsSizeDocument,
|
|
189
3777
|
MICROPAYMENTS_DEPLOYER,
|
|
190
3778
|
MissingTransactionSubmitterError,
|
|
3779
|
+
MultipartUploadStatusResponseSchema,
|
|
191
3780
|
NetworkToGasStationBaseUrl,
|
|
192
3781
|
NetworkToShelbyBlobIndexerBaseUrl,
|
|
193
3782
|
NetworkToShelbyRPCBaseUrl,
|