gethstorage-sdk 0.0.0-stage → 5.0.2

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/README.md CHANGED
@@ -1,3 +1,208 @@
1
- # Temporary Holding Version
1
+ # gethstorage-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This SDK aims to standardize the interaction between applications and the EthStorage network to achieve reliable and
4
+ efficient data management functionality.
5
+
6
+ This SDK provides the `FlatDirectory` class, a higher-level data management tool that provides methods for uploading
7
+ and downloading data of arbitrary size. Uploads are performed by sending the data through calldata.
8
+
9
+ Click here to view [spec](./spec.md).
10
+
11
+ # Installation
12
+
13
+ Install the SDK using [npm](https://www.npmjs.com/package/gethstorage-sdk):
14
+
15
+ ```bash
16
+ $ npm install gethstorage-sdk
17
+ ```
18
+
19
+ # Example Usage
20
+
21
+ ## FlatDirectory
22
+
23
+ ### create
24
+
25
+ Create a `FlatDirectory` instance.
26
+
27
+ ```js
28
+ const { FlatDirectory } = require("gethstorage-sdk");
29
+
30
+ const rpc = "https://rpc.beta.testnet.l2.quarkchain.io:8545";
31
+ const ethStorageRpc = "https://rpc.beta.testnet.l2.ethstorage.io:9596";
32
+ const privateKey = "0xabcd...";
33
+
34
+ const flatDirectory = await FlatDirectory.create({
35
+ rpc: rpc,
36
+ ethStorageRpc: ethStorageRpc,
37
+ privateKey: privateKey,
38
+ });
39
+ ```
40
+
41
+ If `FlatDirectory` has been deployed, it can be set through the `address` field.
42
+
43
+ ```js
44
+ const address = "0x987..."; // FlatDirectory address
45
+ const flatDirectory = await FlatDirectory.create({
46
+ rpc: rpc,
47
+ ethStorageRpc: ethStorageRpc,
48
+ privateKey: privateKey,
49
+ address: address,
50
+ });
51
+ ```
52
+
53
+ If you only need the `download` function, you can create a `FlatDirectory` using the following pattern.
54
+
55
+ ```js
56
+ const address = "0x987..."; // FlatDirectory address
57
+ const flatDirectory = await FlatDirectory.create({
58
+ ethStorageRpc: 'https://rpc.beta.testnet.l2.ethstorage.io:9596',
59
+ address: address
60
+ });
61
+ ```
62
+
63
+ ### deploy
64
+
65
+ Deploy the implementation
66
+ contract [FlatDirectory](https://github.com/ethstorage/evm-large-storage)
67
+ for [EIP-5018](https://eips.ethereum.org/EIPS/eip-5018) standard.
68
+
69
+ ```js
70
+ const contractAddress = await flatDirectory.deploy();
71
+ console.log(`FlatDirectory address is ${contractAddress}.`);
72
+ ```
73
+
74
+ ### upload
75
+
76
+ Upload `buffer | file` to the FlatDirectory. The content is sliced into chunks and each chunk is sent through calldata.
77
+
78
+ ```js
79
+ const callback = {
80
+ onProgress: function (progress, count, isChange) {
81
+ console.log(`Uploaded ${progress} of ${count} chunks`);
82
+ },
83
+ onFail: function (err) {
84
+ console.log(err);
85
+ },
86
+ onFinish: function (totalUploadChunks, totalUploadSize, totalCost) {
87
+ console.log(`Total upload chunk count is ${totalUploadChunks}, size is ${totalUploadSize}, total cost is ${totalCost}`);
88
+ }
89
+ };
90
+
91
+ const request = {
92
+ key: "test.txt",
93
+ content: Buffer.from("big data"),
94
+ callback: callback
95
+ }
96
+ await flatDirectory.upload(request);
97
+ ```
98
+
99
+ If you want to use `file`, it can be divided into browser and Node.js.
100
+
101
+ Browser
102
+ ```javascript
103
+ // <input id='fileToUpload' />
104
+ const file = document.getElementById('fileToUpload').files[0];
105
+
106
+ const request = {
107
+ key: "test.txt",
108
+ content: file,
109
+ callback: callback
110
+ }
111
+ await flatDirectory.upload(request);
112
+ ```
113
+
114
+ Node.js
115
+ ```javascript
116
+ const {NodeFile} = require("gethstorage-sdk/file");
117
+ const file = new NodeFile("/usr/download/test.jpg");
118
+
119
+ const request = {
120
+ key: "test.txt",
121
+ content: file,
122
+ callback: callback
123
+ }
124
+ await flatDirectory.upload(request);
125
+ ```
126
+
127
+ ### download
128
+
129
+ Monitor the download progress by passing in a callback function.
130
+
131
+ ```js
132
+ const key = "test.txt";
133
+ await flatDirectory.download(key, {
134
+ onProgress: function (progress, count, chunk) {
135
+ console.log(`Download ${progress} of ${count} chunks, this chunk is ${Buffer.from(chunk).toString()}`);
136
+ },
137
+ onFail: function (error) {
138
+ console.error("Error download data:", error);
139
+ },
140
+ onFinish: function () {
141
+ console.log("Download success.");
142
+ }
143
+ });
144
+ ```
145
+
146
+ ### estimateCost
147
+
148
+ Estimate gas costs before uploading.
149
+
150
+ ```js
151
+ const request = {
152
+ key: "example1.txt",
153
+ content: Buffer.from("large data to upload")
154
+ }
155
+
156
+ const cost = await flatDirectory.estimateCost(request);
157
+ console.log(`Gas Cost: ${cost.gasCost}, Storage Cost: ${cost.storageCost}`);
158
+ ```
159
+
160
+ Use `file`.
161
+
162
+ Browser
163
+ ```javascript
164
+ // <input id='fileToUpload' />
165
+ const file = document.getElementById('fileToUpload').files[0];
166
+
167
+ const request = {
168
+ key: "example1.txt",
169
+ content: file
170
+ }
171
+ const cost = await flatDirectory.estimateCost(request);
172
+ console.log(`Gas Cost: ${cost.gasCost}, Storage Cost: ${cost.storageCost}`);
173
+ ```
174
+
175
+ Node.js
176
+ ```javascript
177
+ const {NodeFile} = require("gethstorage-sdk/file");
178
+ const file = new NodeFile("/usr/download/test.jpg");
179
+
180
+ const request = {
181
+ key: "example1.txt",
182
+ content: file
183
+ }
184
+ const cost = await flatDirectory.estimateCost(request);
185
+ ```
186
+
187
+
188
+ ### Chunk options
189
+
190
+ Uploads are sliced into calldata chunks. Both `estimateCost` and `upload`
191
+ requests accept two optional fields:
192
+
193
+ | Field | Type | Default | Description |
194
+ |----------------|--------|---------|----------------------------------------------------------|
195
+ | `maxChunkSize` | number | `64` | data size of each chunk, in KB |
196
+ | `maxChunks` | number | `100` | maximum number of chunks requested in a single call |
197
+
198
+ ```js
199
+ const request = {
200
+ key: "example1.txt",
201
+ content: Buffer.from("large data to upload"),
202
+ maxChunkSize: 32, // 32 KB per chunk
203
+ maxChunks: 120 // at most 120 chunks per request
204
+ }
205
+ await flatDirectory.upload({ ...request, callback });
206
+ ```
207
+
208
+ `fetchHashes(keys, maxChunks)` accepts the same chunk limit as its second argument.
@@ -0,0 +1,288 @@
1
+ import { ethers, Contract } from 'ethers';
2
+
3
+ declare const FlatDirectoryAbi: readonly string[];
4
+
5
+ declare const ETHSTORAGE_MAPPING: Record<number, string>;
6
+ /**
7
+ * On-chain max chunk size passed to the FlatDirectory constructor (uint32 maxChunkSize).
8
+ */
9
+ declare const CONTRACT_MAX_CHUNK_SIZE: number;
10
+ /**
11
+ * eth_call consumes gas, so we need to estimate the maximum number of chunks based on a 30 million gas limit.
12
+ * Additionally, we need to reserve a portion of the gas for the cost of the request parameters (which can vary dynamically).
13
+ */
14
+ declare const MAX_CHUNKS: number;
15
+ /**
16
+ * Calldata overhead of a single chunk, i.e. the difference between the chunk size
17
+ * configured by the user and the actual data size carried by the transaction.
18
+ */
19
+ declare const CALLDATA_CHUNK_OVERHEAD: number;
20
+ /**
21
+ * Default size of each data chunk when uploading by calldata, in KB.
22
+ */
23
+ declare const DEFAULT_MAX_CHUNK_SIZE_KB: number;
24
+ /**
25
+ * Default priority fee per gas (tip) for upload transactions, in wei.
26
+ * Used when the caller does not provide maxPriorityFeePerGas.
27
+ */
28
+ declare const DEFAULT_PRIORITY_FEE_PER_GAS: bigint;
29
+ /**
30
+ * Fraction applied to the latest-block base fee when deriving the default
31
+ * maxFeePerGas: baseFee * NUMERATOR / DENOMINATOR + priority fee.
32
+ * 11/10 = 1.1x, leaving headroom for one ~10% base-fee increase between blocks.
33
+ */
34
+ declare const BASE_FEE_MULTIPLIER_NUMERATOR: bigint;
35
+ declare const BASE_FEE_MULTIPLIER_DENOMINATOR: bigint;
36
+ /**
37
+ * Gas fee model classification by chain.
38
+ *
39
+ * LEGACY_GAS_CHAIN_IDS: chains without an EIP-1559 base fee (legacy single-gasPrice
40
+ * model, price paid in full). BNB Smart Chain mainnet (56) and testnet (97).
41
+ *
42
+ * AVALANCHE_GAS_CHAIN_IDS: Avalanche C-Chain mainnet (43114) and Fuji testnet
43
+ * (43113). EIP-1559 like the default class, but the protocol enforces a minimum
44
+ * base fee of 25 nAVAX.
45
+ * All other chains use the default EIP-1559 mechanism (tip + 1.1x base fee).
46
+ */
47
+ declare const LEGACY_GAS_CHAIN_IDS: ReadonlySet<number>;
48
+ declare const AVALANCHE_GAS_CHAIN_IDS: ReadonlySet<number>;
49
+ /**
50
+ * Minimum base fee per gas on Avalanche C-Chain (25 nAVAX), in wei.
51
+ * Used as a floor when deriving the default maxFeePerGas.
52
+ */
53
+ declare const AVALANCHE_MIN_BASE_FEE_PER_GAS: bigint;
54
+ /**
55
+ * Resolve the chunk size (in bytes) used to slice the content.
56
+ * @param maxChunkSizeKB - Chunk size in KB, falls back to DEFAULT_MAX_CHUNK_SIZE_KB when not set.
57
+ */
58
+ declare function toChunkSizeBytes(maxChunkSizeKB?: number): number;
59
+
60
+ declare const FlatDirectoryBytecode: string;
61
+
62
+ interface NodeFile {
63
+ isNodeJs: boolean;
64
+ size: number;
65
+ start: number;
66
+ end: number;
67
+ slice(start: number, end: number): NodeFile;
68
+ arrayBuffer(): Promise<ArrayBuffer>;
69
+ text(): Promise<string>;
70
+ }
71
+ type BufferLike = Uint8Array;
72
+ type FileLike = File | NodeFile;
73
+ type ContentLike = BufferLike | FileLike;
74
+ interface SDKConfig {
75
+ rpc?: string;
76
+ privateKey?: string;
77
+ ethStorageRpc?: string;
78
+ address?: string;
79
+ }
80
+ interface UploadCallback {
81
+ onProgress: (currentChunk: number, totalChunks: number, isChange: boolean) => void;
82
+ onFail: (error: Error) => void;
83
+ onFinish: (totalUploadChunks: number, totalUploadSize: number, totalCost: bigint) => void;
84
+ onTransactionSent?: (txHash: string, chunkIds: number[] | number) => void;
85
+ }
86
+ interface DownloadCallback {
87
+ onProgress: (currentChunk: number, totalChunks: number, chunkData: Uint8Array) => void;
88
+ onFail: (error: Error) => void;
89
+ onFinish: () => void;
90
+ }
91
+ /**
92
+ * Custom gas fees, in wei.
93
+ * EIP-1559 fields apply to EIP-1559 chains; gasPrice applies to legacy chains
94
+ * (e.g. BNB Chain). Unset fields fall back to chain-class defaults.
95
+ */
96
+ interface GasFeeOptions {
97
+ maxFeePerGas?: bigint;
98
+ maxPriorityFeePerGas?: bigint;
99
+ gasPrice?: bigint;
100
+ }
101
+ /**
102
+ * Concrete fees resolved for a transaction, tagged by fee model.
103
+ * effectiveGasPrice is the expected price actually charged per gas unit:
104
+ * min(maxFeePerGas, baseFee + priorityFee) under EIP-1559, gasPrice on legacy chains.
105
+ */
106
+ type ResolvedGasFees = {
107
+ type: 'eip1559';
108
+ maxFeePerGas: bigint;
109
+ maxPriorityFeePerGas: bigint;
110
+ effectiveGasPrice: bigint;
111
+ } | {
112
+ type: 'legacy';
113
+ gasPrice: bigint;
114
+ effectiveGasPrice: bigint;
115
+ };
116
+ interface EstimateGasRequest {
117
+ key: string;
118
+ content: ContentLike;
119
+ /** Custom max fee per gas, in wei (EIP-1559). */
120
+ maxFeePerGas?: bigint;
121
+ /** Custom priority fee per gas, in wei (EIP-1559). */
122
+ maxPriorityFeePerGas?: bigint;
123
+ /** Custom gas price, in wei (legacy chains without EIP-1559, e.g. BNB Chain). */
124
+ gasPrice?: bigint;
125
+ chunkHashes?: string[];
126
+ /** Size of each data chunk, in KB. Defaults to DEFAULT_MAX_CHUNK_SIZE_KB. */
127
+ maxChunkSize?: number;
128
+ /** Maximum number of chunks requested in a single call. Defaults to MAX_CHUNKS. */
129
+ maxChunks?: number;
130
+ }
131
+ interface UploadRequest extends EstimateGasRequest {
132
+ callback: Partial<UploadCallback>;
133
+ }
134
+ interface CostEstimate {
135
+ storageCost: bigint;
136
+ gasCost: bigint;
137
+ }
138
+ interface TxCost {
139
+ normalGasCost: bigint;
140
+ }
141
+ interface UploadResult {
142
+ txCost: TxCost;
143
+ success: boolean;
144
+ }
145
+ interface FileBatch {
146
+ name: string;
147
+ chunkIds: number[];
148
+ }
149
+ interface UploadDetails {
150
+ fileMode: number;
151
+ oldChunkCount: number;
152
+ cost: bigint;
153
+ }
154
+ interface ChunkCountResult {
155
+ key: string;
156
+ chunkCount: number;
157
+ }
158
+ interface ChunkHashResult {
159
+ name: string;
160
+ chunkId: number;
161
+ hash: string;
162
+ }
163
+
164
+ declare const stringToHex: (s: string) => string;
165
+ declare function getChainId(rpc: string): Promise<number>;
166
+ declare function getContentChunk(content: ContentLike, start: number, end: number): Promise<Uint8Array<ArrayBuffer>>;
167
+ declare function isBuffer(content: ContentLike): content is BufferLike;
168
+ declare function isFile(content: ContentLike): content is FileLike;
169
+ declare function isNodejs(): boolean;
170
+ declare function calcTxCost(receipt: ethers.TransactionReceipt | null): TxCost;
171
+
172
+ /**
173
+ * Generic transaction sender: owns the provider and wallet, and exposes
174
+ * gas price / send / receipt helpers used by the calldata upload path.
175
+ * Transactions are sent strictly one at a time; callers must await
176
+ * confirmation before sending the next transaction.
177
+ */
178
+ declare class TxSender {
179
+ #private;
180
+ constructor(rpc: string, privateKey: string);
181
+ getChainId(): Promise<number>;
182
+ getFeeData(): Promise<ethers.FeeData>;
183
+ /**
184
+ * Base fee of the latest block (EIP-1559), or null on legacy chains.
185
+ */
186
+ getBaseFee(): Promise<bigint | null>;
187
+ /**
188
+ * Resolve concrete gas fees for a transaction, by chain fee model:
189
+ * - Legacy chains (no base fee, e.g. BNB Chain): a single gasPrice paid in full,
190
+ * taken from the node unless overridden via custom.gasPrice.
191
+ * - Avalanche C-Chain: EIP-1559 with a protocol-enforced minimum base fee of
192
+ * 25 nAVAX; the node-reported base fee is clamped to that floor.
193
+ * - Other (EIP-1559) chains: tip defaults to 100 wei,
194
+ * maxFeePerGas defaults to 1.1 * baseFee + tip (baseFee from the latest block).
195
+ * Caller-provided fields take precedence. effectiveGasPrice is the expected
196
+ * price actually charged per gas unit.
197
+ */
198
+ resolveGasFees(custom?: GasFeeOptions): Promise<ResolvedGasFees>;
199
+ waitForReceipt(hash: string): Promise<ethers.TransactionReceipt | null>;
200
+ getTransactionResult(hash: string): Promise<UploadResult>;
201
+ /**
202
+ * Sends a single transaction. No locking or manual nonce management:
203
+ * uploads are strictly sequential, so the wallet's automatic nonce
204
+ * handling is always up to date.
205
+ */
206
+ sendTx(tx: ethers.TransactionRequest): Promise<ethers.TransactionResponse>;
207
+ }
208
+
209
+ declare function getUploadInfo(contract: Contract, hexName: string): Promise<UploadDetails>;
210
+ declare function getChunkCounts(contract: Contract, batch: string[]): Promise<ChunkCountResult[]>;
211
+ declare function getChunkHashes(contract: Contract, batch: FileBatch[]): Promise<ChunkHashResult[]>;
212
+
213
+ declare function stableRetry<T>(fn: (...args: unknown[]) => Promise<T>, ...args: unknown[]): Promise<T>;
214
+
215
+ type index_TxSender = TxSender;
216
+ declare const index_TxSender: typeof TxSender;
217
+ declare const index_calcTxCost: typeof calcTxCost;
218
+ declare const index_getChainId: typeof getChainId;
219
+ declare const index_getChunkCounts: typeof getChunkCounts;
220
+ declare const index_getChunkHashes: typeof getChunkHashes;
221
+ declare const index_getContentChunk: typeof getContentChunk;
222
+ declare const index_getUploadInfo: typeof getUploadInfo;
223
+ declare const index_isBuffer: typeof isBuffer;
224
+ declare const index_isFile: typeof isFile;
225
+ declare const index_isNodejs: typeof isNodejs;
226
+ declare const index_stableRetry: typeof stableRetry;
227
+ declare const index_stringToHex: typeof stringToHex;
228
+ declare namespace index {
229
+ export { index_TxSender as TxSender, index_calcTxCost as calcTxCost, index_getChainId as getChainId, index_getChunkCounts as getChunkCounts, index_getChunkHashes as getChunkHashes, index_getContentChunk as getContentChunk, index_getUploadInfo as getUploadInfo, index_isBuffer as isBuffer, index_isFile as isFile, index_isNodejs as isNodejs, index_stableRetry as stableRetry, index_stringToHex as stringToHex };
230
+ }
231
+
232
+ declare class FlatDirectory {
233
+ #private;
234
+ /**
235
+ * Private constructor - use create() factory method
236
+ */
237
+ private constructor();
238
+ static create(config: SDKConfig): Promise<FlatDirectory>;
239
+ /**
240
+ * Enable or disable logging
241
+ * @param value - true to enable logging, false to disable
242
+ */
243
+ setLogEnabled(value: boolean): void;
244
+ /**
245
+ * Deploy a new FlatDirectory contract
246
+ * @returns Contract address if successful, null otherwise
247
+ */
248
+ deploy(): Promise<string | null>;
249
+ /**
250
+ * Set a file as the default file
251
+ * @param filename - Name of the file to set as default
252
+ * @returns true if successful, false otherwise
253
+ */
254
+ setDefault(filename: string): Promise<boolean>;
255
+ /**
256
+ * Remove a file from the contract
257
+ * @param key - File key to remove
258
+ * @returns true if successful, false otherwise
259
+ */
260
+ remove(key: string): Promise<boolean>;
261
+ /**
262
+ * Download a file from the contract
263
+ * @param key - File key to download
264
+ * @param cb - Download callback object
265
+ */
266
+ download(key: string, cb?: DownloadCallback): Promise<void>;
267
+ /**
268
+ * Fetch chunk hashes for multiple files
269
+ * @param keys - Array of file keys
270
+ * @param maxChunks - Maximum number of chunks requested in a single call, defaults to MAX_CHUNKS
271
+ * @returns Object mapping file keys to their chunk hashes
272
+ */
273
+ fetchHashes(keys: string[], maxChunks?: number): Promise<Record<string, string[]>>;
274
+ /**
275
+ * Estimate cost for uploading a file
276
+ * @param request - Upload request details
277
+ * @returns Cost estimation object
278
+ */
279
+ estimateCost(request: EstimateGasRequest): Promise<CostEstimate>;
280
+ /**
281
+ * Upload a file to the contract
282
+ * @param request - Upload request details
283
+ * @throws Error if upload fails
284
+ */
285
+ upload(request: UploadRequest): Promise<void>;
286
+ }
287
+
288
+ export { AVALANCHE_GAS_CHAIN_IDS, AVALANCHE_MIN_BASE_FEE_PER_GAS, BASE_FEE_MULTIPLIER_DENOMINATOR, BASE_FEE_MULTIPLIER_NUMERATOR, type BufferLike, CALLDATA_CHUNK_OVERHEAD, CONTRACT_MAX_CHUNK_SIZE, type ChunkCountResult, type ChunkHashResult, type ContentLike, type CostEstimate, DEFAULT_MAX_CHUNK_SIZE_KB, DEFAULT_PRIORITY_FEE_PER_GAS, type DownloadCallback, ETHSTORAGE_MAPPING, type EstimateGasRequest, type FileBatch, type FileLike, FlatDirectory, FlatDirectoryAbi, FlatDirectoryBytecode, type GasFeeOptions, LEGACY_GAS_CHAIN_IDS, MAX_CHUNKS, type NodeFile, type ResolvedGasFees, type SDKConfig, type TxCost, type UploadCallback, type UploadDetails, type UploadRequest, type UploadResult, toChunkSizeBytes, index as utils };