@shelby-protocol/sdk 0.3.1 → 0.4.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 +9 -9
- package/dist/browser/index.mjs +1068 -589
- package/dist/core/clients/ShelbyBlobClient.d.ts +155 -40
- package/dist/core/clients/ShelbyBlobClient.mjs +314 -112
- package/dist/core/clients/ShelbyClient.d.ts +27 -4
- package/dist/core/clients/ShelbyClient.mjs +869 -551
- package/dist/core/clients/ShelbyClientConfig.d.ts +9 -1
- package/dist/core/clients/ShelbyMetadataClient.d.ts +38 -6
- package/dist/core/clients/ShelbyMetadataClient.mjs +95 -13
- package/dist/core/clients/ShelbyMicropaymentChannelClient.d.ts +42 -0
- package/dist/core/clients/ShelbyMicropaymentChannelClient.mjs +62 -7
- package/dist/core/clients/ShelbyPlacementGroupClient.mjs +10 -7
- package/dist/core/clients/ShelbyRPCClient.d.ts +55 -66
- package/dist/core/clients/ShelbyRPCClient.mjs +344 -360
- package/dist/core/clients/index.d.ts +3 -2
- package/dist/core/clients/index.mjs +933 -551
- package/dist/core/clients/utils.mjs +1 -1
- package/dist/core/commitments.d.ts +12 -1
- package/dist/core/commitments.mjs +7 -0
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.mjs +1 -1
- package/dist/core/erasure/constants.d.ts +16 -1
- package/dist/core/erasure/constants.mjs +15 -1
- package/dist/core/erasure/index.d.ts +1 -1
- package/dist/core/erasure/index.mjs +15 -1
- package/dist/core/errors.d.ts +20 -1
- package/dist/core/errors.mjs +29 -0
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.mjs +1068 -589
- package/dist/core/operations/generated/sdk.d.ts +798 -42
- package/dist/core/operations/generated/sdk.mjs +93 -9
- package/dist/core/operations/index.d.ts +1 -1
- package/dist/core/operations/index.mjs +94 -10
- package/dist/core/rpc-responses.d.ts +1 -57
- package/dist/core/rpc-responses.mjs +1 -21
- package/dist/core/sp/chunk_proof.d.ts +23 -0
- package/dist/core/sp/chunk_proof.mjs +113 -0
- package/dist/core/sp/index.d.ts +3 -0
- package/dist/core/sp/index.mjs +402 -0
- package/dist/core/sp/sp_write_client.d.ts +53 -0
- package/dist/core/sp/sp_write_client.mjs +302 -0
- package/dist/core/types/blobs.d.ts +24 -5
- package/dist/core/types/blobs.mjs +24 -0
- package/dist/core/types/index.d.ts +2 -2
- package/dist/core/types/index.mjs +46 -2
- package/dist/core/types/payments.mjs +1 -1
- package/dist/core/types/storage_providers.d.ts +32 -6
- package/dist/core/types/storage_providers.mjs +22 -0
- package/dist/gen/rpc_server_pb.d.ts +295 -0
- package/dist/gen/rpc_server_pb.mjs +28 -0
- package/dist/node/clients/ShelbyNodeClient.d.ts +1 -0
- package/dist/node/clients/ShelbyNodeClient.mjs +869 -551
- package/dist/node/clients/index.d.ts +1 -0
- package/dist/node/clients/index.mjs +867 -551
- package/dist/node/index.d.ts +9 -9
- package/dist/node/index.mjs +1068 -589
- package/dist/node/parallel/commitment_worker.mjs +36 -249
- package/dist/node/parallel/commitment_worker_pool.mjs +56 -3
- package/dist/node/parallel/coverage_flush.d.ts +16 -0
- package/dist/node/parallel/coverage_flush.mjs +43 -0
- package/dist/node/parallel/default_commitment_worker_pool.mjs +56 -3
- package/dist/node/parallel/index.d.ts +1 -0
- package/dist/node/parallel/index.mjs +72 -4
- package/dist/node/parallel/parallel_commitments.mjs +56 -3
- package/dist/node/parallel/worker_pool.mjs +56 -3
- package/package.json +10 -3
|
@@ -130,6 +130,18 @@ var ERASURE_CODE_PARAMS = {
|
|
|
130
130
|
enumIndex: 1
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
|
+
function findErasureSchemeByErasureN(erasureN) {
|
|
134
|
+
return Object.entries(ERASURE_CODE_PARAMS).find(
|
|
135
|
+
([, params]) => params.erasure_n === erasureN
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function requiredAckCount(erasureN) {
|
|
139
|
+
const scheme = findErasureSchemeByErasureN(erasureN);
|
|
140
|
+
if (!scheme) {
|
|
141
|
+
throw new Error(`Unknown erasure coding scheme with erasure_n=${erasureN}`);
|
|
142
|
+
}
|
|
143
|
+
return scheme[1].erasure_d;
|
|
144
|
+
}
|
|
133
145
|
var DEFAULT_ERASURE_N = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_n;
|
|
134
146
|
var DEFAULT_ERASURE_K = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_k;
|
|
135
147
|
var DEFAULT_ERASURE_D = ERASURE_CODE_PARAMS["ClayCode_16Total_10Data_13Helper" /* ClayCode_16Total_10Data_13Helper */].erasure_d;
|
|
@@ -297,7 +309,7 @@ async function generateCommitments(provider, fullData, onChunk) {
|
|
|
297
309
|
// src/core/constants.ts
|
|
298
310
|
import { Network } from "@aptos-labs/ts-sdk";
|
|
299
311
|
var NetworkToShelbyRPCBaseUrl = {
|
|
300
|
-
[Network.SHELBYNET]: "https://
|
|
312
|
+
[Network.SHELBYNET]: "https://shelby.shelbynet.shelby.xyz/shelby",
|
|
301
313
|
[Network.NETNA]: void 0,
|
|
302
314
|
[Network.DEVNET]: void 0,
|
|
303
315
|
[Network.TESTNET]: "https://api.testnet.shelby.xyz/shelby",
|
|
@@ -326,6 +338,27 @@ var NetworkToGasStationBaseUrl = {
|
|
|
326
338
|
var SHELBY_DEPLOYER = "0x85fdb9a176ab8ef1d9d9c1b60d60b3924f0800ac1de1cc2085fb0b8bb4988e6a";
|
|
327
339
|
var MICROPAYMENTS_DEPLOYER = "0x1ae7275148bf6ef742b658fd9cbcc2e094201606f4a7bc707bab0201da8043ee";
|
|
328
340
|
|
|
341
|
+
// src/core/types/blobs.ts
|
|
342
|
+
var BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX = {
|
|
343
|
+
Unencrypted: 0,
|
|
344
|
+
AES_GCM_V1: 1
|
|
345
|
+
};
|
|
346
|
+
function blobEncryptionToMoveEnumIndex(encryption) {
|
|
347
|
+
return BLOB_ENCRYPTION_TO_MOVE_ENUM_INDEX[encryption];
|
|
348
|
+
}
|
|
349
|
+
function blobEncryptionFromMoveVariant(variant) {
|
|
350
|
+
switch (variant) {
|
|
351
|
+
case "Unencrypted":
|
|
352
|
+
return "Unencrypted";
|
|
353
|
+
case "AES_GCM_V1":
|
|
354
|
+
return "AES_GCM_V1";
|
|
355
|
+
default:
|
|
356
|
+
throw new Error(
|
|
357
|
+
"Could not parse encryption from Shelby Smart Contract, this SDK is out of date."
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
329
362
|
// src/core/erasure/clay-codes.ts
|
|
330
363
|
import {
|
|
331
364
|
createDecoder,
|
|
@@ -503,18 +536,20 @@ import gql from "graphql-tag";
|
|
|
503
536
|
var GetBlobsDocument = gql`
|
|
504
537
|
query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
|
|
505
538
|
blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
|
|
539
|
+
uid
|
|
540
|
+
object_name
|
|
506
541
|
owner
|
|
507
542
|
blob_commitment
|
|
508
|
-
blob_name
|
|
509
543
|
created_at
|
|
510
544
|
expires_at
|
|
545
|
+
updated_at
|
|
511
546
|
num_chunksets
|
|
512
|
-
is_deleted
|
|
513
|
-
is_written
|
|
514
|
-
placement_group
|
|
515
547
|
size
|
|
516
|
-
updated_at
|
|
517
548
|
slice_address
|
|
549
|
+
placement_group
|
|
550
|
+
is_persisted
|
|
551
|
+
is_committed
|
|
552
|
+
is_deleted
|
|
518
553
|
}
|
|
519
554
|
}
|
|
520
555
|
`;
|
|
@@ -526,7 +561,8 @@ var GetBlobActivitiesDocument = gql`
|
|
|
526
561
|
limit: $limit
|
|
527
562
|
offset: $offset
|
|
528
563
|
) {
|
|
529
|
-
|
|
564
|
+
uid
|
|
565
|
+
object_name
|
|
530
566
|
event_index
|
|
531
567
|
event_type
|
|
532
568
|
transaction_hash
|
|
@@ -672,6 +708,33 @@ var MissingTransactionSubmitterError = class extends Error {
|
|
|
672
708
|
this.name = "MissingTransactionSubmitterError";
|
|
673
709
|
}
|
|
674
710
|
};
|
|
711
|
+
var COMMIT_REJECTION_REASONS = /* @__PURE__ */ new Set([
|
|
712
|
+
"AlreadyExists",
|
|
713
|
+
"NoPriorVersion",
|
|
714
|
+
"EtagMismatch"
|
|
715
|
+
]);
|
|
716
|
+
var ObjectCommitRejectedError = class extends Error {
|
|
717
|
+
constructor(blobName, uid, reason) {
|
|
718
|
+
super(
|
|
719
|
+
`commit_object rejected the write for '${blobName}' (uid ${uid}): ${reason}`
|
|
720
|
+
);
|
|
721
|
+
this.blobName = blobName;
|
|
722
|
+
this.uid = uid;
|
|
723
|
+
this.reason = reason;
|
|
724
|
+
this.name = "ObjectCommitRejectedError";
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
function encryptionFunctionArgs(params) {
|
|
728
|
+
if (params.omitEncryptionArg) {
|
|
729
|
+
if (params.encryption && params.encryption !== "Unencrypted") {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Blob encryption (${params.encryption}) is not supported on this network: the deployed contract predates the encryption upgrade (#1739).`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
return [blobEncryptionToMoveEnumIndex(params.encryption ?? "Unencrypted")];
|
|
737
|
+
}
|
|
675
738
|
var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
676
739
|
aptos;
|
|
677
740
|
deployer;
|
|
@@ -720,7 +783,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
720
783
|
this.aptos = new Aptos(getAptosConfig(config));
|
|
721
784
|
this.deployer = config.deployer ?? AccountAddress2.fromString(SHELBY_DEPLOYER);
|
|
722
785
|
this.indexer = getShelbyIndexerClient(config);
|
|
723
|
-
this.defaultOptions =
|
|
786
|
+
this.defaultOptions = {
|
|
787
|
+
locationHint: config.locationHint,
|
|
788
|
+
...defaultOptions
|
|
789
|
+
};
|
|
724
790
|
this.orderless = config.orderless ?? false;
|
|
725
791
|
}
|
|
726
792
|
/**
|
|
@@ -731,7 +797,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
731
797
|
build: options?.build ?? this.defaultOptions.build,
|
|
732
798
|
submit: options?.submit ?? this.defaultOptions.submit,
|
|
733
799
|
usdSponsor: options?.usdSponsor ?? this.defaultOptions.usdSponsor,
|
|
734
|
-
chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes
|
|
800
|
+
chunksetSizeBytes: options?.chunksetSizeBytes ?? this.defaultOptions.chunksetSizeBytes,
|
|
801
|
+
selectedLocation: options?.selectedLocation ?? this.defaultOptions.selectedLocation,
|
|
802
|
+
locationHint: options?.locationHint ?? this.defaultOptions.locationHint
|
|
735
803
|
};
|
|
736
804
|
}
|
|
737
805
|
/**
|
|
@@ -773,17 +841,17 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
773
841
|
*
|
|
774
842
|
* @example
|
|
775
843
|
* ```typescript
|
|
776
|
-
* const metadata = await client.
|
|
844
|
+
* const metadata = await client.getFullObjectMetadata({
|
|
777
845
|
* account: AccountAddress.fromString("0x1"),
|
|
778
846
|
* name: "foo/bar.txt",
|
|
779
847
|
* });
|
|
780
848
|
* ```
|
|
781
849
|
*/
|
|
782
|
-
async
|
|
850
|
+
async getFullObjectMetadata(params) {
|
|
783
851
|
try {
|
|
784
852
|
const rawMetadata = await this.aptos.view({
|
|
785
853
|
payload: {
|
|
786
|
-
function: `${this.deployer.toString()}::blob_metadata::
|
|
854
|
+
function: `${this.deployer.toString()}::blob_metadata::get_full_object_metadata`,
|
|
787
855
|
functionArguments: [
|
|
788
856
|
createBlobKey({
|
|
789
857
|
account: params.account,
|
|
@@ -795,47 +863,94 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
795
863
|
if (!rawMetadata?.[0]?.vec?.[0]) {
|
|
796
864
|
return void 0;
|
|
797
865
|
}
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
encoding = {
|
|
802
|
-
variant: "clay",
|
|
803
|
-
...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
|
|
804
|
-
...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
|
|
805
|
-
};
|
|
806
|
-
} else if (metadata.encoding.__variant__ === "ClayCode_4Total_2Data_3Helper") {
|
|
807
|
-
encoding = {
|
|
808
|
-
variant: "clay",
|
|
809
|
-
...ERASURE_CODE_PARAMS[metadata.encoding.__variant__],
|
|
810
|
-
...ERASURE_CODE_AND_CHUNK_MAPPING[metadata.encoding.__variant__]
|
|
811
|
-
};
|
|
812
|
-
} else {
|
|
813
|
-
throw new Error(
|
|
814
|
-
"Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
return {
|
|
818
|
-
blobMerkleRoot: Hex3.fromHexInput(
|
|
819
|
-
metadata.blob_commitment
|
|
820
|
-
).toUint8Array(),
|
|
821
|
-
owner: normalizeAddress(metadata.owner),
|
|
866
|
+
const view = rawMetadata[0].vec[0];
|
|
867
|
+
return this.parseBlobMetadata(view.blob_metadata, {
|
|
868
|
+
uid: BigInt(view.object_metadata.current_blob_uid),
|
|
822
869
|
name: params.name,
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
creationMicros: Number(metadata.creation_micros),
|
|
828
|
-
sliceAddress: normalizeAddress(metadata.slice.inner),
|
|
829
|
-
isWritten: metadata.is_written
|
|
830
|
-
};
|
|
870
|
+
// Any blob bound under an object name has cleared the commit-time
|
|
871
|
+
// `is_written()` check, so a resolved object is always written.
|
|
872
|
+
isWritten: true
|
|
873
|
+
});
|
|
831
874
|
} catch (error) {
|
|
832
875
|
if (error instanceof Error && // Depending on the network, the error message may show up differently.
|
|
833
|
-
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
|
|
876
|
+
(error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND") || error.message?.includes("EOBJECT_NOT_FOUND"))) {
|
|
877
|
+
return void 0;
|
|
878
|
+
}
|
|
879
|
+
throw error;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Retrieves blob metadata directly by its on-chain UID, including blobs in
|
|
884
|
+
* the pending (registered-but-not-yet-committed) state that
|
|
885
|
+
* {@link getFullObjectMetadata} cannot resolve by object name. Returns `undefined`
|
|
886
|
+
* if no blob has that UID.
|
|
887
|
+
*
|
|
888
|
+
* The returned `name`/`blobNameSuffix` are empty: the blob layer is keyed by
|
|
889
|
+
* UID and carries no object name (a name binding is established only at
|
|
890
|
+
* commit). `isWritten` reflects whether the blob has been committed.
|
|
891
|
+
*/
|
|
892
|
+
async getFullObjectMetadataByUid(uid) {
|
|
893
|
+
try {
|
|
894
|
+
const rawMetadata = await this.aptos.view(
|
|
895
|
+
{
|
|
896
|
+
payload: {
|
|
897
|
+
function: `${this.deployer.toString()}::blob_metadata::get_blob_metadata`,
|
|
898
|
+
functionArguments: [uid]
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
);
|
|
902
|
+
if (!rawMetadata?.[0]?.vec?.[0]) {
|
|
903
|
+
return void 0;
|
|
904
|
+
}
|
|
905
|
+
const raw = rawMetadata[0].vec[0];
|
|
906
|
+
return this.parseBlobMetadata(raw, {
|
|
907
|
+
uid,
|
|
908
|
+
name: "",
|
|
909
|
+
isWritten: raw.state.__variant__ === "CommittedObject"
|
|
910
|
+
});
|
|
911
|
+
} catch (error) {
|
|
912
|
+
if (error instanceof Error && (error.message?.includes("sub_status: Some(404)") || error.message?.includes("EBLOB_NOT_FOUND"))) {
|
|
834
913
|
return void 0;
|
|
835
914
|
}
|
|
836
915
|
throw error;
|
|
837
916
|
}
|
|
838
917
|
}
|
|
918
|
+
/**
|
|
919
|
+
* Parse the on-chain `BlobMetadata::V1` view shape into the SDK
|
|
920
|
+
* {@link FullObjectMetadata}. `uid` / `name` / `isWritten` are supplied by the
|
|
921
|
+
* caller since they depend on the lookup path (by object name vs by UID).
|
|
922
|
+
*/
|
|
923
|
+
parseBlobMetadata(raw, extra) {
|
|
924
|
+
const { content } = raw;
|
|
925
|
+
const variant = content.encoding.__variant__;
|
|
926
|
+
if (variant !== "ClayCode_16Total_10Data_13Helper" && variant !== "ClayCode_4Total_2Data_3Helper") {
|
|
927
|
+
throw new Error(
|
|
928
|
+
"Could not parse encoding from Shelby Smart Contract, this SDK is out of date."
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
const encoding = {
|
|
932
|
+
variant: "clay",
|
|
933
|
+
...ERASURE_CODE_PARAMS[variant],
|
|
934
|
+
...ERASURE_CODE_AND_CHUNK_MAPPING[variant]
|
|
935
|
+
};
|
|
936
|
+
const encryption = blobEncryptionFromMoveVariant(
|
|
937
|
+
content.encryption?.__variant__ ?? "Unencrypted"
|
|
938
|
+
);
|
|
939
|
+
return {
|
|
940
|
+
uid: extra.uid,
|
|
941
|
+
blobMerkleRoot: Hex3.fromHexInput(content.blob_commitment).toUint8Array(),
|
|
942
|
+
owner: normalizeAddress(raw.owner),
|
|
943
|
+
name: extra.name,
|
|
944
|
+
blobNameSuffix: extra.name ? getBlobNameSuffix(extra.name) : "",
|
|
945
|
+
size: Number(content.blob_size),
|
|
946
|
+
encoding,
|
|
947
|
+
encryption,
|
|
948
|
+
expirationMicros: Number(raw.expiration_micros),
|
|
949
|
+
creationMicros: Number(raw.creation_micros),
|
|
950
|
+
sliceAddress: normalizeAddress(raw.slice.inner),
|
|
951
|
+
isWritten: extra.isWritten
|
|
952
|
+
};
|
|
953
|
+
}
|
|
839
954
|
/**
|
|
840
955
|
* Retrieves all the blobs and their metadata for an account from the
|
|
841
956
|
* blockchain.
|
|
@@ -847,7 +962,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
847
962
|
*
|
|
848
963
|
* @example
|
|
849
964
|
* ```typescript
|
|
850
|
-
* //
|
|
965
|
+
* // FullObjectMetadata[]
|
|
851
966
|
* const blobs = await client.getAccountBlobs({
|
|
852
967
|
* account: AccountAddress.fromString("0x1"),
|
|
853
968
|
* });
|
|
@@ -864,6 +979,25 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
864
979
|
orderBy: rest.orderBy
|
|
865
980
|
});
|
|
866
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Object-facing default filter: only committed, non-deleted, unexpired rows.
|
|
984
|
+
* The blobs table is UID-keyed, so during an atomic overwrite a single
|
|
985
|
+
* object_name transiently has two non-deleted rows — the currently committed
|
|
986
|
+
* blob and the new pending (is_committed = "0") blob. Filtering on
|
|
987
|
+
* is_committed keeps name lookups/listings pinned to the committed object and
|
|
988
|
+
* avoids returning or duplicating the in-flight pending row. Applied to
|
|
989
|
+
* getBlobs *and* the getBlobsCount / getTotalBlobsSize aggregates so counts
|
|
990
|
+
* and sizes agree with the listing mid-overwrite. Callers can override any
|
|
991
|
+
* key (e.g. is_committed) via their own `where`.
|
|
992
|
+
*/
|
|
993
|
+
activeBlobsWhere(where) {
|
|
994
|
+
const defaultActiveFilter = {
|
|
995
|
+
expires_at: { _gte: String(Date.now() * 1e3) },
|
|
996
|
+
is_deleted: { _eq: "0" },
|
|
997
|
+
is_committed: { _eq: "1" }
|
|
998
|
+
};
|
|
999
|
+
return { ...defaultActiveFilter, ...where };
|
|
1000
|
+
}
|
|
867
1001
|
/**
|
|
868
1002
|
* Retrieves blobs and their metadata from the blockchain.
|
|
869
1003
|
*
|
|
@@ -874,7 +1008,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
874
1008
|
*
|
|
875
1009
|
* @example
|
|
876
1010
|
* ```typescript
|
|
877
|
-
* //
|
|
1011
|
+
* // FullObjectMetadata[]
|
|
878
1012
|
* const blobs = await client.getBlobs({
|
|
879
1013
|
* where: { owner: { _eq: AccountAddress.fromString("0x1").toString() } },
|
|
880
1014
|
* });
|
|
@@ -883,12 +1017,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
883
1017
|
async getBlobs(params = {}) {
|
|
884
1018
|
const { limit, offset } = params.pagination ?? {};
|
|
885
1019
|
const { orderBy, where } = params;
|
|
886
|
-
const
|
|
887
|
-
const defaultActiveFilter = {
|
|
888
|
-
expires_at: { _gte: currentMicros },
|
|
889
|
-
is_deleted: { _eq: "0" }
|
|
890
|
-
};
|
|
891
|
-
const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
|
|
1020
|
+
const finalWhere = this.activeBlobsWhere(where);
|
|
892
1021
|
const { blobs } = await this.indexer.getBlobs({
|
|
893
1022
|
where: finalWhere,
|
|
894
1023
|
limit,
|
|
@@ -897,9 +1026,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
897
1026
|
});
|
|
898
1027
|
return blobs.map(
|
|
899
1028
|
(blob) => ({
|
|
1029
|
+
uid: BigInt(blob.uid),
|
|
900
1030
|
owner: normalizeAddress(blob.owner),
|
|
901
|
-
name: blob.
|
|
902
|
-
blobNameSuffix: getBlobNameSuffix(blob.
|
|
1031
|
+
name: blob.object_name,
|
|
1032
|
+
blobNameSuffix: getBlobNameSuffix(blob.object_name),
|
|
903
1033
|
blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
|
|
904
1034
|
size: Number(blob.size),
|
|
905
1035
|
// TODO: Add encoding when supported in NCI
|
|
@@ -911,7 +1041,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
911
1041
|
expirationMicros: Number(blob.expires_at),
|
|
912
1042
|
creationMicros: Number(blob.created_at),
|
|
913
1043
|
sliceAddress: normalizeAddress(blob.slice_address),
|
|
914
|
-
isWritten: Boolean(Number(blob.
|
|
1044
|
+
isWritten: Boolean(Number(blob.is_persisted)),
|
|
915
1045
|
isDeleted: Boolean(Number(blob.is_deleted))
|
|
916
1046
|
})
|
|
917
1047
|
);
|
|
@@ -929,12 +1059,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
929
1059
|
[`${this.deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`]: "register_blob",
|
|
930
1060
|
[`${this.deployer.toStringLong()}::blob_metadata::BlobDeletedEvent`]: "delete_blob",
|
|
931
1061
|
[`${this.deployer.toStringLong()}::blob_metadata::BlobExpirationExtendedEvent`]: "extend_blob_expiration",
|
|
932
|
-
[`${this.deployer.toStringLong()}::blob_metadata::
|
|
1062
|
+
[`${this.deployer.toStringLong()}::blob_metadata::BlobPersistedEvent`]: "write_blob",
|
|
1063
|
+
[`${this.deployer.toStringLong()}::blob_metadata::ObjectCommittedEvent`]: "commit_object",
|
|
1064
|
+
[`${this.deployer.toStringLong()}::blob_metadata::ObjectDeletedEvent`]: "delete_object",
|
|
1065
|
+
[`${this.deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`]: "reject_object_commit"
|
|
933
1066
|
};
|
|
934
1067
|
return blob_activities.map(
|
|
935
1068
|
(activity) => ({
|
|
936
|
-
blobName: activity.
|
|
937
|
-
accountAddress: normalizeAddress(activity.
|
|
1069
|
+
blobName: activity.object_name,
|
|
1070
|
+
accountAddress: normalizeAddress(activity.object_name.substring(1, 65)),
|
|
938
1071
|
type: activityTypeMapping[activity.event_type] ?? "unknown",
|
|
939
1072
|
eventType: activity.event_type,
|
|
940
1073
|
eventIndex: Number(activity.event_index),
|
|
@@ -957,9 +1090,10 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
957
1090
|
* const count = await client.getBlobsCount();
|
|
958
1091
|
* ```
|
|
959
1092
|
*/
|
|
960
|
-
async getBlobsCount(params) {
|
|
961
|
-
const {
|
|
962
|
-
|
|
1093
|
+
async getBlobsCount(params = {}) {
|
|
1094
|
+
const { blobs_aggregate } = await this.indexer.getBlobsCount({
|
|
1095
|
+
where: this.activeBlobsWhere(params.where)
|
|
1096
|
+
});
|
|
963
1097
|
return blobs_aggregate?.aggregate?.count ?? 0;
|
|
964
1098
|
}
|
|
965
1099
|
/**
|
|
@@ -974,8 +1108,9 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
974
1108
|
* ```
|
|
975
1109
|
*/
|
|
976
1110
|
async getTotalBlobsSize(params = {}) {
|
|
977
|
-
const {
|
|
978
|
-
|
|
1111
|
+
const { blobs_aggregate } = await this.indexer.getTotalBlobsSize({
|
|
1112
|
+
where: this.activeBlobsWhere(params.where)
|
|
1113
|
+
});
|
|
979
1114
|
return Number(blobs_aggregate?.aggregate?.sum?.size ?? 0);
|
|
980
1115
|
}
|
|
981
1116
|
/**
|
|
@@ -1034,12 +1169,15 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1034
1169
|
deployer: this.deployer,
|
|
1035
1170
|
account: params.account.accountAddress,
|
|
1036
1171
|
blobName: params.blobName,
|
|
1172
|
+
selectedLocation: options.selectedLocation,
|
|
1173
|
+
locationHint: options.locationHint,
|
|
1037
1174
|
blobSize: params.size,
|
|
1038
1175
|
blobMerkleRoot: params.blobMerkleRoot,
|
|
1039
1176
|
numChunksets: expectedTotalChunksets(params.size, chunksetSize),
|
|
1040
1177
|
expirationMicros: params.expirationMicros,
|
|
1041
1178
|
useSponsoredUsdVariant: options.usdSponsor !== void 0,
|
|
1042
|
-
encoding: config.enumIndex
|
|
1179
|
+
encoding: config.enumIndex,
|
|
1180
|
+
encryption: params.encryption
|
|
1043
1181
|
}),
|
|
1044
1182
|
sender: params.account.accountAddress
|
|
1045
1183
|
};
|
|
@@ -1067,16 +1205,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1067
1205
|
* @example
|
|
1068
1206
|
* ```typescript
|
|
1069
1207
|
*
|
|
1070
|
-
* const { transaction } = await client.
|
|
1208
|
+
* const { transaction } = await client.deleteObject({
|
|
1071
1209
|
* account: signer,
|
|
1072
1210
|
* blobName: "foo/bar.txt",
|
|
1073
1211
|
* });
|
|
1074
1212
|
* ```
|
|
1075
1213
|
*/
|
|
1076
|
-
async
|
|
1214
|
+
async deleteObject(params) {
|
|
1077
1215
|
const transaction = await this.aptos.transaction.build.simple({
|
|
1078
1216
|
options: this.orderlessTxOptions(params.options),
|
|
1079
|
-
data: _ShelbyBlobClient.
|
|
1217
|
+
data: _ShelbyBlobClient.createDeleteObjectPayload({
|
|
1080
1218
|
deployer: this.deployer,
|
|
1081
1219
|
blobName: params.blobName
|
|
1082
1220
|
}),
|
|
@@ -1092,10 +1230,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1092
1230
|
/**
|
|
1093
1231
|
* Deletes multiple blobs on the blockchain in a single atomic transaction.
|
|
1094
1232
|
*
|
|
1095
|
-
* **Note:** This function requires the `delete_multiple_blobs` entry function
|
|
1096
|
-
* which will be deployed to the smart contract on 2026-02-04. Using this
|
|
1097
|
-
* function before that date will result in a transaction failure.
|
|
1098
|
-
*
|
|
1099
1233
|
* This operation is atomic: if any blob deletion fails (e.g., blob not found),
|
|
1100
1234
|
* the entire transaction fails and no blobs are deleted.
|
|
1101
1235
|
*
|
|
@@ -1110,16 +1244,16 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1110
1244
|
* @example
|
|
1111
1245
|
* ```typescript
|
|
1112
1246
|
*
|
|
1113
|
-
* const { transaction } = await client.
|
|
1247
|
+
* const { transaction } = await client.deleteMultipleObjects({
|
|
1114
1248
|
* account: signer,
|
|
1115
1249
|
* blobNames: ["foo/bar.txt", "baz.txt"],
|
|
1116
1250
|
* });
|
|
1117
1251
|
* ```
|
|
1118
1252
|
*/
|
|
1119
|
-
async
|
|
1253
|
+
async deleteMultipleObjects(params) {
|
|
1120
1254
|
const transaction = await this.aptos.transaction.build.simple({
|
|
1121
1255
|
options: this.orderlessTxOptions(params.options),
|
|
1122
|
-
data: _ShelbyBlobClient.
|
|
1256
|
+
data: _ShelbyBlobClient.createDeleteMultipleObjectsPayload({
|
|
1123
1257
|
deployer: this.deployer,
|
|
1124
1258
|
blobNames: params.blobNames
|
|
1125
1259
|
}),
|
|
@@ -1177,6 +1311,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1177
1311
|
data: _ShelbyBlobClient.createBatchRegisterBlobsPayload({
|
|
1178
1312
|
deployer: this.deployer,
|
|
1179
1313
|
account: params.account.accountAddress,
|
|
1314
|
+
selectedLocation: options.selectedLocation,
|
|
1315
|
+
locationHint: options.locationHint,
|
|
1180
1316
|
expirationMicros: params.expirationMicros,
|
|
1181
1317
|
blobs: params.blobs.map((blob) => ({
|
|
1182
1318
|
blobName: blob.blobName,
|
|
@@ -1185,7 +1321,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1185
1321
|
numChunksets: expectedTotalChunksets(blob.blobSize, chunksetSize)
|
|
1186
1322
|
})),
|
|
1187
1323
|
useSponsoredUsdVariant: options.usdSponsor !== void 0,
|
|
1188
|
-
encoding: config.enumIndex
|
|
1324
|
+
encoding: config.enumIndex,
|
|
1325
|
+
encryption: params.encryption
|
|
1189
1326
|
})
|
|
1190
1327
|
};
|
|
1191
1328
|
const transaction = options.usdSponsor ? await this.aptos.transaction.build.multiAgent({
|
|
@@ -1200,6 +1337,55 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1200
1337
|
})
|
|
1201
1338
|
};
|
|
1202
1339
|
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Extracts the on-chain UIDs assigned at registration from a committed
|
|
1342
|
+
* register transaction's events.
|
|
1343
|
+
*
|
|
1344
|
+
* `register_blob` / `register_multiple_blobs` create *pending* blobs and emit
|
|
1345
|
+
* one `BlobRegisteredEvent` per blob. The UID is published only on this event
|
|
1346
|
+
* (the blob is not yet in the `objects` map, so it cannot be read back by
|
|
1347
|
+
* name), so callers must parse it here before uploading bytes or committing.
|
|
1348
|
+
*
|
|
1349
|
+
* @param events - The committed transaction's events (from `waitForTransaction`).
|
|
1350
|
+
* @param deployer - The contract deployer address.
|
|
1351
|
+
* @returns One entry per registered blob, keyed by its full object name
|
|
1352
|
+
* (`@<owner>/<suffix>`, matching {@link createBlobKey}).
|
|
1353
|
+
*/
|
|
1354
|
+
static registeredBlobUids(events, deployer) {
|
|
1355
|
+
const eventType = `${deployer.toStringLong()}::blob_metadata::BlobRegisteredEvent`;
|
|
1356
|
+
return events.filter((event) => event.type === eventType).map((event) => {
|
|
1357
|
+
const data = event.data;
|
|
1358
|
+
return { objectName: data.object_name, uid: BigInt(data.uid) };
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Detects whether `commit_object` rejected the write for `uid` rather than
|
|
1363
|
+
* applying it. A rejected commit is still a *successful* transaction — the
|
|
1364
|
+
* contract tears down the pending blob and emits `ObjectCommitRejectedEvent`
|
|
1365
|
+
* instead of aborting — so callers must inspect the finalized transaction's
|
|
1366
|
+
* events to tell a durable write apart from a silent no-op.
|
|
1367
|
+
*
|
|
1368
|
+
* @param events - The committed transaction's events (from `waitForTransaction`).
|
|
1369
|
+
* @param deployer - The contract deployer address.
|
|
1370
|
+
* @param uid - The UID passed to `commit_object`.
|
|
1371
|
+
* @returns The rejection reason, or `undefined` if the commit was applied.
|
|
1372
|
+
*/
|
|
1373
|
+
static findObjectCommitRejection(events, deployer, uid) {
|
|
1374
|
+
const eventType = `${deployer.toStringLong()}::blob_metadata::ObjectCommitRejectedEvent`;
|
|
1375
|
+
const rejection = events.find(
|
|
1376
|
+
(event) => event.type === eventType && BigInt(event.data.uid) === uid
|
|
1377
|
+
);
|
|
1378
|
+
if (!rejection) {
|
|
1379
|
+
return void 0;
|
|
1380
|
+
}
|
|
1381
|
+
const reason = rejection.data.rejection_reason.__variant__;
|
|
1382
|
+
if (!COMMIT_REJECTION_REASONS.has(reason)) {
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
`Unrecognized ObjectCommitRejectedEvent rejection_reason '${reason}' for uid ${uid}`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
return reason;
|
|
1388
|
+
}
|
|
1203
1389
|
/**
|
|
1204
1390
|
* Creates a transaction payload to register a blob on the blockchain.
|
|
1205
1391
|
* This is a static helper method for constructing the Move function call payload.
|
|
@@ -1213,8 +1399,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1213
1399
|
* @param params.numChunksets - The total number of chunksets in the blob.
|
|
1214
1400
|
*
|
|
1215
1401
|
* @returns An Aptos transaction payload data object for the register_blob Move function.
|
|
1216
|
-
*
|
|
1217
|
-
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
1218
1402
|
*/
|
|
1219
1403
|
static createRegisterBlobPayload(params) {
|
|
1220
1404
|
const functionName = params.useSponsoredUsdVariant ? "register_blob_with_sponsor" : "register_blob";
|
|
@@ -1222,6 +1406,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1222
1406
|
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
|
|
1223
1407
|
functionArguments: [
|
|
1224
1408
|
params.blobName,
|
|
1409
|
+
params.selectedLocation ?? null,
|
|
1410
|
+
params.locationHint ?? null,
|
|
1225
1411
|
params.expirationMicros,
|
|
1226
1412
|
Hex3.fromHexString(params.blobMerkleRoot).toUint8Array(),
|
|
1227
1413
|
params.numChunksets,
|
|
@@ -1229,7 +1415,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1229
1415
|
// TODO
|
|
1230
1416
|
0,
|
|
1231
1417
|
// payment tier
|
|
1232
|
-
params.encoding
|
|
1418
|
+
params.encoding,
|
|
1419
|
+
...encryptionFunctionArgs(params)
|
|
1233
1420
|
]
|
|
1234
1421
|
};
|
|
1235
1422
|
}
|
|
@@ -1247,8 +1434,6 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1247
1434
|
* @param params.blobs.numChunksets - The total number of chunksets in the blob.
|
|
1248
1435
|
*
|
|
1249
1436
|
* @returns An Aptos transaction payload data object for the register_multiple_blobs Move function.
|
|
1250
|
-
*
|
|
1251
|
-
* @see https://github.com/shelby/shelby/blob/e08e84742cf2b80ad8bb7227deb3013398076d53/move/shelby_contract/sources/global_metadata.move#L357
|
|
1252
1437
|
*/
|
|
1253
1438
|
static createBatchRegisterBlobsPayload(params) {
|
|
1254
1439
|
const functionName = params.useSponsoredUsdVariant ? "register_multiple_blobs_with_sponsor" : "register_multiple_blobs";
|
|
@@ -1268,6 +1453,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1268
1453
|
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::${functionName}`,
|
|
1269
1454
|
functionArguments: [
|
|
1270
1455
|
blobNames,
|
|
1456
|
+
params.selectedLocation ?? null,
|
|
1457
|
+
params.locationHint ?? null,
|
|
1271
1458
|
params.expirationMicros,
|
|
1272
1459
|
blobMerkleRoots,
|
|
1273
1460
|
blobNumChunksets,
|
|
@@ -1275,7 +1462,8 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1275
1462
|
// TODO
|
|
1276
1463
|
0,
|
|
1277
1464
|
// payment tier
|
|
1278
|
-
params.encoding
|
|
1465
|
+
params.encoding,
|
|
1466
|
+
...encryptionFunctionArgs(params)
|
|
1279
1467
|
]
|
|
1280
1468
|
};
|
|
1281
1469
|
}
|
|
@@ -1286,24 +1474,20 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1286
1474
|
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
1287
1475
|
* @param params.blobName - The blob name (e.g. "bar.txt", without the account address prefix).
|
|
1288
1476
|
*
|
|
1289
|
-
* @returns An Aptos transaction payload data object for the
|
|
1290
|
-
*
|
|
1291
|
-
* @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
|
|
1477
|
+
* @returns An Aptos transaction payload data object for the delete_object Move function.
|
|
1292
1478
|
*/
|
|
1293
|
-
static
|
|
1479
|
+
static createDeleteObjectPayload(params) {
|
|
1294
1480
|
return {
|
|
1295
|
-
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::
|
|
1296
|
-
|
|
1481
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_object`,
|
|
1482
|
+
// Second arg is `if_match_etag: Option<vector<u8>>`; `null` => None
|
|
1483
|
+
// (unconditional delete).
|
|
1484
|
+
functionArguments: [params.blobName, null]
|
|
1297
1485
|
};
|
|
1298
1486
|
}
|
|
1299
1487
|
/**
|
|
1300
1488
|
* Creates a transaction payload to delete multiple blobs on the blockchain.
|
|
1301
1489
|
* This is a static helper method for constructing the Move function call payload.
|
|
1302
1490
|
*
|
|
1303
|
-
* **Note:** This function requires the `delete_multiple_blobs` entry function
|
|
1304
|
-
* which will be deployed to the smart contract on 2026-02-04. Using this
|
|
1305
|
-
* function before that date will result in a transaction failure.
|
|
1306
|
-
*
|
|
1307
1491
|
* This operation is atomic: if any blob deletion fails (e.g., blob not found),
|
|
1308
1492
|
* the entire transaction fails and no blobs are deleted.
|
|
1309
1493
|
*
|
|
@@ -1312,42 +1496,71 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
1312
1496
|
* (e.g. ["foo/bar.txt", "baz.txt"], NOT ["0x1/foo/bar.txt"]). The account address
|
|
1313
1497
|
* prefix is automatically derived from the transaction sender.
|
|
1314
1498
|
*
|
|
1315
|
-
* @returns An Aptos transaction payload data object for the
|
|
1316
|
-
*
|
|
1317
|
-
* @see https://github.com/shelby/shelby/blob/main/move/shelby_contract/sources/blob_metadata.move
|
|
1499
|
+
* @returns An Aptos transaction payload data object for the delete_multiple_objects Move function.
|
|
1318
1500
|
*/
|
|
1319
|
-
static
|
|
1501
|
+
static createDeleteMultipleObjectsPayload(params) {
|
|
1320
1502
|
return {
|
|
1321
|
-
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::
|
|
1322
|
-
|
|
1503
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::delete_multiple_objects`,
|
|
1504
|
+
// Second arg is `if_match_etags: vector<Option<vector<u8>>>`; an empty
|
|
1505
|
+
// vector means "no etag check on any object" (unconditional delete).
|
|
1506
|
+
functionArguments: [params.blobNames, []]
|
|
1323
1507
|
};
|
|
1324
1508
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1509
|
+
/**
|
|
1510
|
+
* Sort acks by slot and reduce to the `(ack_bits, signatures)` pair the
|
|
1511
|
+
* contract expects: it walks the set bits low-to-high and consumes the
|
|
1512
|
+
* signatures in that same order.
|
|
1513
|
+
*/
|
|
1514
|
+
static encodeAcks(storageProviderAcks) {
|
|
1515
|
+
const sortedAcks = [...storageProviderAcks].sort((a, b) => a.slot - b.slot);
|
|
1516
|
+
const ackBitMask = sortedAcks.reduce(
|
|
1327
1517
|
(acc, ack) => acc | 1 << ack.slot,
|
|
1328
1518
|
0
|
|
1329
1519
|
);
|
|
1330
1520
|
return {
|
|
1331
|
-
|
|
1521
|
+
ackBits: new U32(Number(ackBitMask)),
|
|
1522
|
+
signatures: sortedAcks.map((ack) => ack.signature)
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Payload for `commit_object(uid, object_name_suffix, overwrite, if_match_etag,
|
|
1527
|
+
* ack_bits, signatures)` — binds a written pending blob under its object name,
|
|
1528
|
+
* finalizing the upload. SP acks may be batched in here (the contract applies
|
|
1529
|
+
* them before the `is_written` check), so register → upload → commit needs
|
|
1530
|
+
* only a single finalize transaction.
|
|
1531
|
+
*
|
|
1532
|
+
* @param params.uid - The blob UID returned at registration.
|
|
1533
|
+
* @param params.blobName - The object name suffix the blob was registered under.
|
|
1534
|
+
* @param params.overwrite - Allow replacing an existing binding under this name.
|
|
1535
|
+
* @param params.storageProviderAcks - Acks applied atomically with the commit.
|
|
1536
|
+
*/
|
|
1537
|
+
static createCommitObjectPayload(params) {
|
|
1538
|
+
const { ackBits, signatures } = _ShelbyBlobClient.encodeAcks(
|
|
1539
|
+
params.storageProviderAcks
|
|
1540
|
+
);
|
|
1541
|
+
return {
|
|
1542
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::blob_metadata::commit_object`,
|
|
1332
1543
|
functionArguments: [
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1544
|
+
params.uid,
|
|
1545
|
+
params.blobName,
|
|
1546
|
+
params.overwrite,
|
|
1547
|
+
// `if_match_etag: Option<vector<u8>>` is `null` => None; conditional
|
|
1548
|
+
// (CAS) writes are not yet exposed by the SDK.
|
|
1549
|
+
null,
|
|
1550
|
+
ackBits,
|
|
1551
|
+
signatures
|
|
1340
1552
|
]
|
|
1341
1553
|
};
|
|
1342
1554
|
}
|
|
1343
|
-
async
|
|
1555
|
+
async commitObject(params) {
|
|
1344
1556
|
const transaction = await this.aptos.transaction.build.simple({
|
|
1345
1557
|
...params.options?.build,
|
|
1346
1558
|
options: this.orderlessTxOptions(params.options?.build?.options),
|
|
1347
|
-
data: _ShelbyBlobClient.
|
|
1348
|
-
|
|
1559
|
+
data: _ShelbyBlobClient.createCommitObjectPayload({
|
|
1560
|
+
deployer: this.deployer,
|
|
1561
|
+
uid: params.uid,
|
|
1349
1562
|
blobName: params.blobName,
|
|
1350
|
-
|
|
1563
|
+
overwrite: params.overwrite,
|
|
1351
1564
|
storageProviderAcks: params.storageProviderAcks
|
|
1352
1565
|
}),
|
|
1353
1566
|
sender: params.account.accountAddress
|
|
@@ -1370,12 +1583,64 @@ import {
|
|
|
1370
1583
|
} from "@aptos-labs/ts-sdk";
|
|
1371
1584
|
import pLimit from "p-limit";
|
|
1372
1585
|
|
|
1586
|
+
// src/core/errors.ts
|
|
1587
|
+
var ShelbyLocationErrorCodes = {
|
|
1588
|
+
E_LOCATION_NOT_FOUND: "E_LOCATION_NOT_FOUND",
|
|
1589
|
+
E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK: "E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK",
|
|
1590
|
+
E_NO_LOCATION_SELECTED: "E_NO_LOCATION_SELECTED",
|
|
1591
|
+
E_LOCATION_WRITES_FROZEN: "E_LOCATION_WRITES_FROZEN",
|
|
1592
|
+
E_LOCATION_NOT_ACTIVATED: "E_LOCATION_NOT_ACTIVATED"
|
|
1593
|
+
};
|
|
1594
|
+
function describeLocationError(errorMessage) {
|
|
1595
|
+
if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_WRITES_FROZEN)) {
|
|
1596
|
+
return "Cannot write: the resolved location is frozen and not currently accepting new data.";
|
|
1597
|
+
}
|
|
1598
|
+
if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_ACTIVATED)) {
|
|
1599
|
+
return "Cannot write: the resolved location is not activated yet.";
|
|
1600
|
+
}
|
|
1601
|
+
if (errorMessage.includes(
|
|
1602
|
+
ShelbyLocationErrorCodes.E_SELECTED_LOCATION_CONFLICTS_WITH_LOCK
|
|
1603
|
+
)) {
|
|
1604
|
+
return "Cannot write: the selected location conflicts with the account's locked location preference.";
|
|
1605
|
+
}
|
|
1606
|
+
if (errorMessage.includes(ShelbyLocationErrorCodes.E_NO_LOCATION_SELECTED)) {
|
|
1607
|
+
return "No write location could be resolved: none was selected and the account has no default location or location hint.";
|
|
1608
|
+
}
|
|
1609
|
+
if (errorMessage.includes(ShelbyLocationErrorCodes.E_LOCATION_NOT_FOUND)) {
|
|
1610
|
+
return "Cannot write: the requested location does not exist.";
|
|
1611
|
+
}
|
|
1612
|
+
return void 0;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1373
1615
|
// src/core/clients/ShelbyMetadataClient.ts
|
|
1374
1616
|
import {
|
|
1375
1617
|
AccountAddress as AccountAddress3,
|
|
1376
1618
|
Aptos as Aptos2,
|
|
1377
1619
|
Hex as Hex4
|
|
1378
1620
|
} from "@aptos-labs/ts-sdk";
|
|
1621
|
+
|
|
1622
|
+
// src/core/types/storage_providers.ts
|
|
1623
|
+
var ACTIVE_PROVIDER_CONDITIONS = [
|
|
1624
|
+
"Normal",
|
|
1625
|
+
"Faulty",
|
|
1626
|
+
"Leaving",
|
|
1627
|
+
"Evicted",
|
|
1628
|
+
"PendingLeaving"
|
|
1629
|
+
];
|
|
1630
|
+
function isActiveProviderCondition(value) {
|
|
1631
|
+
return typeof value === "string" && ACTIVE_PROVIDER_CONDITIONS.includes(value);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/core/clients/ShelbyMetadataClient.ts
|
|
1635
|
+
function parseActiveProviderCondition(raw) {
|
|
1636
|
+
const variant = raw?.__variant__;
|
|
1637
|
+
if (!isActiveProviderCondition(variant)) {
|
|
1638
|
+
throw new Error(
|
|
1639
|
+
`Unknown ActiveProviderCondition variant: ${JSON.stringify(raw)}`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
return variant;
|
|
1643
|
+
}
|
|
1379
1644
|
function parseStorageProviderState(raw) {
|
|
1380
1645
|
switch (raw.__variant__) {
|
|
1381
1646
|
case "Active":
|
|
@@ -1383,8 +1648,7 @@ function parseStorageProviderState(raw) {
|
|
|
1383
1648
|
variant: "Active",
|
|
1384
1649
|
quota: raw.quota.value,
|
|
1385
1650
|
stakeAtStartOfStakingEpoch: raw.stake_at_start_of_staking_epoch,
|
|
1386
|
-
|
|
1387
|
-
leaving: raw.leaving
|
|
1651
|
+
condition: parseActiveProviderCondition(raw.condition)
|
|
1388
1652
|
};
|
|
1389
1653
|
case "Waitlisted":
|
|
1390
1654
|
return {
|
|
@@ -1397,6 +1661,10 @@ function parseStorageProviderState(raw) {
|
|
|
1397
1661
|
frozenFrom: raw.frozen_from,
|
|
1398
1662
|
frozenTill: raw.frozen_till
|
|
1399
1663
|
};
|
|
1664
|
+
default:
|
|
1665
|
+
throw new Error(
|
|
1666
|
+
`Unknown StorageProviderStateDetails variant: ${JSON.stringify(raw)}`
|
|
1667
|
+
);
|
|
1400
1668
|
}
|
|
1401
1669
|
}
|
|
1402
1670
|
var ShelbyMetadataClient = class {
|
|
@@ -1455,28 +1723,52 @@ var ShelbyMetadataClient = class {
|
|
|
1455
1723
|
}
|
|
1456
1724
|
}
|
|
1457
1725
|
/**
|
|
1458
|
-
* Retrieves the
|
|
1726
|
+
* Retrieves the names of every activated location (region). Locations that are
|
|
1727
|
+
* registered but not yet brought online are admin-internal and not listed.
|
|
1728
|
+
*
|
|
1729
|
+
* @returns The location name list.
|
|
1730
|
+
*
|
|
1731
|
+
* @example
|
|
1732
|
+
* ```typescript
|
|
1733
|
+
* const locations = await client.getLocationNames();
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
1736
|
+
async getLocationNames() {
|
|
1737
|
+
const names = await this.aptos.view({
|
|
1738
|
+
payload: {
|
|
1739
|
+
function: `${this.deployer.toString()}::location::activated_location_names`,
|
|
1740
|
+
functionArguments: []
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
return names[0];
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Retrieves the list of placement group addresses in a location.
|
|
1459
1747
|
*
|
|
1748
|
+
* @param locationName - The location whose placement groups to list.
|
|
1460
1749
|
* @returns The placement group address list, or an empty array if none exist.
|
|
1461
1750
|
*
|
|
1462
1751
|
* @example
|
|
1463
1752
|
* ```typescript
|
|
1464
|
-
* const pgList = await client.getPlacementGroupAddresses();
|
|
1753
|
+
* const pgList = await client.getPlacementGroupAddresses("us-east-1");
|
|
1465
1754
|
* ```
|
|
1466
1755
|
*/
|
|
1467
|
-
async getPlacementGroupAddresses() {
|
|
1756
|
+
async getPlacementGroupAddresses(locationName) {
|
|
1468
1757
|
try {
|
|
1469
1758
|
const pgSizeMetadata = await this.aptos.view({
|
|
1470
1759
|
payload: {
|
|
1471
1760
|
function: `${this.deployer.toString()}::placement_group_registry::get_number_of_placement_groups`,
|
|
1472
|
-
functionArguments: []
|
|
1761
|
+
functionArguments: [locationName]
|
|
1473
1762
|
}
|
|
1474
1763
|
});
|
|
1764
|
+
if (Number(pgSizeMetadata[0]) === 0) {
|
|
1765
|
+
return [];
|
|
1766
|
+
}
|
|
1475
1767
|
const finalPlacementGroupIndex = pgSizeMetadata[0] - 1;
|
|
1476
1768
|
const addressMetadataArray = await this.aptos.view({
|
|
1477
1769
|
payload: {
|
|
1478
1770
|
function: `${this.deployer.toString()}::placement_group_registry::get_placement_group_addresses`,
|
|
1479
|
-
functionArguments: [0, finalPlacementGroupIndex]
|
|
1771
|
+
functionArguments: [locationName, 0, finalPlacementGroupIndex]
|
|
1480
1772
|
}
|
|
1481
1773
|
});
|
|
1482
1774
|
const metadata = addressMetadataArray[0];
|
|
@@ -1490,28 +1782,32 @@ var ShelbyMetadataClient = class {
|
|
|
1490
1782
|
}
|
|
1491
1783
|
}
|
|
1492
1784
|
/**
|
|
1493
|
-
* Retrieves the list of slice addresses.
|
|
1785
|
+
* Retrieves the list of slice addresses in a location.
|
|
1494
1786
|
*
|
|
1787
|
+
* @param locationName - The location whose slices to list.
|
|
1495
1788
|
* @returns The slice group list, or an empty array if none exist.
|
|
1496
1789
|
*
|
|
1497
1790
|
* @example
|
|
1498
1791
|
* ```typescript
|
|
1499
|
-
* const pgList = await client.getSliceAddresses();
|
|
1792
|
+
* const pgList = await client.getSliceAddresses("us-east-1");
|
|
1500
1793
|
* ```
|
|
1501
1794
|
*/
|
|
1502
|
-
async getSliceAddresses() {
|
|
1795
|
+
async getSliceAddresses(locationName) {
|
|
1503
1796
|
try {
|
|
1504
1797
|
const sliceSizeMetadata = await this.aptos.view({
|
|
1505
1798
|
payload: {
|
|
1506
1799
|
function: `${this.deployer.toString()}::slice_registry::get_number_of_slices`,
|
|
1507
|
-
functionArguments: []
|
|
1800
|
+
functionArguments: [locationName]
|
|
1508
1801
|
}
|
|
1509
1802
|
});
|
|
1803
|
+
if (Number(sliceSizeMetadata[0]) === 0) {
|
|
1804
|
+
return [];
|
|
1805
|
+
}
|
|
1510
1806
|
const finalSliceIndex = sliceSizeMetadata[0] - 1;
|
|
1511
1807
|
const addressMetadataArray = await this.aptos.view({
|
|
1512
1808
|
payload: {
|
|
1513
1809
|
function: `${this.deployer.toString()}::slice_registry::get_slice_addresses`,
|
|
1514
|
-
functionArguments: [0, finalSliceIndex]
|
|
1810
|
+
functionArguments: [locationName, 0, finalSliceIndex]
|
|
1515
1811
|
}
|
|
1516
1812
|
});
|
|
1517
1813
|
const metadata = addressMetadataArray[0];
|
|
@@ -1571,6 +1867,36 @@ var ShelbyMetadataClient = class {
|
|
|
1571
1867
|
(opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
|
|
1572
1868
|
);
|
|
1573
1869
|
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Retrieves the active storage providers for a slice.
|
|
1872
|
+
*
|
|
1873
|
+
* Active SPs have a complete copy of the data for the slot, and are not in any data transfer/repair/reconstruction phase.
|
|
1874
|
+
* Active SP can be audited for the data it contains.
|
|
1875
|
+
* Each slot has at most one active SP.
|
|
1876
|
+
*
|
|
1877
|
+
* @param params.account - The address of the slice account.
|
|
1878
|
+
* @returns An array where result[i] is the active SP for slot i, or null if no SP is active.
|
|
1879
|
+
*
|
|
1880
|
+
* @example
|
|
1881
|
+
* ```typescript
|
|
1882
|
+
* const providers = await client.getActiveStorageProvidersForSlice({ account: sliceAddress });
|
|
1883
|
+
* ```
|
|
1884
|
+
*/
|
|
1885
|
+
async getActiveStorageProvidersForSlice(params) {
|
|
1886
|
+
const placementGroupAddress = await this.getPlacementGroupAddressForSlice(
|
|
1887
|
+
params.account
|
|
1888
|
+
);
|
|
1889
|
+
const rawMetadata = await this.aptos.view({
|
|
1890
|
+
payload: {
|
|
1891
|
+
function: `${this.deployer.toString()}::placement_group::get_active_storage_providers`,
|
|
1892
|
+
functionArguments: [placementGroupAddress]
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
const providers = rawMetadata[0];
|
|
1896
|
+
return providers.map(
|
|
1897
|
+
(opt) => opt.vec.length > 0 ? normalizeAddress(opt.vec[0]) : null
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1574
1900
|
/**
|
|
1575
1901
|
* Retrieves the serving storage providers for a slice.
|
|
1576
1902
|
*
|
|
@@ -1630,22 +1956,6 @@ var ChallengeResponseSchema = z3.object({
|
|
|
1630
1956
|
challenge: z3.string(),
|
|
1631
1957
|
expiresAt: z3.number()
|
|
1632
1958
|
});
|
|
1633
|
-
var MultipartUploadStatusResponseSchema = z3.object({
|
|
1634
|
-
uploadId: z3.string(),
|
|
1635
|
-
completedParts: z3.array(z3.number()),
|
|
1636
|
-
partSize: z3.number(),
|
|
1637
|
-
nParts: z3.number(),
|
|
1638
|
-
uploadedBytes: z3.number()
|
|
1639
|
-
});
|
|
1640
|
-
var StartMultipartUploadResponseSchema = z3.object({
|
|
1641
|
-
uploadId: z3.string()
|
|
1642
|
-
});
|
|
1643
|
-
var UploadPartResponseSchema = z3.object({
|
|
1644
|
-
success: z3.literal(true)
|
|
1645
|
-
});
|
|
1646
|
-
var CompleteMultipartUploadResponseSchema = z3.object({
|
|
1647
|
-
success: z3.literal(true)
|
|
1648
|
-
});
|
|
1649
1959
|
var RPCErrorResponseSchema = z3.object({
|
|
1650
1960
|
error: z3.string()
|
|
1651
1961
|
});
|
|
@@ -1862,6 +2172,7 @@ var BLOB_OWNER_AUTH_SCHEME_HEADER = "X-Shelby-Auth-Scheme";
|
|
|
1862
2172
|
var BLOB_OWNER_IDENTITY_HEADER = "X-Shelby-Identity";
|
|
1863
2173
|
var BLOB_OWNER_DOMAIN_HEADER = "X-Shelby-Domain";
|
|
1864
2174
|
var BLOB_OWNER_AUTH_FUNCTION_HEADER = "X-Shelby-Auth-Function";
|
|
2175
|
+
var INCLUSION_PROOF_HEADER = "X-Shelby-Inclusion-Proof";
|
|
1865
2176
|
function buildAuthHeaders(auth) {
|
|
1866
2177
|
const signatureBase64 = btoa(
|
|
1867
2178
|
Array.from(auth.signature, (byte) => String.fromCharCode(byte)).join("")
|
|
@@ -1885,6 +2196,58 @@ function buildAuthHeaders(auth) {
|
|
|
1885
2196
|
function encodeURIComponentKeepSlashes(str) {
|
|
1886
2197
|
return encodeURIComponent(str).replace(/%2F/g, "/");
|
|
1887
2198
|
}
|
|
2199
|
+
async function generateChunksetInclusionProof(chunksetRoots, chunksetIndex) {
|
|
2200
|
+
if (chunksetRoots.length === 0) {
|
|
2201
|
+
throw new Error("Cannot generate inclusion proof for empty chunkset roots");
|
|
2202
|
+
}
|
|
2203
|
+
if (chunksetIndex < 0 || chunksetIndex >= chunksetRoots.length) {
|
|
2204
|
+
throw new Error(
|
|
2205
|
+
`Chunkset index ${chunksetIndex} out of range [0, ${chunksetRoots.length})`
|
|
2206
|
+
);
|
|
2207
|
+
}
|
|
2208
|
+
if (chunksetRoots.length === 1) {
|
|
2209
|
+
return [];
|
|
2210
|
+
}
|
|
2211
|
+
const zeroHash = new Uint8Array(32);
|
|
2212
|
+
const siblings = [];
|
|
2213
|
+
let currentLeaves = chunksetRoots.map((h) => h.toUint8Array());
|
|
2214
|
+
let currentIdx = chunksetIndex;
|
|
2215
|
+
while (currentLeaves.length > 1) {
|
|
2216
|
+
if (currentLeaves.length % 2 !== 0) {
|
|
2217
|
+
currentLeaves.push(zeroHash);
|
|
2218
|
+
}
|
|
2219
|
+
const siblingIdx = currentIdx % 2 === 0 ? currentIdx + 1 : currentIdx - 1;
|
|
2220
|
+
siblings.push(currentLeaves[siblingIdx]);
|
|
2221
|
+
const nextLeaves = [];
|
|
2222
|
+
for (let i = 0; i < currentLeaves.length; i += 2) {
|
|
2223
|
+
const combined = await concatHashes([
|
|
2224
|
+
currentLeaves[i],
|
|
2225
|
+
currentLeaves[i + 1]
|
|
2226
|
+
]);
|
|
2227
|
+
nextLeaves.push(combined.toUint8Array());
|
|
2228
|
+
}
|
|
2229
|
+
currentLeaves = nextLeaves;
|
|
2230
|
+
currentIdx = Math.floor(currentIdx / 2);
|
|
2231
|
+
}
|
|
2232
|
+
return siblings;
|
|
2233
|
+
}
|
|
2234
|
+
function encodeInclusionProof(siblings) {
|
|
2235
|
+
if (siblings.length === 0) {
|
|
2236
|
+
return "NONE";
|
|
2237
|
+
}
|
|
2238
|
+
const totalBytes = siblings.length * 32;
|
|
2239
|
+
const combined = new Uint8Array(totalBytes);
|
|
2240
|
+
let offset = 0;
|
|
2241
|
+
for (const sibling of siblings) {
|
|
2242
|
+
combined.set(sibling, offset);
|
|
2243
|
+
offset += 32;
|
|
2244
|
+
}
|
|
2245
|
+
const binaryString = Array.from(
|
|
2246
|
+
combined,
|
|
2247
|
+
(byte) => String.fromCharCode(byte)
|
|
2248
|
+
).join("");
|
|
2249
|
+
return btoa(binaryString);
|
|
2250
|
+
}
|
|
1888
2251
|
function validateTotalBytes(totalBytes) {
|
|
1889
2252
|
if (!Number.isInteger(totalBytes) || totalBytes < 0) {
|
|
1890
2253
|
throw new Error("totalBytes must be a non-negative integer");
|
|
@@ -1916,7 +2279,7 @@ var ShelbyRPCClient = class {
|
|
|
1916
2279
|
* @param config - The client configuration object.
|
|
1917
2280
|
* @param config.network - The Shelby network to use.
|
|
1918
2281
|
* @param options.signChallengeHandler - Optional override for challenge
|
|
1919
|
-
* signing. When set, `
|
|
2282
|
+
* signing. When set, `putBlobChunksets` uses this instead of the built-in
|
|
1920
2283
|
* `signChallenge`. Intended for kit-level overrides (e.g. Solana DAA).
|
|
1921
2284
|
*
|
|
1922
2285
|
* @example
|
|
@@ -1967,43 +2330,6 @@ var ShelbyRPCClient = class {
|
|
|
1967
2330
|
}
|
|
1968
2331
|
return ChallengeResponseSchema.parse(await response.json());
|
|
1969
2332
|
}
|
|
1970
|
-
/**
|
|
1971
|
-
* Check if there's an existing multipart upload for a blob.
|
|
1972
|
-
* Returns the upload status including which parts have been uploaded.
|
|
1973
|
-
*
|
|
1974
|
-
* @param account - The account that owns the blob.
|
|
1975
|
-
* @param blobName - The name of the blob.
|
|
1976
|
-
* @returns The upload status, or undefined if no pending upload exists.
|
|
1977
|
-
*
|
|
1978
|
-
* @example
|
|
1979
|
-
* ```typescript
|
|
1980
|
-
* const status = await client.getMultipartUploadStatus(account, "myblob.txt");
|
|
1981
|
-
* if (status) {
|
|
1982
|
-
* console.log(`Resuming upload ${status.uploadId}, ${status.completedParts.length} parts done`);
|
|
1983
|
-
* }
|
|
1984
|
-
* ```
|
|
1985
|
-
*/
|
|
1986
|
-
async getMultipartUploadStatus(account, blobName) {
|
|
1987
|
-
const url = new URL(buildRequestUrl("/v1/multipart-uploads", this.baseUrl));
|
|
1988
|
-
url.searchParams.set("account", account.toString());
|
|
1989
|
-
url.searchParams.set("blobName", blobName);
|
|
1990
|
-
const response = await fetch(url.toString(), {
|
|
1991
|
-
method: "GET",
|
|
1992
|
-
headers: {
|
|
1993
|
-
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
1994
|
-
}
|
|
1995
|
-
});
|
|
1996
|
-
if (response.status === 404) {
|
|
1997
|
-
return void 0;
|
|
1998
|
-
}
|
|
1999
|
-
if (!response.ok) {
|
|
2000
|
-
const errorBody = await response.text().catch(() => "");
|
|
2001
|
-
throw new Error(
|
|
2002
|
-
`Failed to get multipart upload status: status ${response.status}, body: ${errorBody}`
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
return MultipartUploadStatusResponseSchema.parse(await response.json());
|
|
2006
|
-
}
|
|
2007
2333
|
/**
|
|
2008
2334
|
* Sign a challenge using the given account and return auth credentials.
|
|
2009
2335
|
*
|
|
@@ -2020,269 +2346,142 @@ var ShelbyRPCClient = class {
|
|
|
2020
2346
|
publicKey: account.publicKey.toUint8Array()
|
|
2021
2347
|
};
|
|
2022
2348
|
}
|
|
2023
|
-
async #uploadPart(uploadId, partIdx, partData) {
|
|
2024
|
-
const nRetries = 5;
|
|
2025
|
-
let lastResponse;
|
|
2026
|
-
let lastError;
|
|
2027
|
-
let attempts = 0;
|
|
2028
|
-
const partUrl = buildRequestUrl(
|
|
2029
|
-
`/v1/multipart-uploads/${uploadId}/parts/${partIdx}`,
|
|
2030
|
-
this.baseUrl
|
|
2031
|
-
);
|
|
2032
|
-
for (let i = 0; i < nRetries; ++i) {
|
|
2033
|
-
attempts++;
|
|
2034
|
-
try {
|
|
2035
|
-
lastResponse = await fetch(partUrl, {
|
|
2036
|
-
method: "PUT",
|
|
2037
|
-
headers: {
|
|
2038
|
-
"Content-Type": "application/octet-stream",
|
|
2039
|
-
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2040
|
-
},
|
|
2041
|
-
body: partData
|
|
2042
|
-
});
|
|
2043
|
-
lastError = void 0;
|
|
2044
|
-
} catch (error) {
|
|
2045
|
-
lastError = error;
|
|
2046
|
-
if (i < nRetries - 1) {
|
|
2047
|
-
const delay = 2 ** i * 100;
|
|
2048
|
-
await sleep(delay);
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
break;
|
|
2052
|
-
}
|
|
2053
|
-
if (lastResponse.ok) return;
|
|
2054
|
-
if (!isRetryableStatus(lastResponse.status)) {
|
|
2055
|
-
break;
|
|
2056
|
-
}
|
|
2057
|
-
if (i < nRetries - 1) {
|
|
2058
|
-
const delay = 2 ** i * 100;
|
|
2059
|
-
await sleep(delay);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
if (lastError !== void 0) {
|
|
2063
|
-
const errorCode = getErrorCode(lastError);
|
|
2064
|
-
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2065
|
-
throw new Error(
|
|
2066
|
-
`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}`,
|
|
2067
|
-
{ cause: lastError }
|
|
2068
|
-
);
|
|
2069
|
-
}
|
|
2070
|
-
const errorBody = await lastResponse?.text().catch(() => "");
|
|
2071
|
-
throw new Error(
|
|
2072
|
-
`Failed to upload part ${partIdx} for multipart upload ${uploadId} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
|
|
2073
|
-
);
|
|
2074
|
-
}
|
|
2075
|
-
async #putBlobMultipart(account, blobName, blobData, totalBytes, partSize = 5 * 1024 * 1024, onProgress) {
|
|
2076
|
-
validateTotalBytes(totalBytes);
|
|
2077
|
-
const startResponse = await fetch(
|
|
2078
|
-
buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
|
|
2079
|
-
{
|
|
2080
|
-
method: "POST",
|
|
2081
|
-
headers: {
|
|
2082
|
-
"Content-Type": "application/json",
|
|
2083
|
-
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2084
|
-
},
|
|
2085
|
-
body: JSON.stringify({
|
|
2086
|
-
rawAccount: account.toString(),
|
|
2087
|
-
rawBlobName: blobName,
|
|
2088
|
-
rawPartSize: partSize
|
|
2089
|
-
})
|
|
2090
|
-
}
|
|
2091
|
-
);
|
|
2092
|
-
if (!startResponse.ok) {
|
|
2093
|
-
let errorBodyText = "Could not read error body";
|
|
2094
|
-
try {
|
|
2095
|
-
errorBodyText = await startResponse.text();
|
|
2096
|
-
} catch (_e) {
|
|
2097
|
-
}
|
|
2098
|
-
throw new Error(
|
|
2099
|
-
`Failed to start multipart upload! status: ${startResponse.status}, body: ${errorBodyText}`
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
const { uploadId } = StartMultipartUploadResponseSchema.parse(
|
|
2103
|
-
await startResponse.json()
|
|
2104
|
-
);
|
|
2105
|
-
const totalParts = Math.ceil(totalBytes / partSize);
|
|
2106
|
-
let uploadedBytes = 0;
|
|
2107
|
-
for await (const [partIdx, partData] of readInChunks(blobData, partSize)) {
|
|
2108
|
-
await this.#uploadPart(uploadId, partIdx, partData);
|
|
2109
|
-
uploadedBytes += partData.length;
|
|
2110
|
-
onProgress?.({
|
|
2111
|
-
phase: "uploading",
|
|
2112
|
-
partIdx,
|
|
2113
|
-
totalParts,
|
|
2114
|
-
partBytes: partData.length,
|
|
2115
|
-
uploadedBytes,
|
|
2116
|
-
totalBytes
|
|
2117
|
-
});
|
|
2118
|
-
}
|
|
2119
|
-
if (uploadedBytes !== totalBytes) {
|
|
2120
|
-
throw new Error(
|
|
2121
|
-
`Uploaded bytes (${uploadedBytes}) did not match declared totalBytes (${totalBytes})`
|
|
2122
|
-
);
|
|
2123
|
-
}
|
|
2124
|
-
const finalPartIdx = totalParts > 0 ? totalParts - 1 : 0;
|
|
2125
|
-
onProgress?.({
|
|
2126
|
-
phase: "finalizing",
|
|
2127
|
-
partIdx: finalPartIdx,
|
|
2128
|
-
totalParts,
|
|
2129
|
-
// no part uploaded in this phase
|
|
2130
|
-
partBytes: 0,
|
|
2131
|
-
uploadedBytes: totalBytes,
|
|
2132
|
-
totalBytes
|
|
2133
|
-
});
|
|
2134
|
-
const completeResponse = await fetch(
|
|
2135
|
-
buildRequestUrl(
|
|
2136
|
-
`/v1/multipart-uploads/${uploadId}/complete`,
|
|
2137
|
-
this.baseUrl
|
|
2138
|
-
),
|
|
2139
|
-
{
|
|
2140
|
-
method: "POST",
|
|
2141
|
-
headers: {
|
|
2142
|
-
"Content-Type": "application/json",
|
|
2143
|
-
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
);
|
|
2147
|
-
if (!completeResponse.ok) {
|
|
2148
|
-
let errorBodyText = "Could not read error body";
|
|
2149
|
-
try {
|
|
2150
|
-
errorBodyText = await completeResponse.text();
|
|
2151
|
-
} catch (_e) {
|
|
2152
|
-
}
|
|
2153
|
-
throw new Error(
|
|
2154
|
-
`Failed to complete multipart upload! status: ${completeResponse.status}, body: ${errorBodyText}`
|
|
2155
|
-
);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Uploads blob data to the Shelby RPC node for storage by storage providers.
|
|
2160
|
-
* This method should be called after blob commitments have been registered on the blockchain.
|
|
2161
|
-
* Uses multipart upload for efficient handling of large files.
|
|
2162
|
-
*
|
|
2163
|
-
* @param params.account - The account that owns the blob.
|
|
2164
|
-
* @param params.blobName - The name/path of the blob (e.g. "folder/file.txt").
|
|
2165
|
-
* @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
|
|
2166
|
-
* @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
|
|
2167
|
-
*
|
|
2168
|
-
* @example
|
|
2169
|
-
* ```typescript
|
|
2170
|
-
* const blobData = new TextEncoder().encode("Hello, world!");
|
|
2171
|
-
*
|
|
2172
|
-
* await client.putBlob({
|
|
2173
|
-
* account: AccountAddress.from("0x1"),
|
|
2174
|
-
* blobName: "greetings/hello.txt",
|
|
2175
|
-
* blobData,
|
|
2176
|
-
* });
|
|
2177
|
-
* ```
|
|
2178
|
-
*/
|
|
2179
|
-
async putBlob(params) {
|
|
2180
|
-
BlobNameSchema.parse(params.blobName);
|
|
2181
|
-
let totalBytes;
|
|
2182
|
-
if (params.blobData instanceof Uint8Array) {
|
|
2183
|
-
totalBytes = params.totalBytes ?? params.blobData.length;
|
|
2184
|
-
if (totalBytes !== params.blobData.length) {
|
|
2185
|
-
throw new Error(
|
|
2186
|
-
"totalBytes must match blobData.length when blobData is a Uint8Array"
|
|
2187
|
-
);
|
|
2188
|
-
}
|
|
2189
|
-
} else {
|
|
2190
|
-
if (params.totalBytes === void 0) {
|
|
2191
|
-
throw new Error(
|
|
2192
|
-
"totalBytes is required when blobData is a ReadableStream"
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2195
|
-
totalBytes = params.totalBytes;
|
|
2196
|
-
}
|
|
2197
|
-
validateTotalBytes(totalBytes);
|
|
2198
|
-
await this.#putBlobMultipart(
|
|
2199
|
-
params.account,
|
|
2200
|
-
params.blobName,
|
|
2201
|
-
params.blobData,
|
|
2202
|
-
totalBytes,
|
|
2203
|
-
void 0,
|
|
2204
|
-
params.onProgress
|
|
2205
|
-
);
|
|
2206
|
-
}
|
|
2207
2349
|
/**
|
|
2208
|
-
* Upload a
|
|
2350
|
+
* Upload a single chunkset via the v2 chunkset API.
|
|
2209
2351
|
*/
|
|
2210
|
-
async #
|
|
2211
|
-
const
|
|
2352
|
+
async #uploadChunkset(params) {
|
|
2353
|
+
const maxNonBusyRetries = 5;
|
|
2212
2354
|
let lastResponse;
|
|
2213
2355
|
let lastError;
|
|
2214
2356
|
let attempts = 0;
|
|
2215
|
-
|
|
2216
|
-
|
|
2357
|
+
let consecutive429s = 0;
|
|
2358
|
+
let nonBusyRetries = 0;
|
|
2359
|
+
const chunksetUrl = buildRequestUrl(
|
|
2360
|
+
`/v2/chunksets/${params.account}/${params.chunksetIndex}/${params.uid}`,
|
|
2217
2361
|
this.baseUrl
|
|
2218
2362
|
);
|
|
2219
|
-
const authHeaders = buildAuthHeaders(auth);
|
|
2220
|
-
|
|
2363
|
+
const authHeaders = buildAuthHeaders(params.auth);
|
|
2364
|
+
while (true) {
|
|
2365
|
+
if (params.signal?.aborted) {
|
|
2366
|
+
throw new DOMException("Upload aborted", "AbortError");
|
|
2367
|
+
}
|
|
2221
2368
|
attempts++;
|
|
2222
2369
|
try {
|
|
2223
|
-
lastResponse = await fetch(
|
|
2370
|
+
lastResponse = await fetch(chunksetUrl, {
|
|
2224
2371
|
method: "PUT",
|
|
2225
2372
|
headers: {
|
|
2226
2373
|
"Content-Type": "application/octet-stream",
|
|
2227
2374
|
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
2228
|
-
...authHeaders
|
|
2375
|
+
...authHeaders,
|
|
2376
|
+
[INCLUSION_PROOF_HEADER]: params.inclusionProof
|
|
2229
2377
|
},
|
|
2230
|
-
body:
|
|
2378
|
+
body: params.chunksetData,
|
|
2379
|
+
signal: params.signal
|
|
2231
2380
|
});
|
|
2232
2381
|
lastError = void 0;
|
|
2233
2382
|
} catch (error) {
|
|
2383
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2384
|
+
throw error;
|
|
2385
|
+
}
|
|
2234
2386
|
lastError = error;
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2387
|
+
consecutive429s = 0;
|
|
2388
|
+
nonBusyRetries++;
|
|
2389
|
+
if (nonBusyRetries < maxNonBusyRetries) {
|
|
2390
|
+
const delay2 = 2 ** nonBusyRetries * 100;
|
|
2391
|
+
await sleep(delay2);
|
|
2238
2392
|
continue;
|
|
2239
2393
|
}
|
|
2240
2394
|
break;
|
|
2241
2395
|
}
|
|
2242
|
-
if (lastResponse.ok)
|
|
2396
|
+
if (lastResponse.ok) {
|
|
2397
|
+
const json = await lastResponse.json();
|
|
2398
|
+
const spAcks = json.spAcks?.map((ack) => ({
|
|
2399
|
+
slot: ack.slot,
|
|
2400
|
+
signature: Uint8Array.from(
|
|
2401
|
+
atob(ack.signature),
|
|
2402
|
+
(c) => c.charCodeAt(0)
|
|
2403
|
+
)
|
|
2404
|
+
}));
|
|
2405
|
+
return {
|
|
2406
|
+
success: json.success,
|
|
2407
|
+
acksReceived: json.acksReceived,
|
|
2408
|
+
spAcks
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
if (lastResponse.status === 429) {
|
|
2412
|
+
consecutive429s++;
|
|
2413
|
+
const baseDelay = 500;
|
|
2414
|
+
const jitter = Math.random() * 200;
|
|
2415
|
+
const delay2 = Math.min(
|
|
2416
|
+
3e4,
|
|
2417
|
+
2 ** consecutive429s * baseDelay + jitter
|
|
2418
|
+
);
|
|
2419
|
+
await sleep(delay2);
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
consecutive429s = 0;
|
|
2243
2423
|
if (!isRetryableStatus(lastResponse.status)) {
|
|
2244
2424
|
break;
|
|
2245
2425
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2426
|
+
nonBusyRetries++;
|
|
2427
|
+
if (nonBusyRetries >= maxNonBusyRetries) {
|
|
2428
|
+
break;
|
|
2249
2429
|
}
|
|
2430
|
+
const delay = 2 ** nonBusyRetries * 100;
|
|
2431
|
+
await sleep(delay);
|
|
2250
2432
|
}
|
|
2251
2433
|
if (lastError !== void 0) {
|
|
2252
2434
|
const errorCode = getErrorCode(lastError);
|
|
2253
2435
|
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2254
2436
|
throw new Error(
|
|
2255
|
-
`Failed to upload
|
|
2437
|
+
`Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. ${errorCode ? `(${errorCode}) ` : ""}Last error: ${errorMessage}`,
|
|
2256
2438
|
{ cause: lastError }
|
|
2257
2439
|
);
|
|
2258
2440
|
}
|
|
2259
2441
|
const errorBody = await lastResponse?.text().catch(() => "");
|
|
2260
2442
|
throw new Error(
|
|
2261
|
-
`Failed to upload
|
|
2443
|
+
`Failed to upload chunkset ${params.chunksetIndex} after ${attempts} attempt${attempts === 1 ? "" : "s"}. status: ${lastResponse?.status}, body: ${errorBody}`
|
|
2262
2444
|
);
|
|
2263
2445
|
}
|
|
2264
2446
|
/**
|
|
2265
|
-
* Uploads blob data to the Shelby RPC node
|
|
2266
|
-
* This method authenticates using challenge-response and
|
|
2447
|
+
* Uploads blob data to the Shelby RPC node using the v2 chunkset API.
|
|
2448
|
+
* This method authenticates using challenge-response and uploads chunksets
|
|
2449
|
+
* directly to storage providers via the RPC's worker pool.
|
|
2450
|
+
*
|
|
2451
|
+
* This method:
|
|
2452
|
+
* - Sends raw chunkset data directly to the RPC for erasure encoding
|
|
2453
|
+
* - Requires pre-computed blob commitments to generate inclusion proofs
|
|
2454
|
+
* - Does not support resume (each chunkset is idempotent on the SP side)
|
|
2267
2455
|
*
|
|
2268
2456
|
* @param params.account - The Aptos Account (with signing capability) that owns the blob.
|
|
2269
|
-
* @param params.
|
|
2457
|
+
* @param params.uid - The blob's on-chain UID (from `BlobRegisteredEvent` at registration).
|
|
2270
2458
|
* @param params.blobData - The raw blob data as a Uint8Array or ReadableStream.
|
|
2459
|
+
* @param params.commitments - Pre-computed blob commitments (from generateCommitments).
|
|
2271
2460
|
* @param params.totalBytes - Total byte length. Required for streams; optional for Uint8Array.
|
|
2461
|
+
* @param params.chunksetConcurrency - Number of chunksets to upload in parallel. Defaults to 4.
|
|
2272
2462
|
* @param params.onProgress - Optional callback for upload progress.
|
|
2463
|
+
* @param params.signal - Optional AbortSignal for cancellation. When aborted, in-flight
|
|
2464
|
+
* HTTP requests are cancelled and an AbortError is thrown.
|
|
2273
2465
|
*
|
|
2274
2466
|
* @example
|
|
2275
2467
|
* ```typescript
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2468
|
+
* // First, generate commitments for the blob
|
|
2469
|
+
* const commitments = await generateCommitments(provider, fileData);
|
|
2470
|
+
*
|
|
2471
|
+
* // Register the blob on chain, then read its UID from the register tx's
|
|
2472
|
+
* // BlobRegisteredEvent (see ShelbyBlobClient.registeredBlobUids).
|
|
2473
|
+
*
|
|
2474
|
+
* // Upload using chunkset API
|
|
2475
|
+
* await rpcClient.putBlobChunksets({
|
|
2476
|
+
* account: myAccount,
|
|
2477
|
+
* uid: blobUid,
|
|
2279
2478
|
* blobData: fileData,
|
|
2280
|
-
*
|
|
2479
|
+
* commitments,
|
|
2480
|
+
* chunksetConcurrency: 8,
|
|
2281
2481
|
* });
|
|
2282
2482
|
* ```
|
|
2283
2483
|
*/
|
|
2284
|
-
async
|
|
2285
|
-
BlobNameSchema.parse(params.blobName);
|
|
2484
|
+
async putBlobChunksets(params) {
|
|
2286
2485
|
let totalBytes;
|
|
2287
2486
|
if (params.blobData instanceof Uint8Array) {
|
|
2288
2487
|
totalBytes = params.totalBytes ?? params.blobData.length;
|
|
@@ -2300,110 +2499,146 @@ var ShelbyRPCClient = class {
|
|
|
2300
2499
|
totalBytes = params.totalBytes;
|
|
2301
2500
|
}
|
|
2302
2501
|
validateTotalBytes(totalBytes);
|
|
2303
|
-
const
|
|
2502
|
+
const chunksetConcurrency = params.chunksetConcurrency ?? 4;
|
|
2503
|
+
const totalChunksets = params.commitments.chunkset_commitments.length;
|
|
2504
|
+
const rawDataSizeFromCommitments = params.commitments.raw_data_size;
|
|
2505
|
+
if (rawDataSizeFromCommitments !== totalBytes) {
|
|
2506
|
+
throw new Error(
|
|
2507
|
+
`Data size mismatch: commitments were generated for ${rawDataSizeFromCommitments} bytes, but totalBytes=${totalBytes}. This will cause inclusion proof verification to fail.`
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
const chunksetRoots = params.commitments.chunkset_commitments.map(
|
|
2511
|
+
(c) => Hex6.fromHexString(c.chunkset_root)
|
|
2512
|
+
);
|
|
2304
2513
|
const { challenge } = await this.getChallenge(
|
|
2305
2514
|
params.account.accountAddress
|
|
2306
2515
|
);
|
|
2307
2516
|
const signFn = this.#signChallengeOverride ?? this.signChallenge.bind(this);
|
|
2308
2517
|
const auth = signFn(params.account, challenge);
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
uploadId = existingUpload.uploadId;
|
|
2319
|
-
completedPartsSet = new Set(existingUpload.completedParts);
|
|
2320
|
-
totalParts = existingUpload.nParts;
|
|
2321
|
-
if (existingUpload.partSize !== partSize) {
|
|
2322
|
-
throw new Error(
|
|
2323
|
-
`Cannot resume upload: part size mismatch. Existing upload uses ${existingUpload.partSize} bytes, but ${partSize} was requested.`
|
|
2324
|
-
);
|
|
2325
|
-
}
|
|
2326
|
-
} else {
|
|
2327
|
-
const startResponse = await fetch(
|
|
2328
|
-
buildRequestUrl("/v1/multipart-uploads", this.baseUrl),
|
|
2329
|
-
{
|
|
2330
|
-
method: "POST",
|
|
2331
|
-
headers: {
|
|
2332
|
-
"Content-Type": "application/json",
|
|
2333
|
-
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {},
|
|
2334
|
-
...authHeaders
|
|
2335
|
-
},
|
|
2336
|
-
body: JSON.stringify({
|
|
2337
|
-
rawAccount: params.account.accountAddress.toString(),
|
|
2338
|
-
rawBlobName: params.blobName,
|
|
2339
|
-
rawPartSize: partSize
|
|
2340
|
-
})
|
|
2341
|
-
}
|
|
2518
|
+
const firstChunkset = params.commitments.chunkset_commitments[0];
|
|
2519
|
+
if (!firstChunkset) {
|
|
2520
|
+
throw new Error("Commitments must have at least one chunkset");
|
|
2521
|
+
}
|
|
2522
|
+
const erasure_n = firstChunkset.chunk_commitments.length;
|
|
2523
|
+
const matchingEntry = findErasureSchemeByErasureN(erasure_n);
|
|
2524
|
+
if (!matchingEntry) {
|
|
2525
|
+
throw new Error(
|
|
2526
|
+
`Unknown erasure coding scheme with erasure_n=${erasure_n}`
|
|
2342
2527
|
);
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2528
|
+
}
|
|
2529
|
+
const [schemeKey, matchingParams] = matchingEntry;
|
|
2530
|
+
const erasure_k = matchingParams.erasure_k;
|
|
2531
|
+
const { chunkSizeBytes } = ERASURE_CODE_AND_CHUNK_MAPPING[schemeKey];
|
|
2532
|
+
const chunksetSize = erasure_k * chunkSizeBytes;
|
|
2533
|
+
const expectedChunksetCount = totalBytes === 0 ? 1 : Math.ceil(totalBytes / chunksetSize);
|
|
2534
|
+
if (expectedChunksetCount !== totalChunksets) {
|
|
2535
|
+
throw new Error(
|
|
2536
|
+
`Chunkset count mismatch: expected ${expectedChunksetCount} chunksets for ${totalBytes} bytes with chunksetSize=${chunksetSize}, but commitments have ${totalChunksets} chunksets. This suggests the commitments were generated with a different encoding scheme.`
|
|
2351
2537
|
);
|
|
2352
|
-
uploadId = parsed.uploadId;
|
|
2353
|
-
completedPartsSet = /* @__PURE__ */ new Set();
|
|
2354
|
-
totalParts = Math.ceil(totalBytes / partSize);
|
|
2355
2538
|
}
|
|
2356
2539
|
let uploadedBytes = 0;
|
|
2357
|
-
let
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
if (completedPartsSet.has(partIdx)) {
|
|
2364
|
-
uploadedBytes += partData.length;
|
|
2365
|
-
continue;
|
|
2540
|
+
let bytesReadFromSource = 0;
|
|
2541
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
2542
|
+
async function* chunksetsFromBlob() {
|
|
2543
|
+
if (totalBytes === 0) {
|
|
2544
|
+
yield [0, new Uint8Array(chunksetSize)];
|
|
2545
|
+
return;
|
|
2366
2546
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2547
|
+
yield* readInChunks(params.blobData, chunksetSize);
|
|
2548
|
+
}
|
|
2549
|
+
const chunksetIterator = chunksetsFromBlob();
|
|
2550
|
+
const aggregatedAcks = /* @__PURE__ */ new Map();
|
|
2551
|
+
const internalAbort = new AbortController();
|
|
2552
|
+
if (params.signal?.aborted) {
|
|
2553
|
+
internalAbort.abort();
|
|
2554
|
+
} else if (params.signal) {
|
|
2555
|
+
params.signal.addEventListener("abort", () => internalAbort.abort(), {
|
|
2556
|
+
once: true
|
|
2376
2557
|
});
|
|
2377
2558
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
...authHeaders
|
|
2559
|
+
let iteratorDone = false;
|
|
2560
|
+
let firstError = null;
|
|
2561
|
+
while (!iteratorDone || inFlight.size > 0) {
|
|
2562
|
+
if (internalAbort.signal.aborted) {
|
|
2563
|
+
throw firstError ?? new DOMException("Upload aborted", "AbortError");
|
|
2564
|
+
}
|
|
2565
|
+
while (inFlight.size < chunksetConcurrency && !iteratorDone) {
|
|
2566
|
+
const { value, done } = await chunksetIterator.next();
|
|
2567
|
+
if (done) {
|
|
2568
|
+
iteratorDone = true;
|
|
2569
|
+
break;
|
|
2570
|
+
}
|
|
2571
|
+
const [chunksetIdx, chunksetData] = value;
|
|
2572
|
+
bytesReadFromSource += totalBytes === 0 ? 0 : chunksetData.byteLength;
|
|
2573
|
+
const expectedCommitment = params.commitments.chunkset_commitments[chunksetIdx];
|
|
2574
|
+
if (!expectedCommitment) {
|
|
2575
|
+
throw new Error(
|
|
2576
|
+
`Chunkset index ${chunksetIdx} out of range. Commitments only have ${totalChunksets} chunksets. This suggests a chunkset size mismatch between commitment generation and upload.`
|
|
2577
|
+
);
|
|
2398
2578
|
}
|
|
2579
|
+
const proofSiblings = await generateChunksetInclusionProof(
|
|
2580
|
+
chunksetRoots,
|
|
2581
|
+
chunksetIdx
|
|
2582
|
+
);
|
|
2583
|
+
const inclusionProof = encodeInclusionProof(proofSiblings);
|
|
2584
|
+
const chunksetDataCopy = new Uint8Array(chunksetData);
|
|
2585
|
+
const chunksetBytes = chunksetDataCopy.length;
|
|
2586
|
+
const uploadPromise = (async () => {
|
|
2587
|
+
const result = await this.#uploadChunkset({
|
|
2588
|
+
account: params.account.accountAddress.toString(),
|
|
2589
|
+
uid: params.uid,
|
|
2590
|
+
chunksetIndex: chunksetIdx,
|
|
2591
|
+
inclusionProof,
|
|
2592
|
+
chunksetData: chunksetDataCopy,
|
|
2593
|
+
auth,
|
|
2594
|
+
signal: internalAbort.signal
|
|
2595
|
+
});
|
|
2596
|
+
const chunksetSpAcks = result.spAcks?.map((ack) => ({
|
|
2597
|
+
slot: ack.slot,
|
|
2598
|
+
signature: ack.signature
|
|
2599
|
+
}));
|
|
2600
|
+
if (chunksetSpAcks) {
|
|
2601
|
+
for (const ack of chunksetSpAcks) {
|
|
2602
|
+
aggregatedAcks.set(ack.slot, ack.signature);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
uploadedBytes += chunksetBytes;
|
|
2606
|
+
params.onProgress?.({
|
|
2607
|
+
phase: "uploading",
|
|
2608
|
+
chunksetIdx,
|
|
2609
|
+
totalChunksets,
|
|
2610
|
+
chunksetBytes,
|
|
2611
|
+
uploadedBytes,
|
|
2612
|
+
totalBytes,
|
|
2613
|
+
acksReceived: result.acksReceived,
|
|
2614
|
+
spAcks: chunksetSpAcks
|
|
2615
|
+
});
|
|
2616
|
+
})().catch((err) => {
|
|
2617
|
+
if (!firstError) {
|
|
2618
|
+
firstError = err;
|
|
2619
|
+
internalAbort.abort();
|
|
2620
|
+
}
|
|
2621
|
+
}).finally(() => {
|
|
2622
|
+
inFlight.delete(uploadPromise);
|
|
2623
|
+
});
|
|
2624
|
+
inFlight.add(uploadPromise);
|
|
2399
2625
|
}
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2626
|
+
if (inFlight.size > 0) {
|
|
2627
|
+
await Promise.race(inFlight);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (firstError) {
|
|
2631
|
+
throw firstError;
|
|
2632
|
+
}
|
|
2633
|
+
if (bytesReadFromSource !== totalBytes) {
|
|
2403
2634
|
throw new Error(
|
|
2404
|
-
`
|
|
2635
|
+
`Data source produced ${bytesReadFromSource} bytes, but expected ${totalBytes} bytes. The stream may have ended prematurely, resulting in an incomplete upload.`
|
|
2405
2636
|
);
|
|
2406
2637
|
}
|
|
2638
|
+
const spAcks = Array.from(
|
|
2639
|
+
aggregatedAcks.entries()
|
|
2640
|
+
).map(([slot, signature]) => ({ slot, signature }));
|
|
2641
|
+
return { spAcks };
|
|
2407
2642
|
}
|
|
2408
2643
|
/**
|
|
2409
2644
|
* Downloads a blob from the Shelby RPC node.
|
|
@@ -2655,6 +2890,59 @@ var ShelbyClient = class {
|
|
|
2655
2890
|
}
|
|
2656
2891
|
return this._provider;
|
|
2657
2892
|
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Build orderless transaction options (a random replay-protection nonce) for
|
|
2895
|
+
* the per-blob commit transactions. batchUpload finalizes blobs
|
|
2896
|
+
* concurrently, so without orderless replay protection the parallel
|
|
2897
|
+
* same-account submissions collide on the sequence number ("transaction
|
|
2898
|
+
* already in mempool with a different payload"). The nonce makes each commit
|
|
2899
|
+
* independent regardless of the client's `orderless` config flag.
|
|
2900
|
+
*/
|
|
2901
|
+
orderlessCommitOptions() {
|
|
2902
|
+
return {
|
|
2903
|
+
build: {
|
|
2904
|
+
options: {
|
|
2905
|
+
replayProtectionNonce: crypto.getRandomValues(new Uint32Array(1))[0]
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
/**
|
|
2911
|
+
* Resolve the on-chain UID assigned to `blobName` from a committed register
|
|
2912
|
+
* transaction. `register_blob` only publishes the uid -> name mapping via
|
|
2913
|
+
* `BlobRegisteredEvent` (a pending blob is not yet in the `objects` map, so it
|
|
2914
|
+
* cannot be read back by name), so the upload flow must parse it here before
|
|
2915
|
+
* uploading bytes or committing.
|
|
2916
|
+
*/
|
|
2917
|
+
uidFromRegisterTx(tx, account, blobName) {
|
|
2918
|
+
const events = "events" in tx ? tx.events : [];
|
|
2919
|
+
const objectName = createBlobKey({ account, blobName });
|
|
2920
|
+
const match = ShelbyBlobClient.registeredBlobUids(
|
|
2921
|
+
events,
|
|
2922
|
+
this.coordination.deployer
|
|
2923
|
+
).find((registered) => registered.objectName === objectName);
|
|
2924
|
+
if (match === void 0) {
|
|
2925
|
+
throw new Error(
|
|
2926
|
+
`No BlobRegisteredEvent for '${blobName}' in register transaction ${tx.hash}`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
return match.uid;
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2932
|
+
* Await a blob-registration transaction, rephrasing on-chain location
|
|
2933
|
+
* failures into a human-readable message.
|
|
2934
|
+
*/
|
|
2935
|
+
async waitForRegistration(transactionHash) {
|
|
2936
|
+
try {
|
|
2937
|
+
return await this.coordination.aptos.waitForTransaction({
|
|
2938
|
+
transactionHash
|
|
2939
|
+
});
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2942
|
+
const described = describeLocationError(message);
|
|
2943
|
+
throw described ? new Error(described, { cause: error }) : error;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2658
2946
|
/**
|
|
2659
2947
|
* The base URL for the Shelby RPC node.
|
|
2660
2948
|
*/
|
|
@@ -2668,8 +2956,8 @@ var ShelbyClient = class {
|
|
|
2668
2956
|
*
|
|
2669
2957
|
* Note: This method accepts only `Uint8Array` and buffers the entire blob in memory.
|
|
2670
2958
|
* For streaming uploads of large files (e.g. >2 GiB), orchestrate the steps manually
|
|
2671
|
-
* using `generateCommitments()`, `coordination.registerBlob()`,
|
|
2672
|
-
* with a `ReadableStream`.
|
|
2959
|
+
* using `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
|
|
2960
|
+
* and `coordination.commitObject()` with a `ReadableStream`.
|
|
2673
2961
|
*
|
|
2674
2962
|
* @param params.blobData - The raw data to upload as a Uint8Array.
|
|
2675
2963
|
* @param params.signer - The account that signs and pays for the transaction.
|
|
@@ -2692,34 +2980,57 @@ var ShelbyClient = class {
|
|
|
2692
2980
|
* ```
|
|
2693
2981
|
*/
|
|
2694
2982
|
async upload(params) {
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2697
|
-
|
|
2983
|
+
const provider = await this.getProvider();
|
|
2984
|
+
const blobCommitments = await generateCommitments(
|
|
2985
|
+
provider,
|
|
2986
|
+
params.blobData
|
|
2987
|
+
);
|
|
2988
|
+
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
|
|
2989
|
+
account: params.signer,
|
|
2990
|
+
blobName: params.blobName,
|
|
2991
|
+
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
2992
|
+
size: params.blobData.length,
|
|
2993
|
+
expirationMicros: params.expirationMicros,
|
|
2994
|
+
config: provider.config,
|
|
2995
|
+
options: params.options
|
|
2996
|
+
});
|
|
2997
|
+
const registerTx = await this.waitForRegistration(
|
|
2998
|
+
pendingRegisterBlobTransaction.hash
|
|
2999
|
+
);
|
|
3000
|
+
const uid = this.uidFromRegisterTx(
|
|
3001
|
+
registerTx,
|
|
3002
|
+
params.signer.accountAddress,
|
|
3003
|
+
params.blobName
|
|
3004
|
+
);
|
|
3005
|
+
const { spAcks } = await this.rpc.putBlobChunksets({
|
|
3006
|
+
account: params.signer,
|
|
3007
|
+
uid,
|
|
3008
|
+
blobData: params.blobData,
|
|
3009
|
+
commitments: blobCommitments,
|
|
3010
|
+
totalBytes: params.blobData.length
|
|
2698
3011
|
});
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
provider,
|
|
2703
|
-
params.blobData
|
|
3012
|
+
const requiredAcks = requiredAckCount(provider.config.erasure_n);
|
|
3013
|
+
if (spAcks.length < requiredAcks) {
|
|
3014
|
+
throw new Error(
|
|
3015
|
+
`Insufficient storage provider acknowledgements for '${params.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
|
|
2704
3016
|
);
|
|
2705
|
-
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.registerBlob({
|
|
2706
|
-
account: params.signer,
|
|
2707
|
-
blobName: params.blobName,
|
|
2708
|
-
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
2709
|
-
size: params.blobData.length,
|
|
2710
|
-
expirationMicros: params.expirationMicros,
|
|
2711
|
-
config: provider.config,
|
|
2712
|
-
options: params.options
|
|
2713
|
-
});
|
|
2714
|
-
await this.coordination.aptos.waitForTransaction({
|
|
2715
|
-
transactionHash: pendingRegisterBlobTransaction.hash
|
|
2716
|
-
});
|
|
2717
3017
|
}
|
|
2718
|
-
await this.
|
|
3018
|
+
const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
|
|
2719
3019
|
account: params.signer,
|
|
3020
|
+
uid,
|
|
2720
3021
|
blobName: params.blobName,
|
|
2721
|
-
|
|
3022
|
+
overwrite: true,
|
|
3023
|
+
storageProviderAcks: spAcks,
|
|
3024
|
+
options: this.orderlessCommitOptions()
|
|
3025
|
+
});
|
|
3026
|
+
const confirmedTx = await this.coordination.aptos.waitForTransaction({
|
|
3027
|
+
transactionHash: pendingCommitTransaction.hash
|
|
2722
3028
|
});
|
|
3029
|
+
if (!confirmedTx.success) {
|
|
3030
|
+
throw new Error(
|
|
3031
|
+
`commit_object tx failed for '${params.blobName}': ${confirmedTx.vm_status}`
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
2723
3034
|
}
|
|
2724
3035
|
/**
|
|
2725
3036
|
* Uploads a batch of blobs to the Shelby network.
|
|
@@ -2728,8 +3039,8 @@ var ShelbyClient = class {
|
|
|
2728
3039
|
*
|
|
2729
3040
|
* Note: This method accepts only `Uint8Array` and buffers each blob in memory.
|
|
2730
3041
|
* For streaming uploads of large files, orchestrate the steps manually using
|
|
2731
|
-
* `generateCommitments()`, `coordination.registerBlob()`,
|
|
2732
|
-
* with a `ReadableStream`.
|
|
3042
|
+
* `generateCommitments()`, `coordination.registerBlob()`, `rpc.putBlobChunksets()`,
|
|
3043
|
+
* and `coordination.commitObject()` with a `ReadableStream`.
|
|
2733
3044
|
*
|
|
2734
3045
|
* @param params.blobs - The blobs to upload.
|
|
2735
3046
|
* @param params.blobs.blobData - The raw data to upload as a Uint8Array.
|
|
@@ -2754,59 +3065,77 @@ var ShelbyClient = class {
|
|
|
2754
3065
|
* ```
|
|
2755
3066
|
*/
|
|
2756
3067
|
async batchUpload(params) {
|
|
2757
|
-
const
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3068
|
+
const provider = await this.getProvider();
|
|
3069
|
+
const prepared = await Promise.all(
|
|
3070
|
+
params.blobs.map(async (blob) => ({
|
|
3071
|
+
blob,
|
|
3072
|
+
commitments: await generateCommitments(provider, blob.blobData)
|
|
3073
|
+
}))
|
|
3074
|
+
);
|
|
3075
|
+
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
|
|
3076
|
+
account: params.signer,
|
|
3077
|
+
expirationMicros: params.expirationMicros,
|
|
3078
|
+
blobs: prepared.map((p) => ({
|
|
3079
|
+
blobName: p.blob.blobName,
|
|
3080
|
+
blobSize: p.blob.blobData.length,
|
|
3081
|
+
blobMerkleRoot: p.commitments.blob_merkle_root
|
|
3082
|
+
})),
|
|
3083
|
+
config: provider.config,
|
|
3084
|
+
options: params.options
|
|
2768
3085
|
});
|
|
2769
|
-
const
|
|
2770
|
-
|
|
2771
|
-
(existingBlob) => existingBlob.name === createBlobKey({
|
|
2772
|
-
account: params.signer.accountAddress,
|
|
2773
|
-
blobName: blob.blobName
|
|
2774
|
-
})
|
|
2775
|
-
)
|
|
3086
|
+
const registerTx = await this.waitForRegistration(
|
|
3087
|
+
pendingRegisterBlobTransaction.hash
|
|
2776
3088
|
);
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
const { transaction: pendingRegisterBlobTransaction } = await this.coordination.batchRegisterBlobs({
|
|
2785
|
-
account: params.signer,
|
|
2786
|
-
expirationMicros: params.expirationMicros,
|
|
2787
|
-
blobs: blobsToRegister.map((blob, index) => ({
|
|
2788
|
-
blobName: blob.blobName,
|
|
2789
|
-
blobSize: blob.blobData.length,
|
|
2790
|
-
blobMerkleRoot: blobCommitments[index].blob_merkle_root
|
|
2791
|
-
})),
|
|
2792
|
-
config: provider.config,
|
|
2793
|
-
options: params.options
|
|
2794
|
-
});
|
|
2795
|
-
await this.coordination.aptos.waitForTransaction({
|
|
2796
|
-
transactionHash: pendingRegisterBlobTransaction.hash,
|
|
2797
|
-
options: { waitForIndexer: true }
|
|
2798
|
-
});
|
|
2799
|
-
}
|
|
3089
|
+
const uidByObjectName = new Map(
|
|
3090
|
+
ShelbyBlobClient.registeredBlobUids(
|
|
3091
|
+
"events" in registerTx ? registerTx.events : [],
|
|
3092
|
+
this.coordination.deployer
|
|
3093
|
+
).map((registered) => [registered.objectName, registered.uid])
|
|
3094
|
+
);
|
|
3095
|
+
const requiredAcks = requiredAckCount(provider.config.erasure_n);
|
|
2800
3096
|
const limit = pLimit(3);
|
|
2801
3097
|
await Promise.all(
|
|
2802
|
-
|
|
2803
|
-
(
|
|
2804
|
-
|
|
3098
|
+
prepared.map(
|
|
3099
|
+
(p) => limit(async () => {
|
|
3100
|
+
const objectName = createBlobKey({
|
|
3101
|
+
account: params.signer.accountAddress,
|
|
3102
|
+
blobName: p.blob.blobName
|
|
3103
|
+
});
|
|
3104
|
+
const uid = uidByObjectName.get(objectName);
|
|
3105
|
+
if (uid === void 0) {
|
|
3106
|
+
throw new Error(
|
|
3107
|
+
`No BlobRegisteredEvent for '${p.blob.blobName}' in batch register transaction ${registerTx.hash}`
|
|
3108
|
+
);
|
|
3109
|
+
}
|
|
3110
|
+
const { spAcks } = await this.rpc.putBlobChunksets({
|
|
3111
|
+
account: params.signer,
|
|
3112
|
+
uid,
|
|
3113
|
+
blobData: p.blob.blobData,
|
|
3114
|
+
commitments: p.commitments,
|
|
3115
|
+
totalBytes: p.blob.blobData.length
|
|
3116
|
+
});
|
|
3117
|
+
if (spAcks.length < requiredAcks) {
|
|
3118
|
+
throw new Error(
|
|
3119
|
+
`Insufficient storage provider acknowledgements for '${p.blob.blobName}': got ${spAcks.length}, need ${requiredAcks} (erasure_d) to finalize on chain`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
const { transaction: pendingCommitTransaction } = await this.coordination.commitObject({
|
|
2805
3123
|
account: params.signer,
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3124
|
+
uid,
|
|
3125
|
+
blobName: p.blob.blobName,
|
|
3126
|
+
overwrite: true,
|
|
3127
|
+
storageProviderAcks: spAcks,
|
|
3128
|
+
options: this.orderlessCommitOptions()
|
|
3129
|
+
});
|
|
3130
|
+
const confirmedTx = await this.coordination.aptos.waitForTransaction({
|
|
3131
|
+
transactionHash: pendingCommitTransaction.hash
|
|
3132
|
+
});
|
|
3133
|
+
if (!confirmedTx.success) {
|
|
3134
|
+
throw new Error(
|
|
3135
|
+
`commit_object tx failed for '${p.blob.blobName}': ${confirmedTx.vm_status}`
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
})
|
|
2810
3139
|
)
|
|
2811
3140
|
);
|
|
2812
3141
|
}
|
|
@@ -3052,6 +3381,58 @@ var ShelbyMicropaymentChannelClient = class _ShelbyMicropaymentChannelClient {
|
|
|
3052
3381
|
]
|
|
3053
3382
|
};
|
|
3054
3383
|
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Withdraws funds from a micropayment channel as the sender.
|
|
3386
|
+
*
|
|
3387
|
+
* @param params.sender - The account that is withdrawing funds.
|
|
3388
|
+
* @param params.receiver - The account the channel is configured to send funds to.
|
|
3389
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3390
|
+
* @param params.options - Optional transaction generation options.
|
|
3391
|
+
*
|
|
3392
|
+
* @returns An object containing the pending transaction.
|
|
3393
|
+
*
|
|
3394
|
+
* @example
|
|
3395
|
+
* ```typescript
|
|
3396
|
+
* const { transaction } = await client.senderWithdraw({
|
|
3397
|
+
* sender: sender,
|
|
3398
|
+
* receiver: receiver,
|
|
3399
|
+
* fungibleAssetAddress: fungibleAssetAddress,
|
|
3400
|
+
* });
|
|
3401
|
+
* ```
|
|
3402
|
+
*/
|
|
3403
|
+
async senderWithdraw(params) {
|
|
3404
|
+
const transaction = await this.aptos.transaction.build.simple({
|
|
3405
|
+
options: params.options,
|
|
3406
|
+
data: _ShelbyMicropaymentChannelClient.createSenderWithdrawPayload({
|
|
3407
|
+
deployer: this.deployer,
|
|
3408
|
+
receiver: params.receiver,
|
|
3409
|
+
fungibleAssetAddress: params.fungibleAssetAddress
|
|
3410
|
+
}),
|
|
3411
|
+
sender: params.sender.accountAddress
|
|
3412
|
+
});
|
|
3413
|
+
return {
|
|
3414
|
+
transaction: await this.aptos.signAndSubmitTransaction({
|
|
3415
|
+
signer: params.sender,
|
|
3416
|
+
transaction
|
|
3417
|
+
})
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Creates a static payload for the sender_withdraw Move function.
|
|
3422
|
+
* This is a helper method for constructing the transaction payload without signing.
|
|
3423
|
+
*
|
|
3424
|
+
* @param params.deployer - Optional deployer account address. Defaults to MICROPAYMENTS_DEPLOYER.
|
|
3425
|
+
* @param params.receiver - The account address of the receiver.
|
|
3426
|
+
* @param params.fungibleAssetAddress - The account address of the fungible asset.
|
|
3427
|
+
*
|
|
3428
|
+
* @returns An Aptos transaction payload data object for the sender_withdraw Move function.
|
|
3429
|
+
*/
|
|
3430
|
+
static createSenderWithdrawPayload(params) {
|
|
3431
|
+
return {
|
|
3432
|
+
function: `${(params.deployer ?? MICROPAYMENTS_DEPLOYER).toString()}::micropayments::sender_withdraw`,
|
|
3433
|
+
functionArguments: [params.receiver, params.fungibleAssetAddress]
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3055
3436
|
/**
|
|
3056
3437
|
* Creates a micropayment that can be sent to a receiver.
|
|
3057
3438
|
* The sender signs a WithdrawApproval message that authorizes the receiver
|
|
@@ -3424,6 +3805,7 @@ var ShelbyPlacementGroupClient = class {
|
|
|
3424
3805
|
};
|
|
3425
3806
|
export {
|
|
3426
3807
|
MissingTransactionSubmitterError,
|
|
3808
|
+
ObjectCommitRejectedError,
|
|
3427
3809
|
ShelbyBlobClient,
|
|
3428
3810
|
ShelbyClient,
|
|
3429
3811
|
ShelbyMetadataClient,
|