@arkade-os/sdk 0.4.15 → 0.4.17
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 +102 -96
- package/dist/cjs/arkfee/estimator.js +1 -1
- package/dist/cjs/arkfee/types.js +2 -1
- package/dist/cjs/arknote/index.js +43 -4
- package/dist/cjs/bip322/index.js +1 -1
- package/dist/cjs/contracts/arkcontract.js +1 -1
- package/dist/cjs/contracts/contractManager.js +60 -28
- package/dist/cjs/contracts/contractWatcher.js +29 -22
- package/dist/cjs/contracts/handlers/default.js +1 -1
- package/dist/cjs/contracts/handlers/delegate.js +1 -1
- package/dist/cjs/contracts/handlers/helpers.js +1 -1
- package/dist/cjs/extension/asset/assetGroup.js +92 -5
- package/dist/cjs/extension/asset/assetId.js +67 -3
- package/dist/cjs/extension/asset/assetInput.js +18 -0
- package/dist/cjs/extension/asset/assetOutput.js +15 -0
- package/dist/cjs/extension/asset/assetRef.js +66 -0
- package/dist/cjs/extension/asset/metadata.js +15 -0
- package/dist/cjs/extension/asset/packet.js +4 -1
- package/dist/cjs/extension/index.js +1 -1
- package/dist/cjs/forfeit.js +14 -0
- package/dist/cjs/identity/seedIdentity.js +2 -2
- package/dist/cjs/identity/singleKey.js +4 -0
- package/dist/cjs/intent/index.js +28 -12
- package/dist/cjs/providers/ark.js +3 -2
- package/dist/cjs/providers/delegator.js +20 -1
- package/dist/cjs/providers/expoArk.js +2 -2
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/providers/onchain.js +2 -1
- package/dist/cjs/repositories/realm/schemas.js +2 -2
- package/dist/cjs/repositories/realm/types.js +1 -1
- package/dist/cjs/script/address.js +37 -6
- package/dist/cjs/script/base.js +70 -1
- package/dist/cjs/script/default.js +3 -0
- package/dist/cjs/script/delegate.js +4 -0
- package/dist/cjs/script/tapscript.js +17 -2
- package/dist/cjs/script/vhtlc.js +35 -27
- package/dist/cjs/storage/fileSystem.js +1 -1
- package/dist/cjs/storage/inMemory.js +1 -1
- package/dist/cjs/storage/indexedDB.js +1 -1
- package/dist/cjs/storage/localStorage.js +1 -1
- package/dist/cjs/tree/validation.js +1 -1
- package/dist/cjs/utils/arkTransaction.js +5 -5
- package/dist/cjs/utils/bip21.js +16 -3
- package/dist/cjs/utils/syncCursors.js +4 -4
- package/dist/cjs/utils/transaction.js +1 -1
- package/dist/cjs/utils/transactionHistory.js +11 -11
- package/dist/cjs/utils/unknownFields.js +3 -3
- package/dist/cjs/wallet/asset-manager.js +4 -4
- package/dist/cjs/wallet/batch.js +5 -5
- package/dist/cjs/wallet/delegator.js +9 -8
- package/dist/cjs/wallet/expo/background.js +3 -3
- package/dist/cjs/wallet/expo/wallet.js +7 -7
- package/dist/cjs/wallet/index.js +43 -0
- package/dist/cjs/wallet/onchain.js +43 -5
- package/dist/cjs/wallet/ramps.js +44 -14
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +22 -22
- package/dist/cjs/wallet/serviceWorker/wallet.js +28 -24
- package/dist/cjs/wallet/unroll.js +12 -8
- package/dist/cjs/wallet/utils.js +1 -1
- package/dist/cjs/wallet/vtxo-manager.js +122 -82
- package/dist/cjs/wallet/wallet.js +140 -77
- package/dist/cjs/worker/expo/asyncStorageTaskQueue.js +1 -1
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +2 -2
- package/dist/cjs/worker/expo/taskRunner.js +3 -3
- package/dist/cjs/worker/messageBus.js +3 -0
- package/dist/esm/arkfee/estimator.js +1 -1
- package/dist/esm/arkfee/types.js +2 -1
- package/dist/esm/arknote/index.js +43 -4
- package/dist/esm/bip322/index.js +1 -1
- package/dist/esm/contracts/arkcontract.js +1 -1
- package/dist/esm/contracts/contractManager.js +60 -28
- package/dist/esm/contracts/contractWatcher.js +29 -22
- package/dist/esm/contracts/handlers/default.js +1 -1
- package/dist/esm/contracts/handlers/delegate.js +1 -1
- package/dist/esm/contracts/handlers/helpers.js +1 -1
- package/dist/esm/extension/asset/assetGroup.js +92 -5
- package/dist/esm/extension/asset/assetId.js +67 -3
- package/dist/esm/extension/asset/assetInput.js +18 -0
- package/dist/esm/extension/asset/assetOutput.js +15 -0
- package/dist/esm/extension/asset/assetRef.js +66 -0
- package/dist/esm/extension/asset/metadata.js +15 -0
- package/dist/esm/extension/asset/packet.js +4 -1
- package/dist/esm/extension/index.js +1 -1
- package/dist/esm/forfeit.js +14 -0
- package/dist/esm/identity/seedIdentity.js +2 -2
- package/dist/esm/identity/singleKey.js +4 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/intent/index.js +28 -12
- package/dist/esm/providers/ark.js +3 -2
- package/dist/esm/providers/delegator.js +20 -1
- package/dist/esm/providers/expoArk.js +2 -2
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/providers/onchain.js +2 -1
- package/dist/esm/repositories/realm/schemas.js +2 -2
- package/dist/esm/repositories/realm/types.js +1 -1
- package/dist/esm/script/address.js +37 -6
- package/dist/esm/script/base.js +70 -1
- package/dist/esm/script/default.js +3 -0
- package/dist/esm/script/delegate.js +4 -0
- package/dist/esm/script/tapscript.js +17 -2
- package/dist/esm/script/vhtlc.js +35 -27
- package/dist/esm/storage/fileSystem.js +1 -1
- package/dist/esm/storage/inMemory.js +1 -1
- package/dist/esm/storage/indexedDB.js +1 -1
- package/dist/esm/storage/localStorage.js +1 -1
- package/dist/esm/tree/validation.js +1 -1
- package/dist/esm/utils/arkTransaction.js +5 -5
- package/dist/esm/utils/bip21.js +16 -3
- package/dist/esm/utils/syncCursors.js +4 -4
- package/dist/esm/utils/transaction.js +1 -1
- package/dist/esm/utils/transactionHistory.js +11 -11
- package/dist/esm/utils/unknownFields.js +3 -3
- package/dist/esm/wallet/asset-manager.js +4 -4
- package/dist/esm/wallet/batch.js +5 -5
- package/dist/esm/wallet/delegator.js +9 -8
- package/dist/esm/wallet/expo/background.js +3 -3
- package/dist/esm/wallet/expo/wallet.js +7 -7
- package/dist/esm/wallet/index.js +43 -0
- package/dist/esm/wallet/onchain.js +43 -5
- package/dist/esm/wallet/ramps.js +44 -14
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +22 -22
- package/dist/esm/wallet/serviceWorker/wallet.js +28 -24
- package/dist/esm/wallet/unroll.js +12 -8
- package/dist/esm/wallet/utils.js +1 -1
- package/dist/esm/wallet/vtxo-manager.js +121 -81
- package/dist/esm/wallet/wallet.js +140 -77
- package/dist/esm/worker/expo/asyncStorageTaskQueue.js +1 -1
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +2 -2
- package/dist/esm/worker/expo/taskRunner.js +3 -3
- package/dist/esm/worker/messageBus.js +3 -0
- package/dist/types/arkfee/estimator.d.ts +1 -1
- package/dist/types/arkfee/types.d.ts +2 -1
- package/dist/types/arknote/index.d.ts +44 -4
- package/dist/types/bip322/index.d.ts +1 -1
- package/dist/types/contracts/arkcontract.d.ts +1 -1
- package/dist/types/contracts/contractManager.d.ts +39 -65
- package/dist/types/contracts/contractWatcher.d.ts +39 -18
- package/dist/types/contracts/handlers/default.d.ts +1 -1
- package/dist/types/contracts/handlers/delegate.d.ts +1 -1
- package/dist/types/contracts/handlers/helpers.d.ts +1 -1
- package/dist/types/contracts/types.d.ts +36 -26
- package/dist/types/extension/asset/assetGroup.d.ts +92 -1
- package/dist/types/extension/asset/assetId.d.ts +67 -3
- package/dist/types/extension/asset/assetInput.d.ts +18 -0
- package/dist/types/extension/asset/assetOutput.d.ts +15 -0
- package/dist/types/extension/asset/assetRef.d.ts +66 -0
- package/dist/types/extension/asset/metadata.d.ts +15 -0
- package/dist/types/extension/asset/packet.d.ts +4 -1
- package/dist/types/extension/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +14 -0
- package/dist/types/identity/index.d.ts +16 -0
- package/dist/types/identity/seedIdentity.d.ts +8 -6
- package/dist/types/identity/singleKey.d.ts +4 -0
- package/dist/types/intent/index.d.ts +19 -6
- package/dist/types/providers/ark.d.ts +40 -2
- package/dist/types/providers/delegator.d.ts +54 -1
- package/dist/types/providers/expoArk.d.ts +2 -2
- package/dist/types/providers/indexer.d.ts +105 -2
- package/dist/types/providers/onchain.d.ts +62 -1
- package/dist/types/repositories/realm/schemas.d.ts +2 -2
- package/dist/types/repositories/realm/types.d.ts +2 -2
- package/dist/types/repositories/walletRepository.d.ts +16 -0
- package/dist/types/script/address.d.ts +35 -2
- package/dist/types/script/base.d.ts +66 -1
- package/dist/types/script/default.d.ts +3 -0
- package/dist/types/script/delegate.d.ts +4 -0
- package/dist/types/script/tapscript.d.ts +17 -2
- package/dist/types/script/vhtlc.d.ts +35 -27
- package/dist/types/storage/fileSystem.d.ts +1 -1
- package/dist/types/storage/inMemory.d.ts +1 -1
- package/dist/types/storage/index.d.ts +1 -1
- package/dist/types/storage/indexedDB.d.ts +1 -1
- package/dist/types/storage/localStorage.d.ts +1 -1
- package/dist/types/utils/arkTransaction.d.ts +3 -3
- package/dist/types/utils/bip21.d.ts +17 -0
- package/dist/types/utils/syncCursors.d.ts +4 -4
- package/dist/types/utils/transaction.d.ts +1 -1
- package/dist/types/utils/transactionHistory.d.ts +3 -3
- package/dist/types/utils/unknownFields.d.ts +5 -5
- package/dist/types/wallet/asset-manager.d.ts +3 -3
- package/dist/types/wallet/batch.d.ts +27 -7
- package/dist/types/wallet/delegator.d.ts +10 -0
- package/dist/types/wallet/expo/background.d.ts +4 -4
- package/dist/types/wallet/expo/wallet.d.ts +10 -10
- package/dist/types/wallet/index.d.ts +457 -25
- package/dist/types/wallet/onchain.d.ts +42 -4
- package/dist/types/wallet/ramps.d.ts +40 -10
- package/dist/types/wallet/serviceWorker/wallet-message-handler.d.ts +4 -4
- package/dist/types/wallet/serviceWorker/wallet.d.ts +71 -33
- package/dist/types/wallet/unroll.d.ts +8 -6
- package/dist/types/wallet/vtxo-manager.d.ts +146 -93
- package/dist/types/wallet/wallet.d.ts +91 -33
- package/dist/types/worker/expo/asyncStorageTaskQueue.d.ts +1 -1
- package/dist/types/worker/expo/processors/contractPollProcessor.d.ts +1 -1
- package/dist/types/worker/expo/taskRunner.d.ts +6 -6
- package/dist/types/worker/messageBus.d.ts +5 -3
- package/package.json +1 -1
|
@@ -10,38 +10,48 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
10
10
|
/**
|
|
11
11
|
* Central manager for contract lifecycle and operations.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
13
|
+
* Responsibilities:
|
|
14
|
+
* - Create and persist contracts
|
|
15
|
+
* - Query stored contracts (optionally with their virtual outputs)
|
|
16
|
+
* - Provide spendable path selection for a contract
|
|
17
|
+
* - Emit contract-related events (virtual output received/spent/expired, connection reset)
|
|
18
|
+
*
|
|
19
|
+
* Notes:
|
|
20
|
+
* - Implementations typically start watching automatically during initialization
|
|
21
|
+
* (so `onContractEvent()` is just for subscribing).
|
|
17
22
|
*
|
|
18
23
|
* @example
|
|
19
24
|
* ```typescript
|
|
20
|
-
* const manager =
|
|
25
|
+
* const manager = await ContractManager.create({
|
|
21
26
|
* indexerProvider: wallet.indexerProvider,
|
|
22
27
|
* contractRepository: wallet.contractRepository,
|
|
23
|
-
* getDefaultAddress: () => wallet.getAddress(),
|
|
24
28
|
* });
|
|
25
29
|
*
|
|
26
|
-
* // Initialize (loads persisted contracts)
|
|
27
|
-
* await manager.initialize();
|
|
28
|
-
*
|
|
29
30
|
* // Create a new VHTLC contract
|
|
30
31
|
* const contract = await manager.createContract({
|
|
31
32
|
* label: "Lightning Receive",
|
|
32
33
|
* type: "vhtlc",
|
|
33
|
-
* params: { sender: "
|
|
34
|
+
* params: { sender: "ark1q...", receiver: "ark1q...", ... },
|
|
34
35
|
* script: "5120...",
|
|
35
|
-
* address: "
|
|
36
|
+
* address: "ark1q...",
|
|
36
37
|
* });
|
|
37
38
|
*
|
|
38
39
|
* // Start watching for events
|
|
39
|
-
* const
|
|
40
|
+
* const unsubscribe = manager.onContractEvent((event) => {
|
|
40
41
|
* console.log(`${event.type} on ${event.contractScript}`);
|
|
41
42
|
* });
|
|
42
43
|
*
|
|
44
|
+
* // Query contracts together with their current virtual outputs
|
|
45
|
+
* const contractsWithVtxos = await manager.getContractsWithVtxos();
|
|
46
|
+
*
|
|
43
47
|
* // Get balance across all contracts
|
|
44
|
-
* const balances =
|
|
48
|
+
* const balances = contractsWithVtxos.flatMap(({vtxos}) => vtxos).reduce((acc, vtxo) => acc + vtxo.value, 0)
|
|
49
|
+
*
|
|
50
|
+
* // Later: unsubscribe from events
|
|
51
|
+
* unsubscribe();
|
|
52
|
+
*
|
|
53
|
+
* // Clean up
|
|
54
|
+
* manager.dispose();
|
|
45
55
|
* ```
|
|
46
56
|
*/
|
|
47
57
|
class ContractManager {
|
|
@@ -49,7 +59,7 @@ class ContractManager {
|
|
|
49
59
|
this.initialized = false;
|
|
50
60
|
this.eventCallbacks = new Set();
|
|
51
61
|
this.config = config;
|
|
52
|
-
// Create watcher with wallet repository for
|
|
62
|
+
// Create watcher with wallet repository for virtual output caching
|
|
53
63
|
this.watcher = new contractWatcher_1.ContractWatcher({
|
|
54
64
|
indexerProvider: config.indexerProvider,
|
|
55
65
|
walletRepository: config.walletRepository,
|
|
@@ -61,7 +71,7 @@ class ContractManager {
|
|
|
61
71
|
* Initialize the manager by loading persisted contracts and starting to watch.
|
|
62
72
|
*
|
|
63
73
|
* After initialization, the manager automatically watches all active contracts
|
|
64
|
-
* and contracts with
|
|
74
|
+
* and contracts with virtual outputs. Use `onContractEvent()` to register event callbacks.
|
|
65
75
|
*
|
|
66
76
|
* @param config ContractManagerConfig
|
|
67
77
|
*/
|
|
@@ -76,10 +86,10 @@ class ContractManager {
|
|
|
76
86
|
}
|
|
77
87
|
// Load persisted contracts
|
|
78
88
|
const contracts = await this.config.contractRepository.getContracts();
|
|
79
|
-
// Delta-sync: fetch only
|
|
89
|
+
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
80
90
|
// falling back to a full bootstrap for scripts seen for the first time.
|
|
81
|
-
await this.deltaSyncContracts(contracts);
|
|
82
|
-
// Reconcile the pending frontier: fetch all not-yet-finalized
|
|
91
|
+
await this.deltaSyncContracts(contracts, undefined, true);
|
|
92
|
+
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
83
93
|
// to catch any that the delta window may have missed.
|
|
84
94
|
if (contracts.length > 0) {
|
|
85
95
|
await this.reconcilePendingFrontier(contracts);
|
|
@@ -148,7 +158,7 @@ class ContractManager {
|
|
|
148
158
|
};
|
|
149
159
|
// Persist
|
|
150
160
|
await this.config.contractRepository.saveContract(contract);
|
|
151
|
-
// fetch all
|
|
161
|
+
// fetch all virtual outputs (including spent/swept) for this contract
|
|
152
162
|
const requestStartedAt = Date.now();
|
|
153
163
|
await this.fetchContractVxosFromIndexer([contract], true);
|
|
154
164
|
// Advance the sync cursor so that the watcher's vtxo_received
|
|
@@ -261,7 +271,6 @@ class ContractManager {
|
|
|
261
271
|
/**
|
|
262
272
|
* Get currently spendable paths for a contract.
|
|
263
273
|
*
|
|
264
|
-
* @param contractScript - The contract script
|
|
265
274
|
* @param options - Options for getting spendable paths
|
|
266
275
|
*/
|
|
267
276
|
async getSpendablePaths(options) {
|
|
@@ -281,6 +290,11 @@ class ContractManager {
|
|
|
281
290
|
};
|
|
282
291
|
return handler.getSpendablePaths(script, contract, context);
|
|
283
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Get every currently valid spending path for a contract.
|
|
295
|
+
*
|
|
296
|
+
* @param options - Options for getting spending paths
|
|
297
|
+
*/
|
|
284
298
|
async getAllSpendingPaths(options) {
|
|
285
299
|
const { contractScript, collaborative = true, walletPubKey } = options;
|
|
286
300
|
const [contract] = await this.getContracts({ script: contractScript });
|
|
@@ -323,7 +337,7 @@ class ContractManager {
|
|
|
323
337
|
};
|
|
324
338
|
}
|
|
325
339
|
/**
|
|
326
|
-
* Force
|
|
340
|
+
* Force refresh virtual outputs from the indexer.
|
|
327
341
|
*
|
|
328
342
|
* Without options, clears all sync cursors and re-fetches every contract.
|
|
329
343
|
* With options, narrows the refresh to specific scripts and/or a time window.
|
|
@@ -385,7 +399,7 @@ class ContractManager {
|
|
|
385
399
|
*/
|
|
386
400
|
async handleContractEvent(event) {
|
|
387
401
|
switch (event.type) {
|
|
388
|
-
// Delta-sync only the changed
|
|
402
|
+
// Delta-sync only the changed virtual outputs for this contract.
|
|
389
403
|
case "vtxo_received":
|
|
390
404
|
case "vtxo_spent":
|
|
391
405
|
await this.deltaSyncContracts([event.contract]);
|
|
@@ -407,21 +421,39 @@ class ContractManager {
|
|
|
407
421
|
if (contracts.length === 0) {
|
|
408
422
|
return new Map();
|
|
409
423
|
}
|
|
410
|
-
|
|
424
|
+
// Deduplicate concurrent callers against an in-flight fetch so we don't
|
|
425
|
+
// issue redundant round-trips. Once the fetch settles we clear the
|
|
426
|
+
// reference so the next call triggers a fresh fetch.
|
|
427
|
+
// TODO: can be removed once we fix the persistence layer (address vs scripts)
|
|
428
|
+
if (this.syncVtxosCallInflight) {
|
|
429
|
+
return this.syncVtxosCallInflight;
|
|
430
|
+
}
|
|
431
|
+
this.syncVtxosCallInflight = this.fetchContractVxosFromIndexer(contracts, true, pageSize).finally(() => {
|
|
432
|
+
this.syncVtxosCallInflight = undefined;
|
|
433
|
+
});
|
|
434
|
+
return this.syncVtxosCallInflight;
|
|
411
435
|
}
|
|
412
436
|
/**
|
|
413
|
-
* Incrementally sync
|
|
437
|
+
* Incrementally sync virtual outputs for the given contracts.
|
|
414
438
|
* Uses per-script cursors to fetch only what changed since the last sync.
|
|
415
439
|
* Scripts without a cursor are bootstrapped with a full fetch.
|
|
416
440
|
*/
|
|
417
|
-
async deltaSyncContracts(contracts, pageSize) {
|
|
441
|
+
async deltaSyncContracts(contracts, pageSize, force) {
|
|
418
442
|
if (contracts.length === 0)
|
|
419
443
|
return new Map();
|
|
444
|
+
// If forced, we are treating all contracts as boostrapped and we clean the VTXO list
|
|
445
|
+
if (force === true) {
|
|
446
|
+
await Promise.all(contracts.map((contract) => this.config.walletRepository.deleteVtxos(contract.address)));
|
|
447
|
+
}
|
|
420
448
|
const cursors = await (0, syncCursors_1.getAllSyncCursors)(this.config.walletRepository);
|
|
421
449
|
// Partition into bootstrap (no cursor) and delta (has cursor) groups.
|
|
422
450
|
const bootstrap = [];
|
|
423
451
|
const delta = [];
|
|
424
452
|
for (const c of contracts) {
|
|
453
|
+
if (force) {
|
|
454
|
+
bootstrap.push(c);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
425
457
|
if (cursors[c.script] !== undefined) {
|
|
426
458
|
delta.push(c);
|
|
427
459
|
}
|
|
@@ -462,8 +494,8 @@ class ContractManager {
|
|
|
462
494
|
return result;
|
|
463
495
|
}
|
|
464
496
|
/**
|
|
465
|
-
* Fetch all pending (
|
|
466
|
-
* repository. This catches
|
|
497
|
+
* Fetch all pending (unfinalized) virtual outputs and upsert them into the
|
|
498
|
+
* repository. This catches virtual outputs whose state changed outside the delta
|
|
467
499
|
* window (e.g. a spend that hasn't settled yet).
|
|
468
500
|
*/
|
|
469
501
|
async reconcilePendingFrontier(contracts) {
|
|
@@ -545,7 +577,7 @@ class ContractManager {
|
|
|
545
577
|
pageSize,
|
|
546
578
|
});
|
|
547
579
|
for (const vtxo of vtxos) {
|
|
548
|
-
// Match the
|
|
580
|
+
// Match the virtual output back to its contract via the script field
|
|
549
581
|
// populated by the indexer.
|
|
550
582
|
if (!vtxo.script)
|
|
551
583
|
continue;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContractWatcher = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* Watches multiple contracts for
|
|
5
|
+
* Watches multiple contracts for virtual output state changes with resilient connection handling.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Automatic reconnection with exponential backoff
|
|
@@ -32,6 +32,12 @@ exports.ContractWatcher = void 0;
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
class ContractWatcher {
|
|
35
|
+
/**
|
|
36
|
+
* Create a contract watcher with the given providers and polling settings.
|
|
37
|
+
*
|
|
38
|
+
* @param config - Contract watcher configuration
|
|
39
|
+
* @see ContractWatcherConfig
|
|
40
|
+
*/
|
|
35
41
|
constructor(config) {
|
|
36
42
|
this.contracts = new Map();
|
|
37
43
|
this.isWatching = false;
|
|
@@ -48,9 +54,10 @@ class ContractWatcher {
|
|
|
48
54
|
/**
|
|
49
55
|
* Add a contract to be watched.
|
|
50
56
|
*
|
|
51
|
-
* Active contracts are immediately subscribed.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
57
|
+
* Active contracts are immediately subscribed.
|
|
58
|
+
*
|
|
59
|
+
* All contracts are polled to discover any existing virtual outputs
|
|
60
|
+
* (which may cause them to be watched even if inactive).
|
|
54
61
|
*/
|
|
55
62
|
async addContract(contract) {
|
|
56
63
|
const state = {
|
|
@@ -58,11 +65,11 @@ class ContractWatcher {
|
|
|
58
65
|
lastKnownVtxos: new Map(),
|
|
59
66
|
};
|
|
60
67
|
this.contracts.set(contract.script, state);
|
|
61
|
-
// If we're already watching, poll to discover
|
|
68
|
+
// If we're already watching, poll to discover virtual outputs and update subscription
|
|
62
69
|
if (this.isWatching) {
|
|
63
|
-
// Poll first to discover
|
|
70
|
+
// Poll first to discover virtual outputs (may affect whether we watch this contract).
|
|
64
71
|
await this.pollContracts([contract.script]);
|
|
65
|
-
// Update subscription based on active state and
|
|
72
|
+
// Update subscription based on active state and virtual outputs.
|
|
66
73
|
await this.tryUpdateSubscription();
|
|
67
74
|
}
|
|
68
75
|
}
|
|
@@ -108,10 +115,10 @@ class ContractWatcher {
|
|
|
108
115
|
*
|
|
109
116
|
* Returns scripts for:
|
|
110
117
|
* - All active contracts
|
|
111
|
-
* - All contracts with known
|
|
118
|
+
* - All contracts with known virtual outputs (regardless of state)
|
|
112
119
|
*
|
|
113
120
|
* This ensures we continue monitoring contracts even after they're
|
|
114
|
-
* deactivated, as long as they have unspent
|
|
121
|
+
* deactivated, as long as they have unspent virtual outputs.
|
|
115
122
|
*/
|
|
116
123
|
getScriptsToWatch() {
|
|
117
124
|
const scripts = new Set();
|
|
@@ -121,7 +128,7 @@ class ContractWatcher {
|
|
|
121
128
|
scripts.add(state.contract.script);
|
|
122
129
|
continue;
|
|
123
130
|
}
|
|
124
|
-
// Also watch inactive/expired contracts that have
|
|
131
|
+
// Also watch inactive/expired contracts that have virtual outputs.
|
|
125
132
|
if (state.lastKnownVtxos.size > 0) {
|
|
126
133
|
scripts.add(state.contract.script);
|
|
127
134
|
}
|
|
@@ -129,8 +136,8 @@ class ContractWatcher {
|
|
|
129
136
|
return Array.from(scripts);
|
|
130
137
|
}
|
|
131
138
|
/**
|
|
132
|
-
* Get
|
|
133
|
-
*
|
|
139
|
+
* Get virtual outputs for contracts, grouped by contract script.
|
|
140
|
+
* @see WalletRepository for `repo`
|
|
134
141
|
*/
|
|
135
142
|
async getContractVtxos(options) {
|
|
136
143
|
const { contractScripts, includeSpent } = options;
|
|
@@ -163,7 +170,7 @@ class ContractWatcher {
|
|
|
163
170
|
return new Map(results.flat(1));
|
|
164
171
|
}
|
|
165
172
|
/**
|
|
166
|
-
* Start watching for
|
|
173
|
+
* Start watching for virtual output events across all active contracts.
|
|
167
174
|
*/
|
|
168
175
|
async startWatching(callback) {
|
|
169
176
|
if (this.isWatching) {
|
|
@@ -342,7 +349,7 @@ class ContractWatcher {
|
|
|
342
349
|
return;
|
|
343
350
|
const now = Date.now();
|
|
344
351
|
try {
|
|
345
|
-
// Load all the
|
|
352
|
+
// Load all the virtual outputs for these contracts, from DB
|
|
346
353
|
const vtxosMap = await this.getContractVtxos({
|
|
347
354
|
contractScripts,
|
|
348
355
|
includeSpent: false, // only spendable ones!
|
|
@@ -353,7 +360,7 @@ class ContractWatcher {
|
|
|
353
360
|
continue;
|
|
354
361
|
const currentVtxos = vtxosMap.get(contractScript) || [];
|
|
355
362
|
const currentKeys = new Set(currentVtxos.map((v) => `${v.txid}:${v.vout}`));
|
|
356
|
-
// Find new
|
|
363
|
+
// Find new virtual outputs and add them to the contract's state
|
|
357
364
|
const newVtxos = [];
|
|
358
365
|
for (const vtxo of currentVtxos) {
|
|
359
366
|
const key = `${vtxo.txid}:${vtxo.vout}`;
|
|
@@ -362,7 +369,7 @@ class ContractWatcher {
|
|
|
362
369
|
state.lastKnownVtxos.set(key, vtxo);
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
|
-
// Find spent
|
|
372
|
+
// Find spent virtual outputs and remove them from the contract's state
|
|
366
373
|
const spentVtxos = [];
|
|
367
374
|
for (const [key, vtxo] of state.lastKnownVtxos) {
|
|
368
375
|
if (!currentKeys.has(key)) {
|
|
@@ -397,7 +404,7 @@ class ContractWatcher {
|
|
|
397
404
|
/**
|
|
398
405
|
* Update the subscription with scripts that should be watched.
|
|
399
406
|
*
|
|
400
|
-
* Watches both active contracts and contracts with
|
|
407
|
+
* Watches both active contracts and contracts with virtual outputs.
|
|
401
408
|
*/
|
|
402
409
|
async updateSubscription() {
|
|
403
410
|
const scriptsToWatch = this.getScriptsToWatch();
|
|
@@ -474,11 +481,11 @@ class ContractWatcher {
|
|
|
474
481
|
}
|
|
475
482
|
}
|
|
476
483
|
/**
|
|
477
|
-
* Process
|
|
484
|
+
* Process virtual outputs from subscription and route to correct contracts.
|
|
478
485
|
* Uses the scripts from the subscription response to determine contract ownership.
|
|
479
486
|
*/
|
|
480
487
|
processSubscriptionVtxos(vtxos, scripts, eventType, timestamp) {
|
|
481
|
-
// If we have exactly one script, all
|
|
488
|
+
// If we have exactly one script, all virtual outputs belong to that contract
|
|
482
489
|
// Otherwise, we can't reliably determine ownership without script in VirtualCoin
|
|
483
490
|
if (scripts.length === 1) {
|
|
484
491
|
const contractScript = scripts[0];
|
|
@@ -500,8 +507,8 @@ class ContractWatcher {
|
|
|
500
507
|
}
|
|
501
508
|
return;
|
|
502
509
|
}
|
|
503
|
-
// Multiple scripts - assign
|
|
504
|
-
// This is a limitation: we can't know which
|
|
510
|
+
// Multiple scripts - assign virtual outputs to all matching contracts
|
|
511
|
+
// This is a limitation: we can't know which virtual output belongs to which script
|
|
505
512
|
// In practice, subscription events usually come with a single script context
|
|
506
513
|
for (const script of scripts) {
|
|
507
514
|
const contractScript = script;
|
|
@@ -523,7 +530,7 @@ class ContractWatcher {
|
|
|
523
530
|
}
|
|
524
531
|
}
|
|
525
532
|
/**
|
|
526
|
-
* Emit a
|
|
533
|
+
* Emit a virtual output event for a contract.
|
|
527
534
|
*/
|
|
528
535
|
emitVtxoEvent(contractScript, vtxos, eventType, timestamp) {
|
|
529
536
|
if (!this.eventCallback)
|
|
@@ -5,7 +5,7 @@ const base_1 = require("@scure/base");
|
|
|
5
5
|
const default_1 = require("../../script/default");
|
|
6
6
|
const helpers_1 = require("./helpers");
|
|
7
7
|
/**
|
|
8
|
-
* Handler for default wallet
|
|
8
|
+
* Handler for default wallet virtual outputs.
|
|
9
9
|
*
|
|
10
10
|
* Default contracts use the standard forfeit + exit tapscript:
|
|
11
11
|
* - forfeit: (Alice + Server) multisig for collaborative spending
|
|
@@ -6,7 +6,7 @@ const delegate_1 = require("../../script/delegate");
|
|
|
6
6
|
const default_1 = require("../../script/default");
|
|
7
7
|
const helpers_1 = require("./helpers");
|
|
8
8
|
/**
|
|
9
|
-
* Handler for delegate wallet
|
|
9
|
+
* Handler for delegate wallet virtual outputs.
|
|
10
10
|
*
|
|
11
11
|
* Delegate contracts extend the default tapscript with an additional delegate path:
|
|
12
12
|
* - forfeit: (Alice + Server) multisig for collaborative spending
|
|
@@ -103,7 +103,7 @@ function isCltvSatisfied(context, locktime) {
|
|
|
103
103
|
return currentTimeSec >= locktime;
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* Check if a CSV timelock is currently satisfied for the given context/
|
|
106
|
+
* Check if a CSV timelock is currently satisfied for the given context/virtual output.
|
|
107
107
|
*/
|
|
108
108
|
function isCsvSpendable(context, sequence) {
|
|
109
109
|
if (sequence === undefined)
|
|
@@ -10,9 +10,25 @@ const assetOutput_1 = require("./assetOutput");
|
|
|
10
10
|
const metadata_1 = require("./metadata");
|
|
11
11
|
const utils_1 = require("./utils");
|
|
12
12
|
/**
|
|
13
|
-
* An asset group contains inputs
|
|
13
|
+
* An asset group contains inputs, outputs, and all data related to a given asset id.
|
|
14
|
+
*
|
|
15
|
+
* @see Packet
|
|
16
|
+
* @see AssetId
|
|
17
|
+
* @see AssetRef
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const group = AssetGroup.create(
|
|
22
|
+
* null, // asset ID: null for new issuance
|
|
23
|
+
* null, // control asset ID: null when reissuance not needed
|
|
24
|
+
* [], // asset inputs: empty for new issuance
|
|
25
|
+
* [AssetOutput.create(0, 1000)], // asset outputs: 1000 units at vout index 0
|
|
26
|
+
* [] // metadata: can be empty
|
|
27
|
+
* )
|
|
28
|
+
* ```
|
|
14
29
|
*/
|
|
15
30
|
class AssetGroup {
|
|
31
|
+
/** @see create */
|
|
16
32
|
constructor(assetId, controlAsset, inputs, outputs, metadata) {
|
|
17
33
|
this.assetId = assetId;
|
|
18
34
|
this.controlAsset = controlAsset;
|
|
@@ -20,12 +36,31 @@ class AssetGroup {
|
|
|
20
36
|
this.outputs = outputs;
|
|
21
37
|
this.metadataList = new metadata_1.MetadataList(metadata);
|
|
22
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Create and validate an asset group.
|
|
41
|
+
*
|
|
42
|
+
* @param assetId - Asset id for this group, or `null` for fresh issuance
|
|
43
|
+
* @param controlAsset - Optional control asset reference for (re) issuance
|
|
44
|
+
* @param inputs - Asset inputs in the group
|
|
45
|
+
* @param outputs - Asset outputs in the group
|
|
46
|
+
* @param metadata - Metadata entries associated with the group
|
|
47
|
+
* @returns A validated asset group
|
|
48
|
+
* @throws Error if the group fails validation
|
|
49
|
+
* @see validate
|
|
50
|
+
*/
|
|
23
51
|
static create(assetId, controlAsset, inputs, outputs, metadata) {
|
|
24
52
|
const ag = new AssetGroup(assetId, controlAsset, inputs, outputs, metadata);
|
|
25
53
|
ag.validate();
|
|
26
54
|
return ag;
|
|
27
55
|
}
|
|
28
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Decode an asset group from its hex string form.
|
|
58
|
+
*
|
|
59
|
+
* @param s - Hex-encoded asset group
|
|
60
|
+
* @returns Decoded asset group
|
|
61
|
+
* @throws Error if the string is not valid hex or does not encode a valid asset group
|
|
62
|
+
* @see toString
|
|
63
|
+
*/
|
|
29
64
|
static fromString(s) {
|
|
30
65
|
let buf;
|
|
31
66
|
try {
|
|
@@ -36,6 +71,13 @@ class AssetGroup {
|
|
|
36
71
|
}
|
|
37
72
|
return AssetGroup.fromBytes(buf);
|
|
38
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode an asset group from its serialized bytes.
|
|
76
|
+
*
|
|
77
|
+
* @param buf - Serialized asset group bytes
|
|
78
|
+
* @returns Decoded asset group
|
|
79
|
+
* @throws Error if the buffer is empty or malformed
|
|
80
|
+
*/
|
|
39
81
|
static fromBytes(buf) {
|
|
40
82
|
if (!buf || buf.length === 0) {
|
|
41
83
|
throw new Error("missing asset group");
|
|
@@ -43,12 +85,21 @@ class AssetGroup {
|
|
|
43
85
|
const reader = new utils_1.BufferReader(buf);
|
|
44
86
|
return AssetGroup.fromReader(reader);
|
|
45
87
|
}
|
|
46
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Return true when the group represents an issuance.
|
|
90
|
+
*
|
|
91
|
+
* @returns `true` when the group has no asset id
|
|
92
|
+
*/
|
|
47
93
|
isIssuance() {
|
|
48
94
|
return this.assetId === null;
|
|
49
95
|
}
|
|
50
|
-
|
|
51
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Return true when the group represents a reissuance.
|
|
98
|
+
*
|
|
99
|
+
* @returns `true` when the group has an asset id and outputs exceed local inputs
|
|
100
|
+
* @remarks
|
|
101
|
+
* Only local inputs contribute to the comparison; intent-backed inputs contribute `0` here.
|
|
102
|
+
*/
|
|
52
103
|
isReissuance() {
|
|
53
104
|
const sumReducer = (s, { amount }) => s + amount;
|
|
54
105
|
const sumOutputs = this.outputs.reduce(sumReducer, 0n);
|
|
@@ -59,12 +110,23 @@ class AssetGroup {
|
|
|
59
110
|
.reduce(sumReducer, 0n);
|
|
60
111
|
return !this.isIssuance() && sumInputs < sumOutputs;
|
|
61
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Serialize the asset group to raw bytes.
|
|
115
|
+
*
|
|
116
|
+
* @returns Serialized asset group bytes
|
|
117
|
+
* @see fromBytes
|
|
118
|
+
*/
|
|
62
119
|
serialize() {
|
|
63
120
|
this.validate();
|
|
64
121
|
const writer = new utils_1.BufferWriter();
|
|
65
122
|
this.serializeTo(writer);
|
|
66
123
|
return writer.toBytes();
|
|
67
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Validate the asset group and its child structures.
|
|
127
|
+
*
|
|
128
|
+
* @throws Error if the group is empty or violates issuance invariants
|
|
129
|
+
*/
|
|
68
130
|
validate() {
|
|
69
131
|
if (this.inputs.length === 0 && this.outputs.length === 0) {
|
|
70
132
|
throw new Error("empty asset group");
|
|
@@ -80,13 +142,33 @@ class AssetGroup {
|
|
|
80
142
|
}
|
|
81
143
|
}
|
|
82
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Convert the group into its batch-leaf representation for the given intent txid.
|
|
147
|
+
*
|
|
148
|
+
* @param intentTxid - Intent transaction id used to build the leaf input reference
|
|
149
|
+
* @returns Batch-leaf asset group
|
|
150
|
+
* @see AssetInput.createIntent
|
|
151
|
+
*/
|
|
83
152
|
toBatchLeafAssetGroup(intentTxid) {
|
|
84
153
|
const leafInput = assetInput_1.AssetInput.createIntent(base_1.hex.encode(intentTxid), 0, 0);
|
|
85
154
|
return new AssetGroup(this.assetId, this.controlAsset, [leafInput], this.outputs, this.metadataList.items);
|
|
86
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Encode the asset group to a hex string.
|
|
158
|
+
*
|
|
159
|
+
* @returns Hex-encoded asset group
|
|
160
|
+
* @see fromString
|
|
161
|
+
*/
|
|
87
162
|
toString() {
|
|
88
163
|
return base_1.hex.encode(this.serialize());
|
|
89
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Decode an asset group from a binary reader.
|
|
167
|
+
*
|
|
168
|
+
* @param reader - Reader positioned at an asset group
|
|
169
|
+
* @returns Decoded asset group
|
|
170
|
+
* @throws Error if the encoded group is malformed
|
|
171
|
+
*/
|
|
90
172
|
static fromReader(reader) {
|
|
91
173
|
const presence = reader.readByte();
|
|
92
174
|
let assetId = null;
|
|
@@ -107,6 +189,11 @@ class AssetGroup {
|
|
|
107
189
|
ag.validate();
|
|
108
190
|
return ag;
|
|
109
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Serialize the asset group into an existing binary writer.
|
|
194
|
+
*
|
|
195
|
+
* @param writer - Writer to append the asset group to
|
|
196
|
+
*/
|
|
110
197
|
serializeTo(writer) {
|
|
111
198
|
let presence = 0;
|
|
112
199
|
if (this.assetId !== null) {
|
|
@@ -5,15 +5,34 @@ const base_1 = require("@scure/base");
|
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const utils_1 = require("./utils");
|
|
7
7
|
/**
|
|
8
|
-
* AssetId
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
8
|
+
* AssetId identifies a specific asset.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Asset ids are derived from the genesis transaction id plus the asset group index.
|
|
12
|
+
*
|
|
13
|
+
* @see AssetRef
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const assetId = AssetId.create('00'.repeat(32), 0)
|
|
18
|
+
* const encoded = assetId.toString()
|
|
19
|
+
* const decoded = AssetId.fromString(encoded)
|
|
20
|
+
* ```
|
|
11
21
|
*/
|
|
12
22
|
class AssetId {
|
|
13
23
|
constructor(txid, groupIndex) {
|
|
14
24
|
this.txid = txid;
|
|
15
25
|
this.groupIndex = groupIndex;
|
|
16
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an asset id from a genesis transaction id and group index.
|
|
29
|
+
*
|
|
30
|
+
* @param txid - Hex-encoded genesis transaction id
|
|
31
|
+
* @param groupIndex - Asset group index within the genesis transaction
|
|
32
|
+
* @returns A validated asset id
|
|
33
|
+
* @throws Error if the txid is missing, malformed, or not 32 bytes long
|
|
34
|
+
* @see fromString
|
|
35
|
+
*/
|
|
17
36
|
static create(txid, groupIndex) {
|
|
18
37
|
if (!txid) {
|
|
19
38
|
throw new Error("missing txid");
|
|
@@ -32,6 +51,14 @@ class AssetId {
|
|
|
32
51
|
assetId.validate();
|
|
33
52
|
return assetId;
|
|
34
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Decode an asset id from its hex string representation.
|
|
56
|
+
*
|
|
57
|
+
* @param s - Hex-encoded asset id
|
|
58
|
+
* @returns Decoded asset id
|
|
59
|
+
* @throws Error if the string is not valid hex or does not encode a valid asset id
|
|
60
|
+
* @see toString
|
|
61
|
+
*/
|
|
35
62
|
static fromString(s) {
|
|
36
63
|
let buf;
|
|
37
64
|
try {
|
|
@@ -42,6 +69,13 @@ class AssetId {
|
|
|
42
69
|
}
|
|
43
70
|
return AssetId.fromBytes(buf);
|
|
44
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Decode an asset id from its serialized bytes.
|
|
74
|
+
*
|
|
75
|
+
* @param buf - Serialized asset id bytes
|
|
76
|
+
* @returns Decoded asset id
|
|
77
|
+
* @throws Error if the buffer length is invalid
|
|
78
|
+
*/
|
|
45
79
|
static fromBytes(buf) {
|
|
46
80
|
if (!buf || buf.length === 0) {
|
|
47
81
|
throw new Error("missing asset id");
|
|
@@ -52,14 +86,31 @@ class AssetId {
|
|
|
52
86
|
const reader = new utils_1.BufferReader(buf);
|
|
53
87
|
return AssetId.fromReader(reader);
|
|
54
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Serialize the asset id to raw bytes.
|
|
91
|
+
*
|
|
92
|
+
* @returns Serialized asset id bytes
|
|
93
|
+
* @see fromBytes
|
|
94
|
+
*/
|
|
55
95
|
serialize() {
|
|
56
96
|
const writer = new utils_1.BufferWriter();
|
|
57
97
|
this.serializeTo(writer);
|
|
58
98
|
return writer.toBytes();
|
|
59
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Encode the asset id to a hex string.
|
|
102
|
+
*
|
|
103
|
+
* @returns Hex-encoded asset id
|
|
104
|
+
* @see fromString
|
|
105
|
+
*/
|
|
60
106
|
toString() {
|
|
61
107
|
return base_1.hex.encode(this.serialize());
|
|
62
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate the asset id fields.
|
|
111
|
+
*
|
|
112
|
+
* @throws Error if the txid is empty or the group index is out of range
|
|
113
|
+
*/
|
|
63
114
|
validate() {
|
|
64
115
|
if ((0, utils_1.isZeroBytes)(this.txid)) {
|
|
65
116
|
throw new Error("empty txid");
|
|
@@ -70,6 +121,13 @@ class AssetId {
|
|
|
70
121
|
throw new Error(`invalid group index: ${this.groupIndex}, must be in range [0, 65535]`);
|
|
71
122
|
}
|
|
72
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Decode an asset id from a binary reader.
|
|
126
|
+
*
|
|
127
|
+
* @param reader - Reader positioned at an asset id
|
|
128
|
+
* @returns Decoded asset id
|
|
129
|
+
* @throws Error if the reader does not contain enough bytes
|
|
130
|
+
*/
|
|
73
131
|
static fromReader(reader) {
|
|
74
132
|
if (reader.remaining() < types_1.ASSET_ID_SIZE) {
|
|
75
133
|
throw new Error(`invalid asset id length: got ${reader.remaining()}, want ${types_1.ASSET_ID_SIZE}`);
|
|
@@ -80,6 +138,12 @@ class AssetId {
|
|
|
80
138
|
assetId.validate();
|
|
81
139
|
return assetId;
|
|
82
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Serialize the asset id into an existing binary writer.
|
|
143
|
+
*
|
|
144
|
+
* @param writer - Writer to append the asset id to
|
|
145
|
+
* @see serialize
|
|
146
|
+
*/
|
|
83
147
|
serializeTo(writer) {
|
|
84
148
|
writer.write(this.txid);
|
|
85
149
|
writer.writeUint16LE(this.groupIndex);
|