@arkade-os/sdk 0.3.0-alpha.8 → 0.3.0
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 +48 -14
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +2 -2
- package/dist/cjs/identity/singleKey.js +8 -8
- package/dist/cjs/index.js +13 -5
- package/dist/cjs/{bip322 → intent}/index.js +38 -61
- package/dist/cjs/musig2/index.js +2 -1
- package/dist/cjs/musig2/nonces.js +4 -0
- package/dist/cjs/providers/ark.js +76 -45
- package/dist/cjs/providers/errors.js +59 -0
- package/dist/cjs/providers/expoArk.js +15 -170
- package/dist/cjs/providers/expoIndexer.js +22 -111
- package/dist/cjs/providers/expoUtils.js +124 -0
- package/dist/cjs/providers/onchain.js +19 -20
- package/dist/cjs/repositories/walletRepository.js +64 -28
- package/dist/cjs/script/base.js +15 -7
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +44 -11
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +105 -15
- package/dist/cjs/utils/transaction.js +28 -0
- package/dist/cjs/utils/unknownFields.js +7 -7
- package/dist/cjs/wallet/onchain.js +6 -7
- package/dist/cjs/wallet/serviceWorker/response.js +32 -0
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +46 -27
- package/dist/cjs/wallet/unroll.js +7 -9
- package/dist/cjs/wallet/utils.js +9 -0
- package/dist/cjs/wallet/vtxo-manager.js +323 -0
- package/dist/cjs/wallet/wallet.js +98 -125
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +1 -1
- package/dist/esm/identity/singleKey.js +9 -9
- package/dist/esm/index.js +14 -10
- package/dist/esm/{bip322 → intent}/index.js +32 -54
- package/dist/esm/musig2/index.js +1 -1
- package/dist/esm/musig2/nonces.js +3 -0
- package/dist/esm/providers/ark.js +76 -45
- package/dist/esm/providers/errors.js +54 -0
- package/dist/esm/providers/expoArk.js +15 -137
- package/dist/esm/providers/expoIndexer.js +22 -78
- package/dist/esm/providers/expoUtils.js +87 -0
- package/dist/esm/providers/onchain.js +19 -20
- package/dist/esm/repositories/walletRepository.js +64 -28
- package/dist/esm/script/base.js +12 -4
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +45 -12
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +97 -8
- package/dist/esm/utils/transaction.js +24 -0
- package/dist/esm/utils/unknownFields.js +3 -3
- package/dist/esm/wallet/onchain.js +3 -4
- package/dist/esm/wallet/serviceWorker/response.js +32 -0
- package/dist/esm/wallet/serviceWorker/utils.js +1 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
- package/dist/esm/wallet/serviceWorker/worker.js +48 -29
- package/dist/esm/wallet/unroll.js +5 -7
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +317 -0
- package/dist/esm/wallet/wallet.js +92 -119
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +2 -2
- package/dist/types/identity/singleKey.d.ts +2 -2
- package/dist/types/index.d.ts +9 -7
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/musig2/index.d.ts +1 -1
- package/dist/types/musig2/nonces.d.ts +1 -0
- package/dist/types/providers/ark.d.ts +62 -26
- package/dist/types/providers/errors.d.ts +13 -0
- package/dist/types/providers/expoIndexer.d.ts +2 -10
- package/dist/types/providers/expoUtils.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +1 -9
- package/dist/types/providers/onchain.d.ts +6 -2
- package/dist/types/repositories/walletRepository.d.ts +9 -5
- package/dist/types/script/base.d.ts +5 -2
- package/dist/types/tree/signingSession.d.ts +16 -11
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +12 -4
- package/dist/types/utils/transaction.d.ts +13 -0
- package/dist/types/utils/unknownFields.d.ts +4 -4
- package/dist/types/wallet/index.d.ts +6 -4
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -1
- package/dist/types/wallet/vtxo-manager.d.ts +179 -0
- package/dist/types/wallet/wallet.d.ts +8 -4
- package/package.json +1 -2
- package/dist/cjs/bip322/errors.js +0 -13
- package/dist/esm/bip322/errors.js +0 -9
- package/dist/types/bip322/errors.d.ts +0 -6
- package/dist/types/bip322/index.d.ts +0 -57
|
@@ -36,10 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.TapscriptType = void 0;
|
|
37
37
|
exports.decodeTapscript = decodeTapscript;
|
|
38
38
|
const bip68 = __importStar(require("bip68"));
|
|
39
|
-
const
|
|
40
|
-
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
39
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
41
40
|
const base_1 = require("@scure/base");
|
|
42
|
-
const MinimalScriptNum = (0,
|
|
41
|
+
const MinimalScriptNum = (0, btc_signer_1.ScriptNum)(undefined, true);
|
|
43
42
|
var TapscriptType;
|
|
44
43
|
(function (TapscriptType) {
|
|
45
44
|
TapscriptType["Multisig"] = "multisig";
|
|
@@ -109,7 +108,7 @@ var MultisigTapscript;
|
|
|
109
108
|
return {
|
|
110
109
|
type: TapscriptType.Multisig,
|
|
111
110
|
params,
|
|
112
|
-
script: (0,
|
|
111
|
+
script: (0, btc_signer_1.p2tr_ms)(params.pubkeys.length, params.pubkeys).script,
|
|
113
112
|
};
|
|
114
113
|
}
|
|
115
114
|
const asm = [];
|
|
@@ -126,7 +125,7 @@ var MultisigTapscript;
|
|
|
126
125
|
return {
|
|
127
126
|
type: TapscriptType.Multisig,
|
|
128
127
|
params,
|
|
129
|
-
script:
|
|
128
|
+
script: btc_signer_1.Script.encode(asm),
|
|
130
129
|
};
|
|
131
130
|
}
|
|
132
131
|
MultisigTapscript.encode = encode;
|
|
@@ -151,7 +150,7 @@ var MultisigTapscript;
|
|
|
151
150
|
MultisigTapscript.decode = decode;
|
|
152
151
|
// <pubkey> CHECKSIG <pubkey> CHECKSIGADD <len_keys> NUMEQUAL
|
|
153
152
|
function decodeChecksigAdd(script) {
|
|
154
|
-
const asm =
|
|
153
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
155
154
|
const pubkeys = [];
|
|
156
155
|
let foundNumEqual = false;
|
|
157
156
|
// Parse through ASM operations
|
|
@@ -201,7 +200,7 @@ var MultisigTapscript;
|
|
|
201
200
|
}
|
|
202
201
|
// <pubkey> CHECKSIGVERIFY <pubkey> CHECKSIG
|
|
203
202
|
function decodeChecksig(script) {
|
|
204
|
-
const asm =
|
|
203
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
205
204
|
const pubkeys = [];
|
|
206
205
|
// Parse through ASM operations
|
|
207
206
|
for (let i = 0; i < asm.length; i++) {
|
|
@@ -278,7 +277,7 @@ var CSVMultisigTapscript;
|
|
|
278
277
|
];
|
|
279
278
|
const multisigScript = MultisigTapscript.encode(params);
|
|
280
279
|
const script = new Uint8Array([
|
|
281
|
-
...
|
|
280
|
+
...btc_signer_1.Script.encode(asm),
|
|
282
281
|
...multisigScript.script,
|
|
283
282
|
]);
|
|
284
283
|
return {
|
|
@@ -292,7 +291,7 @@ var CSVMultisigTapscript;
|
|
|
292
291
|
if (script.length === 0) {
|
|
293
292
|
throw new Error("Failed to decode: script is empty");
|
|
294
293
|
}
|
|
295
|
-
const asm =
|
|
294
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
296
295
|
if (asm.length < 3) {
|
|
297
296
|
throw new Error(`Invalid script: too short (expected at least 3)`);
|
|
298
297
|
}
|
|
@@ -303,7 +302,7 @@ var CSVMultisigTapscript;
|
|
|
303
302
|
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
304
303
|
throw new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
305
304
|
}
|
|
306
|
-
const multisigScript = new Uint8Array(
|
|
305
|
+
const multisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(3)));
|
|
307
306
|
let multisig;
|
|
308
307
|
try {
|
|
309
308
|
multisig = MultisigTapscript.decode(multisigScript);
|
|
@@ -361,7 +360,7 @@ var ConditionCSVMultisigTapscript;
|
|
|
361
360
|
function encode(params) {
|
|
362
361
|
const script = new Uint8Array([
|
|
363
362
|
...params.conditionScript,
|
|
364
|
-
...
|
|
363
|
+
...btc_signer_1.Script.encode(["VERIFY"]),
|
|
365
364
|
...CSVMultisigTapscript.encode(params).script,
|
|
366
365
|
]);
|
|
367
366
|
return {
|
|
@@ -375,7 +374,7 @@ var ConditionCSVMultisigTapscript;
|
|
|
375
374
|
if (script.length === 0) {
|
|
376
375
|
throw new Error("Failed to decode: script is empty");
|
|
377
376
|
}
|
|
378
|
-
const asm =
|
|
377
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
379
378
|
if (asm.length < 1) {
|
|
380
379
|
throw new Error(`Invalid script: too short (expected at least 1)`);
|
|
381
380
|
}
|
|
@@ -388,8 +387,8 @@ var ConditionCSVMultisigTapscript;
|
|
|
388
387
|
if (verifyIndex === -1) {
|
|
389
388
|
throw new Error("Invalid script: missing VERIFY operation");
|
|
390
389
|
}
|
|
391
|
-
const conditionScript = new Uint8Array(
|
|
392
|
-
const csvMultisigScript = new Uint8Array(
|
|
390
|
+
const conditionScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(0, verifyIndex)));
|
|
391
|
+
const csvMultisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(verifyIndex + 1)));
|
|
393
392
|
let csvMultisig;
|
|
394
393
|
try {
|
|
395
394
|
csvMultisig = CSVMultisigTapscript.decode(csvMultisigScript);
|
|
@@ -436,7 +435,7 @@ var ConditionMultisigTapscript;
|
|
|
436
435
|
function encode(params) {
|
|
437
436
|
const script = new Uint8Array([
|
|
438
437
|
...params.conditionScript,
|
|
439
|
-
...
|
|
438
|
+
...btc_signer_1.Script.encode(["VERIFY"]),
|
|
440
439
|
...MultisigTapscript.encode(params).script,
|
|
441
440
|
]);
|
|
442
441
|
return {
|
|
@@ -450,7 +449,7 @@ var ConditionMultisigTapscript;
|
|
|
450
449
|
if (script.length === 0) {
|
|
451
450
|
throw new Error("Failed to decode: script is empty");
|
|
452
451
|
}
|
|
453
|
-
const asm =
|
|
452
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
454
453
|
if (asm.length < 1) {
|
|
455
454
|
throw new Error(`Invalid script: too short (expected at least 1)`);
|
|
456
455
|
}
|
|
@@ -463,8 +462,8 @@ var ConditionMultisigTapscript;
|
|
|
463
462
|
if (verifyIndex === -1) {
|
|
464
463
|
throw new Error("Invalid script: missing VERIFY operation");
|
|
465
464
|
}
|
|
466
|
-
const conditionScript = new Uint8Array(
|
|
467
|
-
const multisigScript = new Uint8Array(
|
|
465
|
+
const conditionScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(0, verifyIndex)));
|
|
466
|
+
const multisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(verifyIndex + 1)));
|
|
468
467
|
let multisig;
|
|
469
468
|
try {
|
|
470
469
|
multisig = MultisigTapscript.decode(multisigScript);
|
|
@@ -515,7 +514,7 @@ var CLTVMultisigTapscript;
|
|
|
515
514
|
"CHECKLOCKTIMEVERIFY",
|
|
516
515
|
"DROP",
|
|
517
516
|
];
|
|
518
|
-
const timelockedScript =
|
|
517
|
+
const timelockedScript = btc_signer_1.Script.encode(asm);
|
|
519
518
|
const script = new Uint8Array([
|
|
520
519
|
...timelockedScript,
|
|
521
520
|
...MultisigTapscript.encode(params).script,
|
|
@@ -531,7 +530,7 @@ var CLTVMultisigTapscript;
|
|
|
531
530
|
if (script.length === 0) {
|
|
532
531
|
throw new Error("Failed to decode: script is empty");
|
|
533
532
|
}
|
|
534
|
-
const asm =
|
|
533
|
+
const asm = btc_signer_1.Script.decode(script);
|
|
535
534
|
if (asm.length < 3) {
|
|
536
535
|
throw new Error(`Invalid script: too short (expected at least 3)`);
|
|
537
536
|
}
|
|
@@ -542,7 +541,7 @@ var CLTVMultisigTapscript;
|
|
|
542
541
|
if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
|
|
543
542
|
throw new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
|
|
544
543
|
}
|
|
545
|
-
const multisigScript = new Uint8Array(
|
|
544
|
+
const multisigScript = new Uint8Array(btc_signer_1.Script.encode(asm.slice(3)));
|
|
546
545
|
let multisig;
|
|
547
546
|
try {
|
|
548
547
|
multisig = MultisigTapscript.decode(multisigScript);
|
package/dist/cjs/script/vhtlc.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VHTLC = void 0;
|
|
4
|
-
const
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
5
|
const tapscript_1 = require("./tapscript");
|
|
6
6
|
const base_1 = require("@scure/base");
|
|
7
7
|
const base_2 = require("./base");
|
|
@@ -158,5 +158,5 @@ var VHTLC;
|
|
|
158
158
|
}
|
|
159
159
|
})(VHTLC || (exports.VHTLC = VHTLC = {}));
|
|
160
160
|
function preimageConditionScript(preimageHash) {
|
|
161
|
-
return
|
|
161
|
+
return btc_signer_1.Script.encode(["HASH160", preimageHash, "EQUAL"]);
|
|
162
162
|
}
|
|
@@ -57,15 +57,15 @@ class TreeSignerSession {
|
|
|
57
57
|
const secretKey = (0, utils_js_1.randomPrivateKeyBytes)();
|
|
58
58
|
return new TreeSignerSession(secretKey);
|
|
59
59
|
}
|
|
60
|
-
init(tree, scriptRoot, rootInputAmount) {
|
|
60
|
+
async init(tree, scriptRoot, rootInputAmount) {
|
|
61
61
|
this.graph = tree;
|
|
62
62
|
this.scriptRoot = scriptRoot;
|
|
63
63
|
this.rootSharedOutputAmount = rootInputAmount;
|
|
64
64
|
}
|
|
65
|
-
getPublicKey() {
|
|
65
|
+
async getPublicKey() {
|
|
66
66
|
return secp256k1_js_1.secp256k1.getPublicKey(this.secretKey);
|
|
67
67
|
}
|
|
68
|
-
getNonces() {
|
|
68
|
+
async getNonces() {
|
|
69
69
|
if (!this.graph)
|
|
70
70
|
throw exports.ErrMissingVtxoGraph;
|
|
71
71
|
if (!this.myNonces) {
|
|
@@ -77,12 +77,46 @@ class TreeSignerSession {
|
|
|
77
77
|
}
|
|
78
78
|
return publicNonces;
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
if (this.
|
|
82
|
-
throw
|
|
83
|
-
this.aggregateNonces
|
|
80
|
+
async aggregatedNonces(txid, noncesByPubkey) {
|
|
81
|
+
if (!this.graph)
|
|
82
|
+
throw exports.ErrMissingVtxoGraph;
|
|
83
|
+
if (!this.aggregateNonces) {
|
|
84
|
+
this.aggregateNonces = new Map();
|
|
85
|
+
}
|
|
86
|
+
if (!this.myNonces) {
|
|
87
|
+
await this.getNonces(); // generate nonces if not generated yet
|
|
88
|
+
}
|
|
89
|
+
if (this.aggregateNonces.has(txid)) {
|
|
90
|
+
return {
|
|
91
|
+
hasAllNonces: this.aggregateNonces.size === this.myNonces?.size,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const myNonce = this.myNonces.get(txid);
|
|
95
|
+
if (!myNonce)
|
|
96
|
+
throw new Error(`missing nonce for txid ${txid}`);
|
|
97
|
+
const myPublicKey = await this.getPublicKey();
|
|
98
|
+
// set my nonce to not rely on server
|
|
99
|
+
noncesByPubkey.set(base_1.hex.encode(myPublicKey.subarray(1)), myNonce);
|
|
100
|
+
const tx = this.graph.find(txid);
|
|
101
|
+
if (!tx)
|
|
102
|
+
throw new Error(`missing tx for txid ${txid}`);
|
|
103
|
+
const cosigners = (0, unknownFields_1.getArkPsbtFields)(tx.root, 0, unknownFields_1.CosignerPublicKey).map((c) => base_1.hex.encode(c.key.subarray(1)) // xonly pubkey
|
|
104
|
+
);
|
|
105
|
+
const pubNonces = [];
|
|
106
|
+
for (const cosigner of cosigners) {
|
|
107
|
+
const nonce = noncesByPubkey.get(cosigner);
|
|
108
|
+
if (!nonce) {
|
|
109
|
+
throw new Error(`missing nonce for cosigner ${cosigner}`);
|
|
110
|
+
}
|
|
111
|
+
pubNonces.push(nonce.pubNonce);
|
|
112
|
+
}
|
|
113
|
+
const aggregateNonce = musig2.aggregateNonces(pubNonces);
|
|
114
|
+
this.aggregateNonces.set(txid, { pubNonce: aggregateNonce });
|
|
115
|
+
return {
|
|
116
|
+
hasAllNonces: this.aggregateNonces.size === this.myNonces?.size,
|
|
117
|
+
};
|
|
84
118
|
}
|
|
85
|
-
sign() {
|
|
119
|
+
async sign() {
|
|
86
120
|
if (!this.graph)
|
|
87
121
|
throw exports.ErrMissingVtxoGraph;
|
|
88
122
|
if (!this.aggregateNonces)
|
|
@@ -166,9 +200,8 @@ async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, vtxoTree
|
|
|
166
200
|
function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
167
201
|
// generate P2TR script from musig2 final key
|
|
168
202
|
const pkScript = script_js_1.Script.encode(["OP_1", finalKey.slice(1)]);
|
|
169
|
-
const txid = base_1.hex.encode((0, utils_js_1.sha256x2)(tx.toBytes(true)).reverse());
|
|
170
203
|
// if the input is the root input, return the shared output amount
|
|
171
|
-
if (
|
|
204
|
+
if (tx.id === graph.txid) {
|
|
172
205
|
return {
|
|
173
206
|
amount: sharedOutputAmount,
|
|
174
207
|
script: pkScript,
|
|
@@ -178,7 +211,7 @@ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
|
178
211
|
const parentInput = tx.getInput(0);
|
|
179
212
|
if (!parentInput.txid)
|
|
180
213
|
throw new Error("missing parent input txid");
|
|
181
|
-
const parentTxid = base_1.hex.encode(
|
|
214
|
+
const parentTxid = base_1.hex.encode(parentInput.txid);
|
|
182
215
|
const parent = graph.find(parentTxid);
|
|
183
216
|
if (!parent)
|
|
184
217
|
throw new Error("parent tx not found");
|
package/dist/cjs/tree/txTree.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.TxTree = void 0;
|
|
|
4
4
|
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
5
5
|
const base_1 = require("@scure/base");
|
|
6
6
|
const base_2 = require("@scure/base");
|
|
7
|
-
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
8
7
|
/**
|
|
9
8
|
* TxTree is a graph of bitcoin transactions.
|
|
10
9
|
* It is used to represent batch tree created during settlement session
|
|
@@ -22,7 +21,7 @@ class TxTree {
|
|
|
22
21
|
const chunksByTxid = new Map();
|
|
23
22
|
for (const chunk of chunks) {
|
|
24
23
|
const decodedChunk = decodeNode(chunk);
|
|
25
|
-
const txid =
|
|
24
|
+
const txid = decodedChunk.tx.id;
|
|
26
25
|
chunksByTxid.set(txid, decodedChunk);
|
|
27
26
|
}
|
|
28
27
|
// Find the root chunks (the ones that aren't referenced as a child)
|
|
@@ -91,7 +90,7 @@ class TxTree {
|
|
|
91
90
|
}
|
|
92
91
|
child.validate();
|
|
93
92
|
const childInput = child.root.getInput(0);
|
|
94
|
-
const parentTxid =
|
|
93
|
+
const parentTxid = this.root.id;
|
|
95
94
|
// verify the input of the child is the output of the parent
|
|
96
95
|
if (!childInput.txid ||
|
|
97
96
|
base_2.hex.encode(childInput.txid) !== parentTxid ||
|
|
@@ -126,7 +125,7 @@ class TxTree {
|
|
|
126
125
|
return leaves;
|
|
127
126
|
}
|
|
128
127
|
get txid() {
|
|
129
|
-
return
|
|
128
|
+
return this.root.id;
|
|
130
129
|
}
|
|
131
130
|
find(txid) {
|
|
132
131
|
if (txid === this.txid) {
|
|
@@ -6,7 +6,6 @@ exports.validateVtxoTxGraph = validateVtxoTxGraph;
|
|
|
6
6
|
const base_1 = require("@scure/base");
|
|
7
7
|
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
8
8
|
const base_2 = require("@scure/base");
|
|
9
|
-
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
10
9
|
const musig2_1 = require("../musig2");
|
|
11
10
|
const unknownFields_1 = require("../utils/unknownFields");
|
|
12
11
|
const ErrInvalidSettlementTx = (tx) => new Error(`invalid settlement transaction: ${tx}`);
|
|
@@ -31,7 +30,7 @@ function validateConnectorsTxGraph(settlementTxB64, connectorsGraph) {
|
|
|
31
30
|
const settlementTx = transaction_js_1.Transaction.fromPSBT(base_2.base64.decode(settlementTxB64));
|
|
32
31
|
if (settlementTx.outputsLength <= BATCH_OUTPUT_CONNECTORS_INDEX)
|
|
33
32
|
throw exports.ErrInvalidSettlementTxOutputs;
|
|
34
|
-
const expectedRootTxid =
|
|
33
|
+
const expectedRootTxid = settlementTx.id;
|
|
35
34
|
if (!rootInput.txid)
|
|
36
35
|
throw exports.ErrWrongSettlementTxid;
|
|
37
36
|
if (base_1.hex.encode(rootInput.txid) !== expectedRootTxid)
|
|
@@ -58,7 +57,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
|
|
|
58
57
|
throw exports.ErrEmptyTree;
|
|
59
58
|
}
|
|
60
59
|
const rootInput = graph.root.getInput(0);
|
|
61
|
-
const commitmentTxid =
|
|
60
|
+
const commitmentTxid = roundTransaction.id;
|
|
62
61
|
if (!rootInput.txid ||
|
|
63
62
|
base_1.hex.encode(rootInput.txid) !== commitmentTxid ||
|
|
64
63
|
rootInput.index !== BATCH_OUTPUT_VTXO_INDEX) {
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildOffchainTx = buildOffchainTx;
|
|
4
4
|
exports.hasBoardingTxExpired = hasBoardingTxExpired;
|
|
5
|
-
|
|
5
|
+
exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
|
|
6
|
+
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
|
|
7
|
+
const base_1 = require("@scure/base");
|
|
8
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
9
|
+
const payment_js_1 = require("@scure/btc-signer/payment.js");
|
|
6
10
|
const tapscript_1 = require("../script/tapscript");
|
|
7
|
-
const
|
|
11
|
+
const base_2 = require("../script/base");
|
|
8
12
|
const anchor_1 = require("./anchor");
|
|
9
|
-
const base_2 = require("@scure/base");
|
|
10
|
-
const utils_js_1 = require("@scure/btc-signer/utils.js");
|
|
11
13
|
const unknownFields_1 = require("./unknownFields");
|
|
14
|
+
const transaction_1 = require("./transaction");
|
|
12
15
|
/**
|
|
13
16
|
* Builds an offchain transaction with checkpoint transactions.
|
|
14
17
|
*
|
|
@@ -32,7 +35,7 @@ function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
|
32
35
|
function buildVirtualTx(inputs, outputs) {
|
|
33
36
|
let lockTime = 0n;
|
|
34
37
|
for (const input of inputs) {
|
|
35
|
-
const tapscript = (0, tapscript_1.decodeTapscript)((0,
|
|
38
|
+
const tapscript = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(input.tapLeafScript));
|
|
36
39
|
if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) {
|
|
37
40
|
if (lockTime !== 0n) {
|
|
38
41
|
// if a locktime is already set, check if the new locktime is in the same unit
|
|
@@ -46,19 +49,17 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
|
-
const tx = new
|
|
52
|
+
const tx = new transaction_1.Transaction({
|
|
50
53
|
version: 3,
|
|
51
|
-
allowUnknown: true,
|
|
52
|
-
allowUnknownOutputs: true,
|
|
53
54
|
lockTime: Number(lockTime),
|
|
54
55
|
});
|
|
55
56
|
for (const [i, input] of inputs.entries()) {
|
|
56
57
|
tx.addInput({
|
|
57
58
|
txid: input.txid,
|
|
58
59
|
index: input.vout,
|
|
59
|
-
sequence: lockTime ?
|
|
60
|
+
sequence: lockTime ? btc_signer_1.DEFAULT_SEQUENCE - 1 : undefined,
|
|
60
61
|
witnessUtxo: {
|
|
61
|
-
script:
|
|
62
|
+
script: base_2.VtxoScript.decode(input.tapTree).pkScript,
|
|
62
63
|
amount: BigInt(input.value),
|
|
63
64
|
},
|
|
64
65
|
tapLeafScript: [input.tapLeafScript],
|
|
@@ -74,10 +75,9 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
74
75
|
}
|
|
75
76
|
function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
76
77
|
// create the checkpoint vtxo script from collaborative closure
|
|
77
|
-
const collaborativeClosure = (0, tapscript_1.decodeTapscript)(vtxo.
|
|
78
|
-
(0, base_1.scriptFromTapLeafScript)(vtxo.tapLeafScript));
|
|
78
|
+
const collaborativeClosure = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
|
|
79
79
|
// create the checkpoint vtxo script combining collaborative closure and server unroll script
|
|
80
|
-
const checkpointVtxoScript = new
|
|
80
|
+
const checkpointVtxoScript = new base_2.VtxoScript([
|
|
81
81
|
serverUnrollScript.script,
|
|
82
82
|
collaborativeClosure.script,
|
|
83
83
|
]);
|
|
@@ -89,10 +89,10 @@ function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
|
89
89
|
},
|
|
90
90
|
]);
|
|
91
91
|
// get the collaborative leaf proof
|
|
92
|
-
const collaborativeLeafProof = checkpointVtxoScript.findLeaf(
|
|
92
|
+
const collaborativeLeafProof = checkpointVtxoScript.findLeaf(base_1.hex.encode(collaborativeClosure.script));
|
|
93
93
|
// create the checkpoint input that will be used as input of the virtual tx
|
|
94
94
|
const checkpointInput = {
|
|
95
|
-
txid:
|
|
95
|
+
txid: checkpointTx.id,
|
|
96
96
|
vout: 0,
|
|
97
97
|
value: vtxo.value,
|
|
98
98
|
tapLeafScript: collaborativeLeafProof,
|
|
@@ -119,3 +119,93 @@ function hasBoardingTxExpired(coin, boardingTimelock) {
|
|
|
119
119
|
const blockTime = BigInt(Math.floor(coin.status.block_time));
|
|
120
120
|
return blockTime + boardingTimelock.value <= now;
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Formats a sighash type as a hex string (e.g., 0x01)
|
|
124
|
+
*/
|
|
125
|
+
function formatSighash(type) {
|
|
126
|
+
return `0x${type.toString(16).padStart(2, "0")}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Verify tapscript signatures on a transaction input
|
|
130
|
+
* @param tx Transaction to verify
|
|
131
|
+
* @param inputIndex Index of the input to verify
|
|
132
|
+
* @param requiredSigners List of required signer pubkeys (hex encoded)
|
|
133
|
+
* @param excludePubkeys List of pubkeys to exclude from verification (hex encoded, e.g., server key not yet signed)
|
|
134
|
+
* @param allowedSighashTypes List of allowed sighash types (defaults to [SigHash.DEFAULT])
|
|
135
|
+
* @throws Error if verification fails
|
|
136
|
+
*/
|
|
137
|
+
function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [btc_signer_1.SigHash.DEFAULT]) {
|
|
138
|
+
const input = tx.getInput(inputIndex);
|
|
139
|
+
// Collect prevout scripts and amounts for ALL inputs (required for preimageWitnessV1)
|
|
140
|
+
const prevoutScripts = [];
|
|
141
|
+
const prevoutAmounts = [];
|
|
142
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
143
|
+
const inp = tx.getInput(i);
|
|
144
|
+
if (!inp.witnessUtxo) {
|
|
145
|
+
throw new Error(`Input ${i} is missing witnessUtxo`);
|
|
146
|
+
}
|
|
147
|
+
prevoutScripts.push(inp.witnessUtxo.script);
|
|
148
|
+
prevoutAmounts.push(inp.witnessUtxo.amount);
|
|
149
|
+
}
|
|
150
|
+
// Verify tapScriptSig signatures
|
|
151
|
+
if (!input.tapScriptSig || input.tapScriptSig.length === 0) {
|
|
152
|
+
throw new Error(`Input ${inputIndex} is missing tapScriptSig`);
|
|
153
|
+
}
|
|
154
|
+
// Verify each signature in tapScriptSig
|
|
155
|
+
for (const [tapScriptSigData, signature] of input.tapScriptSig) {
|
|
156
|
+
const pubKey = tapScriptSigData.pubKey;
|
|
157
|
+
const pubKeyHex = base_1.hex.encode(pubKey);
|
|
158
|
+
// Skip verification for excluded pubkeys
|
|
159
|
+
if (excludePubkeys.includes(pubKeyHex)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Extract sighash type from signature
|
|
163
|
+
// Schnorr signatures are 64 bytes, with optional 1-byte sighash appended
|
|
164
|
+
const sighashType = signature.length === 65 ? signature[64] : btc_signer_1.SigHash.DEFAULT;
|
|
165
|
+
const sig = signature.subarray(0, 64);
|
|
166
|
+
// Verify sighash type is allowed
|
|
167
|
+
if (!allowedSighashTypes.includes(sighashType)) {
|
|
168
|
+
const sighashName = formatSighash(sighashType);
|
|
169
|
+
throw new Error(`Unallowed sighash type ${sighashName} for input ${inputIndex}, pubkey ${pubKeyHex}.`);
|
|
170
|
+
}
|
|
171
|
+
// Find the tapLeafScript that matches this signature's leafHash
|
|
172
|
+
if (!input.tapLeafScript || input.tapLeafScript.length === 0) {
|
|
173
|
+
throw new Error();
|
|
174
|
+
}
|
|
175
|
+
// Search for the leaf that matches the leafHash in tapScriptSigData
|
|
176
|
+
const leafHash = tapScriptSigData.leafHash;
|
|
177
|
+
const leafHashHex = base_1.hex.encode(leafHash);
|
|
178
|
+
let matchingScript;
|
|
179
|
+
let matchingVersion;
|
|
180
|
+
for (const [_, scriptWithVersion] of input.tapLeafScript) {
|
|
181
|
+
const script = scriptWithVersion.subarray(0, -1);
|
|
182
|
+
const version = scriptWithVersion[scriptWithVersion.length - 1];
|
|
183
|
+
// Compute the leaf hash for this script and compare as hex strings
|
|
184
|
+
const computedLeafHash = (0, payment_js_1.tapLeafHash)(script, version);
|
|
185
|
+
const computedHex = base_1.hex.encode(computedLeafHash);
|
|
186
|
+
if (computedHex === leafHashHex) {
|
|
187
|
+
matchingScript = script;
|
|
188
|
+
matchingVersion = version;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!matchingScript || matchingVersion === undefined) {
|
|
193
|
+
throw new Error(`Input ${inputIndex}: No tapLeafScript found matching leafHash ${base_1.hex.encode(leafHash)}`);
|
|
194
|
+
}
|
|
195
|
+
// Reconstruct the message that was signed
|
|
196
|
+
// Note: preimageWitnessV1 requires ALL input prevout scripts and amounts
|
|
197
|
+
const message = tx.preimageWitnessV1(inputIndex, prevoutScripts, sighashType, prevoutAmounts, undefined, matchingScript, matchingVersion);
|
|
198
|
+
// Verify the schnorr signature
|
|
199
|
+
const isValid = secp256k1_js_1.schnorr.verify(sig, message, pubKey);
|
|
200
|
+
if (!isValid) {
|
|
201
|
+
throw new Error(`Invalid signature for input ${inputIndex}, pubkey ${pubKeyHex}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Verify we have signatures from all required signers (excluding those we're skipping)
|
|
205
|
+
const signedPubkeys = input.tapScriptSig.map(([data]) => base_1.hex.encode(data.pubKey));
|
|
206
|
+
const requiredNotExcluded = requiredSigners.filter((pk) => !excludePubkeys.includes(pk));
|
|
207
|
+
const missingSigners = requiredNotExcluded.filter((pk) => !signedPubkeys.includes(pk));
|
|
208
|
+
if (missingSigners.length > 0) {
|
|
209
|
+
throw new Error(`Missing signatures from: ${missingSigners.map((pk) => pk.slice(0, 16)).join(", ")}...`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Transaction = void 0;
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
|
+
/**
|
|
6
|
+
* Transaction is a wrapper around the @scure/btc-signer Transaction class.
|
|
7
|
+
* It adds the Ark protocol specific options to the transaction.
|
|
8
|
+
*/
|
|
9
|
+
class Transaction extends btc_signer_1.Transaction {
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
super(withArkOpts(opts));
|
|
12
|
+
}
|
|
13
|
+
static fromPSBT(psbt_, opts) {
|
|
14
|
+
return btc_signer_1.Transaction.fromPSBT(psbt_, withArkOpts(opts));
|
|
15
|
+
}
|
|
16
|
+
static fromRaw(raw, opts) {
|
|
17
|
+
return btc_signer_1.Transaction.fromRaw(raw, withArkOpts(opts));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.Transaction = Transaction;
|
|
21
|
+
Transaction.ARK_TX_OPTS = {
|
|
22
|
+
allowUnknown: true,
|
|
23
|
+
allowUnknownOutputs: true,
|
|
24
|
+
allowUnknownInputs: true,
|
|
25
|
+
};
|
|
26
|
+
function withArkOpts(opts) {
|
|
27
|
+
return { ...Transaction.ARK_TX_OPTS, ...opts };
|
|
28
|
+
}
|
|
@@ -37,7 +37,7 @@ exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.ConditionWitness =
|
|
|
37
37
|
exports.setArkPsbtField = setArkPsbtField;
|
|
38
38
|
exports.getArkPsbtFields = getArkPsbtFields;
|
|
39
39
|
const bip68 = __importStar(require("bip68"));
|
|
40
|
-
const
|
|
40
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
41
41
|
const base_1 = require("@scure/base");
|
|
42
42
|
/**
|
|
43
43
|
* ArkPsbtFieldKey is the key values for ark psbt fields.
|
|
@@ -51,9 +51,9 @@ var ArkPsbtFieldKey;
|
|
|
51
51
|
})(ArkPsbtFieldKey || (exports.ArkPsbtFieldKey = ArkPsbtFieldKey = {}));
|
|
52
52
|
/**
|
|
53
53
|
* ArkPsbtFieldKeyType is the type of the ark psbt field key.
|
|
54
|
-
* Every ark psbt field has key type
|
|
54
|
+
* Every ark psbt field has key type 222.
|
|
55
55
|
*/
|
|
56
|
-
exports.ArkPsbtFieldKeyType =
|
|
56
|
+
exports.ArkPsbtFieldKeyType = 222;
|
|
57
57
|
/**
|
|
58
58
|
* setArkPsbtField appends a new unknown field to the input at inputIndex
|
|
59
59
|
*
|
|
@@ -126,12 +126,12 @@ exports.ConditionWitness = {
|
|
|
126
126
|
type: exports.ArkPsbtFieldKeyType,
|
|
127
127
|
key: encodedPsbtFieldKey[ArkPsbtFieldKey.ConditionWitness],
|
|
128
128
|
},
|
|
129
|
-
|
|
129
|
+
btc_signer_1.RawWitness.encode(value),
|
|
130
130
|
],
|
|
131
131
|
decode: (value) => nullIfCatch(() => {
|
|
132
132
|
if (!checkKeyIncludes(value[0], ArkPsbtFieldKey.ConditionWitness))
|
|
133
133
|
return null;
|
|
134
|
-
return
|
|
134
|
+
return btc_signer_1.RawWitness.decode(value[1]);
|
|
135
135
|
}),
|
|
136
136
|
};
|
|
137
137
|
/**
|
|
@@ -176,12 +176,12 @@ exports.VtxoTreeExpiry = {
|
|
|
176
176
|
type: exports.ArkPsbtFieldKeyType,
|
|
177
177
|
key: encodedPsbtFieldKey[ArkPsbtFieldKey.VtxoTreeExpiry],
|
|
178
178
|
},
|
|
179
|
-
(0,
|
|
179
|
+
(0, btc_signer_1.ScriptNum)(6, true).encode(value.value === 0n ? 0n : value.value),
|
|
180
180
|
],
|
|
181
181
|
decode: (unknown) => nullIfCatch(() => {
|
|
182
182
|
if (!checkKeyIncludes(unknown[0], ArkPsbtFieldKey.VtxoTreeExpiry))
|
|
183
183
|
return null;
|
|
184
|
-
const v = (0,
|
|
184
|
+
const v = (0, btc_signer_1.ScriptNum)(6, true).decode(unknown[1]);
|
|
185
185
|
if (!v)
|
|
186
186
|
return null;
|
|
187
187
|
const { blocks, seconds } = bip68.decode(Number(v));
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OnchainWallet = void 0;
|
|
4
4
|
exports.selectCoins = selectCoins;
|
|
5
|
-
const
|
|
5
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
6
6
|
const networks_1 = require("../networks");
|
|
7
7
|
const onchain_1 = require("../providers/onchain");
|
|
8
|
-
const transaction_js_1 = require("@scure/btc-signer/transaction.js");
|
|
9
8
|
const anchor_1 = require("../utils/anchor");
|
|
10
9
|
const txSizeEstimator_1 = require("../utils/txSizeEstimator");
|
|
10
|
+
const transaction_1 = require("../utils/transaction");
|
|
11
11
|
/**
|
|
12
12
|
* Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
|
|
13
13
|
*
|
|
@@ -39,7 +39,7 @@ class OnchainWallet {
|
|
|
39
39
|
}
|
|
40
40
|
const network = (0, networks_1.getNetwork)(networkName);
|
|
41
41
|
const onchainProvider = provider || new onchain_1.EsploraProvider(onchain_1.ESPLORA_URL[networkName]);
|
|
42
|
-
const onchainP2TR = (0,
|
|
42
|
+
const onchainP2TR = (0, btc_signer_1.p2tr)(pubkey, undefined, network);
|
|
43
43
|
return new OnchainWallet(identity, network, onchainP2TR, onchainProvider);
|
|
44
44
|
}
|
|
45
45
|
get address() {
|
|
@@ -80,7 +80,7 @@ class OnchainWallet {
|
|
|
80
80
|
// Select coins
|
|
81
81
|
const selected = selectCoins(coins, totalNeeded);
|
|
82
82
|
// Create transaction
|
|
83
|
-
let tx = new
|
|
83
|
+
let tx = new transaction_1.Transaction();
|
|
84
84
|
// Add inputs
|
|
85
85
|
for (const input of selected.inputs) {
|
|
86
86
|
tx.addInput({
|
|
@@ -108,10 +108,9 @@ class OnchainWallet {
|
|
|
108
108
|
}
|
|
109
109
|
async bumpP2A(parent) {
|
|
110
110
|
const parentVsize = parent.vsize;
|
|
111
|
-
let child = new
|
|
112
|
-
allowUnknownInputs: true,
|
|
113
|
-
allowLegacyWitnessUtxo: true,
|
|
111
|
+
let child = new transaction_1.Transaction({
|
|
114
112
|
version: 3,
|
|
113
|
+
allowLegacyWitnessUtxo: true,
|
|
115
114
|
});
|
|
116
115
|
child.addInput((0, anchor_1.findP2AOutput)(parent)); // throws if not found
|
|
117
116
|
const childVsize = txSizeEstimator_1.TxWeightEstimator.create()
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Response = void 0;
|
|
4
|
+
const base_1 = require("@scure/base");
|
|
5
|
+
function getRandomId() {
|
|
6
|
+
const randomValue = crypto.getRandomValues(new Uint8Array(16));
|
|
7
|
+
return base_1.hex.encode(randomValue);
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
10
|
* Response is the namespace that contains the response types for the service worker.
|
|
6
11
|
*/
|
|
@@ -187,4 +192,31 @@ var Response;
|
|
|
187
192
|
};
|
|
188
193
|
}
|
|
189
194
|
Response.walletReloaded = walletReloaded;
|
|
195
|
+
function isVtxoUpdate(response) {
|
|
196
|
+
return response.type === "VTXO_UPDATE";
|
|
197
|
+
}
|
|
198
|
+
Response.isVtxoUpdate = isVtxoUpdate;
|
|
199
|
+
function vtxoUpdate(newVtxos, spentVtxos) {
|
|
200
|
+
return {
|
|
201
|
+
type: "VTXO_UPDATE",
|
|
202
|
+
id: getRandomId(), // spontaneous update, not tied to a request
|
|
203
|
+
success: true,
|
|
204
|
+
spentVtxos,
|
|
205
|
+
newVtxos,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
Response.vtxoUpdate = vtxoUpdate;
|
|
209
|
+
function isUtxoUpdate(response) {
|
|
210
|
+
return response.type === "UTXO_UPDATE";
|
|
211
|
+
}
|
|
212
|
+
Response.isUtxoUpdate = isUtxoUpdate;
|
|
213
|
+
function utxoUpdate(coins) {
|
|
214
|
+
return {
|
|
215
|
+
type: "UTXO_UPDATE",
|
|
216
|
+
id: getRandomId(), // spontaneous update, not tied to a request
|
|
217
|
+
success: true,
|
|
218
|
+
coins,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
Response.utxoUpdate = utxoUpdate;
|
|
190
222
|
})(Response || (exports.Response = Response = {}));
|