@fizzyflow/endless-vector 0.0.8 → 0.0.10

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/EndlessVector.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import EndlessVectorHistory from './EndlessVectorHistory.js';
2
2
  import EndlessVectorArchive from './EndlessVectorArchive.js';
3
+ import EndlessVectorItem from './EndlessVectorItem.js';
4
+ import EndlessVectorWalrus from './EndlessVectorWalrus.js';
5
+ import EndlessVectorSeal from './EndlessVectorSeal.js';
3
6
  import { Transaction } from '@mysten/sui/transactions';
4
7
  import { bcs } from '@mysten/sui/bcs';
5
8
  import ids from './ids.js';
6
9
 
10
+
11
+
7
12
  /**
8
- * @typedef {import('@mysten/sui/client').SuiClient} SuiClient
9
- * @typedef {import('@mysten/sui/client').GetObjectParams} GetObjectParams
10
- * @typedef {import('@mysten/sui/client').GetDynamicFieldsParams} GetDynamicFieldsParams
13
+ * @typedef {import('@mysten/sui/grpc').SuiGrpcClient} SuiGrpcClient
11
14
  * @typedef {import('@mysten/sui/transactions').TransactionResult} TransactionResult
12
15
  */
13
16
 
@@ -27,14 +30,21 @@ export default class EndlessVector {
27
30
  /**
28
31
  * Creates a new EndlessVector instance.
29
32
  * @param {Object} params - Configuration parameters
30
- * @param {SuiClient} [params.suiClient] - Sui client instance for blockchain interactions
33
+ * @param {SuiGrpcClient} [params.suiClient] - gRPC client instance for blockchain interactions
31
34
  * @param {string} [params.id] - ID or address of the EndlessVector on the Sui blockchain
32
- *
33
- * @param {?string} [params.packageId] - Adds write capability if provided, ID of the Move package containing the EndlessVector module or 'mainnet', 'testnet' to use known IDs
34
- * @param {?CustomSignAndExecuteTransactionFunction} [params.signAndExecuteTransaction] - Adds write capability if provided, function should accept Sui transaction, sign and submit it to the blockchain and return its digest
35
+ * @param {?string} [params.packageId] - Adds write capability if provided; ID of the Move package or 'mainnet'/'testnet' to use known IDs
36
+ * @param {?CustomSignAndExecuteTransactionFunction} [params.signAndExecuteTransaction] - Adds write capability if provided; must accept a Transaction and return its digest
37
+ * @param {?import('@mysten/walrus').WalrusClient} [params.walrusClient] - Walrus client for blob reads and writes (preferred)
38
+ * @param {?string} [params.publisherUrl] - Walrus publisher HTTP URL for blob uploads (fallback if no walrusClient)
39
+ * @param {?string} [params.aggregatorUrl] - Walrus aggregator HTTP URL for blob reads (fallback if no walrusClient)
40
+ * @param {?string} [params.senderAddress] - Sui address of the transaction sender, required for Walrus blob writes
41
+ * @param {?import('@mysten/seal').SealClient} [params.sealClient] - SealClient for Seal encryption/decryption
42
+ * @param {?import('@mysten/seal').SessionKey} [params.sessionKey] - Pre-built SessionKey for Seal operations
43
+ * @param {?any} [params.signer] - Keypair/signer to mint a SessionKey when needed
44
+ * @param {?number} [params.sealTtlMin=5] - SessionKey TTL in minutes (default: 5)
35
45
  */
36
46
  constructor(params = {}) {
37
- /** @type {SuiClient} */
47
+ /** @type {SuiGrpcClient} */
38
48
  this.suiClient = params.suiClient;
39
49
  /** @type {string} */
40
50
  this.id = params.id;
@@ -54,7 +64,7 @@ export default class EndlessVector {
54
64
  /** @type {boolean} */
55
65
  this.firstItemIsFromPreviousHistory = false; // from EndlessVector.fields.
56
66
 
57
- /** @type {Array<Uint8Array>} */
67
+ /** @type {Array<EndlessVectorItem>} */
58
68
  this._items = []; // items in current EndlessVector object,
59
69
 
60
70
  /** @type {string} */
@@ -85,6 +95,40 @@ export default class EndlessVector {
85
95
 
86
96
  /** @type {?CustomSignAndExecuteTransactionFunction} */
87
97
  this._signAndExecuteTransaction = params.signAndExecuteTransaction || null;
98
+
99
+ /**
100
+ * EndlessVectorWalrus instance for Walrus blob read/write.
101
+ * Null on plain EndlessVector; set to `this` by EndlessVectorWalrus constructor.
102
+ * @type {?EndlessVectorWalrus}
103
+ */
104
+ this.walrus = new EndlessVectorWalrus({ ...params, endlessVector: this });
105
+
106
+ /**
107
+ * EndlessVectorSeal companion. Always present; only "enabled" when sealClient is supplied.
108
+ * @type {EndlessVectorSeal}
109
+ */
110
+ this.seal = new EndlessVectorSeal({ ...params, endlessVector: this });
111
+
112
+ /**
113
+ * Raw seal_encrypted_key bytes loaded from the on-chain object during initialize().
114
+ * `null` for unsealed vectors. SDK callers usually don't need to read this directly.
115
+ * @type {?Uint8Array}
116
+ */
117
+ this.sealEncryptedKey = null;
118
+ }
119
+
120
+ get packageId() {
121
+ return this._packageId;
122
+ }
123
+
124
+ static getPackageId(network) {
125
+ const entry = ids[network];
126
+ return entry ? entry.packageId : null;
127
+ }
128
+
129
+ async isEncrypted() {
130
+ await this.initialize();
131
+ return !!this.sealEncryptedKey;
88
132
  }
89
133
 
90
134
  /**
@@ -92,7 +136,7 @@ export default class EndlessVector {
92
136
  * Creates a new EndlessVector object via the Move contract and returns a wrapped instance.
93
137
  *
94
138
  * @param {Object} params - Configuration parameters
95
- * @param {SuiClient} params.suiClient - Sui client instance for blockchain interactions
139
+ * @param {SuiGrpcClient} params.suiClient - Sui gRPC client instance for blockchain interactions
96
140
  * @param {string} params.packageId - ID of the Move package containing the EndlessVector module
97
141
  * @param {CustomSignAndExecuteTransactionFunction} params.signAndExecuteTransaction - Function to sign and execute transactions
98
142
  * @param {?Uint8Array|Uint8Array[]} [params.array] - Optional Uint8Array to initialize the vector with as the first item to get with .at(0)
@@ -124,20 +168,31 @@ export default class EndlessVector {
124
168
  }
125
169
 
126
170
  // Create transaction to call empty_entry
171
+ // Sealed mode requires the vector id (only known after the first tx) to scope the
172
+ // Seal-encrypted AES key. We thus:
173
+ // tx 1 — create an empty vector
174
+ // tx 2 — set_seal_encrypted_key
175
+ // tx 3+ — push initial `array` items (encrypted) via the normal push() path
176
+ // Unsealed mode keeps the single-tx fast path below unchanged.
177
+ const sealRequested = !!params.sealClient;
178
+ const sealItemsToPush = sealRequested ? array : null;
179
+ const useArray = sealRequested ? null : array;
180
+
127
181
  const tx = new Transaction();
128
182
 
129
183
  if (gasCoin) {
130
184
  tx.setGasPayment([gasCoin]);
131
185
  }
132
186
 
133
- if (array && array.length) {
187
+ if (useArray && useArray.length) {
188
+ const array = useArray;
134
189
  if ((array instanceof Uint8Array)) {
135
190
  // single chunk
136
191
  const vectorInput = await EndlessVector.getCreateTransactionAndReturnVectorInput({
137
192
  packageId: normalizedPackageId,
138
193
  }, array, tx);
139
194
  tx.moveCall({
140
- target: `${normalizedPackageId}::endless_vector::transfer_to_sender`,
195
+ target: `${normalizedPackageId}::endless_walrus::transfer_to_sender`,
141
196
  arguments: [vectorInput],
142
197
  });
143
198
  } else if (array[0] && (array[0] instanceof Uint8Array)) {
@@ -149,7 +204,7 @@ export default class EndlessVector {
149
204
  EndlessVector.composePushTransaction(normalizedPackageId, vectorInput, array[i], tx);
150
205
  }
151
206
  tx.moveCall({
152
- target: `${normalizedPackageId}::endless_vector::transfer_to_sender`,
207
+ target: `${normalizedPackageId}::endless_walrus::transfer_to_sender`,
153
208
  arguments: [vectorInput],
154
209
  });
155
210
  } else {
@@ -157,48 +212,76 @@ export default class EndlessVector {
157
212
  }
158
213
  } else {
159
214
  tx.moveCall({
160
- target: `${normalizedPackageId}::endless_vector::empty_entry`,
215
+ target: `${normalizedPackageId}::endless_walrus::empty_entry`,
161
216
  arguments: [],
162
217
  });
163
218
  }
164
219
 
165
- // Execute transaction
166
- const digest = await signAndExecuteTransaction(tx);
167
-
168
- // Wait for transaction to complete
169
- const transactionBlockResponse = await suiClient.waitForTransaction({
170
- digest: digest,
220
+ // Execute transaction — callback may return a digest string OR a rich tx-data object
221
+ const execResult = await signAndExecuteTransaction(tx);
222
+ const digest = typeof execResult === 'string'
223
+ ? execResult
224
+ : execResult?.digest ?? execResult?.data?.digest;
225
+ if (!digest) throw new Error('signAndExecuteTransaction returned no digest');
226
+
227
+ // create() always needs objectTypes to find the new EndlessVector, which the
228
+ // callback's effects don't include — so we always do a waitForTransaction here.
229
+ const txResult = await suiClient.waitForTransaction({
230
+ digest,
231
+ include: { effects: true, objectTypes: true },
171
232
  timeout: options.timeout || 30000,
172
- pollIntervalMs: options.pollIntervalMs || 1000,
173
- options: {
174
- showEffects: true,
175
- showObjectChanges: true,
176
- },
233
+ pollInterval: options.pollIntervalMs || 200,
177
234
  });
178
-
179
- if (transactionBlockResponse?.effects?.status?.status !== 'success') {
235
+ const txData = txResult.Transaction ?? txResult.FailedTransaction;
236
+ if (!txData?.status?.success) {
180
237
  throw new Error('Transaction failed to create EndlessVector');
181
238
  }
182
239
 
183
- // Find the created EndlessVector object
184
- const objectChanges = transactionBlockResponse.objectChanges || [];
185
- const createdVector = objectChanges.find(
186
- change => change.type === 'created' &&
187
- change.objectType &&
188
- change.objectType.includes('endless_vector::EndlessVector')
240
+ // Find the created EndlessVector object via objectTypes map (gRPC) or fallback objectChanges
241
+ const objectTypes = txData.objectTypes ?? {};
242
+ const createdVector = txData.effects?.changedObjects?.find(
243
+ c => c.idOperation === 'Created' &&
244
+ (objectTypes[c.objectId] ?? '').includes('endless_walrus::EndlessWalrusVector')
189
245
  );
190
246
 
191
- if (!createdVector || !createdVector.objectId) {
247
+ if (!createdVector?.objectId) {
192
248
  throw new Error('Failed to find created EndlessVector object in transaction response');
193
249
  }
194
250
 
195
- // Create and return the EndlessVector instance
196
- return new EndlessVector({
251
+ // Create the EndlessVector instance
252
+ const ev = new EndlessVector({
253
+ ...params,
197
254
  suiClient,
198
255
  id: createdVector.objectId,
199
256
  packageId: normalizedPackageId,
200
257
  signAndExecuteTransaction,
201
258
  });
259
+
260
+ // Seal layer: generate AES key, Seal-wrap it scoped to the new vector id, attach on-chain.
261
+ // Subsequent push() calls will encrypt every item automatically.
262
+ if (sealRequested) {
263
+ const aesKey = EndlessVectorSeal.generateAesKey();
264
+ ev.seal.setAesKey(aesKey);
265
+ const wrappedKey = await ev.seal.wrapAesKey(aesKey);
266
+
267
+ const setKeyTx = new Transaction();
268
+ setKeyTx.moveCall({
269
+ target: `${normalizedPackageId}::endless_walrus::set_seal_encrypted_key`,
270
+ arguments: [
271
+ setKeyTx.object(ev.id),
272
+ setKeyTx.pure(bcs.vector(bcs.u8()).serialize(wrappedKey)),
273
+ ],
274
+ });
275
+ await ev.executeAndWaitForTransaction(setKeyTx, options);
276
+ ev.sealEncryptedKey = wrappedKey;
277
+
278
+ // Now push any initial items — push() will encrypt them transparently.
279
+ if (sealItemsToPush && sealItemsToPush.length) {
280
+ await ev.push(sealItemsToPush, options);
281
+ }
282
+ }
283
+
284
+ return ev;
202
285
  }
203
286
 
204
287
  /**
@@ -248,7 +331,7 @@ export default class EndlessVector {
248
331
  }
249
332
 
250
333
  const vectorInput = tx.moveCall({
251
- target: `${normalizedPackageId}::endless_vector::empty`,
334
+ target: `${normalizedPackageId}::endless_walrus::empty`,
252
335
  arguments: [],
253
336
  });
254
337
 
@@ -263,6 +346,55 @@ export default class EndlessVector {
263
346
  return !!(this._packageId && this._signAndExecuteTransaction);
264
347
  }
265
348
 
349
+ /**
350
+ * Executes a transaction via the configured `_signAndExecuteTransaction` callback and
351
+ * resolves to the tx data containing effects.
352
+ *
353
+ * If the callback already returns an object with `.effects` (e.g. callers that use
354
+ * `WaitForLocalExecution` and pass the full response through), we trust that result
355
+ * and skip an extra `waitForTransaction` poll round-trip. Otherwise we treat the
356
+ * return value as a digest string and poll until the tx lands.
357
+ *
358
+ * @param {Transaction} tx
359
+ * @param {Object} [params]
360
+ * @param {number} [params.timeout=30000]
361
+ * @param {number} [params.pollIntervalMs=200]
362
+ * @param {Object} [params.include={ effects: true }]
363
+ * @returns {Promise<Object>} Resolves to the tx data ({ digest, effects, ... }).
364
+ * @throws {Error} If the callback returns no digest or the tx failed.
365
+ */
366
+ async executeAndWaitForTransaction(tx, params = {}) {
367
+ const result = await this._signAndExecuteTransaction(tx);
368
+
369
+ // Rich return: tx data containing effects is already present. Accept either the raw
370
+ // tx data ({ digest, effects, status }) or a wrapper exposing effects via `.data`
371
+ // (e.g. suidouble's SuiTransaction). In either case, skip the extra polling round-trip.
372
+ if (result && typeof result === 'object') {
373
+ const txData = result.effects ? result : (result.data?.effects ? result.data : null);
374
+ if (txData) {
375
+ if (txData.status && txData.status.success === false) {
376
+ throw new Error('Transaction failed');
377
+ }
378
+ return txData;
379
+ }
380
+ }
381
+
382
+ const digest = typeof result === 'string' ? result : result?.digest;
383
+ if (!digest) throw new Error('signAndExecuteTransaction returned no digest');
384
+
385
+ const txResult = await this.suiClient.waitForTransaction({
386
+ digest,
387
+ include: params.include ?? { effects: true },
388
+ timeout: params.timeout || 30000,
389
+ pollInterval: params.pollIntervalMs || 200,
390
+ });
391
+ const txData = txResult.Transaction ?? txResult.FailedTransaction;
392
+ if (!txData?.status?.success) {
393
+ throw new Error('Transaction failed');
394
+ }
395
+ return txData;
396
+ }
397
+
266
398
  /**
267
399
  * Attach move calls to transaction, to push item into endlessvector, handling large arrays by chunking them.
268
400
  * This static method can be used to compose transactions for any existing EndlessVector instance:
@@ -278,13 +410,13 @@ export default class EndlessVector {
278
410
  * @param {Uint8Array} arr - The byte array to push to the vector
279
411
  * @param {Transaction} tx - The transaction object to append the move calls to
280
412
  * @returns {Transaction} The transaction object with the push operations added
281
- * @throws {Error} If the array is larger than 120KB (10 * 12KB)
413
+ * @throws {Error} If the array is larger than 120KB (10 * 12KB) — callers should use push() which falls back to walrus.pushBlob() automatically
282
414
  */
283
415
  static composePushTransaction(packageId, vectorInput, arr, tx) {
284
416
  const maxArgLength = 12 * 1024;
285
417
  if (arr.length <= maxArgLength) {
286
418
  tx.moveCall({
287
- target: `${packageId}::endless_vector::push_back`,
419
+ target: `${packageId}::endless_walrus::push_back_bytes`,
288
420
  arguments: [
289
421
  vectorInput,
290
422
  tx.pure(bcs.vector(bcs.u8()).serialize(arr)),
@@ -308,7 +440,7 @@ export default class EndlessVector {
308
440
  args.push(tx.pure(bcs.vector(bcs.u8()).serialize(chunks[i])));
309
441
  }
310
442
  tx.moveCall({
311
- target: `${packageId}::endless_vector::compose_and_push_back`,
443
+ target: `${packageId}::endless_walrus::compose_and_push_back`,
312
444
  arguments: args,
313
445
  });
314
446
  } else {
@@ -358,26 +490,38 @@ export default class EndlessVector {
358
490
  * @param {Uint8Array|Uint8Array[]} arr - Byte array or array of byte arrays to push
359
491
  * @param {?Object} params - Configuration parameters
360
492
  * @param {?Number} [params.timeout] - wait for transaction confirmation timeout in ms, default 30000
361
- * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 1000
493
+ * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 200
362
494
  * @return {Promise<boolean>} True if the push was successful
363
495
  */
364
496
  async push(arr, params = {}) {
365
497
  if (!this.isWritable) {
366
498
  throw new Error('EndlessVector is not writable, packageId and signAndExecuteTransaction are required');
367
499
  }
368
- const tx = this.getPushTransaction(arr);
369
- const digest = await this._signAndExecuteTransaction(tx);
370
500
 
371
- const transactionBlockResponse = await this.suiClient.waitForTransaction({
372
- digest: digest,
373
- timeout: params.timeout || 30000,
374
- pollIntervalMs: params.pollIntervalMs || 1000,
375
- options: { showEffects: true },
376
- });
377
- if (transactionBlockResponse?.effects?.status?.status !== 'success') {
378
- throw new Error('Transaction failed');
501
+ // When the vector is sealed, transparently encrypt every item before it goes on-chain.
502
+ // Encryption adds 28 bytes (12B nonce + 16B tag), so it can shift items across the
503
+ // 120 KB walrus threshold — encrypt first, then route.
504
+ if (this.sealEncryptedKey && this.seal?.isEnabled) {
505
+ if (arr instanceof Uint8Array) {
506
+ arr = await this.seal.encryptItem(arr);
507
+ } else if (Array.isArray(arr)) {
508
+ const encrypted = [];
509
+ for (const item of arr) encrypted.push(await this.seal.encryptItem(item));
510
+ arr = encrypted;
511
+ }
512
+ }
513
+
514
+ const maxBytesPerTx = 10 * 12 * 1024;
515
+ if (arr instanceof Uint8Array && arr.length > maxBytesPerTx) {
516
+ if (!this.walrus) {
517
+ throw new Error('Array too large for a single tx and no Walrus client configured');
518
+ }
519
+ return !!(await this.walrus.pushBlob(arr, params));
379
520
  }
380
521
 
522
+ const tx = this.getPushTransaction(arr);
523
+ await this.executeAndWaitForTransaction(tx, params);
524
+
381
525
  this.reInitialize(); // force re-initialization to load new data
382
526
 
383
527
  return true;
@@ -414,7 +558,7 @@ export default class EndlessVector {
414
558
  const objectRefs = otherIds.map(id => tx.object(id));
415
559
 
416
560
  tx.moveCall({
417
- target: `${this._packageId}::endless_vector::append`,
561
+ target: `${this._packageId}::endless_walrus::append`,
418
562
  arguments: [
419
563
  tx.object(this.id),
420
564
  tx.makeMoveVec({ elements: objectRefs }),
@@ -425,7 +569,7 @@ export default class EndlessVector {
425
569
  const otherEndlessVectorId = (typeof other === 'object' && other.id) ? other.id : other;
426
570
 
427
571
  tx.moveCall({
428
- target: `${this._packageId}::endless_vector::concat`,
572
+ target: `${this._packageId}::endless_walrus::concat`,
429
573
  arguments: [
430
574
  tx.object(this.id),
431
575
  tx.object(otherEndlessVectorId),
@@ -444,7 +588,7 @@ export default class EndlessVector {
444
588
  * @param {string|EndlessVector|Array<string|EndlessVector>} other - The ID of the EndlessVector to concatenate, an EndlessVector instance, or an array of IDs/instances to append
445
589
  * @param {?Object} params - Configuration parameters
446
590
  * @param {?Number} [params.timeout] - wait for transaction confirmation timeout in ms, default 30000
447
- * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 1000
591
+ * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 200
448
592
  * @return {Promise<boolean>} True if the concat was successful
449
593
  * @throws {Error} If the instance is not writable, if the transaction fails, or if any vector has archived items
450
594
  */
@@ -453,20 +597,112 @@ export default class EndlessVector {
453
597
  throw new Error('EndlessVector is not writable, packageId and signAndExecuteTransaction are required');
454
598
  }
455
599
 
600
+ // Sealed vectors hold items encrypted under per-vector AES keys; merging two would
601
+ // require re-encrypting every item under one key. Refuse early.
602
+ if (this.sealEncryptedKey) {
603
+ throw new Error('concat is not supported on sealed vectors');
604
+ }
605
+
456
606
  const tx = this.getConcatTransaction(other);
457
- const digest = await this._signAndExecuteTransaction(tx);
607
+ await this.executeAndWaitForTransaction(tx, params);
458
608
 
459
- const transactionBlockResponse = await this.suiClient.waitForTransaction({
460
- digest: digest,
461
- timeout: params.timeout || 30000,
462
- pollIntervalMs: params.pollIntervalMs || 1000,
463
- options: { showEffects: true },
609
+ this.reInitialize(); // force re-initialization to load new data
610
+
611
+ return true;
612
+ }
613
+
614
+ /**
615
+ * Creates a transaction to archive the current history of this EndlessVector.
616
+ * Moves all history items into a new archive entry, freeing up history capacity.
617
+ * Note: this method only creates the transaction, it does not sign or execute it.
618
+ *
619
+ * @param {Transaction} [txToAppendTo=null] - Optional transaction to append to
620
+ * @returns {Transaction} The transaction object to be signed and executed
621
+ * @throws {Error} If packageId is not set
622
+ */
623
+ getArchiveTransaction(txToAppendTo = null) {
624
+ if (!this._packageId) {
625
+ throw new Error('packageId is required to compose archive transaction');
626
+ }
627
+
628
+ const tx = txToAppendTo ?? new Transaction();
629
+
630
+ tx.moveCall({
631
+ target: `${this._packageId}::endless_walrus::archive`,
632
+ arguments: [tx.object(this.id)],
464
633
  });
465
- if (transactionBlockResponse?.effects?.status?.status !== 'success') {
466
- throw new Error('Transaction failed');
634
+
635
+ return tx;
636
+ }
637
+
638
+ /**
639
+ * Archives the current history of this EndlessVector, creating and executing the necessary transaction.
640
+ * Moves all history items into a new archive entry to free up history capacity for future pushes.
641
+ * Requires the instance to be writable (packageId and signAndExecuteTransaction must be provided).
642
+ *
643
+ * @param {?Object} params - Configuration parameters
644
+ * @param {?Number} [params.timeout] - wait for transaction confirmation timeout in ms, default 30000
645
+ * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 200
646
+ * @returns {Promise<boolean>} True if the archive was successful
647
+ * @throws {Error} If the instance is not writable or if the transaction fails
648
+ */
649
+ async archive(params = {}) {
650
+ if (!this.isWritable) {
651
+ throw new Error('EndlessVector is not writable, packageId and signAndExecuteTransaction are required');
467
652
  }
468
653
 
469
- this.reInitialize(); // force re-initialization to load new data
654
+ const tx = this.getArchiveTransaction();
655
+ await this.executeAndWaitForTransaction(tx, params);
656
+
657
+ this.reInitialize();
658
+
659
+ return true;
660
+ }
661
+
662
+ /**
663
+ * Creates a transaction to burn the oldest archive entry of this EndlessVector.
664
+ * Burned items are permanently deleted and can no longer be read.
665
+ * Note: this method only creates the transaction, it does not sign or execute it.
666
+ *
667
+ * @param {Transaction} [txToAppendTo=null] - Optional transaction to append to
668
+ * @returns {Transaction} The transaction object to be signed and executed
669
+ * @throws {Error} If packageId is not set
670
+ */
671
+ getBurnArchiveTransaction(txToAppendTo = null) {
672
+ if (!this._packageId) {
673
+ throw new Error('packageId is required to compose burn_archive transaction');
674
+ }
675
+
676
+ const tx = txToAppendTo ?? new Transaction();
677
+
678
+ tx.moveCall({
679
+ target: `${this._packageId}::endless_walrus::burn_archive`,
680
+ arguments: [tx.object(this.id)],
681
+ });
682
+
683
+ return tx;
684
+ }
685
+
686
+ /**
687
+ * Burns the oldest archive entry of this EndlessVector, creating and executing the necessary transaction.
688
+ * Burned items are permanently deleted and can no longer be read.
689
+ * Requires the instance to be writable (packageId and signAndExecuteTransaction must be provided).
690
+ *
691
+ * @param {?Object} params - Configuration parameters
692
+ * @param {?Number} [params.timeout] - wait for transaction confirmation timeout in ms, default 30000
693
+ * @param {?Number} [params.pollIntervalMs] - wait for transaction confirmation poll interval in ms, default 200
694
+ * @returns {Promise<boolean>} True if the burn was successful
695
+ * @throws {Error} If the instance is not writable or if the transaction fails
696
+ */
697
+ async burnArchive(params = {}) {
698
+ if (!this.isWritable) {
699
+ throw new Error('EndlessVector is not writable, packageId and signAndExecuteTransaction are required');
700
+ }
701
+
702
+ const tx = this.getBurnArchiveTransaction();
703
+ await this.executeAndWaitForTransaction(tx, params);
704
+
705
+ this.reInitialize();
470
706
 
471
707
  return true;
472
708
  }
@@ -521,15 +757,8 @@ export default class EndlessVector {
521
757
  this.__initializationPromiseResolver = null;
522
758
  this.__initializationPromise = new Promise((res)=>{ this.__initializationPromiseResolver = res; });
523
759
 
524
- /** @type {GetObjectParams} */
525
- const getObjectParams = {
526
- id: this.id,
527
- options: {
528
- showContent: true,
529
- },
530
- };
531
- const endlessVectorObjectResponse = await this.suiClient.getObject(getObjectParams);
532
- const endlessVectorObject = endlessVectorObjectResponse.data?.content?.fields;
760
+ const { object } = await this.suiClient.getObject({ objectId: this.id, include: { json: true } });
761
+ const endlessVectorObject = object?.json;
533
762
 
534
763
  this.binaryLength = parseInt(endlessVectorObject?.binary_length || 0);
535
764
  this.length = parseInt(endlessVectorObject?.length || 0);
@@ -544,12 +773,17 @@ export default class EndlessVector {
544
773
  this._items = [];
545
774
  if (endlessVectorObject?.items && endlessVectorObject.items.length) {
546
775
  for (const item of endlessVectorObject.items) {
547
- this._items.push(new Uint8Array(item));
776
+ this._items.push(EndlessVectorItem.fromGrpcJson(item, { endlessVector: this }));
548
777
  }
549
778
  }
550
779
 
551
- this.archiveTableId = endlessVectorObject?.archive?.fields?.id?.id;
552
- this.historyTableId = endlessVectorObject?.history?.fields?.id?.id;
780
+ // In gRPC json, Table<K,V> appears as { id: "0x...", size: "..." } — id is a plain string
781
+ this.archiveTableId = endlessVectorObject?.archive?.id;
782
+ this.historyTableId = endlessVectorObject?.history?.id;
783
+
784
+ // seal_encrypted_key is an Option<vector<u8>> on-chain.
785
+ // None → null/undefined; Some(bytes) → base64 string (or array fallback).
786
+ this.sealEncryptedKey = EndlessVector._decodeOptionVectorU8(endlessVectorObject?.seal_encrypted_key);
553
787
 
554
788
  this._isInitialized = true;
555
789
  this.__initializationPromiseResolver();
@@ -581,27 +815,19 @@ export default class EndlessVector {
581
815
  throw new Error('historyTableId is not set');
582
816
  }
583
817
 
584
- /** @type {GetDynamicFieldsParams} */
585
- const getDynamicFieldsParams = {
586
- parentId: this.historyTableId,
587
- options: {
588
- showContent: true,
589
- showType: true,
590
- },
591
- };
592
-
818
+ let cursor = undefined;
593
819
  let resp = null;
594
820
  let haveToLookMore = true;
595
821
 
596
822
  do {
597
- resp = await this.suiClient.getDynamicFields(getDynamicFieldsParams);
598
- if (resp && resp.data && resp.data.length) {
599
- for (const df of resp.data) {
600
- if (df?.objectId) {
601
- const itemHistoryIndex = parseInt(df.name.value);
823
+ resp = await this.suiClient.listDynamicFields({ parentId: this.historyTableId, cursor });
824
+ if (resp?.dynamicFields?.length) {
825
+ for (const df of resp.dynamicFields) {
826
+ if (df.fieldId) {
827
+ const itemHistoryIndex = EndlessVector._decodeBcsU64(df.name.bcs);
602
828
  const endlessVectorHistory = new EndlessVectorHistory({
603
829
  suiClient: this.suiClient,
604
- id: df.objectId,
830
+ id: df.fieldId,
605
831
  index: itemHistoryIndex,
606
832
  endlessVector: this,
607
833
  });
@@ -611,7 +837,7 @@ export default class EndlessVector {
611
837
  }
612
838
  }
613
839
  }
614
- getDynamicFieldsParams.cursor = resp.nextCursor;
840
+ cursor = resp.cursor;
615
841
  }
616
842
  } while (resp?.hasNextPage && haveToLookMore);
617
843
 
@@ -644,27 +870,19 @@ export default class EndlessVector {
644
870
  throw new Error('archiveTableId is not set');
645
871
  }
646
872
 
647
- /** @type {GetDynamicFieldsParams} */
648
- const getDynamicFieldsParams = {
649
- parentId: this.archiveTableId,
650
- options: {
651
- showContent: true,
652
- showType: true,
653
- },
654
- };
655
-
873
+ let cursor = undefined;
656
874
  let resp = null;
657
875
  let haveToLookMore = true;
658
876
 
659
877
  do {
660
- resp = await this.suiClient.getDynamicFields(getDynamicFieldsParams);
661
- if (resp && resp.data && resp.data.length) {
662
- for (const df of resp.data) {
663
- if (df?.objectId) {
664
- const itemArchiveIndex = parseInt(df.name.value);
878
+ resp = await this.suiClient.listDynamicFields({ parentId: this.archiveTableId, cursor });
879
+ if (resp?.dynamicFields?.length) {
880
+ for (const df of resp.dynamicFields) {
881
+ if (df.fieldId) {
882
+ const itemArchiveIndex = EndlessVector._decodeBcsU64(df.name.bcs);
665
883
  const endlessVectorArchive = new EndlessVectorArchive({
666
884
  suiClient: this.suiClient,
667
- id: df.objectId,
885
+ id: df.fieldId,
668
886
  index: itemArchiveIndex,
669
887
  endlessVector: this,
670
888
  });
@@ -674,7 +892,7 @@ export default class EndlessVector {
674
892
  }
675
893
  }
676
894
  }
677
- getDynamicFieldsParams.cursor = resp.nextCursor;
895
+ cursor = resp.cursor;
678
896
  }
679
897
  } while (resp?.hasNextPage && haveToLookMore);
680
898
 
@@ -695,27 +913,24 @@ export default class EndlessVector {
695
913
  */
696
914
  async loadHistoryItemsBunch(historyItems) {
697
915
  const ids = historyItems.map(hi => hi.id);
698
- let results = [];
916
+ let objects = [];
699
917
  try {
700
- results = await this.suiClient.multiGetObjects({
701
- ids: ids,
702
- options: { showContent: true, },
703
- });
918
+ const res = await this.suiClient.getObjects({ objectIds: ids, include: { json: true } });
919
+ objects = res.objects ?? [];
704
920
  } catch(e) {
705
921
  console.error(e);
706
922
  }
707
923
 
708
- if (results && results.length) {
709
- for (const res of results) {
710
- const fields = res?.data?.content?.fields?.value?.fields;
711
- const id = res?.data?.content?.fields?.id?.id;
924
+ for (const obj of objects) {
925
+ // Dynamic field Field<K,V>: json = { id: { id: "..." }, name: K, value: V_fields }
926
+ const fields = obj?.json?.value;
927
+ const id = obj?.json?.id?.id ?? obj?.objectId;
712
928
 
713
- historyItems.forEach(hi => {
714
- if (hi.id === id) {
715
- hi.setFields(fields);
716
- }
717
- });
718
- }
929
+ historyItems.forEach(hi => {
930
+ if (hi.id === id) {
931
+ hi.setFields(fields);
932
+ }
933
+ });
719
934
  }
720
935
  }
721
936
 
@@ -804,27 +1019,23 @@ export default class EndlessVector {
804
1019
  */
805
1020
  async loadArchiveItemsBunch(archiveItems) {
806
1021
  const ids = archiveItems.map(ai => ai.id);
807
- let results = [];
1022
+ let objects = [];
808
1023
  try {
809
- results = await this.suiClient.multiGetObjects({
810
- ids: ids,
811
- options: { showContent: true, },
812
- });
1024
+ const res = await this.suiClient.getObjects({ objectIds: ids, include: { json: true } });
1025
+ objects = res.objects ?? [];
813
1026
  } catch(e) {
814
1027
  console.error(e);
815
1028
  }
816
1029
 
817
- if (results && results.length) {
818
- for (const res of results) {
819
- const fields = res?.data?.content?.fields?.value?.fields;
820
- const id = res?.data?.content?.fields?.id?.id;
1030
+ for (const obj of objects) {
1031
+ const fields = obj?.json?.value;
1032
+ const id = obj?.json?.id?.id ?? obj?.objectId;
821
1033
 
822
- archiveItems.forEach(ai => {
823
- if (ai.id === id) {
824
- ai.setFields(fields);
825
- }
826
- });
827
- }
1034
+ archiveItems.forEach(ai => {
1035
+ if (ai.id === id) {
1036
+ ai.setFields(fields);
1037
+ }
1038
+ });
828
1039
  }
829
1040
  }
830
1041
 
@@ -909,11 +1120,26 @@ export default class EndlessVector {
909
1120
 
910
1121
  /**
911
1122
  * Retrieves the byte array at the specified index from either current items or history.
1123
+ * For sealed vectors, transparently decrypts the item via the seal companion.
912
1124
  * @param {number} i - The index to retrieve
913
1125
  * @returns {Promise<Uint8Array>} The byte array at the specified index
914
1126
  * @throws {Error} If the index is out of range or cannot be found
915
1127
  */
916
1128
  async at(i) {
1129
+ const raw = await this._atRaw(i);
1130
+ if (this.sealEncryptedKey) {
1131
+ return await this.seal.decryptItem(raw);
1132
+ }
1133
+ return raw;
1134
+ }
1135
+
1136
+ /**
1137
+ * Raw read of the byte array at the specified index. Returns ciphertext for sealed vectors.
1138
+ * Kept separate so `at()` can wrap with optional decryption without rewriting routing.
1139
+ * @param {number} i
1140
+ * @returns {Promise<Uint8Array>}
1141
+ */
1142
+ async _atRaw(i) {
917
1143
  await this.initialize();
918
1144
 
919
1145
  if (i < 0 || i >= this.length) {
@@ -948,10 +1174,10 @@ export default class EndlessVector {
948
1174
  // in current items
949
1175
  if (this.firstItemIsFromPreviousHistory) {
950
1176
  const indexInItems = i - this.firstNotHistoryIndex + 1;
951
- return this._items[indexInItems];
1177
+ return await this._items[indexInItems].bytes();
952
1178
  } else {
953
1179
  const indexInItems = i - this.firstNotHistoryIndex;
954
- return this._items[indexInItems];
1180
+ return await this._items[indexInItems].bytes();
955
1181
  }
956
1182
  }
957
1183
 
@@ -971,11 +1197,44 @@ export default class EndlessVector {
971
1197
  if (i < this.historyItemsCount) {
972
1198
  const historyItem = await this.getHistory(i);
973
1199
  if (historyItem) {
974
- return historyItem.getSuffixStoredBytes();
1200
+ return await historyItem.getSuffixStoredBytes();
975
1201
  }
976
1202
  } else if (this._history[i - 1] && this.firstItemIsFromPreviousHistory) {
977
- // if there is no such history item, but previous exists, then suffix is the first item of the EndlessVector object items itself
978
- return this._items[0];
1203
+ // if there is no such history item, but previous exists, then suffix is the first item of the EndlessVector object items itself
1204
+ return await this._items[0].bytes();
979
1205
  }
980
1206
  }
1207
+
1208
+ /**
1209
+ * Decode a little-endian u64 from a BCS-encoded Uint8Array (gRPC dynamic-field name).
1210
+ * @param {Uint8Array} bcsBytes
1211
+ * @returns {number}
1212
+ */
1213
+ /**
1214
+ * Decode a (possibly-Option) `vector<u8>` from gRPC json. gRPC serializes binary fields
1215
+ * as base64 strings, but other code paths may pass through Uint8Array, plain arrays of
1216
+ * bytes, or { vec: [...] } shapes — handle all of them.
1217
+ */
1218
+ static _decodeOptionVectorU8(value) {
1219
+ if (value == null) return null;
1220
+ if (value instanceof Uint8Array) return value;
1221
+ if (typeof value === 'string') {
1222
+ if (!value.length) return null;
1223
+ const bin = (typeof Buffer !== 'undefined')
1224
+ ? Buffer.from(value, 'base64')
1225
+ : Uint8Array.from(atob(value), c => c.charCodeAt(0));
1226
+ return new Uint8Array(bin.buffer, bin.byteOffset ?? 0, bin.byteLength ?? bin.length);
1227
+ }
1228
+ if (Array.isArray(value)) return value.length ? new Uint8Array(value) : null;
1229
+ if (typeof value === 'object' && Array.isArray(value.vec)) {
1230
+ return value.vec.length ? new Uint8Array(value.vec) : null;
1231
+ }
1232
+ return null;
1233
+ }
1234
+
1235
+ static _decodeBcsU64(bcsBytes) {
1236
+ const b = bcsBytes instanceof Uint8Array ? bcsBytes : new Uint8Array(bcsBytes);
1237
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
1238
+ return Number(dv.getBigUint64(0, true));
1239
+ }
981
1240
  }