@arkade-os/sdk 0.4.15 → 0.4.16
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 +40 -24
- 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 +125 -67
- 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 +40 -24
- 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 +125 -67
- 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 +40 -63
- 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
package/dist/esm/arkfee/types.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FeeAmount is a wrapper around a number that represents a fee amount in satoshis floating point.
|
|
3
3
|
* @param value - The fee amount in floating point.
|
|
4
|
-
* @method satoshis - Returns the fee amount in satoshis as a integer.
|
|
5
4
|
* @example
|
|
6
5
|
* const fee = new FeeAmount(1.23456789);
|
|
7
6
|
* console.log(fee.value); // 1.23456789
|
|
@@ -11,9 +10,11 @@ export class FeeAmount {
|
|
|
11
10
|
constructor(value) {
|
|
12
11
|
this.value = value;
|
|
13
12
|
}
|
|
13
|
+
/** Returns the fee amount rounded up to whole satoshis. */
|
|
14
14
|
get satoshis() {
|
|
15
15
|
return this.value ? Math.ceil(this.value) : 0;
|
|
16
16
|
}
|
|
17
|
+
/** Add two fee amounts together. */
|
|
17
18
|
add(other) {
|
|
18
19
|
return new FeeAmount(this.value + other.value);
|
|
19
20
|
}
|
|
@@ -3,10 +3,12 @@ import { sha256 } from "@scure/btc-signer/utils.js";
|
|
|
3
3
|
import { Script } from "@scure/btc-signer";
|
|
4
4
|
import { VtxoScript } from '../script/base.js';
|
|
5
5
|
/**
|
|
6
|
-
* ArkNotes are special virtual
|
|
7
|
-
* and spent without requiring any transactions.
|
|
8
|
-
*
|
|
9
|
-
* preimage and value.
|
|
6
|
+
* ArkNotes are special virtual outputs in the Arkade protocol that
|
|
7
|
+
* can be created and spent without requiring any transactions.
|
|
8
|
+
* The server mints them, and they are encoded as base58 strings
|
|
9
|
+
* with a human-readable prefix, a preimage and a value.
|
|
10
|
+
*
|
|
11
|
+
* @see VtxoScript
|
|
10
12
|
*
|
|
11
13
|
* @example
|
|
12
14
|
* ```typescript
|
|
@@ -21,6 +23,13 @@ import { VtxoScript } from '../script/base.js';
|
|
|
21
23
|
* ```
|
|
22
24
|
*/
|
|
23
25
|
export class ArkNote {
|
|
26
|
+
/**
|
|
27
|
+
* Create an ArkNote from a preimage and value.
|
|
28
|
+
*
|
|
29
|
+
* @param preimage - 32-byte preimage revealed to spend the note
|
|
30
|
+
* @param value - Note value in satoshis
|
|
31
|
+
* @param HRP - Optional human-readable prefix for string encoding
|
|
32
|
+
*/
|
|
24
33
|
constructor(preimage, value, HRP = ArkNote.DefaultHRP) {
|
|
25
34
|
this.preimage = preimage;
|
|
26
35
|
this.value = value;
|
|
@@ -37,12 +46,27 @@ export class ArkNote {
|
|
|
37
46
|
this.status = { confirmed: true };
|
|
38
47
|
this.extraWitness = [this.preimage];
|
|
39
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Encode the note as raw bytes.
|
|
51
|
+
*
|
|
52
|
+
* @returns Serialized note bytes
|
|
53
|
+
* @see decode
|
|
54
|
+
*/
|
|
40
55
|
encode() {
|
|
41
56
|
const result = new Uint8Array(ArkNote.Length);
|
|
42
57
|
result.set(this.preimage, 0);
|
|
43
58
|
writeUInt32BE(result, this.value, this.preimage.length);
|
|
44
59
|
return result;
|
|
45
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Decode a note from raw bytes.
|
|
63
|
+
*
|
|
64
|
+
* @param data - Serialized note bytes
|
|
65
|
+
* @param hrp - Human-readable prefix expected for future string encoding
|
|
66
|
+
* @returns Decoded ArkNote
|
|
67
|
+
* @throws Error if the payload length is invalid
|
|
68
|
+
* @see encode
|
|
69
|
+
*/
|
|
46
70
|
static decode(data, hrp = ArkNote.DefaultHRP) {
|
|
47
71
|
if (data.length !== ArkNote.Length) {
|
|
48
72
|
throw new Error(`invalid data length: expected ${ArkNote.Length} bytes, got ${data.length}`);
|
|
@@ -51,6 +75,15 @@ export class ArkNote {
|
|
|
51
75
|
const value = readUInt32BE(data, ArkNote.PreimageLength);
|
|
52
76
|
return new ArkNote(preimage, value, hrp);
|
|
53
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Decode a note from its base58 string form.
|
|
80
|
+
*
|
|
81
|
+
* @param noteStr - Base58-encoded note string
|
|
82
|
+
* @param hrp - Human-readable prefix expected on the note string
|
|
83
|
+
* @returns Decoded ArkNote
|
|
84
|
+
* @throws Error if the prefix or base58 payload is invalid
|
|
85
|
+
* @see toString
|
|
86
|
+
*/
|
|
54
87
|
static fromString(noteStr, hrp = ArkNote.DefaultHRP) {
|
|
55
88
|
noteStr = noteStr.trim();
|
|
56
89
|
if (!noteStr.startsWith(hrp)) {
|
|
@@ -63,6 +96,12 @@ export class ArkNote {
|
|
|
63
96
|
}
|
|
64
97
|
return ArkNote.decode(decoded, hrp);
|
|
65
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Encode the note to its human-readable base58 string form.
|
|
101
|
+
*
|
|
102
|
+
* @returns Base58-encoded note string
|
|
103
|
+
* @see fromString
|
|
104
|
+
*/
|
|
66
105
|
toString() {
|
|
67
106
|
return this.HRP + base58.encode(this.encode());
|
|
68
107
|
}
|
package/dist/esm/bip322/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const TAG_BIP322 = "BIP0322-signed-message";
|
|
|
14
14
|
*
|
|
15
15
|
* Reuses the same toSpend/toSign transaction construction as Intent proofs,
|
|
16
16
|
* but with the standard BIP-322 tagged hash ("BIP0322-signed-message")
|
|
17
|
-
* instead of the
|
|
17
|
+
* instead of the Arkade-specific tag.
|
|
18
18
|
*
|
|
19
19
|
* @see https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
20
20
|
*
|
|
@@ -10,7 +10,7 @@ const ARKCONTRACT_PREFIX = "arkcontract";
|
|
|
10
10
|
* Format: arkcontract={type}&{key1}={value1}&{key2}={value2}...
|
|
11
11
|
*
|
|
12
12
|
* This format is compatible with NArk and allows contracts to be
|
|
13
|
-
* shared/imported across different
|
|
13
|
+
* shared/imported across different Arkade SDKs.
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* ```typescript
|
|
@@ -7,38 +7,50 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
7
7
|
/**
|
|
8
8
|
* Central manager for contract lifecycle and operations.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Create and persist contracts
|
|
12
|
+
* - Query stored contracts (optionally with their virtual outputs)
|
|
13
|
+
* - Provide spendable path selection for a contract
|
|
14
|
+
* - Emit contract-related events (virtual output received/spent/expired, connection reset)
|
|
15
|
+
*
|
|
16
|
+
* Notes:
|
|
17
|
+
* - Implementations typically start watching automatically during initialization
|
|
18
|
+
* (so `onContractEvent()` is just for subscribing).
|
|
14
19
|
*
|
|
15
20
|
* @example
|
|
16
21
|
* ```typescript
|
|
17
|
-
* const manager =
|
|
22
|
+
* const manager = await ContractManager.create({
|
|
18
23
|
* indexerProvider: wallet.indexerProvider,
|
|
19
24
|
* contractRepository: wallet.contractRepository,
|
|
25
|
+
* walletRepository: wallet.walletRepository,
|
|
20
26
|
* getDefaultAddress: () => wallet.getAddress(),
|
|
21
27
|
* });
|
|
22
28
|
*
|
|
23
|
-
* // Initialize (loads persisted contracts)
|
|
24
|
-
* await manager.initialize();
|
|
25
|
-
*
|
|
26
29
|
* // Create a new VHTLC contract
|
|
27
30
|
* const contract = await manager.createContract({
|
|
28
31
|
* label: "Lightning Receive",
|
|
29
32
|
* type: "vhtlc",
|
|
30
|
-
* params: { sender: "
|
|
33
|
+
* params: { sender: "ark1q...", receiver: "ark1q...", ... },
|
|
31
34
|
* script: "5120...",
|
|
32
|
-
* address: "
|
|
35
|
+
* address: "ark1q...",
|
|
33
36
|
* });
|
|
34
37
|
*
|
|
35
38
|
* // Start watching for events
|
|
36
|
-
* const
|
|
39
|
+
* const unsubscribe = manager.onContractEvent((event) => {
|
|
37
40
|
* console.log(`${event.type} on ${event.contractScript}`);
|
|
38
41
|
* });
|
|
39
42
|
*
|
|
43
|
+
* // Query contracts together with their current virtual outputs
|
|
44
|
+
* const contractsWithVtxos = await manager.getContractsWithVtxos();
|
|
45
|
+
*
|
|
40
46
|
* // Get balance across all contracts
|
|
41
|
-
* const balances =
|
|
47
|
+
* const balances = contractsWithVtxos.flatMap(({vtxos}) => vtxos).reduce((acc, vtxo) => acc + vtxo.value, 0)
|
|
48
|
+
*
|
|
49
|
+
* // Later: unsubscribe from events
|
|
50
|
+
* unsubscribe();
|
|
51
|
+
*
|
|
52
|
+
* // Clean up
|
|
53
|
+
* manager.dispose();
|
|
42
54
|
* ```
|
|
43
55
|
*/
|
|
44
56
|
export class ContractManager {
|
|
@@ -46,7 +58,7 @@ export class ContractManager {
|
|
|
46
58
|
this.initialized = false;
|
|
47
59
|
this.eventCallbacks = new Set();
|
|
48
60
|
this.config = config;
|
|
49
|
-
// Create watcher with wallet repository for
|
|
61
|
+
// Create watcher with wallet repository for virtual output caching
|
|
50
62
|
this.watcher = new ContractWatcher({
|
|
51
63
|
indexerProvider: config.indexerProvider,
|
|
52
64
|
walletRepository: config.walletRepository,
|
|
@@ -58,7 +70,7 @@ export class ContractManager {
|
|
|
58
70
|
* Initialize the manager by loading persisted contracts and starting to watch.
|
|
59
71
|
*
|
|
60
72
|
* After initialization, the manager automatically watches all active contracts
|
|
61
|
-
* and contracts with
|
|
73
|
+
* and contracts with virtual outputs. Use `onContractEvent()` to register event callbacks.
|
|
62
74
|
*
|
|
63
75
|
* @param config ContractManagerConfig
|
|
64
76
|
*/
|
|
@@ -73,10 +85,10 @@ export class ContractManager {
|
|
|
73
85
|
}
|
|
74
86
|
// Load persisted contracts
|
|
75
87
|
const contracts = await this.config.contractRepository.getContracts();
|
|
76
|
-
// Delta-sync: fetch only
|
|
88
|
+
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
77
89
|
// falling back to a full bootstrap for scripts seen for the first time.
|
|
78
90
|
await this.deltaSyncContracts(contracts);
|
|
79
|
-
// Reconcile the pending frontier: fetch all not-yet-finalized
|
|
91
|
+
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
80
92
|
// to catch any that the delta window may have missed.
|
|
81
93
|
if (contracts.length > 0) {
|
|
82
94
|
await this.reconcilePendingFrontier(contracts);
|
|
@@ -145,7 +157,7 @@ export class ContractManager {
|
|
|
145
157
|
};
|
|
146
158
|
// Persist
|
|
147
159
|
await this.config.contractRepository.saveContract(contract);
|
|
148
|
-
// fetch all
|
|
160
|
+
// fetch all virtual outputs (including spent/swept) for this contract
|
|
149
161
|
const requestStartedAt = Date.now();
|
|
150
162
|
await this.fetchContractVxosFromIndexer([contract], true);
|
|
151
163
|
// Advance the sync cursor so that the watcher's vtxo_received
|
|
@@ -258,7 +270,6 @@ export class ContractManager {
|
|
|
258
270
|
/**
|
|
259
271
|
* Get currently spendable paths for a contract.
|
|
260
272
|
*
|
|
261
|
-
* @param contractScript - The contract script
|
|
262
273
|
* @param options - Options for getting spendable paths
|
|
263
274
|
*/
|
|
264
275
|
async getSpendablePaths(options) {
|
|
@@ -278,6 +289,11 @@ export class ContractManager {
|
|
|
278
289
|
};
|
|
279
290
|
return handler.getSpendablePaths(script, contract, context);
|
|
280
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Get every currently valid spending path for a contract.
|
|
294
|
+
*
|
|
295
|
+
* @param options - Options for getting spending paths
|
|
296
|
+
*/
|
|
281
297
|
async getAllSpendingPaths(options) {
|
|
282
298
|
const { contractScript, collaborative = true, walletPubKey } = options;
|
|
283
299
|
const [contract] = await this.getContracts({ script: contractScript });
|
|
@@ -320,7 +336,7 @@ export class ContractManager {
|
|
|
320
336
|
};
|
|
321
337
|
}
|
|
322
338
|
/**
|
|
323
|
-
* Force
|
|
339
|
+
* Force refresh virtual outputs from the indexer.
|
|
324
340
|
*
|
|
325
341
|
* Without options, clears all sync cursors and re-fetches every contract.
|
|
326
342
|
* With options, narrows the refresh to specific scripts and/or a time window.
|
|
@@ -382,7 +398,7 @@ export class ContractManager {
|
|
|
382
398
|
*/
|
|
383
399
|
async handleContractEvent(event) {
|
|
384
400
|
switch (event.type) {
|
|
385
|
-
// Delta-sync only the changed
|
|
401
|
+
// Delta-sync only the changed virtual outputs for this contract.
|
|
386
402
|
case "vtxo_received":
|
|
387
403
|
case "vtxo_spent":
|
|
388
404
|
await this.deltaSyncContracts([event.contract]);
|
|
@@ -407,7 +423,7 @@ export class ContractManager {
|
|
|
407
423
|
return await this.fetchContractVxosFromIndexer(contracts, false, pageSize);
|
|
408
424
|
}
|
|
409
425
|
/**
|
|
410
|
-
* Incrementally sync
|
|
426
|
+
* Incrementally sync virtual outputs for the given contracts.
|
|
411
427
|
* Uses per-script cursors to fetch only what changed since the last sync.
|
|
412
428
|
* Scripts without a cursor are bootstrapped with a full fetch.
|
|
413
429
|
*/
|
|
@@ -459,8 +475,8 @@ export class ContractManager {
|
|
|
459
475
|
return result;
|
|
460
476
|
}
|
|
461
477
|
/**
|
|
462
|
-
* Fetch all pending (
|
|
463
|
-
* repository. This catches
|
|
478
|
+
* Fetch all pending (unfinalized) virtual outputs and upsert them into the
|
|
479
|
+
* repository. This catches virtual outputs whose state changed outside the delta
|
|
464
480
|
* window (e.g. a spend that hasn't settled yet).
|
|
465
481
|
*/
|
|
466
482
|
async reconcilePendingFrontier(contracts) {
|
|
@@ -542,7 +558,7 @@ export class ContractManager {
|
|
|
542
558
|
pageSize,
|
|
543
559
|
});
|
|
544
560
|
for (const vtxo of vtxos) {
|
|
545
|
-
// Match the
|
|
561
|
+
// Match the virtual output back to its contract via the script field
|
|
546
562
|
// populated by the indexer.
|
|
547
563
|
if (!vtxo.script)
|
|
548
564
|
continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Watches multiple contracts for
|
|
2
|
+
* Watches multiple contracts for virtual output state changes with resilient connection handling.
|
|
3
3
|
*
|
|
4
4
|
* Features:
|
|
5
5
|
* - Automatic reconnection with exponential backoff
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
export class ContractWatcher {
|
|
32
|
+
/**
|
|
33
|
+
* Create a contract watcher with the given providers and polling settings.
|
|
34
|
+
*
|
|
35
|
+
* @param config - Contract watcher configuration
|
|
36
|
+
* @see ContractWatcherConfig
|
|
37
|
+
*/
|
|
32
38
|
constructor(config) {
|
|
33
39
|
this.contracts = new Map();
|
|
34
40
|
this.isWatching = false;
|
|
@@ -45,9 +51,10 @@ export class ContractWatcher {
|
|
|
45
51
|
/**
|
|
46
52
|
* Add a contract to be watched.
|
|
47
53
|
*
|
|
48
|
-
* Active contracts are immediately subscribed.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
54
|
+
* Active contracts are immediately subscribed.
|
|
55
|
+
*
|
|
56
|
+
* All contracts are polled to discover any existing virtual outputs
|
|
57
|
+
* (which may cause them to be watched even if inactive).
|
|
51
58
|
*/
|
|
52
59
|
async addContract(contract) {
|
|
53
60
|
const state = {
|
|
@@ -55,11 +62,11 @@ export class ContractWatcher {
|
|
|
55
62
|
lastKnownVtxos: new Map(),
|
|
56
63
|
};
|
|
57
64
|
this.contracts.set(contract.script, state);
|
|
58
|
-
// If we're already watching, poll to discover
|
|
65
|
+
// If we're already watching, poll to discover virtual outputs and update subscription
|
|
59
66
|
if (this.isWatching) {
|
|
60
|
-
// Poll first to discover
|
|
67
|
+
// Poll first to discover virtual outputs (may affect whether we watch this contract).
|
|
61
68
|
await this.pollContracts([contract.script]);
|
|
62
|
-
// Update subscription based on active state and
|
|
69
|
+
// Update subscription based on active state and virtual outputs.
|
|
63
70
|
await this.tryUpdateSubscription();
|
|
64
71
|
}
|
|
65
72
|
}
|
|
@@ -105,10 +112,10 @@ export class ContractWatcher {
|
|
|
105
112
|
*
|
|
106
113
|
* Returns scripts for:
|
|
107
114
|
* - All active contracts
|
|
108
|
-
* - All contracts with known
|
|
115
|
+
* - All contracts with known virtual outputs (regardless of state)
|
|
109
116
|
*
|
|
110
117
|
* This ensures we continue monitoring contracts even after they're
|
|
111
|
-
* deactivated, as long as they have unspent
|
|
118
|
+
* deactivated, as long as they have unspent virtual outputs.
|
|
112
119
|
*/
|
|
113
120
|
getScriptsToWatch() {
|
|
114
121
|
const scripts = new Set();
|
|
@@ -118,7 +125,7 @@ export class ContractWatcher {
|
|
|
118
125
|
scripts.add(state.contract.script);
|
|
119
126
|
continue;
|
|
120
127
|
}
|
|
121
|
-
// Also watch inactive/expired contracts that have
|
|
128
|
+
// Also watch inactive/expired contracts that have virtual outputs.
|
|
122
129
|
if (state.lastKnownVtxos.size > 0) {
|
|
123
130
|
scripts.add(state.contract.script);
|
|
124
131
|
}
|
|
@@ -126,8 +133,8 @@ export class ContractWatcher {
|
|
|
126
133
|
return Array.from(scripts);
|
|
127
134
|
}
|
|
128
135
|
/**
|
|
129
|
-
* Get
|
|
130
|
-
*
|
|
136
|
+
* Get virtual outputs for contracts, grouped by contract script.
|
|
137
|
+
* @see WalletRepository for `repo`
|
|
131
138
|
*/
|
|
132
139
|
async getContractVtxos(options) {
|
|
133
140
|
const { contractScripts, includeSpent } = options;
|
|
@@ -160,7 +167,7 @@ export class ContractWatcher {
|
|
|
160
167
|
return new Map(results.flat(1));
|
|
161
168
|
}
|
|
162
169
|
/**
|
|
163
|
-
* Start watching for
|
|
170
|
+
* Start watching for virtual output events across all active contracts.
|
|
164
171
|
*/
|
|
165
172
|
async startWatching(callback) {
|
|
166
173
|
if (this.isWatching) {
|
|
@@ -339,7 +346,7 @@ export class ContractWatcher {
|
|
|
339
346
|
return;
|
|
340
347
|
const now = Date.now();
|
|
341
348
|
try {
|
|
342
|
-
// Load all the
|
|
349
|
+
// Load all the virtual outputs for these contracts, from DB
|
|
343
350
|
const vtxosMap = await this.getContractVtxos({
|
|
344
351
|
contractScripts,
|
|
345
352
|
includeSpent: false, // only spendable ones!
|
|
@@ -350,7 +357,7 @@ export class ContractWatcher {
|
|
|
350
357
|
continue;
|
|
351
358
|
const currentVtxos = vtxosMap.get(contractScript) || [];
|
|
352
359
|
const currentKeys = new Set(currentVtxos.map((v) => `${v.txid}:${v.vout}`));
|
|
353
|
-
// Find new
|
|
360
|
+
// Find new virtual outputs and add them to the contract's state
|
|
354
361
|
const newVtxos = [];
|
|
355
362
|
for (const vtxo of currentVtxos) {
|
|
356
363
|
const key = `${vtxo.txid}:${vtxo.vout}`;
|
|
@@ -359,7 +366,7 @@ export class ContractWatcher {
|
|
|
359
366
|
state.lastKnownVtxos.set(key, vtxo);
|
|
360
367
|
}
|
|
361
368
|
}
|
|
362
|
-
// Find spent
|
|
369
|
+
// Find spent virtual outputs and remove them from the contract's state
|
|
363
370
|
const spentVtxos = [];
|
|
364
371
|
for (const [key, vtxo] of state.lastKnownVtxos) {
|
|
365
372
|
if (!currentKeys.has(key)) {
|
|
@@ -394,7 +401,7 @@ export class ContractWatcher {
|
|
|
394
401
|
/**
|
|
395
402
|
* Update the subscription with scripts that should be watched.
|
|
396
403
|
*
|
|
397
|
-
* Watches both active contracts and contracts with
|
|
404
|
+
* Watches both active contracts and contracts with virtual outputs.
|
|
398
405
|
*/
|
|
399
406
|
async updateSubscription() {
|
|
400
407
|
const scriptsToWatch = this.getScriptsToWatch();
|
|
@@ -471,11 +478,11 @@ export class ContractWatcher {
|
|
|
471
478
|
}
|
|
472
479
|
}
|
|
473
480
|
/**
|
|
474
|
-
* Process
|
|
481
|
+
* Process virtual outputs from subscription and route to correct contracts.
|
|
475
482
|
* Uses the scripts from the subscription response to determine contract ownership.
|
|
476
483
|
*/
|
|
477
484
|
processSubscriptionVtxos(vtxos, scripts, eventType, timestamp) {
|
|
478
|
-
// If we have exactly one script, all
|
|
485
|
+
// If we have exactly one script, all virtual outputs belong to that contract
|
|
479
486
|
// Otherwise, we can't reliably determine ownership without script in VirtualCoin
|
|
480
487
|
if (scripts.length === 1) {
|
|
481
488
|
const contractScript = scripts[0];
|
|
@@ -497,8 +504,8 @@ export class ContractWatcher {
|
|
|
497
504
|
}
|
|
498
505
|
return;
|
|
499
506
|
}
|
|
500
|
-
// Multiple scripts - assign
|
|
501
|
-
// This is a limitation: we can't know which
|
|
507
|
+
// Multiple scripts - assign virtual outputs to all matching contracts
|
|
508
|
+
// This is a limitation: we can't know which virtual output belongs to which script
|
|
502
509
|
// In practice, subscription events usually come with a single script context
|
|
503
510
|
for (const script of scripts) {
|
|
504
511
|
const contractScript = script;
|
|
@@ -520,7 +527,7 @@ export class ContractWatcher {
|
|
|
520
527
|
}
|
|
521
528
|
}
|
|
522
529
|
/**
|
|
523
|
-
* Emit a
|
|
530
|
+
* Emit a virtual output event for a contract.
|
|
524
531
|
*/
|
|
525
532
|
emitVtxoEvent(contractScript, vtxos, eventType, timestamp) {
|
|
526
533
|
if (!this.eventCallback)
|
|
@@ -2,7 +2,7 @@ import { hex } from "@scure/base";
|
|
|
2
2
|
import { DefaultVtxo } from '../../script/default.js';
|
|
3
3
|
import { isCsvSpendable, sequenceToTimelock, timelockToSequence, } from './helpers.js';
|
|
4
4
|
/**
|
|
5
|
-
* Handler for default wallet
|
|
5
|
+
* Handler for default wallet virtual outputs.
|
|
6
6
|
*
|
|
7
7
|
* Default contracts use the standard forfeit + exit tapscript:
|
|
8
8
|
* - forfeit: (Alice + Server) multisig for collaborative spending
|
|
@@ -3,7 +3,7 @@ import { DelegateVtxo } from '../../script/delegate.js';
|
|
|
3
3
|
import { DefaultVtxo } from '../../script/default.js';
|
|
4
4
|
import { isCsvSpendable, sequenceToTimelock, timelockToSequence, } from './helpers.js';
|
|
5
5
|
/**
|
|
6
|
-
* Handler for delegate wallet
|
|
6
|
+
* Handler for delegate wallet virtual outputs.
|
|
7
7
|
*
|
|
8
8
|
* Delegate contracts extend the default tapscript with an additional delegate path:
|
|
9
9
|
* - forfeit: (Alice + Server) multisig for collaborative spending
|
|
@@ -63,7 +63,7 @@ export function isCltvSatisfied(context, locktime) {
|
|
|
63
63
|
return currentTimeSec >= locktime;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Check if a CSV timelock is currently satisfied for the given context/
|
|
66
|
+
* Check if a CSV timelock is currently satisfied for the given context/virtual output.
|
|
67
67
|
*/
|
|
68
68
|
export function isCsvSpendable(context, sequence) {
|
|
69
69
|
if (sequence === undefined)
|
|
@@ -7,9 +7,25 @@ import { AssetOutputs } from './assetOutput.js';
|
|
|
7
7
|
import { MetadataList } from './metadata.js';
|
|
8
8
|
import { BufferReader, BufferWriter } from './utils.js';
|
|
9
9
|
/**
|
|
10
|
-
* An asset group contains inputs
|
|
10
|
+
* An asset group contains inputs, outputs, and all data related to a given asset id.
|
|
11
|
+
*
|
|
12
|
+
* @see Packet
|
|
13
|
+
* @see AssetId
|
|
14
|
+
* @see AssetRef
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const group = AssetGroup.create(
|
|
19
|
+
* null, // asset ID: null for new issuance
|
|
20
|
+
* null, // control asset ID: null when reissuance not needed
|
|
21
|
+
* [], // asset inputs: empty for new issuance
|
|
22
|
+
* [AssetOutput.create(0, 1000)], // asset outputs: 1000 units at vout index 0
|
|
23
|
+
* [] // metadata: can be empty
|
|
24
|
+
* )
|
|
25
|
+
* ```
|
|
11
26
|
*/
|
|
12
27
|
export class AssetGroup {
|
|
28
|
+
/** @see create */
|
|
13
29
|
constructor(assetId, controlAsset, inputs, outputs, metadata) {
|
|
14
30
|
this.assetId = assetId;
|
|
15
31
|
this.controlAsset = controlAsset;
|
|
@@ -17,12 +33,31 @@ export class AssetGroup {
|
|
|
17
33
|
this.outputs = outputs;
|
|
18
34
|
this.metadataList = new MetadataList(metadata);
|
|
19
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Create and validate an asset group.
|
|
38
|
+
*
|
|
39
|
+
* @param assetId - Asset id for this group, or `null` for fresh issuance
|
|
40
|
+
* @param controlAsset - Optional control asset reference for (re) issuance
|
|
41
|
+
* @param inputs - Asset inputs in the group
|
|
42
|
+
* @param outputs - Asset outputs in the group
|
|
43
|
+
* @param metadata - Metadata entries associated with the group
|
|
44
|
+
* @returns A validated asset group
|
|
45
|
+
* @throws Error if the group fails validation
|
|
46
|
+
* @see validate
|
|
47
|
+
*/
|
|
20
48
|
static create(assetId, controlAsset, inputs, outputs, metadata) {
|
|
21
49
|
const ag = new AssetGroup(assetId, controlAsset, inputs, outputs, metadata);
|
|
22
50
|
ag.validate();
|
|
23
51
|
return ag;
|
|
24
52
|
}
|
|
25
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Decode an asset group from its hex string form.
|
|
55
|
+
*
|
|
56
|
+
* @param s - Hex-encoded asset group
|
|
57
|
+
* @returns Decoded asset group
|
|
58
|
+
* @throws Error if the string is not valid hex or does not encode a valid asset group
|
|
59
|
+
* @see toString
|
|
60
|
+
*/
|
|
26
61
|
static fromString(s) {
|
|
27
62
|
let buf;
|
|
28
63
|
try {
|
|
@@ -33,6 +68,13 @@ export class AssetGroup {
|
|
|
33
68
|
}
|
|
34
69
|
return AssetGroup.fromBytes(buf);
|
|
35
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Decode an asset group from its serialized bytes.
|
|
73
|
+
*
|
|
74
|
+
* @param buf - Serialized asset group bytes
|
|
75
|
+
* @returns Decoded asset group
|
|
76
|
+
* @throws Error if the buffer is empty or malformed
|
|
77
|
+
*/
|
|
36
78
|
static fromBytes(buf) {
|
|
37
79
|
if (!buf || buf.length === 0) {
|
|
38
80
|
throw new Error("missing asset group");
|
|
@@ -40,12 +82,21 @@ export class AssetGroup {
|
|
|
40
82
|
const reader = new BufferReader(buf);
|
|
41
83
|
return AssetGroup.fromReader(reader);
|
|
42
84
|
}
|
|
43
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Return true when the group represents an issuance.
|
|
87
|
+
*
|
|
88
|
+
* @returns `true` when the group has no asset id
|
|
89
|
+
*/
|
|
44
90
|
isIssuance() {
|
|
45
91
|
return this.assetId === null;
|
|
46
92
|
}
|
|
47
|
-
|
|
48
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Return true when the group represents a reissuance.
|
|
95
|
+
*
|
|
96
|
+
* @returns `true` when the group has an asset id and outputs exceed local inputs
|
|
97
|
+
* @remarks
|
|
98
|
+
* Only local inputs contribute to the comparison; intent-backed inputs contribute `0` here.
|
|
99
|
+
*/
|
|
49
100
|
isReissuance() {
|
|
50
101
|
const sumReducer = (s, { amount }) => s + amount;
|
|
51
102
|
const sumOutputs = this.outputs.reduce(sumReducer, 0n);
|
|
@@ -56,12 +107,23 @@ export class AssetGroup {
|
|
|
56
107
|
.reduce(sumReducer, 0n);
|
|
57
108
|
return !this.isIssuance() && sumInputs < sumOutputs;
|
|
58
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Serialize the asset group to raw bytes.
|
|
112
|
+
*
|
|
113
|
+
* @returns Serialized asset group bytes
|
|
114
|
+
* @see fromBytes
|
|
115
|
+
*/
|
|
59
116
|
serialize() {
|
|
60
117
|
this.validate();
|
|
61
118
|
const writer = new BufferWriter();
|
|
62
119
|
this.serializeTo(writer);
|
|
63
120
|
return writer.toBytes();
|
|
64
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Validate the asset group and its child structures.
|
|
124
|
+
*
|
|
125
|
+
* @throws Error if the group is empty or violates issuance invariants
|
|
126
|
+
*/
|
|
65
127
|
validate() {
|
|
66
128
|
if (this.inputs.length === 0 && this.outputs.length === 0) {
|
|
67
129
|
throw new Error("empty asset group");
|
|
@@ -77,13 +139,33 @@ export class AssetGroup {
|
|
|
77
139
|
}
|
|
78
140
|
}
|
|
79
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Convert the group into its batch-leaf representation for the given intent txid.
|
|
144
|
+
*
|
|
145
|
+
* @param intentTxid - Intent transaction id used to build the leaf input reference
|
|
146
|
+
* @returns Batch-leaf asset group
|
|
147
|
+
* @see AssetInput.createIntent
|
|
148
|
+
*/
|
|
80
149
|
toBatchLeafAssetGroup(intentTxid) {
|
|
81
150
|
const leafInput = AssetInput.createIntent(hex.encode(intentTxid), 0, 0);
|
|
82
151
|
return new AssetGroup(this.assetId, this.controlAsset, [leafInput], this.outputs, this.metadataList.items);
|
|
83
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Encode the asset group to a hex string.
|
|
155
|
+
*
|
|
156
|
+
* @returns Hex-encoded asset group
|
|
157
|
+
* @see fromString
|
|
158
|
+
*/
|
|
84
159
|
toString() {
|
|
85
160
|
return hex.encode(this.serialize());
|
|
86
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Decode an asset group from a binary reader.
|
|
164
|
+
*
|
|
165
|
+
* @param reader - Reader positioned at an asset group
|
|
166
|
+
* @returns Decoded asset group
|
|
167
|
+
* @throws Error if the encoded group is malformed
|
|
168
|
+
*/
|
|
87
169
|
static fromReader(reader) {
|
|
88
170
|
const presence = reader.readByte();
|
|
89
171
|
let assetId = null;
|
|
@@ -104,6 +186,11 @@ export class AssetGroup {
|
|
|
104
186
|
ag.validate();
|
|
105
187
|
return ag;
|
|
106
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Serialize the asset group into an existing binary writer.
|
|
191
|
+
*
|
|
192
|
+
* @param writer - Writer to append the asset group to
|
|
193
|
+
*/
|
|
107
194
|
serializeTo(writer) {
|
|
108
195
|
let presence = 0;
|
|
109
196
|
if (this.assetId !== null) {
|