@arkade-os/sdk 0.4.14 → 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 +287 -215
- 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 +25 -1
- package/dist/cjs/contracts/handlers/vhtlc.js +2 -4
- 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/index.js +6 -0
- package/dist/cjs/identity/seedIdentity.js +5 -5
- package/dist/cjs/identity/singleKey.js +4 -0
- package/dist/cjs/index.js +5 -3
- 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 +25 -4
- 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 +123 -82
- package/dist/cjs/wallet/wallet.js +231 -98
- 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 +24 -1
- package/dist/esm/contracts/handlers/vhtlc.js +3 -5
- 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/index.js +5 -0
- package/dist/esm/identity/seedIdentity.js +5 -5
- package/dist/esm/identity/singleKey.js +4 -0
- package/dist/esm/index.js +3 -2
- 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 +25 -4
- 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 +122 -81
- package/dist/esm/wallet/wallet.js +232 -99
- 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 +11 -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 +36 -0
- package/dist/types/identity/seedIdentity.d.ts +10 -8
- package/dist/types/identity/singleKey.d.ts +4 -0
- package/dist/types/index.d.ts +3 -3
- 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 +18 -10
|
@@ -15,6 +15,7 @@ const ark_1 = require("../providers/ark");
|
|
|
15
15
|
const forfeit_1 = require("../forfeit");
|
|
16
16
|
const validation_1 = require("../tree/validation");
|
|
17
17
|
const validation_2 = require("./validation");
|
|
18
|
+
const identity_1 = require("../identity");
|
|
18
19
|
const _1 = require(".");
|
|
19
20
|
const asset_1 = require("./asset");
|
|
20
21
|
const base_2 = require("../script/base");
|
|
@@ -112,12 +113,12 @@ class ReadonlyWallet {
|
|
|
112
113
|
const serverIsMainnet = info.network === "bitcoin";
|
|
113
114
|
if (identityIsMainnet && !serverIsMainnet) {
|
|
114
115
|
throw new Error(`Network mismatch: identity uses mainnet derivation (coin type 0) ` +
|
|
115
|
-
`but
|
|
116
|
+
`but the Arkade server is on ${info.network}. ` +
|
|
116
117
|
`Create identity with { isMainnet: false } to use testnet derivation.`);
|
|
117
118
|
}
|
|
118
119
|
if (!identityIsMainnet && serverIsMainnet) {
|
|
119
120
|
throw new Error(`Network mismatch: identity uses testnet derivation (coin type 1) ` +
|
|
120
|
-
`but
|
|
121
|
+
`but the Arkade server is on mainnet. ` +
|
|
121
122
|
`Create identity with { isMainnet: true } or omit isMainnet (defaults to mainnet).`);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
@@ -189,6 +190,12 @@ class ReadonlyWallet {
|
|
|
189
190
|
delegatorProvider: config.delegatorProvider,
|
|
190
191
|
};
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Create a readonly wallet for querying balances, addresses, and history.
|
|
195
|
+
*
|
|
196
|
+
* @param config - Readonly wallet configuration
|
|
197
|
+
* @returns A readonly wallet instance
|
|
198
|
+
*/
|
|
192
199
|
static async create(config) {
|
|
193
200
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
194
201
|
if (!pubkey) {
|
|
@@ -207,12 +214,17 @@ class ReadonlyWallet {
|
|
|
207
214
|
get defaultContractScript() {
|
|
208
215
|
return base_1.hex.encode(this.offchainTapscript.pkScript);
|
|
209
216
|
}
|
|
217
|
+
/** Returns the wallet's Arkade address. */
|
|
210
218
|
async getAddress() {
|
|
211
219
|
return this.arkAddress.encode();
|
|
212
220
|
}
|
|
221
|
+
/** Returns the onchain boarding address used to move funds into Arkade. */
|
|
213
222
|
async getBoardingAddress() {
|
|
214
223
|
return this.boardingTapscript.onchainAddress(this.network);
|
|
215
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Return the wallet's combined onchain and offchain balances.
|
|
227
|
+
*/
|
|
216
228
|
async getBalance() {
|
|
217
229
|
const [boardingUtxos, vtxos] = await Promise.all([
|
|
218
230
|
this.getBoardingUtxos(),
|
|
@@ -244,7 +256,7 @@ class ReadonlyWallet {
|
|
|
244
256
|
.reduce((sum, coin) => sum + coin.value, 0);
|
|
245
257
|
const totalBoarding = confirmed + unconfirmed;
|
|
246
258
|
const totalOffchain = settled + preconfirmed + recoverable;
|
|
247
|
-
// aggregate asset balances from spendable
|
|
259
|
+
// aggregate asset balances from spendable virtual outputs
|
|
248
260
|
const assetBalances = new Map();
|
|
249
261
|
for (const vtxo of vtxos) {
|
|
250
262
|
if (!(0, _1.isSpendable)(vtxo))
|
|
@@ -274,11 +286,16 @@ class ReadonlyWallet {
|
|
|
274
286
|
assets,
|
|
275
287
|
};
|
|
276
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Return virtual outputs tracked by the wallet.
|
|
291
|
+
*
|
|
292
|
+
* @param filter - Optional flags controlling whether recoverable or unrolled VTXOs are included
|
|
293
|
+
*/
|
|
277
294
|
async getVtxos(filter) {
|
|
278
295
|
const { isDelta, fetchedExtended, address } = await this.syncVtxos();
|
|
279
296
|
const f = filter ?? { withRecoverable: true, withUnrolled: false };
|
|
280
297
|
// For delta syncs, read the full merged set from cache so old
|
|
281
|
-
//
|
|
298
|
+
// Virtual outputs that weren't in the delta are still returned.
|
|
282
299
|
const vtxos = isDelta
|
|
283
300
|
? await this.walletRepository.getVtxos(address)
|
|
284
301
|
: fetchedExtended;
|
|
@@ -293,8 +310,11 @@ class ReadonlyWallet {
|
|
|
293
310
|
return !!(f.withUnrolled && vtxo.isUnrolled);
|
|
294
311
|
});
|
|
295
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Return wallet transaction history derived from Arkade state and boarding transactions.
|
|
315
|
+
*/
|
|
296
316
|
async getTransactionHistory() {
|
|
297
|
-
// Delta-sync
|
|
317
|
+
// Delta-sync virtual outputs into cache, then build history from the cache.
|
|
298
318
|
const { isDelta, fetchedExtended, address } = await this.syncVtxos();
|
|
299
319
|
const allVtxos = isDelta
|
|
300
320
|
? await this.walletRepository.getVtxos(address)
|
|
@@ -306,7 +326,7 @@ class ReadonlyWallet {
|
|
|
306
326
|
return (0, transactionHistory_1.buildTransactionHistory)(allVtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
|
|
307
327
|
}
|
|
308
328
|
/**
|
|
309
|
-
* Delta-sync wallet
|
|
329
|
+
* Delta-sync wallet virtual outputs: fetch only changed virtual outputs since the last
|
|
310
330
|
* cursor, or do a full bootstrap when no cursor exists. Upserts
|
|
311
331
|
* the result into the cache and advances the sync cursors.
|
|
312
332
|
*
|
|
@@ -345,6 +365,19 @@ class ReadonlyWallet {
|
|
|
345
365
|
}
|
|
346
366
|
const requestStartedAt = Date.now();
|
|
347
367
|
const allVtxos = [];
|
|
368
|
+
const extendWithScript = (vtxo) => {
|
|
369
|
+
const vtxoScript = vtxo.script
|
|
370
|
+
? scriptMap.get(vtxo.script)
|
|
371
|
+
: undefined;
|
|
372
|
+
if (!vtxoScript)
|
|
373
|
+
return undefined;
|
|
374
|
+
return {
|
|
375
|
+
...vtxo,
|
|
376
|
+
forfeitTapLeafScript: vtxoScript.forfeit(),
|
|
377
|
+
intentTapLeafScript: vtxoScript.forfeit(),
|
|
378
|
+
tapTree: vtxoScript.encode(),
|
|
379
|
+
};
|
|
380
|
+
};
|
|
348
381
|
// Full fetch for scripts with no cursor.
|
|
349
382
|
if (bootstrapScripts.length > 0) {
|
|
350
383
|
const response = await this.indexerProvider.getVtxos({
|
|
@@ -366,49 +399,79 @@ class ReadonlyWallet {
|
|
|
366
399
|
allVtxos.push(...response.vtxos);
|
|
367
400
|
}
|
|
368
401
|
}
|
|
369
|
-
// Extend every fetched
|
|
402
|
+
// Extend every fetched virtual output and upsert into the cache.
|
|
370
403
|
const fetchedExtended = [];
|
|
371
404
|
for (const vtxo of allVtxos) {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (!vtxoScript)
|
|
376
|
-
continue;
|
|
377
|
-
fetchedExtended.push({
|
|
378
|
-
...vtxo,
|
|
379
|
-
forfeitTapLeafScript: vtxoScript.forfeit(),
|
|
380
|
-
intentTapLeafScript: vtxoScript.forfeit(),
|
|
381
|
-
tapTree: vtxoScript.encode(),
|
|
382
|
-
});
|
|
405
|
+
const extended = extendWithScript(vtxo);
|
|
406
|
+
if (extended)
|
|
407
|
+
fetchedExtended.push(extended);
|
|
383
408
|
}
|
|
384
|
-
// Save
|
|
409
|
+
// Save virtual outputs first, then advance cursors only on success.
|
|
385
410
|
const cutoff = (0, syncCursors_1.cursorCutoff)(requestStartedAt);
|
|
386
411
|
await this.walletRepository.saveVtxos(address, fetchedExtended);
|
|
387
412
|
await (0, syncCursors_1.advanceSyncCursors)(this.walletRepository, Object.fromEntries(allScripts.map((s) => [s, cutoff])));
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
413
|
+
// Delta-sync reconciliation: full re-fetch for delta scripts.
|
|
414
|
+
//
|
|
415
|
+
// The delta fetch (above) only returns virtual outputs changed after the
|
|
416
|
+
// cursor, so it can miss preconfirmed virtual outputs that were consumed
|
|
417
|
+
// by a round between syncs. Rather than layering targeted
|
|
418
|
+
// queries (pendingOnly, spendableOnly) with pagination guards
|
|
419
|
+
// and set algebra, we perform a single unfiltered re-fetch for
|
|
420
|
+
// delta scripts. This is slightly more data over the wire but
|
|
421
|
+
// gives us complete, authoritative state in one call and keeps
|
|
422
|
+
// the reconciliation logic simple.
|
|
423
|
+
//
|
|
424
|
+
// Any cached non-spent virtual output that is absent from the full
|
|
425
|
+
// result set is marked spent; any virtual output whose state changed
|
|
426
|
+
// (e.g. preconfirmed → settled) is updated in place.
|
|
391
427
|
if (hasDelta) {
|
|
392
|
-
const { vtxos:
|
|
428
|
+
const { vtxos: fullVtxos, page: fullPage } = await this.indexerProvider.getVtxos({
|
|
393
429
|
scripts: deltaScripts,
|
|
394
|
-
pendingOnly: true,
|
|
395
430
|
});
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
431
|
+
// Reconciliation is best-effort: if the response is
|
|
432
|
+
// paginated we don't have a complete picture, so we skip
|
|
433
|
+
// rather than act on partial data. Wallets with enough
|
|
434
|
+
// virtual outputs to exceed a single page rely solely on the
|
|
435
|
+
// cursor-based delta mechanism for state updates.
|
|
436
|
+
const fullSetComplete = !fullPage || fullPage.total <= 1;
|
|
437
|
+
if (fullSetComplete) {
|
|
438
|
+
const fullOutpoints = new Map(fullVtxos.map((v) => [`${v.txid}:${v.vout}`, v]));
|
|
439
|
+
const deltaScriptSet = new Set(deltaScripts);
|
|
440
|
+
const cachedVtxos = await this.walletRepository.getVtxos(address);
|
|
441
|
+
const reconciledExtended = [];
|
|
442
|
+
for (const cached of cachedVtxos) {
|
|
443
|
+
if (!cached.script ||
|
|
444
|
+
!deltaScriptSet.has(cached.script) ||
|
|
445
|
+
cached.isSpent) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const outpoint = `${cached.txid}:${cached.vout}`;
|
|
449
|
+
const fresh = fullOutpoints.get(outpoint);
|
|
450
|
+
if (!fresh) {
|
|
451
|
+
// Server no longer knows about this virtual output —
|
|
452
|
+
// it was spent between syncs.
|
|
453
|
+
reconciledExtended.push({
|
|
454
|
+
...cached,
|
|
455
|
+
isSpent: true,
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const extended = extendWithScript(fresh);
|
|
460
|
+
if (extended &&
|
|
461
|
+
extended.virtualStatus.state !==
|
|
462
|
+
cached.virtualStatus.state) {
|
|
463
|
+
// State transitioned (e.g. preconfirmed →
|
|
464
|
+
// settled) — update the cached entry.
|
|
465
|
+
reconciledExtended.push(extended);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (reconciledExtended.length > 0) {
|
|
469
|
+
console.warn(`[ark-sdk] delta sync: reconciled ${reconciledExtended.length} stale VTXO(s) via full re-fetch`);
|
|
470
|
+
await this.walletRepository.saveVtxos(address, reconciledExtended);
|
|
471
|
+
}
|
|
409
472
|
}
|
|
410
|
-
|
|
411
|
-
|
|
473
|
+
else {
|
|
474
|
+
console.warn("[ark-sdk] delta sync: skipping reconciliation — full re-fetch was paginated");
|
|
412
475
|
}
|
|
413
476
|
}
|
|
414
477
|
return {
|
|
@@ -418,12 +481,15 @@ class ReadonlyWallet {
|
|
|
418
481
|
};
|
|
419
482
|
}
|
|
420
483
|
/**
|
|
421
|
-
* Clear all
|
|
484
|
+
* Clear all virtual output sync cursors, forcing a full re-bootstrap on next sync.
|
|
422
485
|
* Useful for recovery after indexer reprocessing or debugging.
|
|
423
486
|
*/
|
|
424
487
|
async clearSyncCursors() {
|
|
425
488
|
await (0, syncCursors_1.clearSyncCursors)(this.walletRepository);
|
|
426
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Build a transaction history view for the wallet's boarding address.
|
|
492
|
+
*/
|
|
427
493
|
async getBoardingTxs() {
|
|
428
494
|
const utxos = [];
|
|
429
495
|
const commitmentsToIgnore = new Set();
|
|
@@ -494,16 +560,25 @@ class ReadonlyWallet {
|
|
|
494
560
|
commitmentsToIgnore,
|
|
495
561
|
};
|
|
496
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* Fetch and cache onchain inputs (UTXOs) received at the boarding address.
|
|
565
|
+
*/
|
|
497
566
|
async getBoardingUtxos() {
|
|
498
567
|
const boardingAddress = await this.getBoardingAddress();
|
|
499
568
|
const boardingUtxos = await this.onchainProvider.getCoins(boardingAddress);
|
|
500
569
|
const utxos = boardingUtxos.map((utxo) => {
|
|
501
570
|
return (0, utils_1.extendCoin)(this, utxo);
|
|
502
571
|
});
|
|
503
|
-
// Save
|
|
572
|
+
// Save boarding inputs using unified repository
|
|
504
573
|
await this.walletRepository.saveUtxos(boardingAddress, utxos);
|
|
505
574
|
return utxos;
|
|
506
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Subscribe to onchain and offchain notifications for newly received funds.
|
|
578
|
+
*
|
|
579
|
+
* @param eventCallback - Callback invoked when matching funds are detected
|
|
580
|
+
* @returns A function that stops the subscriptions
|
|
581
|
+
*/
|
|
507
582
|
async notifyIncomingFunds(eventCallback) {
|
|
508
583
|
const arkAddress = await this.getAddress();
|
|
509
584
|
const boardingAddress = await this.getBoardingAddress();
|
|
@@ -514,11 +589,11 @@ class ReadonlyWallet {
|
|
|
514
589
|
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
515
590
|
};
|
|
516
591
|
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
517
|
-
// find all
|
|
592
|
+
// find all onchain outputs belonging to our boarding address
|
|
518
593
|
const coins = txs
|
|
519
594
|
// filter txs where address is in output
|
|
520
595
|
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
521
|
-
// return
|
|
596
|
+
// return boarding input as Coin
|
|
522
597
|
.map((tx) => {
|
|
523
598
|
const { txid, status } = tx;
|
|
524
599
|
const vout = findVoutOnTx(tx);
|
|
@@ -543,8 +618,8 @@ class ReadonlyWallet {
|
|
|
543
618
|
};
|
|
544
619
|
// Handle subscription updates asynchronously without blocking.
|
|
545
620
|
// Note: subscription covers all wallet scripts (default + delegate),
|
|
546
|
-
// but we can't determine which script each
|
|
547
|
-
// subscription event.
|
|
621
|
+
// but we can't determine which script each virtual output belongs to from the
|
|
622
|
+
// subscription event. Virtual outputs are extended with the current offchainTapscript;
|
|
548
623
|
// this is for notification/display only — not for spending.
|
|
549
624
|
// For correct extension metadata, use getVtxos() which queries per-script.
|
|
550
625
|
(async () => {
|
|
@@ -571,8 +646,9 @@ class ReadonlyWallet {
|
|
|
571
646
|
};
|
|
572
647
|
return stopFunc;
|
|
573
648
|
}
|
|
649
|
+
/** Fetch Arkade transaction ids that are still pending final settlement. */
|
|
574
650
|
async fetchPendingTxs() {
|
|
575
|
-
// get non-swept
|
|
651
|
+
// get non-swept virtual outputs, rely on the indexer only in case DB doesn't have the right state
|
|
576
652
|
const scripts = await this.getWalletScripts();
|
|
577
653
|
let { vtxos } = await this.indexerProvider.getVtxos({
|
|
578
654
|
scripts,
|
|
@@ -613,7 +689,7 @@ class ReadonlyWallet {
|
|
|
613
689
|
}
|
|
614
690
|
/**
|
|
615
691
|
* Build a map of scriptHex → VtxoScript for all wallet contracts,
|
|
616
|
-
* so
|
|
692
|
+
* so virtual outputs can be extended with the correct tapscript per contract.
|
|
617
693
|
*/
|
|
618
694
|
async getScriptMap() {
|
|
619
695
|
const map = new Map();
|
|
@@ -726,7 +802,7 @@ class ReadonlyWallet {
|
|
|
726
802
|
address: await this.getAddress(),
|
|
727
803
|
state: "active",
|
|
728
804
|
});
|
|
729
|
-
// Also register the non-delegate version so old
|
|
805
|
+
// Also register the non-delegate version so old virtual outputs remain visible
|
|
730
806
|
const nonDelegateScript = new default_1.DefaultVtxo.Script({
|
|
731
807
|
pubKey: delegateScript.options.pubKey,
|
|
732
808
|
serverPubKey: delegateScript.options.serverPubKey,
|
|
@@ -764,6 +840,7 @@ class ReadonlyWallet {
|
|
|
764
840
|
}
|
|
765
841
|
return manager;
|
|
766
842
|
}
|
|
843
|
+
/** Dispose wallet-owned managers and release background resources. */
|
|
767
844
|
async dispose() {
|
|
768
845
|
const manager = this._contractManager ??
|
|
769
846
|
(this._contractManagerInitializing
|
|
@@ -773,30 +850,31 @@ class ReadonlyWallet {
|
|
|
773
850
|
this._contractManager = undefined;
|
|
774
851
|
this._contractManagerInitializing = undefined;
|
|
775
852
|
}
|
|
853
|
+
/** Async-dispose hook that forwards to `dispose()`. */
|
|
776
854
|
async [Symbol.asyncDispose]() {
|
|
777
855
|
await this.dispose();
|
|
778
856
|
}
|
|
779
857
|
}
|
|
780
858
|
exports.ReadonlyWallet = ReadonlyWallet;
|
|
781
859
|
/**
|
|
782
|
-
* Main wallet implementation for Bitcoin transactions with
|
|
783
|
-
* The wallet does not store any data locally and relies on
|
|
784
|
-
* providers to fetch
|
|
860
|
+
* Main wallet implementation for Bitcoin transactions with Arkade protocol support.
|
|
861
|
+
* The wallet does not store any data locally and relies on Arkade and onchain
|
|
862
|
+
* providers to fetch onchain and virtual outputs.
|
|
785
863
|
*
|
|
786
864
|
* @example
|
|
787
865
|
* ```typescript
|
|
788
866
|
* // Create a wallet with URL configuration
|
|
789
867
|
* const wallet = await Wallet.create({
|
|
790
|
-
* identity:
|
|
791
|
-
* arkServerUrl: 'https://
|
|
868
|
+
* identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
|
|
869
|
+
* arkServerUrl: 'https://arkade.computer',
|
|
792
870
|
* esploraUrl: 'https://mempool.space/api'
|
|
793
871
|
* });
|
|
794
872
|
*
|
|
795
873
|
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
796
874
|
* const wallet = await Wallet.create({
|
|
797
|
-
* identity:
|
|
798
|
-
* arkProvider: new ExpoArkProvider('https://
|
|
799
|
-
* indexerProvider: new ExpoIndexerProvider('https://
|
|
875
|
+
* identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
|
|
876
|
+
* arkProvider: new ExpoArkProvider('https://arkade.computer'),
|
|
877
|
+
* indexerProvider: new ExpoIndexerProvider('https://arkade.computer'),
|
|
800
878
|
* esploraUrl: 'https://mempool.space/api'
|
|
801
879
|
* });
|
|
802
880
|
*
|
|
@@ -805,9 +883,9 @@ exports.ReadonlyWallet = ReadonlyWallet;
|
|
|
805
883
|
* const boardingAddress = await wallet.getBoardingAddress();
|
|
806
884
|
*
|
|
807
885
|
* // Send bitcoin
|
|
808
|
-
* const txid = await wallet.
|
|
809
|
-
* address: '
|
|
810
|
-
* amount: 50000
|
|
886
|
+
* const txid = await wallet.send({
|
|
887
|
+
* address: 'ark1q...',
|
|
888
|
+
* amount: 50000,
|
|
811
889
|
* });
|
|
812
890
|
* ```
|
|
813
891
|
*/
|
|
@@ -836,7 +914,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
836
914
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
837
915
|
this.forfeitPubkey = forfeitPubkey;
|
|
838
916
|
/**
|
|
839
|
-
* Async mutex that serializes all operations submitting VTXOs to the
|
|
917
|
+
* Async mutex that serializes all operations submitting VTXOs to the Arkade
|
|
840
918
|
* server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
|
|
841
919
|
* background renewal from racing with user-initiated transactions for the
|
|
842
920
|
* same VTXO inputs.
|
|
@@ -916,6 +994,19 @@ class Wallet extends ReadonlyWallet {
|
|
|
916
994
|
await super.dispose();
|
|
917
995
|
}
|
|
918
996
|
}
|
|
997
|
+
/**
|
|
998
|
+
* Create a full wallet and initialize its background managers.
|
|
999
|
+
*
|
|
1000
|
+
* @param config - Wallet configuration
|
|
1001
|
+
* @returns A wallet ready to query balances and send transactions
|
|
1002
|
+
* @example
|
|
1003
|
+
* ```typescript
|
|
1004
|
+
* const wallet = await Wallet.create({
|
|
1005
|
+
* identity,
|
|
1006
|
+
* arkServerUrl: 'https://arkade.computer',
|
|
1007
|
+
* });
|
|
1008
|
+
* ```
|
|
1009
|
+
*/
|
|
919
1010
|
static async create(config) {
|
|
920
1011
|
const pubkey = await config.identity.xOnlyPublicKey();
|
|
921
1012
|
if (!pubkey) {
|
|
@@ -947,7 +1038,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
947
1038
|
* @returns A readonly wallet with the same configuration but readonly identity
|
|
948
1039
|
* @example
|
|
949
1040
|
* ```typescript
|
|
950
|
-
* const wallet = await Wallet.create({ identity:
|
|
1041
|
+
* const wallet = await Wallet.create({ identity: MnemonicIdentity.fromMnemonic('abandon abandon...'), ... });
|
|
951
1042
|
* const readonlyWallet = await wallet.toReadonly();
|
|
952
1043
|
*
|
|
953
1044
|
* // Can query balance and addresses
|
|
@@ -955,7 +1046,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
955
1046
|
* const address = await readonlyWallet.getAddress();
|
|
956
1047
|
*
|
|
957
1048
|
* // But cannot send transactions (type error)
|
|
958
|
-
* // readonlyWallet.
|
|
1049
|
+
* // readonlyWallet.send(...); // TypeScript error
|
|
959
1050
|
* ```
|
|
960
1051
|
*/
|
|
961
1052
|
async toReadonly() {
|
|
@@ -965,19 +1056,22 @@ class Wallet extends ReadonlyWallet {
|
|
|
965
1056
|
: this.identity; // Identity extends ReadonlyIdentity, so this is safe
|
|
966
1057
|
return new ReadonlyWallet(readonlyIdentity, this.network, this.onchainProvider, this.indexerProvider, this.arkServerPublicKey, this.offchainTapscript, this.boardingTapscript, this.dustAmount, this.walletRepository, this.contractRepository, this.delegatorProvider, this.watcherConfig);
|
|
967
1058
|
}
|
|
1059
|
+
/** Returns the delegator manager when delegation support is configured. */
|
|
968
1060
|
async getDelegatorManager() {
|
|
969
1061
|
return this._delegatorManager;
|
|
970
1062
|
}
|
|
971
1063
|
/**
|
|
972
|
-
*
|
|
973
|
-
*
|
|
1064
|
+
* Send bitcoin to an Arkade address.
|
|
1065
|
+
*
|
|
1066
|
+
* @deprecated Use `send`.
|
|
1067
|
+
* @param params - Send parameters
|
|
974
1068
|
*/
|
|
975
1069
|
async sendBitcoin(params) {
|
|
976
1070
|
if (params.amount <= 0) {
|
|
977
1071
|
throw new Error("Amount must be positive");
|
|
978
1072
|
}
|
|
979
1073
|
if (!(0, arkTransaction_1.isValidArkAddress)(params.address)) {
|
|
980
|
-
throw new Error("Invalid
|
|
1074
|
+
throw new Error("Invalid Arkade address " + params.address);
|
|
981
1075
|
}
|
|
982
1076
|
if (params.selectedVtxos && params.selectedVtxos.length > 0) {
|
|
983
1077
|
return this._withTxLock(async () => {
|
|
@@ -1022,6 +1116,13 @@ class Wallet extends ReadonlyWallet {
|
|
|
1022
1116
|
amount: params.amount,
|
|
1023
1117
|
});
|
|
1024
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Settle boarding inputs and/or virtual outputs into a finalized mainnet transaction.
|
|
1121
|
+
*
|
|
1122
|
+
* @param params - Optional settlement inputs and outputs. When omitted, the wallet settles all eligible funds.
|
|
1123
|
+
* @param eventCallback - Optional callback invoked for settlement stream events.
|
|
1124
|
+
* @returns The finalized Arkade transaction id
|
|
1125
|
+
*/
|
|
1025
1126
|
async settle(params, eventCallback) {
|
|
1026
1127
|
return this._withTxLock(() => this._settleImpl(params, eventCallback));
|
|
1027
1128
|
}
|
|
@@ -1039,7 +1140,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1039
1140
|
}
|
|
1040
1141
|
}
|
|
1041
1142
|
}
|
|
1042
|
-
// if no params are provided, use all non
|
|
1143
|
+
// if no params are provided, use all non-expired boarding inputs and offchain virtual outputs as inputs
|
|
1043
1144
|
// and send all to the offchain address
|
|
1044
1145
|
if (!params) {
|
|
1045
1146
|
const { fees } = await this.arkProvider.getInfo();
|
|
@@ -1060,7 +1161,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1060
1161
|
amount: BigInt(utxo.value),
|
|
1061
1162
|
});
|
|
1062
1163
|
if (inputFee.value >= utxo.value) {
|
|
1063
|
-
// skip if fees are greater than the
|
|
1164
|
+
// skip if fees are greater than the boarding input value
|
|
1064
1165
|
continue;
|
|
1065
1166
|
}
|
|
1066
1167
|
filteredBoardingUtxos.push(utxo);
|
|
@@ -1081,7 +1182,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1081
1182
|
: new Date(),
|
|
1082
1183
|
});
|
|
1083
1184
|
if (inputFee.value >= vtxo.value) {
|
|
1084
|
-
// skip if fees are greater than the
|
|
1185
|
+
// skip if fees are greater than the virtual output value
|
|
1085
1186
|
continue;
|
|
1086
1187
|
}
|
|
1087
1188
|
filteredVtxos.push(vtxo);
|
|
@@ -1172,7 +1273,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1172
1273
|
const assetPacket = (0, asset_1.createAssetPacket)(assetInputs, recipients);
|
|
1173
1274
|
outputs.push(extension_1.Extension.create([assetPacket]).txOut());
|
|
1174
1275
|
}
|
|
1175
|
-
// session holds the state of the musig2 signing process of the
|
|
1276
|
+
// session holds the state of the musig2 signing process of the virtual output tree
|
|
1176
1277
|
let session;
|
|
1177
1278
|
const signingPublicKeys = [];
|
|
1178
1279
|
if (hasOffchainOutputs) {
|
|
@@ -1223,7 +1324,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1223
1324
|
for (const input of inputs) {
|
|
1224
1325
|
// check if the input is an offchain "virtual" coin
|
|
1225
1326
|
const vtxo = vtxos.find((vtxo) => vtxo.txid === input.txid && vtxo.vout === input.vout);
|
|
1226
|
-
// boarding
|
|
1327
|
+
// boarding input, we need to sign the settlement tx
|
|
1227
1328
|
if (!vtxo) {
|
|
1228
1329
|
for (let i = 0; i < settlementPsbt.inputsLength; i++) {
|
|
1229
1330
|
const settlementInput = settlementPsbt.getInput(i);
|
|
@@ -1301,11 +1402,12 @@ class Wallet extends ReadonlyWallet {
|
|
|
1301
1402
|
}
|
|
1302
1403
|
}
|
|
1303
1404
|
/**
|
|
1304
|
-
*
|
|
1405
|
+
* Create a batch event handler for settlement flows.
|
|
1406
|
+
*
|
|
1305
1407
|
* @param intentId - The intent ID.
|
|
1306
|
-
* @param inputs -
|
|
1307
|
-
* @param
|
|
1308
|
-
* @param
|
|
1408
|
+
* @param inputs - Inputs used by the intent.
|
|
1409
|
+
* @param expectedRecipients - Expected recipients to validate in the virtual output tree.
|
|
1410
|
+
* @param session - Optional musig2 signing session. When omitted, signing steps are skipped.
|
|
1309
1411
|
*/
|
|
1310
1412
|
createBatchHandler(intentId, inputs, expectedRecipients, session) {
|
|
1311
1413
|
let sweepTapTreeRoot;
|
|
@@ -1319,7 +1421,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1319
1421
|
for (const idHash of event.intentIdHashes) {
|
|
1320
1422
|
if (idHash === intentIdHashStr) {
|
|
1321
1423
|
if (!this.arkProvider) {
|
|
1322
|
-
throw new Error("
|
|
1424
|
+
throw new Error("Arkade provider not configured");
|
|
1323
1425
|
}
|
|
1324
1426
|
await this.arkProvider.confirmRegistration(intentId);
|
|
1325
1427
|
skip = false;
|
|
@@ -1352,10 +1454,10 @@ class Wallet extends ReadonlyWallet {
|
|
|
1352
1454
|
// not a cosigner, skip the signing
|
|
1353
1455
|
return { skip: true };
|
|
1354
1456
|
}
|
|
1355
|
-
// validate the unsigned
|
|
1457
|
+
// validate the unsigned virtual output tree
|
|
1356
1458
|
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
1357
1459
|
(0, validation_1.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
1358
|
-
// validate that all expected receivers are in the
|
|
1460
|
+
// validate that all expected receivers are in the virtual output tree with correct amounts and assets
|
|
1359
1461
|
if (expectedRecipients && expectedRecipients.length > 0) {
|
|
1360
1462
|
(0, validation_2.validateBatchRecipients)(commitmentTx, vtxoTree.leaves(), expectedRecipients, this.network);
|
|
1361
1463
|
}
|
|
@@ -1456,7 +1558,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1456
1558
|
/**
|
|
1457
1559
|
* Finalizes pending transactions by retrieving them from the server and finalizing each one.
|
|
1458
1560
|
* Skips the server check entirely when no send was interrupted (no pending tx flag set).
|
|
1459
|
-
* @param vtxos - Optional list of
|
|
1561
|
+
* @param vtxos - Optional list of virtual outputs to use instead of retrieving them from the server
|
|
1460
1562
|
* @returns Array of transaction IDs that were finalized
|
|
1461
1563
|
*/
|
|
1462
1564
|
async finalizePendingTxs(vtxos) {
|
|
@@ -1555,13 +1657,13 @@ class Wallet extends ReadonlyWallet {
|
|
|
1555
1657
|
/**
|
|
1556
1658
|
* Send BTC and/or assets to one or more recipients.
|
|
1557
1659
|
*
|
|
1558
|
-
* @param
|
|
1559
|
-
* @returns Promise resolving to the
|
|
1660
|
+
* @param args - Recipients with their addresses, BTC amounts, and assets
|
|
1661
|
+
* @returns Promise resolving to the Arkade transaction ID
|
|
1560
1662
|
*
|
|
1561
1663
|
* @example
|
|
1562
1664
|
* ```typescript
|
|
1563
1665
|
* const txid = await wallet.send({
|
|
1564
|
-
* address: '
|
|
1666
|
+
* address: 'ark1q...',
|
|
1565
1667
|
* amount: 1000, // (optional, default to dust) btc amount to send to the output
|
|
1566
1668
|
* assets: [{ assetId: 'abc123...', amount: 50 }] // (optional) list of assets to send
|
|
1567
1669
|
* });
|
|
@@ -1709,8 +1811,8 @@ class Wallet extends ReadonlyWallet {
|
|
|
1709
1811
|
}
|
|
1710
1812
|
/**
|
|
1711
1813
|
* Build an offchain transaction from the given inputs and outputs,
|
|
1712
|
-
* sign it, submit to the
|
|
1713
|
-
* @returns The
|
|
1814
|
+
* sign it, submit to the Arkade provider, and finalize.
|
|
1815
|
+
* @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
|
|
1714
1816
|
*/
|
|
1715
1817
|
async buildAndSubmitOffchainTx(inputs, outputs) {
|
|
1716
1818
|
const offchainTx = (0, arkTransaction_1.buildOffchainTx)(inputs.map((input) => {
|
|
@@ -1719,16 +1821,47 @@ class Wallet extends ReadonlyWallet {
|
|
|
1719
1821
|
tapLeafScript: input.forfeitTapLeafScript,
|
|
1720
1822
|
};
|
|
1721
1823
|
}), outputs, this.serverUnrollScript);
|
|
1722
|
-
|
|
1824
|
+
let signedVirtualTx;
|
|
1825
|
+
let userSignedCheckpoints;
|
|
1826
|
+
if ((0, identity_1.isBatchSignable)(this.identity)) {
|
|
1827
|
+
// Batch-sign arkTx + all checkpoints in one wallet popup.
|
|
1828
|
+
// Clone so the provider can't mutate originals before submitTx.
|
|
1829
|
+
const requests = [
|
|
1830
|
+
{ tx: offchainTx.arkTx.clone() },
|
|
1831
|
+
...offchainTx.checkpoints.map((c) => ({ tx: c.clone() })),
|
|
1832
|
+
];
|
|
1833
|
+
const signed = await this.identity.signMultiple(requests);
|
|
1834
|
+
if (signed.length !== requests.length) {
|
|
1835
|
+
throw new Error(`signMultiple returned ${signed.length} transactions, expected ${requests.length}`);
|
|
1836
|
+
}
|
|
1837
|
+
const [firstSignedTx, ...signedCheckpoints] = signed;
|
|
1838
|
+
signedVirtualTx = firstSignedTx;
|
|
1839
|
+
userSignedCheckpoints = signedCheckpoints;
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
signedVirtualTx = await this.identity.sign(offchainTx.arkTx);
|
|
1843
|
+
}
|
|
1723
1844
|
// Mark pending before submitting — if we crash between submit and
|
|
1724
1845
|
// finalize, the next init will recover via finalizePendingTxs.
|
|
1725
1846
|
await this.setPendingTxFlag(true);
|
|
1726
1847
|
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(base_1.base64.encode(signedVirtualTx.toPSBT()), offchainTx.checkpoints.map((c) => base_1.base64.encode(c.toPSBT())));
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1848
|
+
let finalCheckpoints;
|
|
1849
|
+
if (userSignedCheckpoints) {
|
|
1850
|
+
// Merge pre-signed user signatures onto server-signed checkpoints
|
|
1851
|
+
finalCheckpoints = signedCheckpointTxs.map((c, i) => {
|
|
1852
|
+
const serverSigned = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
1853
|
+
(0, arkTransaction_1.combineTapscriptSigs)(userSignedCheckpoints[i], serverSigned);
|
|
1854
|
+
return base_1.base64.encode(serverSigned.toPSBT());
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
else {
|
|
1858
|
+
// Legacy: sign each checkpoint individually (N popups)
|
|
1859
|
+
finalCheckpoints = await Promise.all(signedCheckpointTxs.map(async (c) => {
|
|
1860
|
+
const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(c));
|
|
1861
|
+
const signedCheckpoint = await this.identity.sign(tx);
|
|
1862
|
+
return base_1.base64.encode(signedCheckpoint.toPSBT());
|
|
1863
|
+
}));
|
|
1864
|
+
}
|
|
1732
1865
|
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
1733
1866
|
try {
|
|
1734
1867
|
await this.setPendingTxFlag(false);
|
|
@@ -1738,7 +1871,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1738
1871
|
}
|
|
1739
1872
|
return { arkTxid, signedCheckpointTxs };
|
|
1740
1873
|
}
|
|
1741
|
-
// mark
|
|
1874
|
+
// mark virtual outputs as spent, save change outputs if any
|
|
1742
1875
|
async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, changeAssets) {
|
|
1743
1876
|
try {
|
|
1744
1877
|
const spentVtxos = [];
|
|
@@ -1786,7 +1919,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1786
1919
|
}
|
|
1787
1920
|
const createdAt = Date.now();
|
|
1788
1921
|
const addr = this.arkAddress.encode();
|
|
1789
|
-
// Only save a change
|
|
1922
|
+
// Only save a change virtual output for preconfirmed coins (those with a batchExpiry).
|
|
1790
1923
|
// Inputs without a batchExpiry are already settled/unrolled and don't need tracking.
|
|
1791
1924
|
let changeVtxo;
|
|
1792
1925
|
if (changeAmount > 0n && batchExpiry !== Number.MAX_SAFE_INTEGER) {
|
|
@@ -1830,7 +1963,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1830
1963
|
console.warn("error saving offchain tx to repository", e);
|
|
1831
1964
|
}
|
|
1832
1965
|
}
|
|
1833
|
-
// mark
|
|
1966
|
+
// mark virtual outputs as spent/settled, remove boarding inputs
|
|
1834
1967
|
async updateDbAfterSettle(inputs, commitmentTxid) {
|
|
1835
1968
|
try {
|
|
1836
1969
|
const addr = this.arkAddress.encode();
|
|
@@ -1841,7 +1974,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1841
1974
|
const isVtxo = (input) => "virtualStatus" in input;
|
|
1842
1975
|
for (const input of inputs) {
|
|
1843
1976
|
if (isVtxo(input)) {
|
|
1844
|
-
//
|
|
1977
|
+
// virtual output = mark it settled
|
|
1845
1978
|
const vtxo = (0, utils_1.extendVirtualCoin)(this, input);
|
|
1846
1979
|
if (vtxo.arkTxId) {
|
|
1847
1980
|
inputArkTxIds.add(vtxo.arkTxId);
|
|
@@ -1857,7 +1990,7 @@ class Wallet extends ReadonlyWallet {
|
|
|
1857
1990
|
});
|
|
1858
1991
|
}
|
|
1859
1992
|
else {
|
|
1860
|
-
// boarding
|
|
1993
|
+
// boarding input = remove it
|
|
1861
1994
|
boardingUtxoToRemove.add(`${input.txid}:${input.vout}`);
|
|
1862
1995
|
}
|
|
1863
1996
|
}
|
|
@@ -1882,13 +2015,13 @@ class Wallet extends ReadonlyWallet {
|
|
|
1882
2015
|
exports.Wallet = Wallet;
|
|
1883
2016
|
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
1884
2017
|
/**
|
|
1885
|
-
* Select virtual
|
|
1886
|
-
* @param coins List of virtual
|
|
2018
|
+
* Select virtual outputs to reach a target amount, prioritizing those closer to expiry
|
|
2019
|
+
* @param coins List of virtual outputs to select from
|
|
1887
2020
|
* @param targetAmount Target amount to reach in satoshis
|
|
1888
|
-
* @returns Selected
|
|
2021
|
+
* @returns Selected virtual outputs and change amount
|
|
1889
2022
|
*/
|
|
1890
2023
|
function selectVirtualCoins(coins, targetAmount) {
|
|
1891
|
-
// Sort
|
|
2024
|
+
// Sort virtual outputs by expiry (ascending) and amount (descending)
|
|
1892
2025
|
const sortedCoins = [...coins].sort((a, b) => {
|
|
1893
2026
|
// First sort by expiry if available
|
|
1894
2027
|
const expiryA = a.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
|