@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.
Files changed (88) hide show
  1. package/README.md +115 -14
  2. package/dist/cjs/adapters/expo.js +8 -0
  3. package/dist/cjs/arknote/index.js +3 -3
  4. package/dist/cjs/forfeit.js +5 -2
  5. package/dist/cjs/identity/singleKey.js +5 -4
  6. package/dist/cjs/index.js +7 -3
  7. package/dist/cjs/{bip322 → intent}/index.js +37 -55
  8. package/dist/cjs/providers/ark.js +62 -23
  9. package/dist/cjs/providers/expoArk.js +82 -0
  10. package/dist/cjs/providers/expoIndexer.js +105 -0
  11. package/dist/cjs/providers/indexer.js +3 -1
  12. package/dist/cjs/providers/utils.js +122 -0
  13. package/dist/cjs/script/base.js +1 -2
  14. package/dist/cjs/script/tapscript.js +20 -21
  15. package/dist/cjs/script/vhtlc.js +2 -2
  16. package/dist/cjs/tree/signingSession.js +7 -8
  17. package/dist/cjs/tree/txTree.js +3 -4
  18. package/dist/cjs/tree/validation.js +2 -3
  19. package/dist/cjs/utils/arkTransaction.js +117 -12
  20. package/dist/cjs/utils/unknownFields.js +5 -5
  21. package/dist/cjs/wallet/index.js +1 -1
  22. package/dist/cjs/wallet/onchain.js +4 -5
  23. package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
  24. package/dist/cjs/wallet/serviceWorker/wallet.js +4 -8
  25. package/dist/cjs/wallet/serviceWorker/worker.js +25 -23
  26. package/dist/cjs/wallet/unroll.js +6 -7
  27. package/dist/cjs/wallet/utils.js +11 -0
  28. package/dist/cjs/wallet/vtxo-manager.js +381 -0
  29. package/dist/cjs/wallet/wallet.js +130 -143
  30. package/dist/esm/adapters/expo.js +3 -0
  31. package/dist/esm/arknote/index.js +2 -2
  32. package/dist/esm/forfeit.js +4 -1
  33. package/dist/esm/identity/singleKey.js +7 -6
  34. package/dist/esm/index.js +7 -6
  35. package/dist/esm/{bip322 → intent}/index.js +31 -48
  36. package/dist/esm/providers/ark.js +62 -23
  37. package/dist/esm/providers/expoArk.js +78 -0
  38. package/dist/esm/providers/expoIndexer.js +101 -0
  39. package/dist/esm/providers/indexer.js +3 -1
  40. package/dist/esm/providers/utils.js +87 -0
  41. package/dist/esm/script/base.js +1 -2
  42. package/dist/esm/script/tapscript.js +1 -2
  43. package/dist/esm/script/vhtlc.js +1 -1
  44. package/dist/esm/tree/signingSession.js +8 -9
  45. package/dist/esm/tree/txTree.js +3 -4
  46. package/dist/esm/tree/validation.js +2 -3
  47. package/dist/esm/utils/arkTransaction.js +108 -5
  48. package/dist/esm/utils/unknownFields.js +1 -1
  49. package/dist/esm/wallet/index.js +1 -1
  50. package/dist/esm/wallet/onchain.js +1 -2
  51. package/dist/esm/wallet/serviceWorker/utils.js +1 -8
  52. package/dist/esm/wallet/serviceWorker/wallet.js +5 -9
  53. package/dist/esm/wallet/serviceWorker/worker.js +26 -24
  54. package/dist/esm/wallet/unroll.js +2 -3
  55. package/dist/esm/wallet/utils.js +8 -0
  56. package/dist/esm/wallet/vtxo-manager.js +372 -0
  57. package/dist/esm/wallet/wallet.js +124 -137
  58. package/dist/types/adapters/expo.d.ts +4 -0
  59. package/dist/types/arknote/index.d.ts +1 -1
  60. package/dist/types/forfeit.d.ts +2 -2
  61. package/dist/types/identity/index.d.ts +1 -1
  62. package/dist/types/identity/singleKey.d.ts +1 -1
  63. package/dist/types/index.d.ts +8 -7
  64. package/dist/types/intent/index.d.ts +41 -0
  65. package/dist/types/providers/ark.d.ts +190 -22
  66. package/dist/types/providers/expoArk.d.ts +22 -0
  67. package/dist/types/providers/expoIndexer.d.ts +18 -0
  68. package/dist/types/providers/indexer.d.ts +8 -8
  69. package/dist/types/providers/utils.d.ts +18 -0
  70. package/dist/types/script/base.d.ts +3 -2
  71. package/dist/types/tree/signingSession.d.ts +10 -10
  72. package/dist/types/utils/anchor.d.ts +2 -2
  73. package/dist/types/utils/arkTransaction.d.ts +16 -4
  74. package/dist/types/utils/unknownFields.d.ts +2 -2
  75. package/dist/types/wallet/index.d.ts +47 -7
  76. package/dist/types/wallet/onchain.d.ts +1 -1
  77. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
  78. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  79. package/dist/types/wallet/serviceWorker/worker.d.ts +3 -1
  80. package/dist/types/wallet/unroll.d.ts +1 -1
  81. package/dist/types/wallet/utils.d.ts +2 -0
  82. package/dist/types/wallet/vtxo-manager.d.ts +207 -0
  83. package/dist/types/wallet/wallet.d.ts +16 -4
  84. package/package.json +11 -3
  85. package/dist/cjs/bip322/errors.js +0 -13
  86. package/dist/esm/bip322/errors.js +0 -9
  87. package/dist/types/bip322/errors.d.ts +0 -6
  88. 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, sha256x2 } from "@scure/btc-signer/utils.js";
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 (txid === graph.txid) {
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(new Uint8Array(parentInput.txid));
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");
@@ -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 = hex.encode(sha256x2(decodedChunk.tx.toBytes(true)).reverse());
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 = hex.encode(sha256x2(this.root.toBytes(true)).reverse());
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 hex.encode(sha256x2(this.root.toBytes(true)).reverse());
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 = hex.encode(sha256x2(settlementTx.toBytes(true)).reverse());
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 = hex.encode(sha256x2(roundTransaction.toBytes(true)).reverse());
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 { DEFAULT_SEQUENCE, Transaction, } from "@scure/btc-signer/transaction.js";
2
- import { CLTVMultisigTapscript, decodeTapscript } from '../script/tapscript.js';
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: hex.encode(sha256x2(checkpointTx.toBytes(true)).reverse()),
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
+ }
@@ -1,5 +1,5 @@
1
1
  import * as bip68 from "bip68";
