@arkade-os/sdk 0.4.16 → 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.
|
@@ -25,8 +25,6 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
25
25
|
* const manager = await ContractManager.create({
|
|
26
26
|
* indexerProvider: wallet.indexerProvider,
|
|
27
27
|
* contractRepository: wallet.contractRepository,
|
|
28
|
-
* walletRepository: wallet.walletRepository,
|
|
29
|
-
* getDefaultAddress: () => wallet.getAddress(),
|
|
30
28
|
* });
|
|
31
29
|
*
|
|
32
30
|
* // Create a new VHTLC contract
|
|
@@ -90,7 +88,7 @@ class ContractManager {
|
|
|
90
88
|
const contracts = await this.config.contractRepository.getContracts();
|
|
91
89
|
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
92
90
|
// falling back to a full bootstrap for scripts seen for the first time.
|
|
93
|
-
await this.deltaSyncContracts(contracts);
|
|
91
|
+
await this.deltaSyncContracts(contracts, undefined, true);
|
|
94
92
|
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
95
93
|
// to catch any that the delta window may have missed.
|
|
96
94
|
if (contracts.length > 0) {
|
|
@@ -423,21 +421,39 @@ class ContractManager {
|
|
|
423
421
|
if (contracts.length === 0) {
|
|
424
422
|
return new Map();
|
|
425
423
|
}
|
|
426
|
-
|
|
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;
|
|
427
435
|
}
|
|
428
436
|
/**
|
|
429
437
|
* Incrementally sync virtual outputs for the given contracts.
|
|
430
438
|
* Uses per-script cursors to fetch only what changed since the last sync.
|
|
431
439
|
* Scripts without a cursor are bootstrapped with a full fetch.
|
|
432
440
|
*/
|
|
433
|
-
async deltaSyncContracts(contracts, pageSize) {
|
|
441
|
+
async deltaSyncContracts(contracts, pageSize, force) {
|
|
434
442
|
if (contracts.length === 0)
|
|
435
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
|
+
}
|
|
436
448
|
const cursors = await (0, syncCursors_1.getAllSyncCursors)(this.config.walletRepository);
|
|
437
449
|
// Partition into bootstrap (no cursor) and delta (has cursor) groups.
|
|
438
450
|
const bootstrap = [];
|
|
439
451
|
const delta = [];
|
|
440
452
|
for (const c of contracts) {
|
|
453
|
+
if (force) {
|
|
454
|
+
bootstrap.push(c);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
441
457
|
if (cursors[c.script] !== undefined) {
|
|
442
458
|
delta.push(c);
|
|
443
459
|
}
|
|
@@ -39,6 +39,10 @@ const contractManager_1 = require("../contracts/contractManager");
|
|
|
39
39
|
const handlers_1 = require("../contracts/handlers");
|
|
40
40
|
const helpers_1 = require("../contracts/handlers/helpers");
|
|
41
41
|
const syncCursors_1 = require("../utils/syncCursors");
|
|
42
|
+
// Hardcoded unilateral exit delay for mainnet (~7 days in seconds).
|
|
43
|
+
// Pinned here so that address derivation stays stable for existing mainnet
|
|
44
|
+
// wallets even after the server lowers the delay it advertises.
|
|
45
|
+
const MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
|
|
42
46
|
/**
|
|
43
47
|
* Type guard function to check if an identity has a toReadonly method.
|
|
44
48
|
*/
|
|
@@ -134,10 +138,16 @@ class ReadonlyWallet {
|
|
|
134
138
|
throw new Error("invalid exitTimelock");
|
|
135
139
|
}
|
|
136
140
|
}
|
|
141
|
+
// On mainnet, pin the unilateral exit delay to the historical value so
|
|
142
|
+
// that addresses derived by existing wallets remain stable even if the
|
|
143
|
+
// server starts advertising a shorter delay.
|
|
144
|
+
const unilateralExitDelay = info.network === "bitcoin"
|
|
145
|
+
? MAINNET_UNILATERAL_EXIT_DELAY
|
|
146
|
+
: info.unilateralExitDelay;
|
|
137
147
|
// create unilateral exit timelock
|
|
138
148
|
const exitTimelock = config.exitTimelock ?? {
|
|
139
|
-
value:
|
|
140
|
-
type:
|
|
149
|
+
value: unilateralExitDelay,
|
|
150
|
+
type: unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
141
151
|
};
|
|
142
152
|
// validate boarding timelock passed in config if any
|
|
143
153
|
if (config.boardingTimelock) {
|
|
@@ -292,14 +302,10 @@ class ReadonlyWallet {
|
|
|
292
302
|
* @param filter - Optional flags controlling whether recoverable or unrolled VTXOs are included
|
|
293
303
|
*/
|
|
294
304
|
async getVtxos(filter) {
|
|
295
|
-
const { isDelta, fetchedExtended, address } = await this.syncVtxos();
|
|
296
305
|
const f = filter ?? { withRecoverable: true, withUnrolled: false };
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
? await this.walletRepository.getVtxos(address)
|
|
301
|
-
: fetchedExtended;
|
|
302
|
-
return vtxos.filter((vtxo) => {
|
|
306
|
+
const contractManager = await this.getContractManager();
|
|
307
|
+
const contractsWithVtxos = await contractManager.getContractsWithVtxos();
|
|
308
|
+
return contractsWithVtxos.flatMap(({ vtxos }) => vtxos.filter((vtxo) => {
|
|
303
309
|
if ((0, _1.isSpendable)(vtxo)) {
|
|
304
310
|
if (!f.withRecoverable &&
|
|
305
311
|
((0, _1.isRecoverable)(vtxo) || (0, _1.isExpired)(vtxo))) {
|
|
@@ -308,7 +314,7 @@ class ReadonlyWallet {
|
|
|
308
314
|
return true;
|
|
309
315
|
}
|
|
310
316
|
return !!(f.withUnrolled && vtxo.isUnrolled);
|
|
311
|
-
});
|
|
317
|
+
}));
|
|
312
318
|
}
|
|
313
319
|
/**
|
|
314
320
|
* Return wallet transaction history derived from Arkade state and boarding transactions.
|
|
@@ -778,7 +784,6 @@ class ReadonlyWallet {
|
|
|
778
784
|
indexerProvider: this.indexerProvider,
|
|
779
785
|
contractRepository: this.contractRepository,
|
|
780
786
|
walletRepository: this.walletRepository,
|
|
781
|
-
getDefaultAddress: () => this.getAddress(),
|
|
782
787
|
watcherConfig: this.watcherConfig,
|
|
783
788
|
});
|
|
784
789
|
// Register the wallet's current address as a contract
|
|
@@ -22,8 +22,6 @@ const DEFAULT_PAGE_SIZE = 500;
|
|
|
22
22
|
* const manager = await ContractManager.create({
|
|
23
23
|
* indexerProvider: wallet.indexerProvider,
|
|
24
24
|
* contractRepository: wallet.contractRepository,
|
|
25
|
-
* walletRepository: wallet.walletRepository,
|
|
26
|
-
* getDefaultAddress: () => wallet.getAddress(),
|
|
27
25
|
* });
|
|
28
26
|
*
|
|
29
27
|
* // Create a new VHTLC contract
|
|
@@ -87,7 +85,7 @@ export class ContractManager {
|
|
|
87
85
|
const contracts = await this.config.contractRepository.getContracts();
|
|
88
86
|
// Delta-sync: fetch only virtual outputs that changed since the last cursor,
|
|
89
87
|
// falling back to a full bootstrap for scripts seen for the first time.
|
|
90
|
-
await this.deltaSyncContracts(contracts);
|
|
88
|
+
await this.deltaSyncContracts(contracts, undefined, true);
|
|
91
89
|
// Reconcile the pending frontier: fetch all not-yet-finalized virtual outputs
|
|
92
90
|
// to catch any that the delta window may have missed.
|
|
93
91
|
if (contracts.length > 0) {
|
|
@@ -420,21 +418,39 @@ export class ContractManager {
|
|
|
420
418
|
if (contracts.length === 0) {
|
|
421
419
|
return new Map();
|
|
422
420
|
}
|
|
423
|
-
|
|
421
|
+
// Deduplicate concurrent callers against an in-flight fetch so we don't
|
|
422
|
+
// issue redundant round-trips. Once the fetch settles we clear the
|
|
423
|
+
// reference so the next call triggers a fresh fetch.
|
|
424
|
+
// TODO: can be removed once we fix the persistence layer (address vs scripts)
|
|
425
|
+
if (this.syncVtxosCallInflight) {
|
|
426
|
+
return this.syncVtxosCallInflight;
|
|
427
|
+
}
|
|
428
|
+
this.syncVtxosCallInflight = this.fetchContractVxosFromIndexer(contracts, true, pageSize).finally(() => {
|
|
429
|
+
this.syncVtxosCallInflight = undefined;
|
|
430
|
+
});
|
|
431
|
+
return this.syncVtxosCallInflight;
|
|
424
432
|
}
|
|
425
433
|
/**
|
|
426
434
|
* Incrementally sync virtual outputs for the given contracts.
|
|
427
435
|
* Uses per-script cursors to fetch only what changed since the last sync.
|
|
428
436
|
* Scripts without a cursor are bootstrapped with a full fetch.
|
|
429
437
|
*/
|
|
430
|
-
async deltaSyncContracts(contracts, pageSize) {
|
|
438
|
+
async deltaSyncContracts(contracts, pageSize, force) {
|
|
431
439
|
if (contracts.length === 0)
|
|
432
440
|
return new Map();
|
|
441
|
+
// If forced, we are treating all contracts as boostrapped and we clean the VTXO list
|
|
442
|
+
if (force === true) {
|
|
443
|
+
await Promise.all(contracts.map((contract) => this.config.walletRepository.deleteVtxos(contract.address)));
|
|
444
|
+
}
|
|
433
445
|
const cursors = await getAllSyncCursors(this.config.walletRepository);
|
|
434
446
|
// Partition into bootstrap (no cursor) and delta (has cursor) groups.
|
|
435
447
|
const bootstrap = [];
|
|
436
448
|
const delta = [];
|
|
437
449
|
for (const c of contracts) {
|
|
450
|
+
if (force) {
|
|
451
|
+
bootstrap.push(c);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
438
454
|
if (cursors[c.script] !== undefined) {
|
|
439
455
|
delta.push(c);
|
|
440
456
|
}
|
|
@@ -34,6 +34,10 @@ import { ContractManager } from '../contracts/contractManager.js';
|
|
|
34
34
|
import { contractHandlers } from '../contracts/handlers/index.js';
|
|
35
35
|
import { timelockToSequence } from '../contracts/handlers/helpers.js';
|
|
36
36
|
import { advanceSyncCursors, clearSyncCursors, computeSyncWindow, cursorCutoff, getAllSyncCursors, updateWalletState, } from '../utils/syncCursors.js';
|
|
37
|
+
// Hardcoded unilateral exit delay for mainnet (~7 days in seconds).
|
|
38
|
+
// Pinned here so that address derivation stays stable for existing mainnet
|
|
39
|
+
// wallets even after the server lowers the delay it advertises.
|
|
40
|
+
const MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
|
|
37
41
|
/**
|
|
38
42
|
* Type guard function to check if an identity has a toReadonly method.
|
|
39
43
|
*/
|
|
@@ -129,10 +133,16 @@ export class ReadonlyWallet {
|
|
|
129
133
|
throw new Error("invalid exitTimelock");
|
|
130
134
|
}
|
|
131
135
|
}
|
|
136
|
+
// On mainnet, pin the unilateral exit delay to the historical value so
|
|
137
|
+
// that addresses derived by existing wallets remain stable even if the
|
|
138
|
+
// server starts advertising a shorter delay.
|
|
139
|
+
const unilateralExitDelay = info.network === "bitcoin"
|
|
140
|
+
? MAINNET_UNILATERAL_EXIT_DELAY
|
|
141
|
+
: info.unilateralExitDelay;
|
|
132
142
|
// create unilateral exit timelock
|
|
133
143
|
const exitTimelock = config.exitTimelock ?? {
|
|
134
|
-
value:
|
|
135
|
-
type:
|
|
144
|
+
value: unilateralExitDelay,
|
|
145
|
+
type: unilateralExitDelay < 512n ? "blocks" : "seconds",
|
|
136
146
|
};
|
|
137
147
|
// validate boarding timelock passed in config if any
|
|
138
148
|
if (config.boardingTimelock) {
|
|
@@ -287,14 +297,10 @@ export class ReadonlyWallet {
|
|
|
287
297
|
* @param filter - Optional flags controlling whether recoverable or unrolled VTXOs are included
|
|
288
298
|
*/
|
|
289
299
|
async getVtxos(filter) {
|
|
290
|
-
const { isDelta, fetchedExtended, address } = await this.syncVtxos();
|
|
291
300
|
const f = filter ?? { withRecoverable: true, withUnrolled: false };
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
? await this.walletRepository.getVtxos(address)
|
|
296
|
-
: fetchedExtended;
|
|
297
|
-
return vtxos.filter((vtxo) => {
|
|
301
|
+
const contractManager = await this.getContractManager();
|
|
302
|
+
const contractsWithVtxos = await contractManager.getContractsWithVtxos();
|
|
303
|
+
return contractsWithVtxos.flatMap(({ vtxos }) => vtxos.filter((vtxo) => {
|
|
298
304
|
if (isSpendable(vtxo)) {
|
|
299
305
|
if (!f.withRecoverable &&
|
|
300
306
|
(isRecoverable(vtxo) || isExpired(vtxo))) {
|
|
@@ -303,7 +309,7 @@ export class ReadonlyWallet {
|
|
|
303
309
|
return true;
|
|
304
310
|
}
|
|
305
311
|
return !!(f.withUnrolled && vtxo.isUnrolled);
|
|
306
|
-
});
|
|
312
|
+
}));
|
|
307
313
|
}
|
|
308
314
|
/**
|
|
309
315
|
* Return wallet transaction history derived from Arkade state and boarding transactions.
|
|
@@ -773,7 +779,6 @@ export class ReadonlyWallet {
|
|
|
773
779
|
indexerProvider: this.indexerProvider,
|
|
774
780
|
contractRepository: this.contractRepository,
|
|
775
781
|
walletRepository: this.walletRepository,
|
|
776
|
-
getDefaultAddress: () => this.getAddress(),
|
|
777
782
|
watcherConfig: this.watcherConfig,
|
|
778
783
|
});
|
|
779
784
|
// Register the wallet's current address as a contract
|
|
@@ -118,8 +118,6 @@ export interface ContractManagerConfig {
|
|
|
118
118
|
contractRepository: ContractRepository;
|
|
119
119
|
/** The wallet repository for virtual output storage (single source of truth) */
|
|
120
120
|
walletRepository: WalletRepository;
|
|
121
|
-
/** Function to get the wallet's default Arkade address */
|
|
122
|
-
getDefaultAddress: () => Promise<string>;
|
|
123
121
|
/** Watcher configuration */
|
|
124
122
|
watcherConfig?: Partial<ContractWatcherConfig>;
|
|
125
123
|
}
|
|
@@ -148,8 +146,6 @@ export type CreateContractParams = Omit<Contract, "createdAt" | "state"> & {
|
|
|
148
146
|
* const manager = await ContractManager.create({
|
|
149
147
|
* indexerProvider: wallet.indexerProvider,
|
|
150
148
|
* contractRepository: wallet.contractRepository,
|
|
151
|
-
* walletRepository: wallet.walletRepository,
|
|
152
|
-
* getDefaultAddress: () => wallet.getAddress(),
|
|
153
149
|
* });
|
|
154
150
|
*
|
|
155
151
|
* // Create a new VHTLC contract
|
|
@@ -185,6 +181,7 @@ export declare class ContractManager implements IContractManager {
|
|
|
185
181
|
private initialized;
|
|
186
182
|
private eventCallbacks;
|
|
187
183
|
private stopWatcherFn?;
|
|
184
|
+
private syncVtxosCallInflight?;
|
|
188
185
|
private constructor();
|
|
189
186
|
/**
|
|
190
187
|
* Static factory method for creating a new ContractManager.
|