@arkade-os/sdk 0.3.0-alpha.7 → 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.
Files changed (109) hide show
  1. package/README.md +99 -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 +2 -2
  5. package/dist/cjs/identity/singleKey.js +8 -8
  6. package/dist/cjs/index.js +14 -5
  7. package/dist/cjs/{bip322 → intent}/index.js +38 -61
  8. package/dist/cjs/musig2/index.js +2 -1
  9. package/dist/cjs/musig2/nonces.js +4 -0
  10. package/dist/cjs/providers/ark.js +76 -45
  11. package/dist/cjs/providers/errors.js +59 -0
  12. package/dist/cjs/providers/expoArk.js +82 -0
  13. package/dist/cjs/providers/expoIndexer.js +105 -0
  14. package/dist/cjs/providers/expoUtils.js +124 -0
  15. package/dist/cjs/providers/indexer.js +3 -1
  16. package/dist/cjs/providers/onchain.js +19 -20
  17. package/dist/cjs/repositories/walletRepository.js +64 -28
  18. package/dist/cjs/script/base.js +15 -7
  19. package/dist/cjs/script/tapscript.js +20 -21
  20. package/dist/cjs/script/vhtlc.js +2 -2
  21. package/dist/cjs/tree/signingSession.js +44 -11
  22. package/dist/cjs/tree/txTree.js +3 -4
  23. package/dist/cjs/tree/validation.js +2 -3
  24. package/dist/cjs/utils/arkTransaction.js +118 -15
  25. package/dist/cjs/utils/transaction.js +28 -0
  26. package/dist/cjs/utils/unknownFields.js +7 -7
  27. package/dist/cjs/wallet/index.js +1 -1
  28. package/dist/cjs/wallet/onchain.js +6 -7
  29. package/dist/cjs/wallet/serviceWorker/response.js +32 -0
  30. package/dist/cjs/wallet/serviceWorker/utils.js +2 -9
  31. package/dist/cjs/wallet/serviceWorker/wallet.js +7 -8
  32. package/dist/cjs/wallet/serviceWorker/worker.js +48 -32
  33. package/dist/cjs/wallet/unroll.js +7 -9
  34. package/dist/cjs/wallet/utils.js +20 -0
  35. package/dist/cjs/wallet/vtxo-manager.js +323 -0
  36. package/dist/cjs/wallet/wallet.js +165 -174
  37. package/dist/esm/adapters/expo.js +3 -0
  38. package/dist/esm/arknote/index.js +2 -2
  39. package/dist/esm/forfeit.js +1 -1
  40. package/dist/esm/identity/singleKey.js +9 -9
  41. package/dist/esm/index.js +14 -10
  42. package/dist/esm/{bip322 → intent}/index.js +32 -54
  43. package/dist/esm/musig2/index.js +1 -1
  44. package/dist/esm/musig2/nonces.js +3 -0
  45. package/dist/esm/providers/ark.js +76 -45
  46. package/dist/esm/providers/errors.js +54 -0
  47. package/dist/esm/providers/expoArk.js +78 -0
  48. package/dist/esm/providers/expoIndexer.js +101 -0
  49. package/dist/esm/providers/expoUtils.js +87 -0
  50. package/dist/esm/providers/indexer.js +3 -1
  51. package/dist/esm/providers/onchain.js +19 -20
  52. package/dist/esm/repositories/walletRepository.js +64 -28
  53. package/dist/esm/script/base.js +12 -4
  54. package/dist/esm/script/tapscript.js +1 -2
  55. package/dist/esm/script/vhtlc.js +1 -1
  56. package/dist/esm/tree/signingSession.js +45 -12
  57. package/dist/esm/tree/txTree.js +3 -4
  58. package/dist/esm/tree/validation.js +2 -3
  59. package/dist/esm/utils/arkTransaction.js +110 -9
  60. package/dist/esm/utils/transaction.js +24 -0
  61. package/dist/esm/utils/unknownFields.js +3 -3
  62. package/dist/esm/wallet/index.js +1 -1
  63. package/dist/esm/wallet/onchain.js +3 -4
  64. package/dist/esm/wallet/serviceWorker/response.js +32 -0
  65. package/dist/esm/wallet/serviceWorker/utils.js +1 -8
  66. package/dist/esm/wallet/serviceWorker/wallet.js +8 -9
  67. package/dist/esm/wallet/serviceWorker/worker.js +49 -33
  68. package/dist/esm/wallet/unroll.js +5 -7
  69. package/dist/esm/wallet/utils.js +16 -0
  70. package/dist/esm/wallet/vtxo-manager.js +317 -0
  71. package/dist/esm/wallet/wallet.js +159 -168
  72. package/dist/types/adapters/expo.d.ts +4 -0
  73. package/dist/types/arknote/index.d.ts +1 -1
  74. package/dist/types/forfeit.d.ts +2 -2
  75. package/dist/types/identity/index.d.ts +2 -2
  76. package/dist/types/identity/singleKey.d.ts +2 -2
  77. package/dist/types/index.d.ts +11 -9
  78. package/dist/types/intent/index.d.ts +41 -0
  79. package/dist/types/musig2/index.d.ts +1 -1
  80. package/dist/types/musig2/nonces.d.ts +1 -0
  81. package/dist/types/providers/ark.d.ts +197 -27
  82. package/dist/types/providers/errors.d.ts +13 -0
  83. package/dist/types/providers/expoArk.d.ts +22 -0
  84. package/dist/types/providers/expoIndexer.d.ts +18 -0
  85. package/dist/types/providers/expoUtils.d.ts +18 -0
  86. package/dist/types/providers/indexer.d.ts +8 -8
  87. package/dist/types/providers/onchain.d.ts +6 -2
  88. package/dist/types/repositories/walletRepository.d.ts +9 -5
  89. package/dist/types/script/base.d.ts +5 -2
  90. package/dist/types/tree/signingSession.d.ts +16 -11
  91. package/dist/types/utils/anchor.d.ts +2 -2
  92. package/dist/types/utils/arkTransaction.d.ts +15 -5
  93. package/dist/types/utils/transaction.d.ts +13 -0
  94. package/dist/types/utils/unknownFields.d.ts +4 -4
  95. package/dist/types/wallet/index.d.ts +47 -7
  96. package/dist/types/wallet/onchain.d.ts +1 -1
  97. package/dist/types/wallet/serviceWorker/response.d.ts +16 -2
  98. package/dist/types/wallet/serviceWorker/utils.d.ts +1 -2
  99. package/dist/types/wallet/serviceWorker/wallet.d.ts +2 -2
  100. package/dist/types/wallet/serviceWorker/worker.d.ts +7 -1
  101. package/dist/types/wallet/unroll.d.ts +1 -1
  102. package/dist/types/wallet/utils.d.ts +3 -0
  103. package/dist/types/wallet/vtxo-manager.d.ts +179 -0
  104. package/dist/types/wallet/wallet.d.ts +17 -5
  105. package/package.json +11 -3
  106. package/dist/cjs/bip322/errors.js +0 -13
  107. package/dist/esm/bip322/errors.js +0 -9
  108. package/dist/types/bip322/errors.d.ts +0 -6
  109. package/dist/types/bip322/index.d.ts +0 -57
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildOffchainTx = buildOffchainTx;
4
- const transaction_js_1 = require("@scure/btc-signer/transaction.js");
4
+ exports.hasBoardingTxExpired = hasBoardingTxExpired;
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");
5
10
  const tapscript_1 = require("../script/tapscript");
