@arkade-os/sdk 0.4.23 → 0.4.24
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 +21 -1
- package/dist/cjs/contracts/contractManager.js +29 -4
- package/dist/cjs/contracts/contractWatcher.js +9 -3
- package/dist/cjs/contracts/handlers/default.js +3 -2
- package/dist/cjs/contracts/handlers/delegate.js +3 -2
- package/dist/cjs/contracts/handlers/helpers.js +2 -58
- package/dist/cjs/contracts/handlers/vhtlc.js +7 -6
- package/dist/cjs/contracts/vtxoOwnership.js +60 -0
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/script/base.js +12 -47
- package/dist/cjs/script/tapscript.js +97 -73
- package/dist/cjs/utils/timelock.js +59 -0
- package/dist/cjs/utils/unknownFields.js +2 -39
- package/dist/cjs/wallet/serviceWorker/wallet-message-handler.js +59 -9
- package/dist/cjs/wallet/unroll.js +79 -67
- package/dist/cjs/wallet/wallet.js +78 -8
- package/dist/cjs/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/esm/contracts/contractManager.js +29 -4
- package/dist/esm/contracts/contractWatcher.js +9 -3
- package/dist/esm/contracts/handlers/default.js +2 -1
- package/dist/esm/contracts/handlers/delegate.js +2 -1
- package/dist/esm/contracts/handlers/helpers.js +1 -22
- package/dist/esm/contracts/handlers/vhtlc.js +2 -1
- package/dist/esm/contracts/vtxoOwnership.js +53 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/script/base.js +12 -14
- package/dist/esm/script/tapscript.js +97 -40
- package/dist/esm/utils/timelock.js +22 -0
- package/dist/esm/utils/unknownFields.js +2 -6
- package/dist/esm/wallet/serviceWorker/wallet-message-handler.js +59 -9
- package/dist/esm/wallet/unroll.js +78 -67
- package/dist/esm/wallet/wallet.js +76 -6
- package/dist/esm/worker/expo/processors/contractPollProcessor.js +7 -2
- package/dist/types/contracts/handlers/helpers.d.ts +0 -9
- package/dist/types/contracts/vtxoOwnership.d.ts +25 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/script/tapscript.d.ts +4 -0
- package/dist/types/utils/timelock.d.ts +9 -0
- package/dist/types/wallet/unroll.d.ts +10 -0
- package/package.json +1 -1
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.TapscriptType = void 0;
|
|
37
4
|
exports.decodeTapscript = decodeTapscript;
|
|
38
|
-
const bip68 = __importStar(require("bip68"));
|
|
39
5
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
40
6
|
const base_1 = require("@scure/base");
|
|
7
|
+
const timelock_1 = require("../utils/timelock");
|
|
41
8
|
const MinimalScriptNum = (0, btc_signer_1.ScriptNum)(undefined, true);
|
|
42
9
|
var TapscriptType;
|
|
43
10
|
(function (TapscriptType) {
|
|
@@ -271,9 +238,7 @@ var CSVMultisigTapscript;
|
|
|
271
238
|
throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
|
|
272
239
|
}
|
|
273
240
|
}
|
|
274
|
-
const sequence = MinimalScriptNum.encode(BigInt(
|
|
275
|
-
? { blocks: Number(params.timelock.value) }
|
|
276
|
-
: { seconds: Number(params.timelock.value) })));
|
|
241
|
+
const sequence = MinimalScriptNum.encode(BigInt((0, timelock_1.timelockToSequence)(params.timelock)));
|
|
277
242
|
const asm = [
|
|
278
243
|
sequence.length === 1 ? sequence[0] : sequence,
|
|
279
244
|
"CHECKSEQUENCEVERIFY",
|
|
@@ -296,17 +261,12 @@ var CSVMultisigTapscript;
|
|
|
296
261
|
if (script.length === 0) {
|
|
297
262
|
throw new Error("Failed to decode: script is empty");
|
|
298
263
|
}
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
301
|
-
throw
|
|
264
|
+
const isValid = isScriptValid(script);
|
|
265
|
+
if (isValid instanceof Error) {
|
|
266
|
+
throw isValid;
|
|
302
267
|
}
|
|
268
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
303
269
|
const sequence = asm[0];
|
|
304
|
-
if (typeof sequence === "string") {
|
|
305
|
-
throw new Error("Invalid script: expected sequence number");
|
|
306
|
-
}
|
|
307
|
-
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
308
|
-
throw new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
309
|
-
}
|
|
310
270
|
const multisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(3)));
|
|
311
271
|
let multisig;
|
|
312
272
|
try {
|
|
@@ -322,10 +282,7 @@ var CSVMultisigTapscript;
|
|
|
322
282
|
else {
|
|
323
283
|
sequenceNum = Number(MinimalScriptNum.decode(sequence));
|
|
324
284
|
}
|
|
325
|
-
const
|
|
326
|
-
const timelock = decodedTimelock.blocks !== undefined
|
|
327
|
-
? { type: "blocks", value: BigInt(decodedTimelock.blocks) }
|
|
328
|
-
: { type: "seconds", value: BigInt(decodedTimelock.seconds) };
|
|
285
|
+
const timelock = (0, timelock_1.sequenceToTimelock)(sequenceNum);
|
|
329
286
|
const reconstructed = encode({
|
|
330
287
|
timelock,
|
|
331
288
|
...multisig.params,
|
|
@@ -348,6 +305,21 @@ var CSVMultisigTapscript;
|
|
|
348
305
|
return tapscript.type === TapscriptType.CSVMultisig;
|
|
349
306
|
}
|
|
350
307
|
CSVMultisigTapscript.is = is;
|
|
308
|
+
function isScriptValid(script) {
|
|
309
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
310
|
+
if (asm.length < 3) {
|
|
311
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
312
|
+
}
|
|
313
|
+
const sequence = asm[0];
|
|
314
|
+
if (typeof sequence === "string") {
|
|
315
|
+
return new Error("Invalid script: expected sequence number");
|
|
316
|
+
}
|
|
317
|
+
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
318
|
+
return new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
319
|
+
}
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
CSVMultisigTapscript.isScriptValid = isScriptValid;
|
|
351
323
|
})(CSVMultisigTapscript || (exports.CSVMultisigTapscript = CSVMultisigTapscript = {}));
|
|
352
324
|
/**
|
|
353
325
|
* Combines a condition script with an exit closure. The resulting script requires
|
|
@@ -382,18 +354,14 @@ var ConditionCSVMultisigTapscript;
|
|
|
382
354
|
if (script.length === 0) {
|
|
383
355
|
throw new Error("Failed to decode: script is empty");
|
|
384
356
|
}
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
throw
|
|
388
|
-
}
|
|
389
|
-
let verifyIndex = -1;
|
|
390
|
-
for (let i = asm.length - 1; i >= 0; i--) {
|
|
391
|
-
if (asm[i] === "VERIFY") {
|
|
392
|
-
verifyIndex = i;
|
|
393
|
-
}
|
|
357
|
+
const isValid = isScriptValid(script);
|
|
358
|
+
if (isValid instanceof Error) {
|
|
359
|
+
throw isValid;
|
|
394
360
|
}
|
|
361
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
362
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
395
363
|
if (verifyIndex === -1) {
|
|
396
|
-
throw
|
|
364
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
397
365
|
}
|
|
398
366
|
const conditionScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(0, verifyIndex)));
|
|
399
367
|
const csvMultisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(verifyIndex + 1)));
|
|
@@ -426,6 +394,28 @@ var ConditionCSVMultisigTapscript;
|
|
|
426
394
|
return tapscript.type === TapscriptType.ConditionCSVMultisig;
|
|
427
395
|
}
|
|
428
396
|
ConditionCSVMultisigTapscript.is = is;
|
|
397
|
+
function getVerifyIndex(asm) {
|
|
398
|
+
let verifyIndex = -1;
|
|
399
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
400
|
+
if (asm[i] === "VERIFY") {
|
|
401
|
+
verifyIndex = i;
|
|
402
|
+
return verifyIndex;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return verifyIndex;
|
|
406
|
+
}
|
|
407
|
+
function isScriptValid(script) {
|
|
408
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
409
|
+
if (asm.length < 1) {
|
|
410
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
411
|
+
}
|
|
412
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
413
|
+
if (verifyIndex === -1) {
|
|
414
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
ConditionCSVMultisigTapscript.isScriptValid = isScriptValid;
|
|
429
419
|
})(ConditionCSVMultisigTapscript || (exports.ConditionCSVMultisigTapscript = ConditionCSVMultisigTapscript = {}));
|
|
430
420
|
/**
|
|
431
421
|
* Combines a condition script with a forfeit closure. The resulting script requires
|
|
@@ -460,18 +450,14 @@ var ConditionMultisigTapscript;
|
|
|
460
450
|
if (script.length === 0) {
|
|
461
451
|
throw new Error("Failed to decode: script is empty");
|
|
462
452
|
}
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
throw
|
|
466
|
-
}
|
|
467
|
-
let verifyIndex = -1;
|
|
468
|
-
for (let i = asm.length - 1; i >= 0; i--) {
|
|
469
|
-
if (asm[i] === "VERIFY") {
|
|
470
|
-
verifyIndex = i;
|
|
471
|
-
}
|
|
453
|
+
const isValid = isScriptValid(script);
|
|
454
|
+
if (isValid instanceof Error) {
|
|
455
|
+
throw isValid;
|
|
472
456
|
}
|
|
457
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
458
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
473
459
|
if (verifyIndex === -1) {
|
|
474
|
-
throw
|
|
460
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
475
461
|
}
|
|
476
462
|
const conditionScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(0, verifyIndex)));
|
|
477
463
|
const multisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(verifyIndex + 1)));
|
|
@@ -504,6 +490,28 @@ var ConditionMultisigTapscript;
|
|
|
504
490
|
return tapscript.type === TapscriptType.ConditionMultisig;
|
|
505
491
|
}
|
|
506
492
|
ConditionMultisigTapscript.is = is;
|
|
493
|
+
function getVerifyIndex(asm) {
|
|
494
|
+
let verifyIndex = -1;
|
|
495
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
496
|
+
if (asm[i] === "VERIFY") {
|
|
497
|
+
verifyIndex = i;
|
|
498
|
+
return verifyIndex;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return verifyIndex;
|
|
502
|
+
}
|
|
503
|
+
function isScriptValid(script) {
|
|
504
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
505
|
+
if (asm.length < 1) {
|
|
506
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
507
|
+
}
|
|
508
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
509
|
+
if (verifyIndex === -1) {
|
|
510
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
ConditionMultisigTapscript.isScriptValid = isScriptValid;
|
|
507
515
|
})(ConditionMultisigTapscript || (exports.ConditionMultisigTapscript = ConditionMultisigTapscript = {}));
|
|
508
516
|
/**
|
|
509
517
|
* Implements an absolute timelock (CLTV) script combined with a forfeit closure.
|
|
@@ -544,10 +552,11 @@ var CLTVMultisigTapscript;
|
|
|
544
552
|
if (script.length === 0) {
|
|
545
553
|
throw new Error("Failed to decode: script is empty");
|
|
546
554
|
}
|
|
547
|
-
const
|
|
548
|
-
if (
|
|
549
|
-
throw
|
|
555
|
+
const isValid = isScriptValid(script);
|
|
556
|
+
if (isValid instanceof Error) {
|
|
557
|
+
throw isValid;
|
|
550
558
|
}
|
|
559
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
551
560
|
const locktime = asm[0];
|
|
552
561
|
if (typeof locktime === "string") {
|
|
553
562
|
throw new Error("Invalid script: expected locktime number");
|
|
@@ -592,4 +601,19 @@ var CLTVMultisigTapscript;
|
|
|
592
601
|
return tapscript.type === TapscriptType.CLTVMultisig;
|
|
593
602
|
}
|
|
594
603
|
CLTVMultisigTapscript.is = is;
|
|
604
|
+
function isScriptValid(script) {
|
|
605
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
606
|
+
if (asm.length < 3) {
|
|
607
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
608
|
+
}
|
|
609
|
+
const locktime = asm[0];
|
|
610
|
+
if (typeof locktime === "string") {
|
|
611
|
+
return new Error("Invalid script: expected locktime as number or bytes");
|
|
612
|
+
}
|
|
613
|
+
if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
|
|
614
|
+
return new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
|
|
615
|
+
}
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
CLTVMultisigTapscript.isScriptValid = isScriptValid;
|
|
595
619
|
})(CLTVMultisigTapscript || (exports.CLTVMultisigTapscript = CLTVMultisigTapscript = {}));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.timelockToSequence = timelockToSequence;
|
|
37
|
+
exports.sequenceToTimelock = sequenceToTimelock;
|
|
38
|
+
const bip68 = __importStar(require("bip68"));
|
|
39
|
+
/**
|
|
40
|
+
* Convert RelativeTimelock to BIP68 sequence number.
|
|
41
|
+
*/
|
|
42
|
+
function timelockToSequence(timelock) {
|
|
43
|
+
return bip68.encode(timelock.type === "blocks"
|
|
44
|
+
? { blocks: Number(timelock.value) }
|
|
45
|
+
: { seconds: Number(timelock.value) });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert BIP68 sequence number back to RelativeTimelock.
|
|
49
|
+
*/
|
|
50
|
+
function sequenceToTimelock(sequence) {
|
|
51
|
+
const decoded = bip68.decode(sequence);
|
|
52
|
+
if ("blocks" in decoded && decoded.blocks !== undefined) {
|
|
53
|
+
return { type: "blocks", value: BigInt(decoded.blocks) };
|
|
54
|
+
}
|
|
55
|
+
if ("seconds" in decoded && decoded.seconds !== undefined) {
|
|
56
|
+
return { type: "seconds", value: BigInt(decoded.seconds) };
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Invalid BIP68 sequence: ${sequence}`);
|
|
59
|
+
}
|
|
@@ -1,44 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.ConditionWitness = exports.VtxoTaprootTree = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = void 0;
|
|
37
4
|
exports.setArkPsbtField = setArkPsbtField;
|
|
38
5
|
exports.getArkPsbtFields = getArkPsbtFields;
|
|
39
|
-
const bip68 = __importStar(require("bip68"));
|
|
40
6
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
41
7
|
const base_1 = require("@scure/base");
|
|
8
|
+
const timelock_1 = require("./timelock");
|
|
42
9
|
/**
|
|
43
10
|
* ArkPsbtFieldKey are the available key names for the Arkade PSBT custom fields.
|
|
44
11
|
*/
|
|
@@ -184,11 +151,7 @@ exports.VtxoTreeExpiry = {
|
|
|
184
151
|
const v = (0, btc_signer_1.ScriptNum)(6, true).decode(unknown[1]);
|
|
185
152
|
if (!v)
|
|
186
153
|
return null;
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
type: blocks ? "blocks" : "seconds",
|
|
190
|
-
value: BigInt(blocks ?? seconds ?? 0),
|
|
191
|
-
};
|
|
154
|
+
return (0, timelock_1.sequenceToTimelock)(Number(v));
|
|
192
155
|
}),
|
|
193
156
|
};
|
|
194
157
|
const encodedPsbtFieldKey = Object.fromEntries(Object.values(ArkPsbtFieldKey).map((key) => [
|
|
@@ -5,6 +5,8 @@ const indexer_1 = require("../../providers/indexer");
|
|
|
5
5
|
const index_1 = require("../index");
|
|
6
6
|
const utils_1 = require("../utils");
|
|
7
7
|
const transactionHistory_1 = require("../../utils/transactionHistory");
|
|
8
|
+
const vtxoOwnership_1 = require("../../contracts/vtxoOwnership");
|
|
9
|
+
const scriptFromAddress_1 = require("../../repositories/scriptFromAddress");
|
|
8
10
|
class WalletNotInitializedError extends Error {
|
|
9
11
|
constructor() {
|
|
10
12
|
super("Wallet handler not initialized");
|
|
@@ -587,11 +589,45 @@ class WalletMessageHandler {
|
|
|
587
589
|
const { newVtxos, spentVtxos } = funds;
|
|
588
590
|
if (newVtxos.length + spentVtxos.length === 0)
|
|
589
591
|
return;
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
592
|
+
// Save virtual outputs using unified repository. The
|
|
593
|
+
// event may carry rows for several scripts (other
|
|
594
|
+
// contracts the wallet watches), so split by script and
|
|
595
|
+
// save each bucket under its own contract address rather
|
|
596
|
+
// than saving a mixed-script array under one address.
|
|
597
|
+
const byScript = new Map();
|
|
598
|
+
for (const v of [...newVtxos, ...spentVtxos]) {
|
|
599
|
+
if (!v.script) {
|
|
600
|
+
// Without a script we can't route the row to the
|
|
601
|
+
// right contract bucket; surface the drop instead
|
|
602
|
+
// of silently losing the VTXO.
|
|
603
|
+
console.warn(`WalletMessageHandler.notifyIncomingFunds: dropping VTXO without script ${v.txid}:${v.vout}`);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const arr = byScript.get(v.script) ?? [];
|
|
607
|
+
arr.push(v);
|
|
608
|
+
byScript.set(v.script, arr);
|
|
609
|
+
}
|
|
610
|
+
let walletScript;
|
|
611
|
+
try {
|
|
612
|
+
walletScript = (0, scriptFromAddress_1.scriptFromArkAddress)(address);
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
walletScript = undefined;
|
|
616
|
+
}
|
|
617
|
+
const cm = await this.readonlyWallet.getContractManager();
|
|
618
|
+
const contracts = await cm.getContracts();
|
|
619
|
+
const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
|
|
620
|
+
for (const [script, vtxos] of byScript) {
|
|
621
|
+
const filtered = (0, vtxoOwnership_1.warnAndFilterVtxosForScript)(vtxos, script, "WalletMessageHandler.notifyIncomingFunds");
|
|
622
|
+
if (filtered.length === 0)
|
|
623
|
+
continue;
|
|
624
|
+
const targetAddress = script === walletScript
|
|
625
|
+
? address
|
|
626
|
+
: addrByScript.get(script);
|
|
627
|
+
if (!targetAddress)
|
|
628
|
+
continue;
|
|
629
|
+
await this.walletRepository?.saveVtxos(targetAddress, filtered);
|
|
630
|
+
}
|
|
595
631
|
// notify all clients about the virtual output state update
|
|
596
632
|
this.scheduleForNextTick(() => this.tagged({
|
|
597
633
|
type: "VTXO_UPDATE",
|
|
@@ -803,17 +839,31 @@ class WalletMessageHandler {
|
|
|
803
839
|
}
|
|
804
840
|
}
|
|
805
841
|
};
|
|
806
|
-
// Aggregate virtual outputs from all contract addresses
|
|
842
|
+
// Aggregate virtual outputs from all contract addresses. Address
|
|
843
|
+
// buckets may carry legacy duplicate rows from other contracts; gate
|
|
844
|
+
// each bucket by its owning contract script before deduplication so a
|
|
845
|
+
// wrong-script row never wins the txid:vout race.
|
|
807
846
|
const manager = await this.readonlyWallet.getContractManager();
|
|
808
847
|
const contracts = await manager.getContracts();
|
|
809
848
|
for (const contract of contracts) {
|
|
810
849
|
const vtxos = await this.walletRepository.getVtxos(contract.address);
|
|
811
|
-
addVtxos(vtxos);
|
|
850
|
+
addVtxos((0, vtxoOwnership_1.filterVtxosForScript)(vtxos, contract.script));
|
|
812
851
|
}
|
|
813
|
-
// Also check the wallet's primary address
|
|
852
|
+
// Also check the wallet's primary address. Decode it to its script
|
|
853
|
+
// and apply the same script gate. Failing to decode the wallet's own
|
|
854
|
+
// address is a structural bug — surfacing the error is safer than
|
|
855
|
+
// silently dropping the primary bucket and zeroing the user's
|
|
856
|
+
// visible balance.
|
|
814
857
|
const walletAddress = await this.readonlyWallet.getAddress();
|
|
858
|
+
let walletScript;
|
|
859
|
+
try {
|
|
860
|
+
walletScript = (0, scriptFromAddress_1.scriptFromArkAddress)(walletAddress);
|
|
861
|
+
}
|
|
862
|
+
catch (e) {
|
|
863
|
+
throw new Error(`WalletMessageHandler.getVtxosFromRepo: failed to derive script from wallet address ${walletAddress}: ${e instanceof Error ? e.message : String(e)}`);
|
|
864
|
+
}
|
|
815
865
|
const walletVtxos = await this.walletRepository.getVtxos(walletAddress);
|
|
816
|
-
addVtxos(walletVtxos);
|
|
866
|
+
addVtxos((0, vtxoOwnership_1.filterVtxosForScript)(walletVtxos, walletScript));
|
|
817
867
|
return allVtxos;
|
|
818
868
|
}
|
|
819
869
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Unroll = void 0;
|
|
4
|
+
exports.prepareUnrollTransaction = prepareUnrollTransaction;
|
|
4
5
|
const base_1 = require("@scure/base");
|
|
5
6
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
|
-
const
|
|
7
|
+
const timelock_1 = require("../utils/timelock");
|
|
7
8
|
const indexer_1 = require("../providers/indexer");
|
|
8
9
|
const base_2 = require("../script/base");
|
|
9
10
|
const txSizeEstimator_1 = require("../utils/txSizeEstimator");
|
|
@@ -129,10 +130,12 @@ var Unroll;
|
|
|
129
130
|
// finalize Arkade transaction
|
|
130
131
|
tx.finalize();
|
|
131
132
|
}
|
|
133
|
+
const pkg = await this.bumper.bumpP2A(tx);
|
|
132
134
|
return {
|
|
133
135
|
type: StepType.UNROLL,
|
|
134
136
|
tx,
|
|
135
|
-
|
|
137
|
+
pkg,
|
|
138
|
+
do: doUnroll(this.explorer, pkg),
|
|
136
139
|
};
|
|
137
140
|
}
|
|
138
141
|
/**
|
|
@@ -164,79 +167,88 @@ var Unroll;
|
|
|
164
167
|
* @returns the txid of the transaction spending the unrolled funds
|
|
165
168
|
*/
|
|
166
169
|
async function completeUnroll(wallet, vtxoTxids, outputAddress) {
|
|
167
|
-
const
|
|
168
|
-
let vtxos = await wallet.getVtxos({ withUnrolled: true });
|
|
169
|
-
vtxos = vtxos.filter((vtxo) => vtxoTxids.includes(vtxo.txid));
|
|
170
|
-
if (vtxos.length === 0) {
|
|
171
|
-
throw new Error("No vtxos to complete unroll");
|
|
172
|
-
}
|
|
173
|
-
const inputs = [];
|
|
174
|
-
let totalAmount = 0n;
|
|
175
|
-
const txWeightEstimator = txSizeEstimator_1.TxWeightEstimator.create();
|
|
176
|
-
for (const vtxo of vtxos) {
|
|
177
|
-
if (!vtxo.isUnrolled) {
|
|
178
|
-
throw new Error(`Vtxo ${vtxo.txid}:${vtxo.vout} is not fully unrolled, use unroll first`);
|
|
179
|
-
}
|
|
180
|
-
const txStatus = await wallet.onchainProvider.getTxStatus(vtxo.txid);
|
|
181
|
-
if (!txStatus.confirmed) {
|
|
182
|
-
throw new Error(`tx ${vtxo.txid} is not confirmed`);
|
|
183
|
-
}
|
|
184
|
-
const exit = availableExitPath({ height: txStatus.blockHeight, time: txStatus.blockTime }, chainTip, vtxo);
|
|
185
|
-
if (!exit) {
|
|
186
|
-
throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
187
|
-
}
|
|
188
|
-
const spendingLeaf = base_2.VtxoScript.decode(vtxo.tapTree).findLeaf(base_1.hex.encode(exit.script));
|
|
189
|
-
if (!spendingLeaf) {
|
|
190
|
-
throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
191
|
-
}
|
|
192
|
-
totalAmount += BigInt(vtxo.value);
|
|
193
|
-
const sequence = (0, helpers_1.timelockToSequence)(exit.params.timelock);
|
|
194
|
-
inputs.push({
|
|
195
|
-
txid: vtxo.txid,
|
|
196
|
-
index: vtxo.vout,
|
|
197
|
-
tapLeafScript: [spendingLeaf],
|
|
198
|
-
sequence,
|
|
199
|
-
witnessUtxo: {
|
|
200
|
-
amount: BigInt(vtxo.value),
|
|
201
|
-
script: base_2.VtxoScript.decode(vtxo.tapTree).pkScript,
|
|
202
|
-
},
|
|
203
|
-
sighashType: btc_signer_1.SigHash.DEFAULT,
|
|
204
|
-
});
|
|
205
|
-
txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, btc_signer_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
|
|
206
|
-
}
|
|
207
|
-
const tx = new transaction_1.Transaction({ version: 2 });
|
|
208
|
-
for (const input of inputs) {
|
|
209
|
-
tx.addInput(input);
|
|
210
|
-
}
|
|
211
|
-
txWeightEstimator.addOutputAddress(outputAddress, wallet.network);
|
|
212
|
-
let feeRate = await wallet.onchainProvider.getFeeRate();
|
|
213
|
-
if (!feeRate || feeRate < wallet_1.Wallet.MIN_FEE_RATE) {
|
|
214
|
-
feeRate = wallet_1.Wallet.MIN_FEE_RATE;
|
|
215
|
-
}
|
|
216
|
-
const feeAmount = txWeightEstimator.vsize().fee(BigInt(feeRate));
|
|
217
|
-
if (feeAmount > totalAmount) {
|
|
218
|
-
throw new Error("fee amount is greater than the total amount");
|
|
219
|
-
}
|
|
220
|
-
const sendAmount = totalAmount - feeAmount;
|
|
221
|
-
if (sendAmount < BigInt(utils_1.DUST_AMOUNT)) {
|
|
222
|
-
throw new Error("send amount is less than dust amount");
|
|
223
|
-
}
|
|
224
|
-
tx.addOutputAddress(outputAddress, sendAmount);
|
|
225
|
-
const signedTx = await wallet.identity.sign(tx);
|
|
226
|
-
signedTx.finalize();
|
|
170
|
+
const signedTx = await prepareUnrollTransaction(wallet, vtxoTxids, outputAddress);
|
|
227
171
|
await wallet.onchainProvider.broadcastTransaction(signedTx.hex);
|
|
228
172
|
return signedTx.id;
|
|
229
173
|
}
|
|
230
174
|
Unroll.completeUnroll = completeUnroll;
|
|
231
175
|
})(Unroll || (exports.Unroll = Unroll = {}));
|
|
176
|
+
/**
|
|
177
|
+
* Prepares the transaction that spends the CSV path to complete unrolling a VTXO.
|
|
178
|
+
* @param wallet the wallet owning the VTXO(s)
|
|
179
|
+
* @param vtxoTxIds the txids of the VTXO(s) to complete unroll
|
|
180
|
+
* @param outputAddress the address to send the unrolled funds to
|
|
181
|
+
* @throws if the VTXO(s) are not fully unrolled, if the txids are not found, if the tx is not confirmed, if no exit path is found or not available
|
|
182
|
+
* @returns the transaction spending the unrolled funds
|
|
183
|
+
*/
|
|
184
|
+
async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
|
|
185
|
+
const chainTip = await wallet.onchainProvider.getChainTip();
|
|
186
|
+
let vtxos = await wallet.getVtxos({ withUnrolled: true });
|
|
187
|
+
vtxos = vtxos.filter((vtxo) => vtxoTxIds.includes(vtxo.txid));
|
|
188
|
+
if (vtxos.length === 0) {
|
|
189
|
+
throw new Error("No vtxos to complete unroll");
|
|
190
|
+
}
|
|
191
|
+
const inputs = [];
|
|
192
|
+
let totalAmount = 0n;
|
|
193
|
+
const txWeightEstimator = txSizeEstimator_1.TxWeightEstimator.create();
|
|
194
|
+
for (const vtxo of vtxos) {
|
|
195
|
+
if (!vtxo.isUnrolled) {
|
|
196
|
+
throw new Error(`Vtxo ${vtxo.txid}:${vtxo.vout} is not fully unrolled, use unroll first`);
|
|
197
|
+
}
|
|
198
|
+
const txStatus = await wallet.onchainProvider.getTxStatus(vtxo.txid);
|
|
199
|
+
if (!txStatus.confirmed) {
|
|
200
|
+
throw new Error(`tx ${vtxo.txid} is not confirmed`);
|
|
201
|
+
}
|
|
202
|
+
const exit = availableExitPath({ height: txStatus.blockHeight, time: txStatus.blockTime }, chainTip, vtxo);
|
|
203
|
+
if (!exit) {
|
|
204
|
+
throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
205
|
+
}
|
|
206
|
+
const spendingLeaf = base_2.VtxoScript.decode(vtxo.tapTree).findLeaf(base_1.hex.encode(exit.script));
|
|
207
|
+
if (!spendingLeaf) {
|
|
208
|
+
throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
209
|
+
}
|
|
210
|
+
totalAmount += BigInt(vtxo.value);
|
|
211
|
+
const sequence = (0, timelock_1.timelockToSequence)(exit.params.timelock);
|
|
212
|
+
inputs.push({
|
|
213
|
+
txid: vtxo.txid,
|
|
214
|
+
index: vtxo.vout,
|
|
215
|
+
tapLeafScript: [spendingLeaf],
|
|
216
|
+
sequence,
|
|
217
|
+
witnessUtxo: {
|
|
218
|
+
amount: BigInt(vtxo.value),
|
|
219
|
+
script: base_2.VtxoScript.decode(vtxo.tapTree).pkScript,
|
|
220
|
+
},
|
|
221
|
+
sighashType: btc_signer_1.SigHash.DEFAULT,
|
|
222
|
+
});
|
|
223
|
+
txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, btc_signer_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
|
|
224
|
+
}
|
|
225
|
+
const tx = new transaction_1.Transaction({ version: 2 });
|
|
226
|
+
for (const input of inputs) {
|
|
227
|
+
tx.addInput(input);
|
|
228
|
+
}
|
|
229
|
+
txWeightEstimator.addOutputAddress(outputAddress, wallet.network);
|
|
230
|
+
let feeRate = await wallet.onchainProvider.getFeeRate();
|
|
231
|
+
if (!feeRate || feeRate < wallet_1.Wallet.MIN_FEE_RATE) {
|
|
232
|
+
feeRate = wallet_1.Wallet.MIN_FEE_RATE;
|
|
233
|
+
}
|
|
234
|
+
const feeAmount = txWeightEstimator.vsize().fee(BigInt(feeRate));
|
|
235
|
+
if (feeAmount > totalAmount) {
|
|
236
|
+
throw new Error("fee amount is greater than the total amount");
|
|
237
|
+
}
|
|
238
|
+
const sendAmount = totalAmount - feeAmount;
|
|
239
|
+
if (sendAmount < BigInt(utils_1.DUST_AMOUNT)) {
|
|
240
|
+
throw new Error("send amount is less than dust amount");
|
|
241
|
+
}
|
|
242
|
+
tx.addOutputAddress(outputAddress, sendAmount, wallet.network);
|
|
243
|
+
const signedTx = await wallet.identity.sign(tx);
|
|
244
|
+
signedTx.finalize();
|
|
245
|
+
return signedTx;
|
|
246
|
+
}
|
|
232
247
|
function sleep(ms) {
|
|
233
248
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
234
249
|
}
|
|
235
|
-
function doUnroll(
|
|
236
|
-
return
|
|
237
|
-
const [parent, child] = await bumper.bumpP2A(tx);
|
|
238
|
-
await onchainProvider.broadcastTransaction(parent, child);
|
|
239
|
-
};
|
|
250
|
+
function doUnroll(onchainProvider, pkg) {
|
|
251
|
+
return () => onchainProvider.broadcastTransaction(...pkg).then(() => undefined);
|
|
240
252
|
}
|
|
241
253
|
function doWait(onchainProvider, txid) {
|
|
242
254
|
return () => {
|