@arkade-os/sdk 0.3.0-alpha.7 → 0.3.1-alpha.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 +115 -14
- package/dist/cjs/adapters/expo.js +8 -0
- package/dist/cjs/arknote/index.js +3 -3
- package/dist/cjs/forfeit.js +5 -2
- package/dist/cjs/identity/singleKey.js +5 -4
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/{bip322 → intent}/index.js +37 -55
- package/dist/cjs/providers/ark.js +62 -23
- package/dist/cjs/providers/expoArk.js +82 -0
- package/dist/cjs/providers/expoIndexer.js +105 -0
- package/dist/cjs/providers/indexer.js +3 -1
- package/dist/cjs/providers/utils.js +122 -0
- package/dist/cjs/script/base.js +1 -2
- package/dist/cjs/script/tapscript.js +20 -21
- package/dist/cjs/script/vhtlc.js +2 -2
- package/dist/cjs/tree/signingSession.js +7 -8
- package/dist/cjs/tree/txTree.js +3 -4
- package/dist/cjs/tree/validation.js +2 -3
- package/dist/cjs/utils/arkTransaction.js +117 -12
- package/dist/cjs/utils/unknownFields.js +5 -5
- package/dist/cjs/wallet/index.js +1 -1
- package/dist/cjs/wallet/onchain.js +4 -5
- package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
- package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
- package/dist/cjs/wallet/serviceWorker/worker.js +25 -23
- package/dist/cjs/wallet/unroll.js +6 -7
- package/dist/cjs/wallet/utils.js +11 -0
- package/dist/cjs/wallet/vtxo-manager.js +381 -0
- package/dist/cjs/wallet/wallet.js +130 -143
- package/dist/esm/adapters/expo.js +3 -0
- package/dist/esm/arknote/index.js +2 -2
- package/dist/esm/forfeit.js +4 -1
- package/dist/esm/identity/singleKey.js +7 -6
- package/dist/esm/index.js +7 -6
- package/dist/esm/{bip322 → intent}/index.js +31 -48
- package/dist/esm/providers/ark.js +62 -23
- package/dist/esm/providers/expoArk.js +78 -0
- package/dist/esm/providers/expoIndexer.js +101 -0
- package/dist/esm/providers/indexer.js +3 -1
- package/dist/esm/providers/utils.js +87 -0
- package/dist/esm/script/base.js +1 -2
- package/dist/esm/script/tapscript.js +1 -2
- package/dist/esm/script/vhtlc.js +1 -1
- package/dist/esm/tree/signingSession.js +8 -9
- package/dist/esm/tree/txTree.js +3 -4
- package/dist/esm/tree/validation.js +2 -3
- package/dist/esm/utils/arkTransaction.js +108 -5
- package/dist/esm/utils/unknownFields.js +1 -1
- package/dist/esm/wallet/index.js +1 -1
- package/dist/esm/wallet/onchain.js +1 -2
- package/dist/esm/wallet/serviceWorker/utils.js +1 -8
- package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
- package/dist/esm/wallet/serviceWorker/worker.js +26 -24
- package/dist/esm/wallet/unroll.js +2 -3
- package/dist/esm/wallet/utils.js +8 -0
- package/dist/esm/wallet/vtxo-manager.js +372 -0
- package/dist/esm/wallet/wallet.js +124 -137
- package/dist/types/adapters/expo.d.ts +4 -0
- package/dist/types/arknote/index.d.ts +1 -1
- package/dist/types/forfeit.d.ts +2 -2
- package/dist/types/identity/index.d.ts +1 -1
- package/dist/types/identity/singleKey.d.ts +1 -1
- package/dist/types/index.d.ts +8 -7
- package/dist/types/intent/index.d.ts +41 -0
- package/dist/types/providers/ark.d.ts +190 -22
- package/dist/types/providers/expoArk.d.ts +22 -0
- package/dist/types/providers/expoIndexer.d.ts +18 -0
- package/dist/types/providers/indexer.d.ts +8 -8
- package/dist/types/providers/utils.d.ts +18 -0
- package/dist/types/script/base.d.ts +3 -2
- package/dist/types/tree/signingSession.d.ts +10 -10
- package/dist/types/utils/anchor.d.ts +2 -2
- package/dist/types/utils/arkTransaction.d.ts +16 -4
- package/dist/types/utils/unknownFields.d.ts +2 -2
- package/dist/types/wallet/index.d.ts +47 -7
- package/dist/types/wallet/onchain.d.ts +1 -1
- package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
- package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
- package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
- package/dist/types/wallet/unroll.d.ts +1 -1
- package/dist/types/wallet/utils.d.ts +2 -0
- package/dist/types/wallet/vtxo-manager.d.ts +207 -0
- package/dist/types/wallet/wallet.d.ts +16 -4
- package/package.json +11 -3
- 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
|
@@ -3,7 +3,7 @@ import { Script } from "@scure/btc-signer/script.js";
|
|
|
3
3
|
import { SigHash } from "@scure/btc-signer/transaction.js";
|
|
4
4
|
import { hex } from "@scure/base";
|
|
5
5
|
import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
-
import { randomPrivateKeyBytes
|
|
6
|
+
import { randomPrivateKeyBytes } from "@scure/btc-signer/utils.js";
|
|
7
7
|
import { CosignerPublicKey, getArkPsbtFields } from '../utils/unknownFields.js';
|
|
8
8
|
export const ErrMissingVtxoGraph = new Error("missing vtxo graph");
|
|
9
9
|
export const ErrMissingAggregateKey = new Error("missing aggregate key");
|
|
@@ -20,15 +20,15 @@ export class TreeSignerSession {
|
|
|
20
20
|
const secretKey = randomPrivateKeyBytes();
|
|
21
21
|
return new TreeSignerSession(secretKey);
|
|
22
22
|
}
|
|
23
|
-
init(tree, scriptRoot, rootInputAmount) {
|
|
23
|
+
async init(tree, scriptRoot, rootInputAmount) {
|
|
24
24
|
this.graph = tree;
|
|
25
25
|
this.scriptRoot = scriptRoot;
|
|
26
26
|
this.rootSharedOutputAmount = rootInputAmount;
|
|
27
27
|
}
|
|
28
|
-
getPublicKey() {
|
|
28
|
+
async getPublicKey() {
|
|
29
29
|
return secp256k1.getPublicKey(this.secretKey);
|
|
30
30
|
}
|
|
31
|
-
getNonces() {
|
|
31
|
+
async getNonces() {
|
|
32
32
|
if (!this.graph)
|
|
33
33
|
throw ErrMissingVtxoGraph;
|
|
34
34
|
if (!this.myNonces) {
|
|
@@ -40,12 +40,12 @@ export class TreeSignerSession {
|
|
|
40
40
|
}
|
|
41
41
|
return publicNonces;
|
|
42
42
|
}
|
|
43
|
-
setAggregatedNonces(nonces) {
|
|
43
|
+
async setAggregatedNonces(nonces) {
|
|
44
44
|
if (this.aggregateNonces)
|
|
45
45
|
throw new Error("nonces already set");
|
|
46
46
|
this.aggregateNonces = nonces;
|
|
47
47
|
}
|
|
48
|
-
sign() {
|
|
48
|
+
async sign() {
|
|
49
49
|
if (!this.graph)
|
|
50
50
|
throw ErrMissingVtxoGraph;
|
|
51
51
|
if (!this.aggregateNonces)
|
|
@@ -128,9 +128,8 @@ export async function validateTreeSigs(finalAggregatedKey, sharedOutputAmount, v
|
|
|
128
128
|
function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
129
129
|
// generate P2TR script from musig2 final key
|
|
130
130
|
const pkScript = Script.encode(["OP_1", finalKey.slice(1)]);
|
|
131
|
-
const txid = hex.encode(sha256x2(tx.toBytes(true)).reverse());
|
|
132
131
|
// if the input is the root input, return the shared output amount
|
|
133
|
-
if (
|
|
132
|
+
if (tx.id === graph.txid) {
|
|
134
133
|
return {
|
|
135
134
|
amount: sharedOutputAmount,
|
|
136
135
|
script: pkScript,
|
|
@@ -140,7 +139,7 @@ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
|
|
|
140
139
|
const parentInput = tx.getInput(0);
|
|
141
140
|
if (!parentInput.txid)
|
|
142
141
|
throw new Error("missing parent input txid");
|
|
143
|
-
const parentTxid = hex.encode(
|
|
142
|
+
const parentTxid = hex.encode(parentInput.txid);
|
|
144
143
|
const parent = graph.find(parentTxid);
|
|
145
144
|
if (!parent)
|
|
146
145
|
throw new Error("parent tx not found");
|
package/dist/esm/tree/txTree.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Transaction } from "@scure/btc-signer/transaction.js";
|
|
2
2
|
import { base64 } from "@scure/base";
|
|
3
3
|
import { hex } from "@scure/base";
|
|
4
|
-
import { sha256x2 } from "@scure/btc-signer/utils.js";
|
|
5
4
|
/**
|
|
6
5
|
* TxTree is a graph of bitcoin transactions.
|
|
7
6
|
* It is used to represent batch tree created during settlement session
|
|
@@ -19,7 +18,7 @@ export class TxTree {
|
|
|
19
18
|
const chunksByTxid = new Map();
|
|
20
19
|
for (const chunk of chunks) {
|
|
21
20
|
const decodedChunk = decodeNode(chunk);
|
|
22
|
-
const txid =
|
|
21
|
+
const txid = decodedChunk.tx.id;
|
|
23
22
|
chunksByTxid.set(txid, decodedChunk);
|
|
24
23
|
}
|
|
25
24
|
// Find the root chunks (the ones that aren't referenced as a child)
|
|
@@ -88,7 +87,7 @@ export class TxTree {
|
|
|
88
87
|
}
|
|
89
88
|
child.validate();
|
|
90
89
|
const childInput = child.root.getInput(0);
|
|
91
|
-
const parentTxid =
|
|
90
|
+
const parentTxid = this.root.id;
|
|
92
91
|
// verify the input of the child is the output of the parent
|
|
93
92
|
if (!childInput.txid ||
|
|
94
93
|
hex.encode(childInput.txid) !== parentTxid ||
|
|
@@ -123,7 +122,7 @@ export class TxTree {
|
|
|
123
122
|
return leaves;
|
|
124
123
|
}
|
|
125
124
|
get txid() {
|
|
126
|
-
return
|
|
125
|
+
return this.root.id;
|
|
127
126
|
}
|
|
128
127
|
find(txid) {
|
|
129
128
|
if (txid === this.txid) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { hex } from "@scure/base";
|
|
2
2
|
import { Transaction } from "@scure/btc-signer/transaction.js";
|
|
3
3
|
import { base64 } from "@scure/base";
|
|
4
|
-
import { sha256x2 } from "@scure/btc-signer/utils.js";
|
|
5
4
|
import { aggregateKeys } from '../musig2/index.js';
|
|
6
5
|
import { CosignerPublicKey, getArkPsbtFields } from '../utils/unknownFields.js';
|
|
7
6
|
export const ErrInvalidSettlementTx = (tx) => new Error(`invalid settlement transaction: ${tx}`);
|
|
@@ -25,7 +24,7 @@ export function validateConnectorsTxGraph(settlementTxB64, connectorsGraph) {
|
|
|
25
24
|
const settlementTx = Transaction.fromPSBT(base64.decode(settlementTxB64));
|
|
26
25
|
if (settlementTx.outputsLength <= BATCH_OUTPUT_CONNECTORS_INDEX)
|
|
27
26
|
throw ErrInvalidSettlementTxOutputs;
|
|
28
|
-
const expectedRootTxid =
|
|
27
|
+
const expectedRootTxid = settlementTx.id;
|
|
29
28
|
if (!rootInput.txid)
|
|
30
29
|
throw ErrWrongSettlementTxid;
|
|
31
30
|
if (hex.encode(rootInput.txid) !== expectedRootTxid)
|
|
@@ -52,7 +51,7 @@ export function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
|
|
|
52
51
|
throw ErrEmptyTree;
|
|
53
52
|
}
|
|
54
53
|
const rootInput = graph.root.getInput(0);
|
|
55
|
-
const commitmentTxid =
|
|
54
|
+
const commitmentTxid = roundTransaction.id;
|
|
56
55
|
if (!rootInput.txid ||
|
|
57
56
|
hex.encode(rootInput.txid) !== commitmentTxid ||
|
|
58
57
|
rootInput.index !== BATCH_OUTPUT_VTXO_INDEX) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
2
|
+
import { hex } from "@scure/base";
|
|
3
|
+
import { DEFAULT_SEQUENCE, Transaction, SigHash } from "@scure/btc-signer";
|
|
4
|
+
import { tapLeafHash } from "@scure/btc-signer/payment.js";
|
|
5
|
+
import { CLTVMultisigTapscript, decodeTapscript, } from '../script/tapscript.js';
|
|
3
6
|
import { scriptFromTapLeafScript, VtxoScript, } from '../script/base.js';
|
|
4
7
|
import { P2A } from './anchor.js';
|
|
5
|
-
import { hex } from "@scure/base";
|
|
6
|
-
import { sha256x2 } from "@scure/btc-signer/utils.js";
|
|
7
8
|
import { setArkPsbtField, VtxoTaprootTree } from './unknownFields.js';
|
|
8
9
|
/**
|
|
9
10
|
* Builds an offchain transaction with checkpoint transactions.
|
|
@@ -88,7 +89,7 @@ function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
|
88
89
|
const collaborativeLeafProof = checkpointVtxoScript.findLeaf(hex.encode(collaborativeClosure.script));
|
|
89
90
|
// create the checkpoint input that will be used as input of the virtual tx
|
|
90
91
|
const checkpointInput = {
|
|
91
|
-
txid:
|
|
92
|
+
txid: checkpointTx.id,
|
|
92
93
|
vout: 0,
|
|
93
94
|
value: vtxo.value,
|
|
94
95
|
tapLeafScript: collaborativeLeafProof,
|
|
@@ -103,3 +104,105 @@ const nLocktimeMinSeconds = 500000000n;
|
|
|
103
104
|
function isSeconds(locktime) {
|
|
104
105
|
return locktime >= nLocktimeMinSeconds;
|
|
105
106
|
}
|
|
107
|
+
export function hasBoardingTxExpired(coin, boardingTimelock) {
|
|
108
|
+
if (!coin.status.block_time)
|
|
109
|
+
return false;
|
|
110
|
+
if (boardingTimelock.value === 0n)
|
|
111
|
+
return true;
|
|
112
|
+
if (boardingTimelock.type !== "blocks")
|
|
113
|
+
return false; // TODO: handle get chain tip
|
|
114
|
+
// validate expiry in terms of seconds
|
|
115
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
116
|
+
const blockTime = BigInt(Math.floor(coin.status.block_time));
|
|
117
|
+
return blockTime + boardingTimelock.value <= now;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Formats a sighash type as a hex string (e.g., 0x01)
|
|
121
|
+
*/
|
|
122
|
+
function formatSighash(type) {
|
|
123
|
+
return `0x${type.toString(16).padStart(2, "0")}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Verify tapscript signatures on a transaction input
|
|
127
|
+
* @param tx Transaction to verify
|
|
128
|
+
* @param inputIndex Index of the input to verify
|
|
129
|
+
* @param requiredSigners List of required signer pubkeys (hex encoded)
|
|
130
|
+
* @param excludePubkeys List of pubkeys to exclude from verification (hex encoded, e.g., server key not yet signed)
|
|
131
|
+
* @param allowedSighashTypes List of allowed sighash types (defaults to [SigHash.DEFAULT])
|
|
132
|
+
* @throws Error if verification fails
|
|
133
|
+
*/
|
|
134
|
+
export function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT]) {
|
|
135
|
+
const input = tx.getInput(inputIndex);
|
|
136
|
+
// Collect prevout scripts and amounts for ALL inputs (required for preimageWitnessV1)
|
|
137
|
+
const prevoutScripts = [];
|
|
138
|
+
const prevoutAmounts = [];
|
|
139
|
+
for (let i = 0; i < tx.inputsLength; i++) {
|
|
140
|
+
const inp = tx.getInput(i);
|
|
141
|
+
if (!inp.witnessUtxo) {
|
|
142
|
+
throw new Error(`Input ${i} is missing witnessUtxo`);
|
|
143
|
+
}
|
|
144
|
+
prevoutScripts.push(inp.witnessUtxo.script);
|
|
145
|
+
prevoutAmounts.push(inp.witnessUtxo.amount);
|
|
146
|
+
}
|
|
147
|
+
// Verify tapScriptSig signatures
|
|
148
|
+
if (!input.tapScriptSig || input.tapScriptSig.length === 0) {
|
|
149
|
+
throw new Error(`Input ${inputIndex} is missing tapScriptSig`);
|
|
150
|
+
}
|
|
151
|
+
// Verify each signature in tapScriptSig
|
|
152
|
+
for (const [tapScriptSigData, signature] of input.tapScriptSig) {
|
|
153
|
+
const pubKey = tapScriptSigData.pubKey;
|
|
154
|
+
const pubKeyHex = hex.encode(pubKey);
|
|
155
|
+
// Skip verification for excluded pubkeys
|
|
156
|
+
if (excludePubkeys.includes(pubKeyHex)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Extract sighash type from signature
|
|
160
|
+
// Schnorr signatures are 64 bytes, with optional 1-byte sighash appended
|
|
161
|
+
const sighashType = signature.length === 65 ? signature[64] : SigHash.DEFAULT;
|
|
162
|
+
const sig = signature.subarray(0, 64);
|
|
163
|
+
// Verify sighash type is allowed
|
|
164
|
+
if (!allowedSighashTypes.includes(sighashType)) {
|
|
165
|
+
const sighashName = formatSighash(sighashType);
|
|
166
|
+
throw new Error(`Unallowed sighash type ${sighashName} for input ${inputIndex}, pubkey ${pubKeyHex}.`);
|
|
167
|
+
}
|
|
168
|
+
// Find the tapLeafScript that matches this signature's leafHash
|
|
169
|
+
if (!input.tapLeafScript || input.tapLeafScript.length === 0) {
|
|
170
|
+
throw new Error();
|
|
171
|
+
}
|
|
172
|
+
// Search for the leaf that matches the leafHash in tapScriptSigData
|
|
173
|
+
const leafHash = tapScriptSigData.leafHash;
|
|
174
|
+
const leafHashHex = hex.encode(leafHash);
|
|
175
|
+
let matchingScript;
|
|
176
|
+
let matchingVersion;
|
|
177
|
+
for (const [_, scriptWithVersion] of input.tapLeafScript) {
|
|
178
|
+
const script = scriptWithVersion.subarray(0, -1);
|
|
179
|
+
const version = scriptWithVersion[scriptWithVersion.length - 1];
|
|
180
|
+
// Compute the leaf hash for this script and compare as hex strings
|
|
181
|
+
const computedLeafHash = tapLeafHash(script, version);
|
|
182
|
+
const computedHex = hex.encode(computedLeafHash);
|
|
183
|
+
if (computedHex === leafHashHex) {
|
|
184
|
+
matchingScript = script;
|
|
185
|
+
matchingVersion = version;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!matchingScript || matchingVersion === undefined) {
|
|
190
|
+
throw new Error(`Input ${inputIndex}: No tapLeafScript found matching leafHash ${hex.encode(leafHash)}`);
|
|
191
|
+
}
|
|
192
|
+
// Reconstruct the message that was signed
|
|
193
|
+
// Note: preimageWitnessV1 requires ALL input prevout scripts and amounts
|
|
194
|
+
const message = tx.preimageWitnessV1(inputIndex, prevoutScripts, sighashType, prevoutAmounts, undefined, matchingScript, matchingVersion);
|
|
195
|
+
// Verify the schnorr signature
|
|
196
|
+
const isValid = schnorr.verify(sig, message, pubKey);
|
|
197
|
+
if (!isValid) {
|
|
198
|
+
throw new Error(`Invalid signature for input ${inputIndex}, pubkey ${pubKeyHex}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Verify we have signatures from all required signers (excluding those we're skipping)
|
|
202
|
+
const signedPubkeys = input.tapScriptSig.map(([data]) => hex.encode(data.pubKey));
|
|
203
|
+
const requiredNotExcluded = requiredSigners.filter((pk) => !excludePubkeys.includes(pk));
|
|
204
|
+
const missingSigners = requiredNotExcluded.filter((pk) => !signedPubkeys.includes(pk));
|
|
205
|
+
if (missingSigners.length > 0) {
|
|
206
|
+
throw new Error(`Missing signatures from: ${missingSigners.map((pk) => pk.slice(0, 16)).join(", ")}...`);
|
|
207
|
+
}
|
|
208
|
+
}
|
package/dist/esm/wallet/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export var TxType;
|
|
|
4
4
|
TxType["TxReceived"] = "RECEIVED";
|
|
5
5
|
})(TxType || (TxType = {}));
|
|
6
6
|
export function isSpendable(vtxo) {
|
|
7
|
-
return vtxo.
|
|
7
|
+
return !vtxo.isSpent;
|
|
8
8
|
}
|
|
9
9
|
export function isRecoverable(vtxo) {
|
|
10
10
|
return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { p2tr } from "@scure/btc-signer
|
|
1
|
+
import { Transaction, p2tr } from "@scure/btc-signer";
|
|
2
2
|
import { getNetwork } from '../networks.js';
|
|
3
3
|
import { ESPLORA_URL, EsploraProvider, } from '../providers/onchain.js';
|
|
4
|
-
import { Transaction } from "@scure/btc-signer/transaction.js";
|
|
5
4
|
import { findP2AOutput, P2A } from '../utils/anchor.js';
|
|
6
5
|
import { TxWeightEstimator } from '../utils/txSizeEstimator.js';
|
|
7
6
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const DEFAULT_DB_NAME = "arkade-service-worker";
|
|
1
2
|
/**
|
|
2
3
|
* setupServiceWorker sets up the service worker.
|
|
3
4
|
* @param path - the path to the service worker script
|
|
@@ -44,11 +45,3 @@ export async function setupServiceWorker(path) {
|
|
|
44
45
|
navigator.serviceWorker.addEventListener("error", onError);
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
|
-
export function extendVirtualCoin(wallet, vtxo) {
|
|
48
|
-
return {
|
|
49
|
-
...vtxo,
|
|
50
|
-
forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
|
|
51
|
-
intentTapLeafScript: wallet.offchainTapscript.exit(),
|
|
52
|
-
tapTree: wallet.offchainTapscript.encode(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
@@ -3,7 +3,7 @@ import { hex } from "@scure/base";
|
|
|
3
3
|
import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
|
|
4
4
|
import { WalletRepositoryImpl } from '../../repositories/walletRepository.js';
|
|
5
5
|
import { ContractRepositoryImpl } from '../../repositories/contractRepository.js';
|
|
6
|
-
import { setupServiceWorker } from './utils.js';
|
|
6
|
+
import { DEFAULT_DB_NAME, setupServiceWorker } from './utils.js';
|
|
7
7
|
const isPrivateKeyIdentity = (identity) => {
|
|
8
8
|
return typeof identity.toHex === "function";
|
|
9
9
|
};
|
|
@@ -22,7 +22,7 @@ export class ServiceWorkerWallet {
|
|
|
22
22
|
}
|
|
23
23
|
static async create(options) {
|
|
24
24
|
// Default to IndexedDB for service worker context
|
|
25
|
-
const storage = options.
|
|
25
|
+
const storage = new IndexedDBStorageAdapter(options.dbName || DEFAULT_DB_NAME, options.dbVersion);
|
|
26
26
|
// Create repositories
|
|
27
27
|
const walletRepo = new WalletRepositoryImpl(storage);
|
|
28
28
|
const contractRepo = new ContractRepositoryImpl(storage);
|
|
@@ -31,7 +31,7 @@ export class ServiceWorkerWallet {
|
|
|
31
31
|
? options.identity
|
|
32
32
|
: null;
|
|
33
33
|
if (!identity) {
|
|
34
|
-
throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose
|
|
34
|
+
throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
|
|
35
35
|
}
|
|
36
36
|
// Extract private key for service worker initialization
|
|
37
37
|
const privateKey = identity.toHex();
|
|
@@ -74,13 +74,9 @@ export class ServiceWorkerWallet {
|
|
|
74
74
|
// Register and setup the service worker
|
|
75
75
|
const serviceWorker = await setupServiceWorker(options.serviceWorkerPath);
|
|
76
76
|
// Use the existing create method
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
arkServerUrl: options.arkServerUrl,
|
|
80
|
-
esploraUrl: options.esploraUrl,
|
|
81
|
-
identity: options.identity,
|
|
77
|
+
return ServiceWorkerWallet.create({
|
|
78
|
+
...options,
|
|
82
79
|
serviceWorker,
|
|
83
|
-
storage: options.storage,
|
|
84
80
|
});
|
|
85
81
|
}
|
|
86
82
|
// send a message and wait for a response
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="webworker" />
|
|
2
2
|
import { SingleKey } from '../../identity/singleKey.js';
|
|
3
|
-
import { isSpendable, isSubdust } from '../index.js';
|
|
3
|
+
import { isRecoverable, isSpendable, isSubdust } from '../index.js';
|
|
4
4
|
import { Wallet } from '../wallet.js';
|
|
5
5
|
import { Request } from './request.js';
|
|
6
6
|
import { Response } from './response.js';
|
|
@@ -10,15 +10,18 @@ import { RestIndexerProvider } from '../../providers/indexer.js';
|
|
|
10
10
|
import { hex } from "@scure/base";
|
|
11
11
|
import { IndexedDBStorageAdapter } from '../../storage/indexedDB.js';
|
|
12
12
|
import { WalletRepositoryImpl, } from '../../repositories/walletRepository.js';
|
|
13
|
-
import { extendVirtualCoin } from '
|
|
13
|
+
import { extendVirtualCoin } from '../utils.js';
|
|
14
|
+
import { DEFAULT_DB_NAME } from './utils.js';
|
|
14
15
|
/**
|
|
15
16
|
* Worker is a class letting to interact with ServiceWorkerWallet from the client
|
|
16
17
|
* it aims to be run in a service worker context
|
|
17
18
|
*/
|
|
18
19
|
export class Worker {
|
|
19
|
-
constructor(messageCallback = () => { }) {
|
|
20
|
+
constructor(dbName = DEFAULT_DB_NAME, dbVersion = 1, messageCallback = () => { }) {
|
|
21
|
+
this.dbName = dbName;
|
|
22
|
+
this.dbVersion = dbVersion;
|
|
20
23
|
this.messageCallback = messageCallback;
|
|
21
|
-
this.storage = new IndexedDBStorageAdapter(
|
|
24
|
+
this.storage = new IndexedDBStorageAdapter(dbName, dbVersion);
|
|
22
25
|
this.walletRepository = new WalletRepositoryImpl(this.storage);
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
@@ -74,6 +77,8 @@ export class Worker {
|
|
|
74
77
|
this.incomingFundsSubscription();
|
|
75
78
|
// Clear storage - this replaces vtxoRepository.close()
|
|
76
79
|
await this.storage.clear();
|
|
80
|
+
// Reset in-memory caches by recreating the repository
|
|
81
|
+
this.walletRepository = new WalletRepositoryImpl(this.storage);
|
|
77
82
|
this.wallet = undefined;
|
|
78
83
|
this.arkProvider = undefined;
|
|
79
84
|
this.indexerProvider = undefined;
|
|
@@ -102,7 +107,7 @@ export class Worker {
|
|
|
102
107
|
const txs = await this.wallet.getTransactionHistory();
|
|
103
108
|
if (txs)
|
|
104
109
|
await this.walletRepository.saveTransactions(address, txs);
|
|
105
|
-
//
|
|
110
|
+
// unsubscribe previous subscription if any
|
|
106
111
|
if (this.incomingFundsSubscription)
|
|
107
112
|
this.incomingFundsSubscription();
|
|
108
113
|
// subscribe for incoming funds and notify all clients when new funds arrive
|
|
@@ -124,7 +129,7 @@ export class Worker {
|
|
|
124
129
|
// notify all clients about the vtxo update
|
|
125
130
|
this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
|
|
126
131
|
}
|
|
127
|
-
if (funds.type === "utxo"
|
|
132
|
+
if (funds.type === "utxo") {
|
|
128
133
|
// notify all clients about the utxo update
|
|
129
134
|
this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
|
|
130
135
|
}
|
|
@@ -351,23 +356,21 @@ export class Worker {
|
|
|
351
356
|
return;
|
|
352
357
|
}
|
|
353
358
|
try {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
event.source?.postMessage(Response.vtxos(message.id, vtxos));
|
|
359
|
+
const vtxos = await this.getSpendableVtxos();
|
|
360
|
+
const dustAmount = this.wallet.dustAmount;
|
|
361
|
+
const includeRecoverable = message.filter?.withRecoverable ?? false;
|
|
362
|
+
const filteredVtxos = includeRecoverable
|
|
363
|
+
? vtxos
|
|
364
|
+
: vtxos.filter((v) => {
|
|
365
|
+
if (dustAmount != null && isSubdust(v, dustAmount)) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (isRecoverable(v)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
return true;
|
|
372
|
+
});
|
|
373
|
+
event.source?.postMessage(Response.vtxos(message.id, filteredVtxos));
|
|
371
374
|
}
|
|
372
375
|
catch (error) {
|
|
373
376
|
console.error("Error getting vtxos:", error);
|
|
@@ -526,7 +529,6 @@ export class Worker {
|
|
|
526
529
|
}
|
|
527
530
|
async handleReloadWallet(event) {
|
|
528
531
|
const message = event.data;
|
|
529
|
-
console.log("RELOAD_WALLET message received", message);
|
|
530
532
|
if (!Request.isReloadWallet(message)) {
|
|
531
533
|
console.error("Invalid RELOAD_WALLET message format", message);
|
|
532
534
|
event.source?.postMessage(Response.error(message.id, "Invalid RELOAD_WALLET message format"));
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { SigHash, Transaction } from "@scure/btc-signer/transaction.js";
|
|
2
|
-
import { ChainTxType } from '../providers/indexer.js';
|
|
3
1
|
import { base64, hex } from "@scure/base";
|
|
2
|
+
import { SigHash, Transaction, TaprootControlBlock } from "@scure/btc-signer";
|
|
3
|
+
import { ChainTxType } from '../providers/indexer.js';
|
|
4
4
|
import { VtxoScript } from '../script/base.js';
|
|
5
|
-
import { TaprootControlBlock, } from "@scure/btc-signer/psbt.js";
|
|
6
5
|
import { TxWeightEstimator } from '../utils/txSizeEstimator.js';
|
|
7
6
|
import { Wallet } from './wallet.js';
|
|
8
7
|
export var Unroll;
|