@arkade-os/sdk 0.3.1-alpha.4 → 0.3.1-alpha.6

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 (47) hide show
  1. package/README.md +11 -27
  2. package/dist/cjs/forfeit.js +2 -5
  3. package/dist/cjs/identity/singleKey.js +4 -5
  4. package/dist/cjs/index.js +5 -4
  5. package/dist/cjs/intent/index.js +3 -8
  6. package/dist/cjs/providers/onchain.js +19 -20
  7. package/dist/cjs/repositories/walletRepository.js +64 -2
  8. package/dist/cjs/script/base.js +14 -5
  9. package/dist/cjs/utils/arkTransaction.js +3 -5
  10. package/dist/cjs/utils/transaction.js +28 -0
  11. package/dist/cjs/wallet/onchain.js +4 -4
  12. package/dist/cjs/wallet/serviceWorker/worker.js +19 -2
  13. package/dist/cjs/wallet/unroll.js +3 -4
  14. package/dist/cjs/wallet/utils.js +9 -0
  15. package/dist/cjs/wallet/vtxo-manager.js +31 -89
  16. package/dist/cjs/wallet/wallet.js +11 -13
  17. package/dist/esm/forfeit.js +1 -4
  18. package/dist/esm/identity/singleKey.js +3 -4
  19. package/dist/esm/index.js +3 -3
  20. package/dist/esm/intent/index.js +2 -7
  21. package/dist/esm/providers/onchain.js +19 -20
  22. package/dist/esm/repositories/walletRepository.js +64 -2
  23. package/dist/esm/script/base.js +11 -2
  24. package/dist/esm/utils/arkTransaction.js +3 -5
  25. package/dist/esm/utils/transaction.js +24 -0
  26. package/dist/esm/wallet/onchain.js +3 -3
  27. package/dist/esm/wallet/serviceWorker/worker.js +21 -4
  28. package/dist/esm/wallet/unroll.js +4 -5
  29. package/dist/esm/wallet/utils.js +8 -0
  30. package/dist/esm/wallet/vtxo-manager.js +30 -85
  31. package/dist/esm/wallet/wallet.js +12 -14
  32. package/dist/types/forfeit.d.ts +1 -1
  33. package/dist/types/identity/index.d.ts +1 -1
  34. package/dist/types/identity/singleKey.d.ts +1 -1
  35. package/dist/types/index.d.ts +3 -3
  36. package/dist/types/intent/index.d.ts +1 -1
  37. package/dist/types/providers/onchain.d.ts +6 -2
  38. package/dist/types/repositories/walletRepository.d.ts +9 -1
  39. package/dist/types/script/base.d.ts +2 -0
  40. package/dist/types/utils/arkTransaction.d.ts +1 -3
  41. package/dist/types/utils/transaction.d.ts +13 -0
  42. package/dist/types/wallet/onchain.d.ts +1 -1
  43. package/dist/types/wallet/serviceWorker/worker.d.ts +4 -0
  44. package/dist/types/wallet/unroll.d.ts +1 -1
  45. package/dist/types/wallet/utils.d.ts +2 -1
  46. package/dist/types/wallet/vtxo-manager.d.ts +7 -35
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -127,25 +127,16 @@ const manager = new VtxoManager(wallet, {
127
127
 
128
128
  #### Renewal: Prevent Expiration
129
129
 
130
- Renew VTXOs before they expire to keep your liquidity accessible. This settles all VTXOs (including recoverable ones) back to your wallet with a fresh expiration time.
130
+ Renew VTXOs before they expire to retain unilateral control of funds.
131
+ This settles expiring and recoverable VTXOs back to your wallet, refreshing their expiration time.
131
132
 
132
133
  ```typescript
134
+ // Renew all VTXOs to prevent expiration
135
+ const txid = await manager.renewVtxos()
136
+ console.log('Renewed:', txid)
137
+
133
138
  // Check which VTXOs are expiring soon
134
139
  const expiringVtxos = await manager.getExpiringVtxos()
135
- if (expiringVtxos.length > 0) {
136
- console.log(`${expiringVtxos.length} VTXOs expiring soon`)
137
-
138
- expiringVtxos.forEach(vtxo => {
139
- const timeLeft = vtxo.virtualStatus.batchExpiry! - Date.now()
140
- const hoursLeft = Math.floor(timeLeft / (1000 * 60 * 60))
141
- console.log(`VTXO ${vtxo.txid} expires in ${hoursLeft} hours`)
142
- })
143
-
144
- // Renew all VTXOs to prevent expiration
145
- const txid = await manager.renewVtxos()
146
- console.log('Renewed:', txid)
147
- }
148
-
149
140
  // Override threshold percentage (e.g., renew when 5% of time remains)
150
141
  const urgentlyExpiring = await manager.getExpiringVtxos(5)
151
142
  ```
@@ -156,20 +147,13 @@ const urgentlyExpiring = await manager.getExpiringVtxos(5)
156
147
  Recover VTXOs that have been swept by the server or consolidate small amounts (subdust).
157
148
 
158
149
  ```typescript
150
+ // Recover swept VTXOs and preconfirmed subdust
151
+ const txid = await manager.recoverVtxos((event) => {
152
+ console.log('Settlement event:', event.type)
153
+ })
154
+ console.log('Recovered:', txid)
159
155
  // Check what's recoverable
160
156
  const balance = await manager.getRecoverableBalance()
161
- console.log(`Recoverable: ${balance.recoverable} sats`)
162
- console.log(`Subdust: ${balance.subdust} sats`)
163
- console.log(`Subdust included: ${balance.includesSubdust}`)
164
- console.log(`VTXO count: ${balance.vtxoCount}`)
165
-
166
- if (balance.recoverable > 0n) {
167
- // Recover swept VTXOs and preconfirmed subdust
168
- const txid = await manager.recoverVtxos((event) => {
169
- console.log('Settlement event:', event.type)
170
- })
171
- console.log('Recovered:', txid)
172
- }
173
157
  ```
174
158
 
175
159
 
@@ -1,15 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildForfeitTx = buildForfeitTx;
4
- const btc_signer_1 = require("@scure/btc-signer");
4
+ const transaction_1 = require("./utils/transaction");
5
5
  const anchor_1 = require("./utils/anchor");
6
6
  function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
7
- const tx = new btc_signer_1.Transaction({
7
+ const tx = new transaction_1.Transaction({
8
8
  version: 3,
9
9
  lockTime: txLocktime,
10
- allowUnknownOutputs: true,
11
- allowUnknown: true,
12
- allowUnknownInputs: true,
13
10
  });
14
11
  let amount = 0n;
15
12
  for (const input of inputs) {
@@ -2,12 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SingleKey = void 0;
4
4
  const utils_js_1 = require("@scure/btc-signer/utils.js");
5
- const transaction_js_1 = require("@scure/btc-signer/transaction.js");
5
+ const btc_signer_1 = require("@scure/btc-signer");
6
6
  const base_1 = require("@scure/base");
7
7
  const signingSession_1 = require("../tree/signingSession");
8
8
  const secp256k1_1 = require("@noble/secp256k1");
9
- const ZERO_32 = new Uint8Array(32).fill(0);
10
- const ALL_SIGHASH = Object.values(transaction_js_1.SigHash).filter((x) => typeof x === "number");
9
+ const ALL_SIGHASH = Object.values(btc_signer_1.SigHash).filter((x) => typeof x === "number");
11
10
  /**
12
11
  * In-memory single key implementation for Bitcoin transaction signing.
13
12
  *
@@ -51,7 +50,7 @@ class SingleKey {
51
50
  const txCpy = tx.clone();
52
51
  if (!inputIndexes) {
53
52
  try {
54
- if (!txCpy.sign(this.key, ALL_SIGHASH, ZERO_32)) {
53
+ if (!txCpy.sign(this.key, ALL_SIGHASH)) {
55
54
  throw new Error("Failed to sign transaction");
56
55
  }
57
56
  }
@@ -67,7 +66,7 @@ class SingleKey {
67
66
  return txCpy;
68
67
  }
69
68
  for (const inputIndex of inputIndexes) {
70
- if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH, ZERO_32)) {
69
+ if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH)) {
71
70
  throw new Error(`Failed to sign input #${inputIndex}`);
72
71
  }
73
72
  }
package/dist/cjs/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ArkError = exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
4
- exports.maybeArkError = void 0;
5
- const transaction_js_1 = require("@scure/btc-signer/transaction.js");
6
- Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_js_1.Transaction; } });
3
+ exports.Transaction = exports.Unroll = exports.P2A = exports.TxTree = exports.Intent = exports.ContractRepositoryImpl = exports.WalletRepositoryImpl = exports.networks = exports.ArkNote = exports.hasBoardingTxExpired = exports.waitForIncomingFunds = exports.verifyTapscriptSignatures = exports.buildOffchainTx = exports.ConditionWitness = exports.VtxoTaprootTree = exports.VtxoTreeExpiry = exports.CosignerPublicKey = exports.getArkPsbtFields = exports.setArkPsbtField = exports.ArkPsbtFieldKeyType = exports.ArkPsbtFieldKey = exports.TapTreeCoder = exports.CLTVMultisigTapscript = exports.ConditionMultisigTapscript = exports.ConditionCSVMultisigTapscript = exports.CSVMultisigTapscript = exports.MultisigTapscript = exports.decodeTapscript = exports.Response = exports.Request = exports.ServiceWorkerWallet = exports.Worker = exports.setupServiceWorker = exports.SettlementEventType = exports.ChainTxType = exports.IndexerTxType = exports.TxType = exports.VHTLC = exports.VtxoScript = exports.DefaultVtxo = exports.ArkAddress = exports.RestIndexerProvider = exports.RestArkProvider = exports.EsploraProvider = exports.ESPLORA_URL = exports.VtxoManager = exports.Ramps = exports.OnchainWallet = exports.SingleKey = exports.Wallet = void 0;
4
+ exports.maybeArkError = exports.ArkError = void 0;
5
+ const transaction_1 = require("./utils/transaction");
6
+ Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
7
7
  const singleKey_1 = require("./identity/singleKey");
8
8
  Object.defineProperty(exports, "SingleKey", { enumerable: true, get: function () { return singleKey_1.SingleKey; } });
9
9
  const address_1 = require("./script/address");
@@ -14,6 +14,7 @@ const default_1 = require("./script/default");
14
14
  Object.defineProperty(exports, "DefaultVtxo", { enumerable: true, get: function () { return default_1.DefaultVtxo; } });
15
15
  const base_1 = require("./script/base");
16
16
  Object.defineProperty(exports, "VtxoScript", { enumerable: true, get: function () { return base_1.VtxoScript; } });
17
+ Object.defineProperty(exports, "TapTreeCoder", { enumerable: true, get: function () { return base_1.TapTreeCoder; } });
17
18
  const wallet_1 = require("./wallet");
18
19
  Object.defineProperty(exports, "TxType", { enumerable: true, get: function () { return wallet_1.TxType; } });
19
20
  const wallet_2 = require("./wallet/wallet");
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Intent = void 0;
4
4
  const btc_signer_1 = require("@scure/btc-signer");
5
5
  const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
6
+ const transaction_1 = require("../utils/transaction");
6
7
  /**
7
8
  * Intent proof implementation for Bitcoin message signing.
8
9
  *
@@ -85,11 +86,8 @@ function validateOutputs(outputs) {
85
86
  // craftToSpendTx creates the initial transaction that will be spent in the proof
86
87
  function craftToSpendTx(message, pkScript) {
87
88
  const messageHash = hashMessage(message);
88
- const tx = new btc_signer_1.Transaction({
89
+ const tx = new transaction_1.Transaction({
89
90
  version: 0,
90
- allowUnknownOutputs: true,
91
- allowUnknown: true,
92
- allowUnknownInputs: true,
93
91
  });
94
92
  // add input with zero hash and max index
95
93
  tx.addInput({
@@ -110,11 +108,8 @@ function craftToSpendTx(message, pkScript) {
110
108
  // craftToSignTx creates the transaction that will be signed for the proof
111
109
  function craftToSignTx(toSpend, inputs, outputs) {
112
110
  const firstInput = inputs[0];
113
- const tx = new btc_signer_1.Transaction({
111
+ const tx = new transaction_1.Transaction({
114
112
  version: 2,
115
- allowUnknownOutputs: outputs.length === 0,
116
- allowUnknown: true,
117
- allowUnknownInputs: true,
118
113
  lockTime: 0,
119
114
  });
120
115
  // add the first "toSpend" input
@@ -21,9 +21,10 @@ exports.ESPLORA_URL = {
21
21
  * ```
22
22
  */
23
23
  class EsploraProvider {
24
- constructor(baseUrl) {
24
+ constructor(baseUrl, opts) {
25
25
  this.baseUrl = baseUrl;
26
- this.polling = false;
26
+ this.pollingInterval = opts?.pollingInterval ?? 15000;
27
+ this.forcePolling = opts?.forcePolling ?? false;
27
28
  }
28
29
  async getCoins(address) {
29
30
  const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
@@ -94,13 +95,9 @@ class EsploraProvider {
94
95
  let intervalId = null;
95
96
  const wsUrl = this.baseUrl.replace(/^http(s)?:/, "ws$1:") + "/v1/ws";
96
97
  const poll = async () => {
97
- if (this.polling)
98
- return;
99
- this.polling = true;
100
- // websocket is not reliable, so we will fallback to polling
101
- const pollingInterval = 5000; // 5 seconds
102
- const getAllTxs = () => {
103
- return Promise.all(addresses.map((address) => this.getTransactions(address))).then((txArrays) => txArrays.flat());
98
+ const getAllTxs = async () => {
99
+ const txArrays = await Promise.all(addresses.map((address) => this.getTransactions(address)));
100
+ return txArrays.flat();
104
101
  };
105
102
  // initial fetch to get existing transactions
106
103
  const initialTxs = await getAllTxs();
@@ -125,9 +122,19 @@ class EsploraProvider {
125
122
  catch (error) {
126
123
  console.error("Error in polling mechanism:", error);
127
124
  }
128
- }, pollingInterval);
125
+ }, this.pollingInterval);
129
126
  };
130
127
  let ws = null;
128
+ const stopFunc = () => {
129
+ if (ws)
130
+ ws.close();
131
+ if (intervalId)
132
+ clearInterval(intervalId);
133
+ };
134
+ if (this.forcePolling) {
135
+ await poll();
136
+ return stopFunc;
137
+ }
131
138
  try {
132
139
  ws = new WebSocket(wsUrl);
133
140
  ws.addEventListener("open", () => {
@@ -174,13 +181,6 @@ class EsploraProvider {
174
181
  // if websocket is not available, fallback to polling
175
182
  await poll();
176
183
  }
177
- const stopFunc = () => {
178
- if (ws && ws.readyState === WebSocket.OPEN)
179
- ws.close();
180
- if (intervalId)
181
- clearInterval(intervalId);
182
- this.polling = false;
183
- };
184
184
  return stopFunc;
185
185
  }
186
186
  async getChainTip() {
@@ -249,8 +249,7 @@ const isExplorerTransaction = (tx) => {
249
249
  return (typeof tx.txid === "string" &&
250
250
  Array.isArray(tx.vout) &&
251
251
  tx.vout.every((vout) => typeof vout.scriptpubkey_address === "string" &&
252
- typeof vout.value === "string") &&
252
+ typeof vout.value === "number") &&
253
253
  typeof tx.status === "object" &&
254
- typeof tx.status.confirmed === "boolean" &&
255
- typeof tx.status.block_time === "number");
254
+ typeof tx.status.confirmed === "boolean");
256
255
  };
@@ -15,7 +15,14 @@ const serializeVtxo = (v) => ({
15
15
  tapTree: toHex(v.tapTree),
16
16
  forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
17
17
  intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
18
- extraWitness: v.extraWitness?.map((w) => toHex(w)),
18
+ extraWitness: v.extraWitness?.map(toHex),
19
+ });
20
+ const serializeUtxo = (u) => ({
21
+ ...u,
22
+ tapTree: toHex(u.tapTree),
23
+ forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
24
+ intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
25
+ extraWitness: u.extraWitness?.map(toHex),
19
26
  });
20
27
  const deserializeTapLeaf = (t) => {
21
28
  const cb = btc_signer_1.TaprootControlBlock.decode(fromHex(t.cb));
@@ -27,13 +34,21 @@ const deserializeVtxo = (o) => ({
27
34
  tapTree: fromHex(o.tapTree),
28
35
  forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
29
36
  intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
30
- extraWitness: o.extraWitness?.map((w) => fromHex(w)),
37
+ extraWitness: o.extraWitness?.map(fromHex),
38
+ });
39
+ const deserializeUtxo = (o) => ({
40
+ ...o,
41
+ tapTree: fromHex(o.tapTree),
42
+ forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
43
+ intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
44
+ extraWitness: o.extraWitness?.map(fromHex),
31
45
  });
32
46
  class WalletRepositoryImpl {
33
47
  constructor(storage) {
34
48
  this.storage = storage;
35
49
  this.cache = {
36
50
  vtxos: new Map(),
51
+ utxos: new Map(),
37
52
  transactions: new Map(),
38
53
  walletState: null,
39
54
  initialized: new Set(),
@@ -86,6 +101,53 @@ class WalletRepositoryImpl {
86
101
  this.cache.vtxos.set(address, []);
87
102
  await this.storage.removeItem(`vtxos:${address}`);
88
103
  }
104
+ async getUtxos(address) {
105
+ const cacheKey = `utxos:${address}`;
106
+ if (this.cache.utxos.has(address)) {
107
+ return this.cache.utxos.get(address);
108
+ }
109
+ const stored = await this.storage.getItem(cacheKey);
110
+ if (!stored) {
111
+ this.cache.utxos.set(address, []);
112
+ return [];
113
+ }
114
+ try {
115
+ const parsed = JSON.parse(stored);
116
+ const utxos = parsed.map(deserializeUtxo);
117
+ this.cache.utxos.set(address, utxos.slice());
118
+ return utxos.slice();
119
+ }
120
+ catch (error) {
121
+ console.error(`Failed to parse UTXOs for address ${address}:`, error);
122
+ this.cache.utxos.set(address, []);
123
+ return [];
124
+ }
125
+ }
126
+ async saveUtxos(address, utxos) {
127
+ const storedUtxos = await this.getUtxos(address);
128
+ utxos.forEach((utxo) => {
129
+ const existing = storedUtxos.findIndex((u) => u.txid === utxo.txid && u.vout === utxo.vout);
130
+ if (existing !== -1) {
131
+ storedUtxos[existing] = utxo;
132
+ }
133
+ else {
134
+ storedUtxos.push(utxo);
135
+ }
136
+ });
137
+ this.cache.utxos.set(address, storedUtxos.slice());
138
+ await this.storage.setItem(`utxos:${address}`, JSON.stringify(storedUtxos.map(serializeUtxo)));
139
+ }
140
+ async removeUtxo(address, utxoId) {
141
+ const utxos = await this.getUtxos(address);
142
+ const [txid, vout] = utxoId.split(":");
143
+ const filtered = utxos.filter((v) => !(v.txid === txid && v.vout === parseInt(vout, 10)));
144
+ this.cache.utxos.set(address, filtered.slice());
145
+ await this.storage.setItem(`utxos:${address}`, JSON.stringify(filtered.map(serializeUtxo)));
146
+ }
147
+ async clearUtxos(address) {
148
+ this.cache.utxos.set(address, []);
149
+ await this.storage.removeItem(`utxos:${address}`);
150
+ }
89
151
  async getTransactionHistory(address) {
90
152
  const cacheKey = `tx:${address}`;
91
153
  if (this.cache.transactions.has(address)) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VtxoScript = void 0;
3
+ exports.VtxoScript = exports.TapTreeCoder = void 0;
4
4
  exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
5
5
  const btc_signer_1 = require("@scure/btc-signer");
6
6
  const payment_js_1 = require("@scure/btc-signer/payment.js");
@@ -8,7 +8,7 @@ const psbt_js_1 = require("@scure/btc-signer/psbt.js");
8
8
  const base_1 = require("@scure/base");
9
9
  const address_1 = require("./address");
10
10
  const tapscript_1 = require("./tapscript");
11
- const TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
11
+ exports.TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
12
12
  function scriptFromTapLeafScript(leaf) {
13
13
  return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
14
14
  }
@@ -22,13 +22,22 @@ function scriptFromTapLeafScript(leaf) {
22
22
  */
23
23
  class VtxoScript {
24
24
  static decode(tapTree) {
25
- const leaves = TapTreeCoder.decode(tapTree);
25
+ const leaves = exports.TapTreeCoder.decode(tapTree);
26
26
  const scripts = leaves.map((leaf) => leaf.script);
27
27
  return new VtxoScript(scripts);
28
28
  }
29
29
  constructor(scripts) {
30
30
  this.scripts = scripts;
31
- const tapTree = (0, btc_signer_1.taprootListToTree)(scripts.map((script) => ({ script, leafVersion: payment_js_1.TAP_LEAF_VERSION })));
31
+ // reverse the scripts if the number of scripts is odd
32
+ // this is to be compatible with arkd algorithm computing taproot tree from list of tapscripts
33
+ // the scripts must be reversed only HERE while we compute the tweaked public key
34
+ // but the original order should be preserved while encoding as taptree
35
+ // note: .slice().reverse() is used instead of .reverse() to avoid mutating the original array
36
+ const list = scripts.length % 2 !== 0 ? scripts.slice().reverse() : scripts;
37
+ const tapTree = (0, btc_signer_1.taprootListToTree)(list.map((script) => ({
38
+ script,
39
+ leafVersion: payment_js_1.TAP_LEAF_VERSION,
40
+ })));
32
41
  const payment = (0, btc_signer_1.p2tr)(btc_signer_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
33
42
  if (!payment.tapLeafScript ||
34
43
  payment.tapLeafScript.length !== scripts.length) {
@@ -38,7 +47,7 @@ class VtxoScript {
38
47
  this.tweakedPublicKey = payment.tweakedPubkey;
39
48
  }
40
49
  encode() {
41
- const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
50
+ const tapTree = exports.TapTreeCoder.encode(this.scripts.map((script) => ({
42
51
  depth: 1,
43
52
  version: payment_js_1.TAP_LEAF_VERSION,
44
53
  script,
@@ -11,6 +11,7 @@ const tapscript_1 = require("../script/tapscript");
11
11
  const base_2 = require("../script/base");
12
12
  const anchor_1 = require("./anchor");
13
13
  const unknownFields_1 = require("./unknownFields");
14
+ const transaction_1 = require("./transaction");
14
15
  /**
15
16
  * Builds an offchain transaction with checkpoint transactions.
16
17
  *
@@ -48,10 +49,8 @@ function buildVirtualTx(inputs, outputs) {
48
49
  }
49
50
  }
50
51
  }
51
- const tx = new btc_signer_1.Transaction({
52
+ const tx = new transaction_1.Transaction({
52
53
  version: 3,
53
- allowUnknown: true,
54
- allowUnknownOutputs: true,
55
54
  lockTime: Number(lockTime),
56
55
  });
57
56
  for (const [i, input] of inputs.entries()) {
@@ -76,8 +75,7 @@ function buildVirtualTx(inputs, outputs) {
76
75
  }
77
76
  function buildCheckpointTx(vtxo, serverUnrollScript) {
78
77
  // create the checkpoint vtxo script from collaborative closure
79
- const collaborativeClosure = (0, tapscript_1.decodeTapscript)(vtxo.checkpointTapLeafScript ??
80
- (0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
78
+ const collaborativeClosure = (0, tapscript_1.decodeTapscript)((0, base_2.scriptFromTapLeafScript)(vtxo.tapLeafScript));
81
79
  // create the checkpoint vtxo script combining collaborative closure and server unroll script
82
80
  const checkpointVtxoScript = new base_2.VtxoScript([
83
81
  serverUnrollScript.script,
@@ -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
+ }
@@ -7,6 +7,7 @@ const networks_1 = require("../networks");
7
7
  const onchain_1 = require("../providers/onchain");
8
8
  const anchor_1 = require("../utils/anchor");
9
9
  const txSizeEstimator_1 = require("../utils/txSizeEstimator");
10
+ const transaction_1 = require("../utils/transaction");
10
11
  /**
11
12
  * Onchain Bitcoin wallet implementation for traditional Bitcoin transactions.
12
13
  *
@@ -79,7 +80,7 @@ class OnchainWallet {
79
80
  // Select coins
80
81
  const selected = selectCoins(coins, totalNeeded);
81
82
  // Create transaction
82
- let tx = new btc_signer_1.Transaction();
83
+ let tx = new transaction_1.Transaction();
83
84
  // Add inputs
84
85
  for (const input of selected.inputs) {
85
86
  tx.addInput({
@@ -107,10 +108,9 @@ class OnchainWallet {
107
108
  }
108
109
  async bumpP2A(parent) {
109
110
  const parentVsize = parent.vsize;
110
- let child = new btc_signer_1.Transaction({
111
- allowUnknownInputs: true,
112
- allowLegacyWitnessUtxo: true,
111
+ let child = new transaction_1.Transaction({
113
112
  version: 3,
113
+ allowLegacyWitnessUtxo: true,
114
114
  });
115
115
  child.addInput((0, anchor_1.findP2AOutput)(parent)); // throws if not found
116
116
  const childVsize = txSizeEstimator_1.TxWeightEstimator.create()
@@ -60,6 +60,15 @@ class Worker {
60
60
  spent: allVtxos.filter((vtxo) => !(0, __1.isSpendable)(vtxo)),
61
61
  };
62
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
+ }
63
72
  async start(withServiceWorkerUpdate = true) {
64
73
  self.addEventListener("message", async (event) => {
65
74
  await this.handleMessage(event);
@@ -133,6 +142,14 @@ class Worker {
133
142
  this.sendMessageToAllClients(response_1.Response.vtxoUpdate(newVtxos, spentVtxos));
134
143
  }
135
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);
136
153
  // notify all clients about the utxo update
137
154
  this.sendMessageToAllClients(response_1.Response.utxoUpdate(funds.coins));
138
155
  }
@@ -291,7 +308,7 @@ class Worker {
291
308
  }
292
309
  try {
293
310
  const [boardingUtxos, spendableVtxos, sweptVtxos] = await Promise.all([
294
- this.wallet.getBoardingUtxos(),
311
+ this.getAllBoardingUtxos(),
295
312
  this.getSpendableVtxos(),
296
313
  this.getSweptVtxos(),
297
314
  ]);
@@ -396,7 +413,7 @@ class Worker {
396
413
  return;
397
414
  }
398
415
  try {
399
- const boardingUtxos = await this.wallet.getBoardingUtxos();
416
+ const boardingUtxos = await this.getAllBoardingUtxos();
400
417
  event.source?.postMessage(response_1.Response.boardingUtxos(message.id, boardingUtxos));
401
418
  }
402
419
  catch (error) {
@@ -7,6 +7,7 @@ const indexer_1 = require("../providers/indexer");
7
7
  const base_2 = require("../script/base");
8
8
  const txSizeEstimator_1 = require("../utils/txSizeEstimator");
9
9
  const wallet_1 = require("./wallet");
10
+ const transaction_1 = require("../utils/transaction");
10
11
  var Unroll;
11
12
  (function (Unroll) {
12
13
  let StepType;
@@ -105,9 +106,7 @@ var Unroll;
105
106
  if (virtualTxs.txs.length === 0) {
106
107
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
107
108
  }
108
- const tx = btc_signer_1.Transaction.fromPSBT(base_1.base64.decode(virtualTxs.txs[0]), {
109
- allowUnknownInputs: true,
110
- });
109
+ const tx = transaction_1.Transaction.fromPSBT(base_1.base64.decode(virtualTxs.txs[0]));
111
110
  // finalize the tree transaction
112
111
  if (nextTxToBroadcast.type === indexer_1.ChainTxType.TREE) {
113
112
  const input = tx.getInput(0);
@@ -200,7 +199,7 @@ var Unroll;
200
199
  });
201
200
  txWeightEstimator.addTapscriptInput(64, spendingLeaf[1].length, btc_signer_1.TaprootControlBlock.encode(spendingLeaf[0]).length);
202
201
  }
203
- const tx = new btc_signer_1.Transaction({ allowUnknownInputs: true, version: 2 });
202
+ const tx = new transaction_1.Transaction({ version: 2 });
204
203
  for (const input of inputs) {
205
204
  tx.addInput(input);
206
205
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extendVirtualCoin = extendVirtualCoin;
4
+ exports.extendCoin = extendCoin;
4
5
  function extendVirtualCoin(wallet, vtxo) {
5
6
  return {
6
7
  ...vtxo,
@@ -9,3 +10,11 @@ function extendVirtualCoin(wallet, vtxo) {
9
10
  tapTree: wallet.offchainTapscript.encode(),
10
11
  };
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
+ }