6
- const base_1 = require("../script/base");
11
+ const base_2 = require("../script/base");
7
12
  const anchor_1 = require("./anchor");
8
- const base_2 = require("@scure/base");
9
- const utils_js_1 = require("@scure/btc-signer/utils.js");
10
13
  const unknownFields_1 = require("./unknownFields");
14
+ const transaction_1 = require("./transaction");
11
15
  /**
12
16
  * Builds an offchain transaction with checkpoint transactions.
13
17
  *
@@ -31,7 +35,7 @@ function buildOffchainTx(inputs, outputs, serverUnrollScript) {
31
35
  function buildVirtualTx(inputs, outputs) {
32
36
  let lockTime = 0n;
33
37
  for (const input of inputs) {
34
- const tapscript = (0, tapscript_1.decodeTapscript)((0, base_1.scriptFromTapLeafScript)(input.tapLeafScript));
38
+ const tapscript = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(input.tapLeafScript));
35
39
  if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) {
36
40
  if (lockTime !== 0n) {
37
41
  // if a locktime is already set, check if the new locktime is in the same unit
@@ -45,19 +49,17 @@ function buildVirtualTx(inputs, outputs) {
45
49
  }
46
50
  }
47
51
  }
48
- const tx = new transaction_js_1.Transaction({
52
+ const tx = new transaction_1.Transaction({
49
53
  version: 3,
50
- allowUnknown: true,
51
- allowUnknownOutputs: true,
52
54
  lockTime: Number(lockTime),
53
55
  });
54
56
  for (const [i, input] of inputs.entries()) {
55
57
  tx.addInput({
56
58
  txid: input.txid,
57
59
  index: input.vout,
58
- sequence: lockTime ? transaction_js_1.DEFAULT_SEQUENCE - 1 : undefined,
60
+ sequence: lockTime ? btc_signer_1.DEFAULT_SEQUENCE - 1 : undefined,
59
61
  witnessUtxo: {
60
- script: base_1.VtxoScript.decode(input.tapTree).pkScript,
62
+ script: base_2.VtxoScript.decode(input.tapTree).pkScript,
61
63
  amount: BigInt(input.value),
62
64
  },
63
65
  tapLeafScript: [input.tapLeafScript],
@@ -73,10 +75,9 @@ function buildVirtualTx(inputs, outputs) {
73
75
  }
74
76
  function buildCheckpointTx(vtxo, serverUnrollScript) {
75
77
  // create the checkpoint vtxo script from collaborative closure
76
- const collaborativeClosure = (0, tapscript_1.decodeTapscript)(vtxo.checkpointTapLeafScript ??
77
- (0, base_1.scriptFromTapLeafScript)(vtxo.tapLeafScript));
78
+ const collaborativeClosure = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
78
79
  // create the checkpoint vtxo script combining collaborative closure and server unroll script
79
- const checkpointVtxoScript = new base_1.VtxoScript([
80
+ const checkpointVtxoScript = new base_2.VtxoScript([
80
81
  serverUnrollScript.script,
81
82
  collaborativeClosure.script,
82
83
  ]);
@@ -88,10 +89,10 @@ function buildCheckpointTx(vtxo, serverUnrollScript) {
88
89
  },
89
90
  ]);
90
91
  // get the collaborative leaf proof
91
- const collaborativeLeafProof = checkpointVtxoScript.findLeaf(base_2.hex.encode(collaborativeClosure.script));
92
+ const collaborativeLeafProof = checkpointVtxoScript.findLeaf(base_1.hex.encode(collaborativeClosure.script));
92
93
  // create the checkpoint input that will be used as input of the virtual tx
93
94
  const checkpointInput = {
94
- txid: base_2.hex.encode((0, utils_js_1.sha256x2)(checkpointTx.toBytes(true)).reverse()),
95
+ txid: checkpointTx.id,
95
96
  vout: 0,
96
97
  value: vtxo.value,
97
98
  tapLeafScript: collaborativeLeafProof,
@@ -106,3 +107,105 @@ const nLocktimeMinSeconds = 500000000n;
106
107
  function isSeconds(locktime) {
107
108
  return locktime >= nLocktimeMinSeconds;
108
109
  }
110
+ function hasBoardingTxExpired(coin, boardingTimelock) {
111
+ if (!coin.status.block_time)
112
+ return false;
113
+ if (boardingTimelock.value === 0n)
114
+ return true;
115
+ if (boardingTimelock.type !== "blocks")
116
+ return false; // TODO: handle get chain tip
117
+ // validate expiry in terms of seconds
118
+ const now = BigInt(Math.floor(Date.now() / 1000));
119
+ const blockTime = BigInt(Math.floor(coin.status.block_time));
120
+ return blockTime + boardingTimelock.value <= now;
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 script_js_1 = require("@scure/btc-signer/script.js");
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 255.
54
+ * Every ark psbt field has key type 222.
55
55
  */
