@arkade-os/sdk 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -1
- package/dist/cjs/identity/singleKey.js +33 -1
- package/dist/cjs/index.js +17 -2
- package/dist/cjs/intent/index.js +31 -2
- package/dist/cjs/providers/ark.js +15 -5
- package/dist/cjs/providers/indexer.js +2 -2
- package/dist/cjs/wallet/batch.js +183 -0
- package/dist/cjs/wallet/index.js +15 -0
- package/dist/cjs/wallet/serviceWorker/request.js +0 -2
- package/dist/cjs/wallet/serviceWorker/wallet.js +98 -34
- package/dist/cjs/wallet/serviceWorker/worker.js +163 -72
- package/dist/cjs/wallet/utils.js +2 -2
- package/dist/cjs/wallet/vtxo-manager.js +5 -0
- package/dist/cjs/wallet/wallet.js +358 -360
- package/dist/esm/identity/singleKey.js +31 -0
- package/dist/esm/index.js +12 -7
- package/dist/esm/intent/index.js +31 -2
- package/dist/esm/providers/ark.js +15 -5
- package/dist/esm/providers/indexer.js +2 -2
- package/dist/esm/wallet/batch.js +180 -0
- package/dist/esm/wallet/index.js +14 -0
- package/dist/esm/wallet/serviceWorker/request.js +0 -2
- package/dist/esm/wallet/serviceWorker/wallet.js +96 -33
- package/dist/esm/wallet/serviceWorker/worker.js +165 -74
- package/dist/esm/wallet/utils.js +2 -2
- package/dist/esm/wallet/vtxo-manager.js +6 -1
- package/dist/esm/wallet/wallet.js +359 -363
- package/dist/types/identity/index.d.ts +5 -3
- package/dist/types/identity/singleKey.d.ts +20 -1
- package/dist/types/index.d.ts +11 -8
- package/dist/types/intent/index.d.ts +19 -2
- package/dist/types/providers/ark.d.ts +9 -8
- package/dist/types/providers/indexer.d.ts +2 -2
- package/dist/types/wallet/batch.d.ts +87 -0
- package/dist/types/wallet/index.d.ts +76 -16
- package/dist/types/wallet/serviceWorker/request.d.ts +5 -1
- package/dist/types/wallet/serviceWorker/wallet.d.ts +46 -15
- package/dist/types/wallet/serviceWorker/worker.d.ts +6 -3
- package/dist/types/wallet/utils.d.ts +8 -3
- package/dist/types/wallet/wallet.d.ts +87 -36
- package/package.json +1 -1
|
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Wallet = void 0;
|
|
36
|
+
exports.Wallet = exports.ReadonlyWallet = void 0;
|
|
37
|
+
exports.getSequence = getSequence;
|
|
37
38
|
exports.waitForIncomingFunds = waitForIncomingFunds;
|
|
38
39
|
const base_1 = require("@scure/base");
|
|
39
40
|
const bip68 = __importStar(require("bip68"));
|
|
@@ -56,74 +57,40 @@ const vtxo_manager_1 = require("./vtxo-manager");
|
|
|
56
57
|
const arknote_1 = require("../arknote");
|
|
57
58
|
const intent_1 = require("../intent");
|
|
58
59
|
const indexer_1 = require("../providers/indexer");
|
|
59
|
-
const txTree_1 = require("../tree/txTree");
|
|
60
60
|
const unknownFields_1 = require("../utils/unknownFields");
|
|
61
61
|
const inMemory_1 = require("../storage/inMemory");
|
|
62
62
|
const walletRepository_1 = require("../repositories/walletRepository");
|
|
63
63
|
const contractRepository_1 = require("../repositories/contractRepository");
|
|
64
64
|
const utils_1 = require("./utils");
|
|
65
65
|
const errors_1 = require("../providers/errors");
|
|
66
|
+
const batch_1 = require("./batch");
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
* The wallet does not store any data locally and relies on Ark and onchain
|
|
69
|
-
* providers to fetch UTXOs and VTXOs.
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```typescript
|
|
73
|
-
* // Create a wallet with URL configuration
|
|
74
|
-
* const wallet = await Wallet.create({
|
|
75
|
-
* identity: SingleKey.fromHex('your_private_key'),
|
|
76
|
-
* arkServerUrl: 'https://ark.example.com',
|
|
77
|
-
* esploraUrl: 'https://mempool.space/api'
|
|
78
|
-
* });
|
|
79
|
-
*
|
|
80
|
-
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
81
|
-
* const wallet = await Wallet.create({
|
|
82
|
-
* identity: SingleKey.fromHex('your_private_key'),
|
|
83
|
-
* arkProvider: new ExpoArkProvider('https://ark.example.com'),
|
|
84
|
-
* indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
|
|
85
|
-
* esploraUrl: 'https://mempool.space/api'
|
|
86
|
-
* });
|
|
87
|
-
*
|
|
88
|
-
* // Get addresses
|
|
89
|
-
* const arkAddress = await wallet.getAddress();
|
|
90
|
-
* const boardingAddress = await wallet.getBoardingAddress();
|
|
91
|
-
*
|
|
92
|
-
* // Send bitcoin
|
|
93
|
-
* const txid = await wallet.sendBitcoin({
|
|
94
|
-
* address: 'tb1...',
|
|
95
|
-
* amount: 50000
|
|
96
|
-
* });
|
|
97
|
-
* ```
|
|
68
|
+
* Type guard function to check if an identity has a toReadonly method.
|
|
98
69
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
70
|
+
function hasToReadonly(identity) {
|
|
71
|
+
return (typeof identity === "object" &&
|
|
72
|
+
identity !== null &&
|
|
73
|
+
"toReadonly" in identity &&
|
|
74
|
+
typeof identity.toReadonly === "function");
|
|
75
|
+
}
|
|
76
|
+
class ReadonlyWallet {
|
|
77
|
+
constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository) {
|
|
101
78
|
this.identity = identity;
|
|
102
79
|
this.network = network;
|
|
103
|
-
this.networkName = networkName;
|
|
104
80
|
this.onchainProvider = onchainProvider;
|
|
105
|
-
this.arkProvider = arkProvider;
|
|
106
81
|
this.indexerProvider = indexerProvider;
|
|
107
82
|
this.arkServerPublicKey = arkServerPublicKey;
|
|
108
83
|
this.offchainTapscript = offchainTapscript;
|
|
109
84
|
this.boardingTapscript = boardingTapscript;
|
|
110
|
-
this.serverUnrollScript = serverUnrollScript;
|
|
111
|
-
this.forfeitOutputScript = forfeitOutputScript;
|
|
112
|
-
this.forfeitPubkey = forfeitPubkey;
|
|
113
85
|
this.dustAmount = dustAmount;
|
|
114
86
|
this.walletRepository = walletRepository;
|
|
115
87
|
this.contractRepository = contractRepository;
|
|
116
|
-
this.renewalConfig = {
|
|
117
|
-
enabled: renewalConfig?.enabled ?? false,
|
|
118
|
-
...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
|
|
119
|
-
...renewalConfig,
|
|
120
|
-
};
|
|
121
88
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Protected helper to set up shared wallet configuration.
|
|
91
|
+
* Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
|
|
92
|
+
*/
|
|
93
|
+
static async setupWalletConfig(config, pubkey) {
|
|
127
94
|
// Use provided arkProvider instance or create a new one from arkServerUrl
|
|
128
95
|
const arkProvider = config.arkProvider ||
|
|
129
96
|
(() => {
|
|
@@ -187,25 +154,32 @@ class Wallet {
|
|
|
187
154
|
});
|
|
188
155
|
// Save tapscripts
|
|
189
156
|
const offchainTapscript = bareVtxoTapscript;
|
|
190
|
-
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
191
|
-
let serverUnrollScript;
|
|
192
|
-
try {
|
|
193
|
-
const raw = base_1.hex.decode(info.checkpointTapscript);
|
|
194
|
-
serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
throw new Error("Invalid checkpointTapscript from server");
|
|
198
|
-
}
|
|
199
|
-
// parse the server forfeit address
|
|
200
|
-
// server is expecting funds to be sent to this address
|
|
201
|
-
const forfeitPubkey = base_1.hex.decode(info.forfeitPubkey).slice(1);
|
|
202
|
-
const forfeitAddress = (0, btc_signer_1.Address)(network).decode(info.forfeitAddress);
|
|
203
|
-
const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
|
|
204
157
|
// Set up storage and repositories
|
|
205
158
|
const storage = config.storage || new inMemory_1.InMemoryStorageAdapter();
|
|
206
159
|
const walletRepository = new walletRepository_1.WalletRepositoryImpl(storage);
|
|
207
160
|
const contractRepository = new contractRepository_1.ContractRepositoryImpl(storage);
|
|
208
|
-
return
|
|
161
|
+
return {
|
|
162
|
+
arkProvider,
|
|
163
|
+
indexerProvider,
|
|
164
|
+
onchainProvider,
|
|
165
|
+
network,
|
|
166
|
+
networkName: info.network,
|
|
167
|
+
serverPubKey,
|
|
168
|
+
offchainTapscript,
|
|
169
|
+
boardingTapscript,
|
|
170
|
+
dustAmount: info.dust,
|
|
171
|
+
walletRepository,
|
|
172
|
+
contractRepository,
|
|
173
|
+
info,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
static async create(config) {
|
|
177
|
+
const pubkey = await config.identity.xOnlyPublicKey();
|
|
178
|
+
if (!pubkey) {
|
|
179
|
+
throw new Error("Invalid configured public key");
|
|
180
|
+
}
|
|
181
|
+
const setup = await ReadonlyWallet.setupWalletConfig(config, pubkey);
|
|
182
|
+
return new ReadonlyWallet(config.identity, setup.network, setup.onchainProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, setup.dustAmount, setup.walletRepository, setup.contractRepository);
|
|
209
183
|
}
|
|
210
184
|
get arkAddress() {
|
|
211
185
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -280,7 +254,7 @@ class Wallet {
|
|
|
280
254
|
let vtxos = allVtxos.filter(_1.isSpendable);
|
|
281
255
|
// all recoverable vtxos are spendable by definition
|
|
282
256
|
if (!filter.withRecoverable) {
|
|
283
|
-
vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo));
|
|
257
|
+
vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo) && !(0, _1.isExpired)(vtxo));
|
|
284
258
|
}
|
|
285
259
|
if (filter.withUnrolled) {
|
|
286
260
|
const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
|
|
@@ -289,9 +263,6 @@ class Wallet {
|
|
|
289
263
|
return vtxos;
|
|
290
264
|
}
|
|
291
265
|
async getTransactionHistory() {
|
|
292
|
-
if (!this.indexerProvider) {
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
266
|
const response = await this.indexerProvider.getVtxos({
|
|
296
267
|
scripts: [base_1.hex.encode(this.offchainTapscript.pkScript)],
|
|
297
268
|
});
|
|
@@ -395,6 +366,179 @@ class Wallet {
|
|
|
395
366
|
await this.walletRepository.saveUtxos(boardingAddress, utxos);
|
|
396
367
|
return utxos;
|
|
397
368
|
}
|
|
369
|
+
async notifyIncomingFunds(eventCallback) {
|
|
370
|
+
const arkAddress = await this.getAddress();
|
|
371
|
+
const boardingAddress = await this.getBoardingAddress();
|
|
372
|
+
let onchainStopFunc;
|
|
373
|
+
let indexerStopFunc;
|
|
374
|
+
if (this.onchainProvider && boardingAddress) {
|
|
375
|
+
const findVoutOnTx = (tx) => {
|
|
376
|
+
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
377
|
+
};
|
|
378
|
+
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
379
|
+
// find all utxos belonging to our boarding address
|
|
380
|
+
const coins = txs
|
|
381
|
+
// filter txs where address is in output
|
|
382
|
+
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
383
|
+
// return utxo as Coin
|
|
384
|
+
.map((tx) => {
|
|
385
|
+
const { txid, status } = tx;
|
|
386
|
+
const vout = findVoutOnTx(tx);
|
|
387
|
+
const value = Number(tx.vout[vout].value);
|
|
388
|
+
return { txid, vout, value, status };
|
|
389
|
+
});
|
|
390
|
+
// and notify via callback
|
|
391
|
+
eventCallback({
|
|
392
|
+
type: "utxo",
|
|
393
|
+
coins,
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (this.indexerProvider && arkAddress) {
|
|
398
|
+
const offchainScript = this.offchainTapscript;
|
|
399
|
+
const subscriptionId = await this.indexerProvider.subscribeForScripts([
|
|
400
|
+
base_1.hex.encode(offchainScript.pkScript),
|
|
401
|
+
]);
|
|
402
|
+
const abortController = new AbortController();
|
|
403
|
+
const subscription = this.indexerProvider.getSubscription(subscriptionId, abortController.signal);
|
|
404
|
+
indexerStopFunc = async () => {
|
|
405
|
+
abortController.abort();
|
|
406
|
+
await this.indexerProvider?.unsubscribeForScripts(subscriptionId);
|
|
407
|
+
};
|
|
408
|
+
// Handle subscription updates asynchronously without blocking
|
|
409
|
+
(async () => {
|
|
410
|
+
try {
|
|
411
|
+
for await (const update of subscription) {
|
|
412
|
+
if (update.newVtxos?.length > 0 ||
|
|
413
|
+
update.spentVtxos?.length > 0) {
|
|
414
|
+
eventCallback({
|
|
415
|
+
type: "vtxo",
|
|
416
|
+
newVtxos: update.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
417
|
+
spentVtxos: update.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
console.error("Subscription error:", error);
|
|
424
|
+
}
|
|
425
|
+
})();
|
|
426
|
+
}
|
|
427
|
+
const stopFunc = () => {
|
|
428
|
+
onchainStopFunc?.();
|
|
429
|
+
indexerStopFunc?.();
|
|
430
|
+
};
|
|
431
|
+
return stopFunc;
|
|
432
|
+
}
|
|
433
|
+
async fetchPendingTxs() {
|
|
434
|
+
// get non-swept VTXOs, rely on the indexer only in case DB doesn't have the right state
|
|
435
|
+
const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
|
|
436
|
+
let { vtxos } = await this.indexerProvider.getVtxos({
|
|
437
|
+
scripts,
|
|
438
|
+
});
|
|
439
|
+
return vtxos
|
|
440
|
+
.filter((vtxo) => vtxo.virtualStatus.state !== "swept" &&
|
|
441
|
+
vtxo.virtualStatus.state !== "settled" &&
|
|
442
|
+
vtxo.arkTxId !== undefined)
|
|
443
|
+
.map((_) => _.arkTxId);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
exports.ReadonlyWallet = ReadonlyWallet;
|
|
447
|
+
/**
|
|
448
|
+
* Main wallet implementation for Bitcoin transactions with Ark protocol support.
|
|
449
|
+
* The wallet does not store any data locally and relies on Ark and onchain
|
|
450
|
+
* providers to fetch UTXOs and VTXOs.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* // Create a wallet with URL configuration
|
|
455
|
+
* const wallet = await Wallet.create({
|
|
456
|
+
* identity: SingleKey.fromHex('your_private_key'),
|
|
457
|
+
* arkServerUrl: 'https://ark.example.com',
|
|
458
|
+
* esploraUrl: 'https://mempool.space/api'
|
|
459
|
+
* });
|
|
460
|
+
*
|
|
461
|
+
* // Or with custom provider instances (e.g., for Expo/React Native)
|
|
462
|
+
* const wallet = await Wallet.create({
|
|
463
|
+
* identity: SingleKey.fromHex('your_private_key'),
|
|
464
|
+
* arkProvider: new ExpoArkProvider('https://ark.example.com'),
|
|
465
|
+
* indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
|
|
466
|
+
* esploraUrl: 'https://mempool.space/api'
|
|
467
|
+
* });
|
|
468
|
+
*
|
|
469
|
+
* // Get addresses
|
|
470
|
+
* const arkAddress = await wallet.getAddress();
|
|
471
|
+
* const boardingAddress = await wallet.getBoardingAddress();
|
|
472
|
+
*
|
|
473
|
+
* // Send bitcoin
|
|
474
|
+
* const txid = await wallet.sendBitcoin({
|
|
475
|
+
* address: 'tb1...',
|
|
476
|
+
* amount: 50000
|
|
477
|
+
* });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
class Wallet extends ReadonlyWallet {
|
|
481
|
+
constructor(identity, network, networkName, onchainProvider, arkProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, dustAmount, walletRepository, contractRepository, renewalConfig) {
|
|
482
|
+
super(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository);
|
|
483
|
+
this.networkName = networkName;
|
|
484
|
+
this.arkProvider = arkProvider;
|
|
485
|
+
this.serverUnrollScript = serverUnrollScript;
|
|
486
|
+
this.forfeitOutputScript = forfeitOutputScript;
|
|
487
|
+
this.forfeitPubkey = forfeitPubkey;
|
|
488
|
+
this.identity = identity;
|
|
489
|
+
this.renewalConfig = {
|
|
490
|
+
enabled: renewalConfig?.enabled ?? false,
|
|
491
|
+
...vtxo_manager_1.DEFAULT_RENEWAL_CONFIG,
|
|
492
|
+
...renewalConfig,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
static async create(config) {
|
|
496
|
+
const pubkey = await config.identity.xOnlyPublicKey();
|
|
497
|
+
if (!pubkey) {
|
|
498
|
+
throw new Error("Invalid configured public key");
|
|
499
|
+
}
|
|
500
|
+
const setup = await ReadonlyWallet.setupWalletConfig(config, pubkey);
|
|
501
|
+
// Compute Wallet-specific forfeit and unroll scripts
|
|
502
|
+
// the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
|
|
503
|
+
let serverUnrollScript;
|
|
504
|
+
try {
|
|
505
|
+
const raw = base_1.hex.decode(setup.info.checkpointTapscript);
|
|
506
|
+
serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
|
|
507
|
+
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
throw new Error("Invalid checkpointTapscript from server");
|
|
510
|
+
}
|
|
511
|
+
// parse the server forfeit address
|
|
512
|
+
// server is expecting funds to be sent to this address
|
|
513
|
+
const forfeitPubkey = base_1.hex.decode(setup.info.forfeitPubkey).slice(1);
|
|
514
|
+
const forfeitAddress = (0, btc_signer_1.Address)(setup.network).decode(setup.info.forfeitAddress);
|
|
515
|
+
const forfeitOutputScript = btc_signer_1.OutScript.encode(forfeitAddress);
|
|
516
|
+
return new Wallet(config.identity, setup.network, setup.networkName, setup.onchainProvider, setup.arkProvider, setup.indexerProvider, setup.serverPubKey, setup.offchainTapscript, setup.boardingTapscript, serverUnrollScript, forfeitOutputScript, forfeitPubkey, setup.dustAmount, setup.walletRepository, setup.contractRepository, config.renewalConfig);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Convert this wallet to a readonly wallet.
|
|
520
|
+
*
|
|
521
|
+
* @returns A readonly wallet with the same configuration but readonly identity
|
|
522
|
+
* @example
|
|
523
|
+
* ```typescript
|
|
524
|
+
* const wallet = await Wallet.create({ identity: SingleKey.fromHex('...'), ... });
|
|
525
|
+
* const readonlyWallet = await wallet.toReadonly();
|
|
526
|
+
*
|
|
527
|
+
* // Can query balance and addresses
|
|
528
|
+
* const balance = await readonlyWallet.getBalance();
|
|
529
|
+
* const address = await readonlyWallet.getAddress();
|
|
530
|
+
*
|
|
531
|
+
* // But cannot send transactions (type error)
|
|
532
|
+
* // readonlyWallet.sendBitcoin(...); // TypeScript error
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
async toReadonly() {
|
|
536
|
+
// Check if the identity has a toReadonly method using type guard
|
|
537
|
+
const readonlyIdentity = hasToReadonly(this.identity)
|
|
538
|
+
? await this.identity.toReadonly()
|
|
539
|
+
: this.identity; // Identity extends ReadonlyIdentity, so this is safe
|
|
540
|
+
return new ReadonlyWallet(readonlyIdentity, this.network, this.onchainProvider, this.indexerProvider, this.arkServerPublicKey, this.offchainTapscript, this.boardingTapscript, this.dustAmount, this.walletRepository, this.contractRepository);
|
|
541
|
+
}
|
|
398
542
|
async sendBitcoin(params) {
|
|
399
543
|
if (params.amount <= 0) {
|
|
400
544
|
throw new Error("Amount must be positive");
|
|
@@ -406,7 +550,23 @@ class Wallet {
|
|
|
406
550
|
const virtualCoins = await this.getVirtualCoins({
|
|
407
551
|
withRecoverable: false,
|
|
408
552
|
});
|
|
409
|
-
|
|
553
|
+
let selected;
|
|
554
|
+
if (params.selectedVtxos) {
|
|
555
|
+
const selectedVtxoSum = params.selectedVtxos
|
|
556
|
+
.map((v) => v.value)
|
|
557
|
+
.reduce((a, b) => a + b, 0);
|
|
558
|
+
if (selectedVtxoSum < params.amount) {
|
|
559
|
+
throw new Error("Selected VTXOs do not cover specified amount");
|
|
560
|
+
}
|
|
561
|
+
const changeAmount = selectedVtxoSum - params.amount;
|
|
562
|
+
selected = {
|
|
563
|
+
inputs: params.selectedVtxos,
|
|
564
|
+
changeAmount: BigInt(changeAmount),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
selected = selectVirtualCoins(virtualCoins, params.amount);
|
|
569
|
+
}
|
|
410
570
|
const selectedLeaf = this.offchainTapscript.forfeit();
|
|
411
571
|
if (!selectedLeaf) {
|
|
412
572
|
throw new Error("Selected leaf not found");
|
|
@@ -481,7 +641,7 @@ class Wallet {
|
|
|
481
641
|
vout: outputs.length - 1,
|
|
482
642
|
createdAt: new Date(createdAt),
|
|
483
643
|
forfeitTapLeafScript: this.offchainTapscript.forfeit(),
|
|
484
|
-
intentTapLeafScript: this.offchainTapscript.
|
|
644
|
+
intentTapLeafScript: this.offchainTapscript.forfeit(),
|
|
485
645
|
isUnrolled: false,
|
|
486
646
|
isSpent: false,
|
|
487
647
|
tapTree: this.offchainTapscript.encode(),
|
|
@@ -591,285 +751,31 @@ class Wallet {
|
|
|
591
751
|
this.makeDeleteIntentSignature(params.inputs),
|
|
592
752
|
]);
|
|
593
753
|
const intentId = await this.safeRegisterIntent(intent);
|
|
754
|
+
const topics = [
|
|
755
|
+
...signingPublicKeys,
|
|
756
|
+
...params.inputs.map((input) => `${input.txid}:${input.vout}`),
|
|
757
|
+
];
|
|
758
|
+
const handler = this.createBatchHandler(intentId, params.inputs, session);
|
|
594
759
|
const abortController = new AbortController();
|
|
595
|
-
// listen to settlement events
|
|
596
760
|
try {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
let sweepTapTreeRoot;
|
|
606
|
-
const vtxoChunks = [];
|
|
607
|
-
const connectorsChunks = [];
|
|
608
|
-
let vtxoGraph;
|
|
609
|
-
let connectorsGraph;
|
|
610
|
-
for await (const event of settlementStream) {
|
|
611
|
-
if (eventCallback) {
|
|
612
|
-
eventCallback(event);
|
|
613
|
-
}
|
|
614
|
-
switch (event.type) {
|
|
615
|
-
// the settlement failed
|
|
616
|
-
case ark_1.SettlementEventType.BatchFailed:
|
|
617
|
-
throw new Error(event.reason);
|
|
618
|
-
case ark_1.SettlementEventType.BatchStarted:
|
|
619
|
-
if (step !== undefined) {
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
const res = await this.handleBatchStartedEvent(event, intentId, this.forfeitPubkey, this.forfeitOutputScript);
|
|
623
|
-
if (!res.skip) {
|
|
624
|
-
step = event.type;
|
|
625
|
-
sweepTapTreeRoot = res.sweepTapTreeRoot;
|
|
626
|
-
batchId = res.roundId;
|
|
627
|
-
if (!hasOffchainOutputs) {
|
|
628
|
-
// if there are no offchain outputs, we don't have to handle musig2 tree signatures
|
|
629
|
-
// we can directly advance to the finalization step
|
|
630
|
-
step = ark_1.SettlementEventType.TreeNonces;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
break;
|
|
634
|
-
case ark_1.SettlementEventType.TreeTx:
|
|
635
|
-
if (step !== ark_1.SettlementEventType.BatchStarted &&
|
|
636
|
-
step !== ark_1.SettlementEventType.TreeNonces) {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
// index 0 = vtxo tree
|
|
640
|
-
if (event.batchIndex === 0) {
|
|
641
|
-
vtxoChunks.push(event.chunk);
|
|
642
|
-
// index 1 = connectors tree
|
|
643
|
-
}
|
|
644
|
-
else if (event.batchIndex === 1) {
|
|
645
|
-
connectorsChunks.push(event.chunk);
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
throw new Error(`Invalid batch index: ${event.batchIndex}`);
|
|
649
|
-
}
|
|
650
|
-
break;
|
|
651
|
-
case ark_1.SettlementEventType.TreeSignature:
|
|
652
|
-
if (step !== ark_1.SettlementEventType.TreeNonces) {
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
if (!hasOffchainOutputs) {
|
|
656
|
-
continue;
|
|
657
|
-
}
|
|
658
|
-
if (!vtxoGraph) {
|
|
659
|
-
throw new Error("Vtxo graph not set, something went wrong");
|
|
660
|
-
}
|
|
661
|
-
// index 0 = vtxo graph
|
|
662
|
-
if (event.batchIndex === 0) {
|
|
663
|
-
const tapKeySig = base_1.hex.decode(event.signature);
|
|
664
|
-
vtxoGraph.update(event.txid, (tx) => {
|
|
665
|
-
tx.updateInput(0, {
|
|
666
|
-
tapKeySig,
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
break;
|
|
671
|
-
// the server has started the signing process of the vtxo tree transactions
|
|
672
|
-
// the server expects the partial musig2 nonces for each tx
|
|
673
|
-
case ark_1.SettlementEventType.TreeSigningStarted:
|
|
674
|
-
if (step !== ark_1.SettlementEventType.BatchStarted) {
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
if (hasOffchainOutputs) {
|
|
678
|
-
if (!session) {
|
|
679
|
-
throw new Error("Signing session not set");
|
|
680
|
-
}
|
|
681
|
-
if (!sweepTapTreeRoot) {
|
|
682
|
-
throw new Error("Sweep tap tree root not set");
|
|
683
|
-
}
|
|
684
|
-
if (vtxoChunks.length === 0) {
|
|
685
|
-
throw new Error("unsigned vtxo graph not received");
|
|
686
|
-
}
|
|
687
|
-
vtxoGraph = txTree_1.TxTree.create(vtxoChunks);
|
|
688
|
-
await this.handleSettlementSigningEvent(event, sweepTapTreeRoot, session, vtxoGraph);
|
|
689
|
-
}
|
|
690
|
-
step = event.type;
|
|
691
|
-
break;
|
|
692
|
-
// the musig2 nonces of the vtxo tree transactions are generated
|
|
693
|
-
// the server expects now the partial musig2 signatures
|
|
694
|
-
case ark_1.SettlementEventType.TreeNonces:
|
|
695
|
-
if (step !== ark_1.SettlementEventType.TreeSigningStarted) {
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
if (hasOffchainOutputs) {
|
|
699
|
-
if (!session) {
|
|
700
|
-
throw new Error("Signing session not set");
|
|
701
|
-
}
|
|
702
|
-
const signed = await this.handleSettlementTreeNoncesEvent(event, session);
|
|
703
|
-
if (signed) {
|
|
704
|
-
step = event.type;
|
|
705
|
-
}
|
|
706
|
-
break;
|
|
707
|
-
}
|
|
708
|
-
step = event.type;
|
|
709
|
-
break;
|
|
710
|
-
// the vtxo tree is signed, craft, sign and submit forfeit transactions
|
|
711
|
-
// if any boarding utxos are involved, the settlement tx is also signed
|
|
712
|
-
case ark_1.SettlementEventType.BatchFinalization:
|
|
713
|
-
if (step !== ark_1.SettlementEventType.TreeNonces) {
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
if (!this.forfeitOutputScript) {
|
|
717
|
-
throw new Error("Forfeit output script not set");
|
|
718
|
-
}
|
|
719
|
-
if (connectorsChunks.length > 0) {
|
|
720
|
-
connectorsGraph = txTree_1.TxTree.create(connectorsChunks);
|
|
721
|
-
(0, validation_1.validateConnectorsTxGraph)(event.commitmentTx, connectorsGraph);
|
|
722
|
-
}
|
|
723
|
-
await this.handleSettlementFinalizationEvent(event, params.inputs, this.forfeitOutputScript, connectorsGraph);
|
|
724
|
-
step = event.type;
|
|
725
|
-
break;
|
|
726
|
-
// the settlement is done, last event to be received
|
|
727
|
-
case ark_1.SettlementEventType.BatchFinalized:
|
|
728
|
-
if (step !== ark_1.SettlementEventType.BatchFinalization) {
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
if (event.id === batchId) {
|
|
732
|
-
abortController.abort();
|
|
733
|
-
return event.commitmentTxid;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
761
|
+
const stream = this.arkProvider.getEventStream(abortController.signal, topics);
|
|
762
|
+
return await batch_1.Batch.join(stream, handler, {
|
|
763
|
+
abortController,
|
|
764
|
+
skipVtxoTreeSigning: !hasOffchainOutputs,
|
|
765
|
+
eventCallback: eventCallback
|
|
766
|
+
? (event) => Promise.resolve(eventCallback(event))
|
|
767
|
+
: undefined,
|
|
768
|
+
});
|
|
737
769
|
}
|
|
738
770
|
catch (error) {
|
|
739
|
-
//
|
|
740
|
-
|
|
741
|
-
try {
|
|
742
|
-
// delete the intent to not be stuck in the queue
|
|
743
|
-
await this.arkProvider.deleteIntent(deleteIntent);
|
|
744
|
-
}
|
|
745
|
-
catch (error) {
|
|
746
|
-
console.error("failed to delete intent: ", error);
|
|
747
|
-
}
|
|
771
|
+
// delete the intent to not be stuck in the queue
|
|
772
|
+
await this.arkProvider.deleteIntent(deleteIntent).catch(() => { });
|
|
748
773
|
throw error;
|
|
749
774
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const arkAddress = await this.getAddress();
|
|
754
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
755
|
-
let onchainStopFunc;
|
|
756
|
-
let indexerStopFunc;
|
|
757
|
-
if (this.onchainProvider && boardingAddress) {
|
|
758
|
-
const findVoutOnTx = (tx) => {
|
|
759
|
-
return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
|
|
760
|
-
};
|
|
761
|
-
onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
|
|
762
|
-
// find all utxos belonging to our boarding address
|
|
763
|
-
const coins = txs
|
|
764
|
-
// filter txs where address is in output
|
|
765
|
-
.filter((tx) => findVoutOnTx(tx) !== -1)
|
|
766
|
-
// return utxo as Coin
|
|
767
|
-
.map((tx) => {
|
|
768
|
-
const { txid, status } = tx;
|
|
769
|
-
const vout = findVoutOnTx(tx);
|
|
770
|
-
const value = Number(tx.vout[vout].value);
|
|
771
|
-
return { txid, vout, value, status };
|
|
772
|
-
});
|
|
773
|
-
// and notify via callback
|
|
774
|
-
eventCallback({
|
|
775
|
-
type: "utxo",
|
|
776
|
-
coins,
|
|
777
|
-
});
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
if (this.indexerProvider && arkAddress) {
|
|
781
|
-
const offchainScript = this.offchainTapscript;
|
|
782
|
-
const subscriptionId = await this.indexerProvider.subscribeForScripts([
|
|
783
|
-
base_1.hex.encode(offchainScript.pkScript),
|
|
784
|
-
]);
|
|
785
|
-
const abortController = new AbortController();
|
|
786
|
-
const subscription = this.indexerProvider.getSubscription(subscriptionId, abortController.signal);
|
|
787
|
-
indexerStopFunc = async () => {
|
|
788
|
-
abortController.abort();
|
|
789
|
-
await this.indexerProvider?.unsubscribeForScripts(subscriptionId);
|
|
790
|
-
};
|
|
791
|
-
// Handle subscription updates asynchronously without blocking
|
|
792
|
-
(async () => {
|
|
793
|
-
try {
|
|
794
|
-
for await (const update of subscription) {
|
|
795
|
-
if (update.newVtxos?.length > 0 ||
|
|
796
|
-
update.spentVtxos?.length > 0) {
|
|
797
|
-
eventCallback({
|
|
798
|
-
type: "vtxo",
|
|
799
|
-
newVtxos: update.newVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
800
|
-
spentVtxos: update.spentVtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo)),
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
catch (error) {
|
|
806
|
-
console.error("Subscription error:", error);
|
|
807
|
-
}
|
|
808
|
-
})();
|
|
809
|
-
}
|
|
810
|
-
const stopFunc = () => {
|
|
811
|
-
onchainStopFunc?.();
|
|
812
|
-
indexerStopFunc?.();
|
|
813
|
-
};
|
|
814
|
-
return stopFunc;
|
|
815
|
-
}
|
|
816
|
-
async handleBatchStartedEvent(event, intentId, forfeitPubKey, forfeitOutputScript) {
|
|
817
|
-
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
818
|
-
const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
|
|
819
|
-
const intentIdHashStr = base_1.hex.encode(intentIdHash);
|
|
820
|
-
let skip = true;
|
|
821
|
-
// check if our intent ID hash matches any in the event
|
|
822
|
-
for (const idHash of event.intentIdHashes) {
|
|
823
|
-
if (idHash === intentIdHashStr) {
|
|
824
|
-
if (!this.arkProvider) {
|
|
825
|
-
throw new Error("Ark provider not configured");
|
|
826
|
-
}
|
|
827
|
-
await this.arkProvider.confirmRegistration(intentId);
|
|
828
|
-
skip = false;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (skip) {
|
|
832
|
-
return { skip };
|
|
833
|
-
}
|
|
834
|
-
const sweepTapscript = tapscript_1.CSVMultisigTapscript.encode({
|
|
835
|
-
timelock: {
|
|
836
|
-
value: event.batchExpiry,
|
|
837
|
-
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
838
|
-
},
|
|
839
|
-
pubkeys: [forfeitPubKey],
|
|
840
|
-
}).script;
|
|
841
|
-
const sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
|
|
842
|
-
return {
|
|
843
|
-
roundId: event.id,
|
|
844
|
-
sweepTapTreeRoot,
|
|
845
|
-
forfeitOutputScript,
|
|
846
|
-
skip: false,
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
// validates the vtxo tree, creates a signing session and generates the musig2 nonces
|
|
850
|
-
async handleSettlementSigningEvent(event, sweepTapTreeRoot, session, vtxoGraph) {
|
|
851
|
-
// validate the unsigned vtxo tree
|
|
852
|
-
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
853
|
-
(0, validation_1.validateVtxoTxGraph)(vtxoGraph, commitmentTx, sweepTapTreeRoot);
|
|
854
|
-
// TODO check if our registered outputs are in the vtxo tree
|
|
855
|
-
const sharedOutput = commitmentTx.getOutput(0);
|
|
856
|
-
if (!sharedOutput?.amount) {
|
|
857
|
-
throw new Error("Shared output not found");
|
|
775
|
+
finally {
|
|
776
|
+
// close the stream
|
|
777
|
+
abortController.abort();
|
|
858
778
|
}
|
|
859
|
-
session.init(vtxoGraph, sweepTapTreeRoot, sharedOutput.amount);
|
|
860
|
-
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
861
|
-
const nonces = await session.getNonces();
|
|
862
|
-
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
863
|
-
}
|
|
864
|
-
async handleSettlementTreeNoncesEvent(event, session) {
|
|
865
|
-
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
866
|
-
// wait to receive and aggregate all nonces before sending signatures
|
|
867
|
-
if (!hasAllNonces)
|
|
868
|
-
return false;
|
|
869
|
-
const signatures = await session.sign();
|
|
870
|
-
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
871
|
-
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
872
|
-
return true;
|
|
873
779
|
}
|
|
874
780
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
875
781
|
// the signed forfeits transactions to submit
|
|
@@ -959,9 +865,98 @@ class Wallet {
|
|
|
959
865
|
: undefined);
|
|
960
866
|
}
|
|
961
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* @implements Batch.Handler interface.
|
|
870
|
+
* @param intentId - The intent ID.
|
|
871
|
+
* @param inputs - The inputs of the intent.
|
|
872
|
+
* @param session - The musig2 signing session, if not provided, the signing will be skipped.
|
|
873
|
+
*/
|
|
874
|
+
createBatchHandler(intentId, inputs, session) {
|
|
875
|
+
let sweepTapTreeRoot;
|
|
876
|
+
return {
|
|
877
|
+
onBatchStarted: async (event) => {
|
|
878
|
+
const utf8IntentId = new TextEncoder().encode(intentId);
|
|
879
|
+
const intentIdHash = (0, utils_js_1.sha256)(utf8IntentId);
|
|
880
|
+
const intentIdHashStr = base_1.hex.encode(intentIdHash);
|
|
881
|
+
let skip = true;
|
|
882
|
+
// check if our intent ID hash matches any in the event
|
|
883
|
+
for (const idHash of event.intentIdHashes) {
|
|
884
|
+
if (idHash === intentIdHashStr) {
|
|
885
|
+
if (!this.arkProvider) {
|
|
886
|
+
throw new Error("Ark provider not configured");
|
|
887
|
+
}
|
|
888
|
+
await this.arkProvider.confirmRegistration(intentId);
|
|
889
|
+
skip = false;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (skip) {
|
|
893
|
+
return { skip };
|
|
894
|
+
}
|
|
895
|
+
const sweepTapscript = tapscript_1.CSVMultisigTapscript.encode({
|
|
896
|
+
timelock: {
|
|
897
|
+
value: event.batchExpiry,
|
|
898
|
+
type: event.batchExpiry >= 512n ? "seconds" : "blocks",
|
|
899
|
+
},
|
|
900
|
+
pubkeys: [this.forfeitPubkey],
|
|
901
|
+
}).script;
|
|
902
|
+
sweepTapTreeRoot = (0, payment_js_1.tapLeafHash)(sweepTapscript);
|
|
903
|
+
return { skip: false };
|
|
904
|
+
},
|
|
905
|
+
onTreeSigningStarted: async (event, vtxoTree) => {
|
|
906
|
+
if (!session) {
|
|
907
|
+
return { skip: true };
|
|
908
|
+
}
|
|
909
|
+
if (!sweepTapTreeRoot) {
|
|
910
|
+
throw new Error("Sweep tap tree root not set");
|
|
911
|
+
}
|
|
912
|
+
const xOnlyPublicKeys = event.cosignersPublicKeys.map((k) => k.slice(2));
|
|
913
|
+
const signerPublicKey = await session.getPublicKey();
|
|
914
|
+
const xonlySignerPublicKey = signerPublicKey.subarray(1);
|
|
915
|
+
if (!xOnlyPublicKeys.includes(base_1.hex.encode(xonlySignerPublicKey))) {
|
|
916
|
+
// not a cosigner, skip the signing
|
|
917
|
+
return { skip: true };
|
|
918
|
+
}
|
|
919
|
+
// validate the unsigned vtxo tree
|
|
920
|
+
const commitmentTx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(event.unsignedCommitmentTx));
|
|
921
|
+
(0, validation_1.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
|
|
922
|
+
// TODO check if our registered outputs are in the vtxo tree
|
|
923
|
+
const sharedOutput = commitmentTx.getOutput(0);
|
|
924
|
+
if (!sharedOutput?.amount) {
|
|
925
|
+
throw new Error("Shared output not found");
|
|
926
|
+
}
|
|
927
|
+
await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
|
|
928
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
929
|
+
const nonces = await session.getNonces();
|
|
930
|
+
await this.arkProvider.submitTreeNonces(event.id, pubkey, nonces);
|
|
931
|
+
return { skip: false };
|
|
932
|
+
},
|
|
933
|
+
onTreeNonces: async (event) => {
|
|
934
|
+
if (!session) {
|
|
935
|
+
return { fullySigned: true }; // Signing complete (no signing needed)
|
|
936
|
+
}
|
|
937
|
+
const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
|
|
938
|
+
// wait to receive and aggregate all nonces before sending signatures
|
|
939
|
+
if (!hasAllNonces)
|
|
940
|
+
return { fullySigned: false };
|
|
941
|
+
const signatures = await session.sign();
|
|
942
|
+
const pubkey = base_1.hex.encode(await session.getPublicKey());
|
|
943
|
+
await this.arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
|
|
944
|
+
return { fullySigned: true };
|
|
945
|
+
},
|
|
946
|
+
onBatchFinalization: async (event, _, connectorTree) => {
|
|
947
|
+
if (!this.forfeitOutputScript) {
|
|
948
|
+
throw new Error("Forfeit output script not set");
|
|
949
|
+
}
|
|
950
|
+
if (connectorTree) {
|
|
951
|
+
(0, validation_1.validateConnectorsTxGraph)(event.commitmentTx, connectorTree);
|
|
952
|
+
}
|
|
953
|
+
await this.handleSettlementFinalizationEvent(event, inputs, this.forfeitOutputScript, connectorTree);
|
|
954
|
+
},
|
|
955
|
+
};
|
|
956
|
+
}
|
|
962
957
|
async safeRegisterIntent(intent) {
|
|
963
958
|
try {
|
|
964
|
-
return this.arkProvider.registerIntent(intent);
|
|
959
|
+
return await this.arkProvider.registerIntent(intent);
|
|
965
960
|
}
|
|
966
961
|
catch (error) {
|
|
967
962
|
// catch the "already registered by another intent" error
|
|
@@ -989,12 +984,11 @@ class Wallet {
|
|
|
989
984
|
expire_at: 0,
|
|
990
985
|
cosigners_public_keys: cosignerPubKeys,
|
|
991
986
|
};
|
|
992
|
-
const
|
|
993
|
-
const proof = intent_1.Intent.create(encodedMessage, inputs, outputs);
|
|
987
|
+
const proof = intent_1.Intent.create(message, inputs, outputs);
|
|
994
988
|
const signedProof = await this.identity.sign(proof);
|
|
995
989
|
return {
|
|
996
990
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
997
|
-
message
|
|
991
|
+
message,
|
|
998
992
|
};
|
|
999
993
|
}
|
|
1000
994
|
async makeDeleteIntentSignature(coins) {
|
|
@@ -1003,12 +997,11 @@ class Wallet {
|
|
|
1003
997
|
type: "delete",
|
|
1004
998
|
expire_at: 0,
|
|
1005
999
|
};
|
|
1006
|
-
const
|
|
1007
|
-
const proof = intent_1.Intent.create(encodedMessage, inputs, []);
|
|
1000
|
+
const proof = intent_1.Intent.create(message, inputs, []);
|
|
1008
1001
|
const signedProof = await this.identity.sign(proof);
|
|
1009
1002
|
return {
|
|
1010
1003
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
1011
|
-
message
|
|
1004
|
+
message,
|
|
1012
1005
|
};
|
|
1013
1006
|
}
|
|
1014
1007
|
async makeGetPendingTxIntentSignature(vtxos) {
|
|
@@ -1017,12 +1010,11 @@ class Wallet {
|
|
|
1017
1010
|
type: "get-pending-tx",
|
|
1018
1011
|
expire_at: 0,
|
|
1019
1012
|
};
|
|
1020
|
-
const
|
|
1021
|
-
const proof = intent_1.Intent.create(encodedMessage, inputs, []);
|
|
1013
|
+
const proof = intent_1.Intent.create(message, inputs, []);
|
|
1022
1014
|
const signedProof = await this.identity.sign(proof);
|
|
1023
1015
|
return {
|
|
1024
1016
|
proof: base_1.base64.encode(signedProof.toPSBT()),
|
|
1025
|
-
message
|
|
1017
|
+
message,
|
|
1026
1018
|
};
|
|
1027
1019
|
}
|
|
1028
1020
|
/**
|
|
@@ -1076,7 +1068,7 @@ class Wallet {
|
|
|
1076
1068
|
const inputs = [];
|
|
1077
1069
|
for (const input of coins) {
|
|
1078
1070
|
const vtxoScript = base_2.VtxoScript.decode(input.tapTree);
|
|
1079
|
-
const sequence = getSequence(input);
|
|
1071
|
+
const sequence = getSequence(input.intentTapLeafScript);
|
|
1080
1072
|
const unknown = [unknownFields_1.VtxoTaprootTree.encode(input.tapTree)];
|
|
1081
1073
|
if (input.extraWitness) {
|
|
1082
1074
|
unknown.push(unknownFields_1.ConditionWitness.encode(input.extraWitness));
|
|
@@ -1098,15 +1090,21 @@ class Wallet {
|
|
|
1098
1090
|
}
|
|
1099
1091
|
exports.Wallet = Wallet;
|
|
1100
1092
|
Wallet.MIN_FEE_RATE = 1; // sats/vbyte
|
|
1101
|
-
function getSequence(
|
|
1093
|
+
function getSequence(tapLeafScript) {
|
|
1102
1094
|
let sequence = undefined;
|
|
1103
1095
|
try {
|
|
1104
|
-
const scriptWithLeafVersion =
|
|
1096
|
+
const scriptWithLeafVersion = tapLeafScript[1];
|
|
1105
1097
|
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1098
|
+
try {
|
|
1099
|
+
const params = tapscript_1.CSVMultisigTapscript.decode(script).params;
|
|
1100
|
+
sequence = bip68.encode(params.timelock.type === "blocks"
|
|
1101
|
+
? { blocks: Number(params.timelock.value) }
|
|
1102
|
+
: { seconds: Number(params.timelock.value) });
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
const params = tapscript_1.CLTVMultisigTapscript.decode(script).params;
|
|
1106
|
+
sequence = Number(params.absoluteTimelock);
|
|
1107
|
+
}
|
|
1110
1108
|
}
|
|
1111
1109
|
catch { }
|
|
1112
1110
|
return sequence;
|