2
- import { RawWitness, ScriptNum } from "@scure/btc-signer/script.js";
2
+ import { RawWitness, ScriptNum } from "@scure/btc-signer";
3
3
  import { hex } from "@scure/base";
4
4
  /**
5
5
  * ArkPsbtFieldKey is the key values for ark psbt fields.
@@ -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.spentBy === undefined || vtxo.spentBy === "";
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/payment.js";
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.storage || new IndexedDBStorageAdapter("wallet-db");
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 its private key");
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 await ServiceWorkerWallet.create({
78
- arkServerPublicKey: options.arkServerPublicKey,
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 './utils.js';
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("arkade-service-worker", 1);
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
- // stop previous subscriptions if any
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" && funds.coins.length > 0) {
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
- let vtxos = await this.getSpendableVtxos();
355
- if (!message.filter?.withRecoverable) {
356
- if (!this.wallet)
357
- throw new Error("Wallet not initialized");
358
- // exclude subdust is we don't want recoverable
359
- const dustAmount = this.wallet?.dustAmount;
360
- vtxos =
361
- dustAmount == null
362
- ? vtxos
363
- : vtxos.filter((v) => !isSubdust(v, dustAmount));
364
- }
365
- if (message.filter?.withRecoverable) {
366
- // get also swept and spendable vtxos
367
- const sweptVtxos = await this.getSweptVtxos();
368
- vtxos.push(...sweptVtxos.filter(isSpendable));
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;
@@ -0,0 +1,8 @@
1
+ export function extendVirtualCoin(wallet, vtxo) {
2
+ return {
3
+ ...vtxo,
4
+ forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
5
+ intentTapLeafScript: wallet.offchainTapscript.exit(),
6
+ tapTree: wallet.offchainTapscript.encode(),
7
+ };
8
+ }