56
- exports.ArkPsbtFieldKeyType = 255;
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
- script_js_1.RawWitness.encode(value),
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 script_js_1.RawWitness.decode(value[1]);
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, script_js_1.ScriptNum)(6, true).encode(value.value === 0n ? 0n : value.value),
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, script_js_1.ScriptNum)(6, true).decode(unknown[1]);
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));
@@ -10,7 +10,7 @@ var TxType;
10
10
  TxType["TxReceived"] = "RECEIVED";
11
11
  })(TxType || (exports.TxType = TxType = {}));
12
12
  function isSpendable(vtxo) {
13
- return vtxo.spentBy === undefined || vtxo.spentBy === "";
13
+ return !vtxo.isSpent;
14
14
  }
15
15
  function isRecoverable(vtxo) {
16
16
  return vtxo.virtualStatus.state === "swept" && isSpendable(vtxo);
@@ -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 payment_js_1 = require("@scure/btc-signer/payment.js");
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, payment_js_1.p2tr)(pubkey, undefined, network);
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 transaction_js_1.Transaction();
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 transaction_js_1.Transaction({
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 = {}));
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_DB_NAME = void 0;
3
4
  exports.setupServiceWorker = setupServiceWorker;
4
- exports.extendVirtualCoin = extendVirtualCoin;
5
+ exports.DEFAULT_DB_NAME = "arkade-service-worker";
5
6
  /**
6
7
  * setupServiceWorker sets up the service worker.
7
8
  * @param path - the path to the service worker script
@@ -48,11 +49,3 @@ async function setupServiceWorker(path) {
48
49
  navigator.serviceWorker.addEventListener("error", onError);
49
50
  });
50
51
  }
51
- function extendVirtualCoin(wallet, vtxo) {
52
- return {
53
- ...vtxo,
54
- forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
55
- intentTapLeafScript: wallet.offchainTapscript.exit(),
56
- tapTree: wallet.offchainTapscript.encode(),
57
- };
58
- }
@@ -25,7 +25,7 @@ class ServiceWorkerWallet {
25
25
  }
26
26
  static async create(options) {
27
27
  // Default to IndexedDB for service worker context
28
- const storage = options.storage || new indexedDB_1.IndexedDBStorageAdapter("wallet-db");
28
+ const storage = new indexedDB_1.IndexedDBStorageAdapter(options.dbName || utils_1.DEFAULT_DB_NAME, options.dbVersion);
29
29
  // Create repositories
30
30
  const walletRepo = new walletRepository_1.WalletRepositoryImpl(storage);
31
31
  const contractRepo = new contractRepository_1.ContractRepositoryImpl(storage);
@@ -34,7 +34,7 @@ class ServiceWorkerWallet {
34
34
  ? options.identity
35
35
  : null;
36
36
  if (!identity) {
37
- throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose its private key");
37
+ throw new Error("ServiceWorkerWallet.create() requires a Identity that can expose a single private key");
38
38
  }
39
39
  // Extract private key for service worker initialization
40
40
  const privateKey = identity.toHex();
@@ -77,13 +77,9 @@ class ServiceWorkerWallet {
77
77
  // Register and setup the service worker
78
78
  const serviceWorker = await (0, utils_1.setupServiceWorker)(options.serviceWorkerPath);
79
79
  // Use the existing create method
80
- return await ServiceWorkerWallet.create({
81
- arkServerPublicKey: options.arkServerPublicKey,
82
- arkServerUrl: options.arkServerUrl,
83
- esploraUrl: options.esploraUrl,
84
- identity: options.identity,
80
+ return ServiceWorkerWallet.create({
81
+ ...options,
85
82
  serviceWorker,
86
- storage: options.storage,
87
83
  });
88
84
  }
89
85
  // send a message and wait for a response
@@ -260,6 +256,9 @@ class ServiceWorkerWallet {
260
256
  return new Promise((resolve, reject) => {
261
257
  const messageHandler = (event) => {
262
258
  const response = event.data;
259
+ if (response.id !== message.id) {
260
+ return;
261
+ }
263
262
  if (!response.success) {
264
263
  navigator.serviceWorker.removeEventListener("message", messageHandler);
265
264
  reject(new Error(response.message));
@@ -13,15 +13,18 @@ const indexer_1 = require("../../providers/indexer");
13
13
  const base_1 = require("@scure/base");
14
14
  const indexedDB_1 = require("../../storage/indexedDB");
15
15
  const walletRepository_1 = require("../../repositories/walletRepository");
16
- const utils_1 = require("./utils");
16
+ const utils_1 = require("../utils");
17
+ const utils_2 = require("./utils");
17
18
  /**
18
19
  * Worker is a class letting to interact with ServiceWorkerWallet from the client
19
20
  * it aims to be run in a service worker context
20
21
  */
21
22
  class Worker {
22
- constructor(messageCallback = () => { }) {
23
+ constructor(dbName = utils_2.DEFAULT_DB_NAME, dbVersion = 1, messageCallback = () => { }) {
24
+ this.dbName = dbName;
25
+ this.dbVersion = dbVersion;
23
26
  this.messageCallback = messageCallback;
24
- this.storage = new indexedDB_1.IndexedDBStorageAdapter("arkade-service-worker", 1);
27
+ this.storage = new indexedDB_1.IndexedDBStorageAdapter(dbName, dbVersion);
25
28
  this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
26
29
  }
27
30
  /**
@@ -57,6 +60,15 @@ class Worker {
57
60
  spent: allVtxos.filter((vtxo) => !(0, __1.isSpendable)(vtxo)),
58
61
  };
59
62
  }
63
+ /**
64
+ * Get all boarding utxos from wallet repository
65
+ */
66
+ async getAllBoardingUtxos() {
67
+ if (!this.wallet)
68
+ return [];
69
+ const address = await this.wallet.getBoardingAddress();
70
+ return await this.walletRepository.getUtxos(address);
71
+ }
60
72
  async start(withServiceWorkerUpdate = true) {
61
73
  self.addEventListener("message", async (event) => {
62
74
  await this.handleMessage(event);
@@ -77,6 +89,8 @@ class Worker {
77
89
  this.incomingFundsSubscription();
78
90
  // Clear storage - this replaces vtxoRepository.close()
79
91
  await this.storage.clear();
92
+ // Reset in-memory caches by recreating the repository
93
+ this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
80
94
  this.wallet = undefined;
81
95
  this.arkProvider = undefined;
82
96
  this.indexerProvider = undefined;
@@ -105,7 +119,7 @@ class Worker {
105
119
  const txs = await this.wallet.getTransactionHistory();
106
120
  if (txs)
107
121
  await this.walletRepository.saveTransactions(address, txs);
108
- // stop previous subscriptions if any
122
+ // unsubscribe previous subscription if any
109
123
  if (this.incomingFundsSubscription)
110
124
  this.incomingFundsSubscription();
111
125
  // subscribe for incoming funds and notify all clients when new funds arrive
@@ -125,11 +139,19 @@ class Worker {
125
139
  ...spentVtxos,
126
140
  ]);
127
141
  // notify all clients about the vtxo update
128
- this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
142
+ this.sendMessageToAllClients(response_1.Response.vtxoUpdate(newVtxos, spentVtxos));
129
143
  }
130
- if (funds.type === "utxo" && funds.coins.length > 0) {
144
+ if (funds.type === "utxo") {
145
+ const newUtxos = funds.coins.map((utxo) => (0, utils_1.extendCoin)(this.wallet, utxo));
146
+ if (newUtxos.length === 0) {
147
+ this.sendMessageToAllClients(response_1.Response.utxoUpdate([]));
148
+ return;
149
+ }
150
+ const boardingAddress = await this.wallet?.getBoardingAddress();
151
+ // save utxos using unified repository
152
+ await this.walletRepository.saveUtxos(boardingAddress, newUtxos);
131
153
  // notify all clients about the utxo update
132
- this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
154
+ this.sendMessageToAllClients(response_1.Response.utxoUpdate(funds.coins));
133
155
  }
134
156
  });
135
157
  }
@@ -286,7 +308,7 @@ class Worker {
286
308
  }
287
309
  try {
288
310
  const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
289
- this.wallet.getBoardingUtxos(),
311
+ this.getAllBoardingUtxos(),
290
312
  this.getSpendableVtxos(),
291
313
  this.getSweptVtxos(),
292
314
  ]);
@@ -354,23 +376,21 @@ class Worker {
354
376
  return;
355
377
  }
356
378
  try {
357
- let vtxos = await this.getSpendableVtxos();
358
- if (!message.filter?.withRecoverable) {
359
- if (!this.wallet)
360
- throw new Error("Wallet not initialized");
361
- // exclude subdust is we don't want recoverable
362
- const dustAmount = this.wallet?.dustAmount;
363
- vtxos =
364
- dustAmount == null
365
- ? vtxos
366
- : vtxos.filter((v) => !(0, __1.isSubdust)(v, dustAmount));
367
- }
368
- if (message.filter?.withRecoverable) {
369
- // get also swept and spendable vtxos
370
- const sweptVtxos = await this.getSweptVtxos();
371
- vtxos.push(...sweptVtxos.filter(__1.isSpendable));
372
- }
373
- event.source?.postMessage(response_1.Response.vtxos(message.id, vtxos));
379
+ const vtxos = await this.getSpendableVtxos();
380
+ const dustAmount = this.wallet.dustAmount;
381
+ const includeRecoverable = message.filter?.withRecoverable ?? false;
382
+ const filteredVtxos = includeRecoverable
383
+ ? vtxos
384
+ : vtxos.filter((v) => {
385
+ if (dustAmount != null && (0, __1.isSubdust)(v, dustAmount)) {
386
+ return false;
387
+ }
388
+ if ((0, __1.isRecoverable)(v)) {
389
+ return false;
390
+ }
391
+ return true;
392
+ });
393
+ event.source?.postMessage(response_1.Response.vtxos(message.id, filteredVtxos));
374
394
  }
375
395
  catch (error) {
376
396
  console.error("Error getting vtxos:", error);
@@ -393,7 +413,7 @@ class Worker {
393
413
  return;
394
414
  }
395
415
  try {
396
- const boardingUtxos = await this.wallet.getBoardingUtxos();
416
+ const boardingUtxos = await this.getAllBoardingUtxos();
397
417
  event.source?.postMessage(response_1.Response.boardingUtxos(message.id, boardingUtxos));
398
418
  }
399
419
  catch (error) {
@@ -515,21 +535,17 @@ class Worker {
515
535
  event.source?.postMessage(response_1.Response.error(message.id, "Unknown message type"));
516
536
  }
517
537
  }
518
- async sendMessageToAllClients(type, message) {
538
+ async sendMessageToAllClients(message) {
519
539
  self.clients
520
540
  .matchAll({ includeUncontrolled: true, type: "window" })
521
541
  .then((clients) => {
522
542
  clients.forEach((client) => {
523
- client.postMessage({
524
- type,
525
- message,
526
- });
543
+ client.postMessage(message);
527
544
  });
528
545
  });
529
546
  }
530
547
  async handleReloadWallet(event) {
531
548
  const message = event.data;
532
- console.log("RELOAD_WALLET message received", message);
533
549
  if (!request_1.Request.isReloadWallet(message)) {
534
550
  console.error("Invalid RELOAD_WALLET message format", message);
535
551
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid RELOAD_WALLET message format"));
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Unroll = void 0;
4
- const transaction_js_1 = require("@scure/btc-signer/transaction.js");
5
- const indexer_1 = require("../providers/indexer");
6
4
  const base_1 = require("@scure/base");
5
+ const btc_signer_1 = require("@scure/btc-signer");
6
+ const indexer_1 = require("../providers/indexer");
7
7
  const base_2 = require("../script/base");
8
- const psbt_js_1 = require("@scure/btc-signer/psbt.js");
9
8
  const txSizeEstimator_1 = require("../utils/txSizeEstimator");
10
9
  const wallet_1 = require("./wallet");
10
+ const transaction_1 = require("../utils/transaction");
11
11
  var Unroll;
12
12
  (function (Unroll) {
13
13
  let StepType;
@@ -106,9 +106,7 @@ var Unroll;
106
106
  if (virtualTxs.txs.length === 0) {
107
107
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
108
108
  }
109
- const tx = transaction_js_1.Transaction.fromPSBT(base_1.base64.decode(virtualTxs.txs[0]), {
110
- allowUnknownInputs: true,
111
- });
109
+ const tx = transaction_1.Transaction.fromPSBT(base_1.base64.decode(virtualTxs.txs[0]));
112
110
  // finalize the tree transaction
113
111
  if (nextTxToBroadcast.type === indexer_1.ChainTxType.TREE) {
114
112
  const input = tx.getInput(0);
@@ -197,11 +195,11 @@ var Unroll;
197
195
  amount: BigInt(vtxo.value),
198
196
  script: base_2.VtxoScript.decode(vtxo.tapTree).pkScript,
199
197
  },
200
- sighashType: transaction_js_1.SigHash.DEFAULT,
198
+ sighashType: btc_signer_1.SigHash.DEFAULT,
201
199
  });
202
- txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, psbt_js_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
200
+ txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, btc_signer_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
203
201
  }
204
- const tx = new transaction_js_1.Transaction({ allowUnknownInputs: true, version: 2 });
202
+ const tx = new transaction_1.Transaction({ version: 2 });
205
203
  for (const input of inputs) {
206
204
  tx.addInput(input);
207
205
  }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extendVirtualCoin = extendVirtualCoin;
4
+ exports.extendCoin = extendCoin;
5
+ function extendVirtualCoin(wallet, vtxo) {
6
+ return {
7
+ ...vtxo,
8
+ forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
9
+ intentTapLeafScript: wallet.offchainTapscript.exit(),
10
+ tapTree: wallet.offchainTapscript.encode(),
11
+ };
12
+ }
13
+ function extendCoin(wallet, utxo) {
14
+ return {
15
+ ...utxo,
16
+ forfeitTapLeafScript: wallet.boardingTapscript.forfeit(),
17
+ intentTapLeafScript: wallet.boardingTapscript.exit(),
18
+ tapTree: wallet.boardingTapscript.encode(),
19
+ };
20
+ }