@arkade-os/sdk 0.3.0-alpha.6 → 0.3.0-alpha.8

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 (37) hide show
  1. package/README.md +51 -0
  2. package/dist/cjs/adapters/expo.js +8 -0
  3. package/dist/cjs/index.js +2 -1
  4. package/dist/cjs/providers/expoArk.js +237 -0
  5. package/dist/cjs/providers/expoIndexer.js +194 -0
  6. package/dist/cjs/providers/indexer.js +3 -1
  7. package/dist/cjs/script/base.js +16 -95
  8. package/dist/cjs/utils/arkTransaction.js +13 -0
  9. package/dist/cjs/wallet/index.js +1 -1
  10. package/dist/cjs/wallet/serviceWorker/utils.js +0 -9
  11. package/dist/cjs/wallet/serviceWorker/worker.js +14 -17
  12. package/dist/cjs/wallet/utils.js +11 -0
  13. package/dist/cjs/wallet/wallet.js +69 -51
  14. package/dist/esm/adapters/expo.js +3 -0
  15. package/dist/esm/index.js +2 -2
  16. package/dist/esm/providers/expoArk.js +200 -0
  17. package/dist/esm/providers/expoIndexer.js +157 -0
  18. package/dist/esm/providers/indexer.js +3 -1
  19. package/dist/esm/script/base.js +13 -92
  20. package/dist/esm/utils/arkTransaction.js +13 -1
  21. package/dist/esm/wallet/index.js +1 -1
  22. package/dist/esm/wallet/serviceWorker/utils.js +0 -8
  23. package/dist/esm/wallet/serviceWorker/worker.js +15 -18
  24. package/dist/esm/wallet/utils.js +8 -0
  25. package/dist/esm/wallet/wallet.js +70 -52
  26. package/dist/types/adapters/expo.d.ts +4 -0
  27. package/dist/types/index.d.ts +5 -5
  28. package/dist/types/providers/ark.d.ts +136 -2
  29. package/dist/types/providers/expoArk.d.ts +22 -0
  30. package/dist/types/providers/expoIndexer.d.ts +26 -0
  31. package/dist/types/providers/indexer.d.ts +8 -0
  32. package/dist/types/utils/arkTransaction.d.ts +3 -1
  33. package/dist/types/wallet/index.d.ts +44 -6
  34. package/dist/types/wallet/serviceWorker/utils.d.ts +0 -2
  35. package/dist/types/wallet/utils.d.ts +2 -0
  36. package/dist/types/wallet/wallet.d.ts +9 -1
  37. package/package.json +11 -2
@@ -2,12 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VtxoScript = void 0;
4
4
  exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
5
+ const btc_signer_1 = require("@scure/btc-signer");
5
6
  const payment_js_1 = require("@scure/btc-signer/payment.js");
7
+ const psbt_js_1 = require("@scure/btc-signer/psbt.js");
6
8
  const utils_js_1 = require("@scure/btc-signer/utils.js");
7
- const address_1 = require("./address");
8
- const script_js_1 = require("@scure/btc-signer/script.js");
9
9
  const base_1 = require("@scure/base");
10
+ const address_1 = require("./address");
10
11
  const tapscript_1 = require("./tapscript");
12
+ const TapTreeCoder = psbt_js_1.PSBTOutput.tapTree[2];
11
13
  function scriptFromTapLeafScript(leaf) {
12
14
  return leaf[1].subarray(0, leaf[1].length - 1); // remove the version byte
13
15
  }
@@ -21,13 +23,14 @@ function scriptFromTapLeafScript(leaf) {
21
23
  */
22
24
  class VtxoScript {
23
25
  static decode(tapTree) {
24
- const leaves = decodeTaprootTree(tapTree);
25
- return new VtxoScript(leaves);
26
+ const leaves = TapTreeCoder.decode(tapTree);
27
+ const scripts = leaves.map((leaf) => leaf.script);
28
+ return new VtxoScript(scripts);
26
29
  }
27
30
  constructor(scripts) {
28
31
  this.scripts = scripts;
29
- const tapTree = (0, payment_js_1.taprootListToTree)(scripts.map((script) => ({ script, leafVersion: payment_js_1.TAP_LEAF_VERSION })));
30
- const payment = (0, payment_js_1.p2tr)(utils_js_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
32
+ const tapTree = (0, btc_signer_1.taprootListToTree)(scripts.map((script) => ({ script, leafVersion: payment_js_1.TAP_LEAF_VERSION })));
33
+ const payment = (0, btc_signer_1.p2tr)(utils_js_1.TAPROOT_UNSPENDABLE_KEY, tapTree, undefined, true);
31
34
  if (!payment.tapLeafScript ||
32
35
  payment.tapLeafScript.length !== scripts.length) {
33
36
  throw new Error("invalid scripts");
@@ -36,17 +39,21 @@ class VtxoScript {
36
39
  this.tweakedPublicKey = payment.tweakedPubkey;
37
40
  }
38
41
  encode() {
39
- const tapTree = encodeTaprootTree(this.scripts);
42
+ const tapTree = TapTreeCoder.encode(this.scripts.map((script) => ({
43
+ depth: 1,
44
+ version: payment_js_1.TAP_LEAF_VERSION,
45
+ script,
46
+ })));
40
47
  return tapTree;
41
48
  }
42
49
  address(prefix, serverPubKey) {
43
50
  return new address_1.ArkAddress(serverPubKey, this.tweakedPublicKey, prefix);
44
51
  }
45
52
  get pkScript() {
46
- return script_js_1.Script.encode(["OP_1", this.tweakedPublicKey]);
53
+ return btc_signer_1.Script.encode(["OP_1", this.tweakedPublicKey]);
47
54
  }
48
55
  onchainAddress(network) {
49
- return (0, payment_js_1.Address)(network).encode({
56
+ return (0, btc_signer_1.Address)(network).encode({
50
57
  type: "tr",
51
58
  pubkey: this.tweakedPublicKey,
52
59
  });
@@ -80,89 +87,3 @@ class VtxoScript {
80
87
  }
81
88
  }
82
89
  exports.VtxoScript = VtxoScript;
83
- function decodeTaprootTree(tapTree) {
84
- let offset = 0;
85
- const scripts = [];
86
- // Read number of leaves
87
- const [numLeaves, numLeavesSize] = decodeCompactSizeUint(tapTree, offset);
88
- offset += numLeavesSize;
89
- // Read each leaf
90
- for (let i = 0; i < numLeaves; i++) {
91
- // Skip depth (1 byte)
92
- offset += 1;
93
- // Skip leaf version (1 byte)
94
- offset += 1;
95
- // Read script length
96
- const [scriptLength, scriptLengthSize] = decodeCompactSizeUint(tapTree, offset);
97
- offset += scriptLengthSize;
98
- // Read script content
99
- const script = tapTree.slice(offset, offset + scriptLength);
100
- scripts.push(script);
101
- offset += scriptLength;
102
- }
103
- return scripts;
104
- }
105
- function decodeCompactSizeUint(data, offset) {
106
- const firstByte = data[offset];
107
- if (firstByte < 0xfd) {
108
- return [firstByte, 1];
109
- }
110
- else if (firstByte === 0xfd) {
111
- const value = new DataView(data.buffer).getUint16(offset + 1, true);
112
- return [value, 3];
113
- }
114
- else if (firstByte === 0xfe) {
115
- const value = new DataView(data.buffer).getUint32(offset + 1, true);
116
- return [value, 5];
117
- }
118
- else {
119
- const value = Number(new DataView(data.buffer).getBigUint64(offset + 1, true));
120
- return [value, 9];
121
- }
122
- }
123
- function encodeTaprootTree(leaves) {
124
- const chunks = [];
125
- // Write number of leaves as compact size uint
126
- chunks.push(encodeCompactSizeUint(leaves.length));
127
- for (const tapscript of leaves) {
128
- // Write depth (always 1 for now)
129
- chunks.push(new Uint8Array([1]));
130
- // Write leaf version (0xc0 for tapscript)
131
- chunks.push(new Uint8Array([0xc0]));
132
- // Write script length and script
133
- chunks.push(encodeCompactSizeUint(tapscript.length));
134
- chunks.push(tapscript);
135
- }
136
- // Concatenate all chunks
137
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
138
- const result = new Uint8Array(totalLength);
139
- let offset = 0;
140
- for (const chunk of chunks) {
141
- result.set(chunk, offset);
142
- offset += chunk.length;
143
- }
144
- return result;
145
- }
146
- function encodeCompactSizeUint(value) {
147
- if (value < 0xfd) {
148
- return new Uint8Array([value]);
149
- }
150
- else if (value <= 0xffff) {
151
- const buffer = new Uint8Array(3);
152
- buffer[0] = 0xfd;
153
- new DataView(buffer.buffer).setUint16(1, value, true);
154
- return buffer;
155
- }
156
- else if (value <= 0xffffffff) {
157
- const buffer = new Uint8Array(5);
158
- buffer[0] = 0xfe;
159
- new DataView(buffer.buffer).setUint32(1, value, true);
160
- return buffer;
161
- }
162
- else {
163
- const buffer = new Uint8Array(9);
164
- buffer[0] = 0xff;
165
- new DataView(buffer.buffer).setBigUint64(1, BigInt(value), true);
166
- return buffer;
167
- }
168
- }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildOffchainTx = buildOffchainTx;
4
+ exports.hasBoardingTxExpired = hasBoardingTxExpired;
4
5
  const transaction_js_1 = require("@scure/btc-signer/transaction.js");
5
6
  const tapscript_1 = require("../script/tapscript");
6
7
  const base_1 = require("../script/base");
@@ -106,3 +107,15 @@ 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
+ }
@@ -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);
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setupServiceWorker = setupServiceWorker;
4
- exports.extendVirtualCoin = extendVirtualCoin;
5
4
  /**
6
5
  * setupServiceWorker sets up the service worker.
7
6
  * @param path - the path to the service worker script
@@ -48,11 +47,3 @@ async function setupServiceWorker(path) {
48
47
  navigator.serviceWorker.addEventListener("error", onError);
49
48
  });
50
49
  }
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
- }
@@ -13,7 +13,7 @@ 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
17
  /**
18
18
  * Worker is a class letting to interact with ServiceWorkerWallet from the client
19
19
  * it aims to be run in a service worker context
@@ -77,6 +77,8 @@ class Worker {
77
77
  this.incomingFundsSubscription();
78
78
  // Clear storage - this replaces vtxoRepository.close()
79
79
  await this.storage.clear();
80
+ // Reset in-memory caches by recreating the repository
81
+ this.walletRepository = new walletRepository_1.WalletRepositoryImpl(this.storage);
80
82
  this.wallet = undefined;
81
83
  this.arkProvider = undefined;
82
84
  this.indexerProvider = undefined;
@@ -105,9 +107,6 @@ class Worker {
105
107
  const txs = await this.wallet.getTransactionHistory();
106
108
  if (txs)
107
109
  await this.walletRepository.saveTransactions(address, txs);
108
- // stop previous subscriptions if any
109
- if (this.incomingFundsSubscription)
110
- this.incomingFundsSubscription();
111
110
  // subscribe for incoming funds and notify all clients when new funds arrive
112
111
  this.incomingFundsSubscription = await this.wallet.notifyIncomingFunds(async (funds) => {
113
112
  if (funds.type === "vtxo") {
@@ -127,7 +126,7 @@ class Worker {
127
126
  // notify all clients about the vtxo update
128
127
  this.sendMessageToAllClients("VTXO_UPDATE", JSON.stringify({ newVtxos, spentVtxos }));
129
128
  }
130
- if (funds.type === "utxo" && funds.coins.length > 0) {
129
+ if (funds.type === "utxo") {
131
130
  // notify all clients about the utxo update
132
131
  this.sendMessageToAllClients("UTXO_UPDATE", JSON.stringify(funds.coins));
133
132
  }
@@ -358,17 +357,16 @@ class Worker {
358
357
  if (!message.filter?.withRecoverable) {
359
358
  if (!this.wallet)
360
359
  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));
360
+ // exclude subdust and recoverable if we don't want recoverable
361
+ const notSubdust = (v) => {
362
+ const dustAmount = this.wallet?.dustAmount;
363
+ return dustAmount == null
364
+ ? true
365
+ : !(0, __1.isSubdust)(v, dustAmount);
366
+ };
367
+ vtxos = vtxos
368
+ .filter(notSubdust)
369
+ .filter((v) => !(0, __1.isRecoverable)(v));
372
370
  }
373
371
  event.source?.postMessage(response_1.Response.vtxos(message.id, vtxos));
374
372
  }
@@ -529,7 +527,6 @@ class Worker {
529
527
  }
530
528
  async handleReloadWallet(event) {
531
529
  const message = event.data;
532
- console.log("RELOAD_WALLET message received", message);
533
530
  if (!request_1.Request.isReloadWallet(message)) {
534
531
  console.error("Invalid RELOAD_WALLET message format", message);
535
532
  event.source?.postMessage(response_1.Response.error(message.id, "Invalid RELOAD_WALLET message format"));
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extendVirtualCoin = extendVirtualCoin;
4
+ function extendVirtualCoin(wallet, vtxo) {
5
+ return {
6
+ ...vtxo,
7
+ forfeitTapLeafScript: wallet.offchainTapscript.forfeit(),
8
+ intentTapLeafScript: wallet.offchainTapscript.exit(),
9
+ tapTree: wallet.offchainTapscript.encode(),
10
+ };
11
+ }
@@ -60,7 +60,7 @@ const txTree_1 = require("../tree/txTree");
60
60
  const inMemory_1 = require("../storage/inMemory");
61
61
  const walletRepository_1 = require("../repositories/walletRepository");
62
62
  const contractRepository_1 = require("../repositories/contractRepository");
63
- const utils_1 = require("./serviceWorker/utils");
63
+ const utils_1 = require("./utils");
64
64
  /**
65
65
  * Main wallet implementation for Bitcoin transactions with Ark protocol support.
66
66
  * The wallet does not store any data locally and relies on Ark and onchain
@@ -68,13 +68,21 @@ const utils_1 = require("./serviceWorker/utils");
68
68
  *
69
69
  * @example
70
70
  * ```typescript
71
- * // Create a wallet
71
+ * // Create a wallet with URL configuration
72
72
  * const wallet = await Wallet.create({
73
73
  * identity: SingleKey.fromHex('your_private_key'),
74
74
  * arkServerUrl: 'https://ark.example.com',
75
75
  * esploraUrl: 'https://mempool.space/api'
76
76
  * });
77
77
  *
78
+ * // Or with custom provider instances (e.g., for Expo/React Native)
79
+ * const wallet = await Wallet.create({
80
+ * identity: SingleKey.fromHex('your_private_key'),
81
+ * arkProvider: new ExpoArkProvider('https://ark.example.com'),
82
+ * indexerProvider: new ExpoIndexerProvider('https://ark.example.com'),
83
+ * esploraUrl: 'https://mempool.space/api'
84
+ * });
85
+ *
78
86
  * // Get addresses
79
87
  * const arkAddress = await wallet.getAddress();
80
88
  * const boardingAddress = await wallet.getBoardingAddress();
@@ -108,11 +116,29 @@ class Wallet {
108
116
  if (!pubkey) {
109
117
  throw new Error("Invalid configured public key");
110
118
  }
111
- const arkProvider = new ark_1.RestArkProvider(config.arkServerUrl);
112
- const indexerProvider = new indexer_1.RestIndexerProvider(config.arkServerUrl);
119
+ // Use provided arkProvider instance or create a new one from arkServerUrl
120
+ const arkProvider = config.arkProvider ||
121
+ (() => {
122
+ if (!config.arkServerUrl) {
123
+ throw new Error("Either arkProvider or arkServerUrl must be provided");
124
+ }
125
+ return new ark_1.RestArkProvider(config.arkServerUrl);
126
+ })();
127
+ // Extract arkServerUrl from provider if not explicitly provided
128
+ const arkServerUrl = config.arkServerUrl || arkProvider.serverUrl;
129
+ if (!arkServerUrl) {
130
+ throw new Error("Could not determine arkServerUrl from provider");
131
+ }
132
+ // Use provided indexerProvider instance or create a new one
133
+ // indexerUrl defaults to arkServerUrl if not provided
134
+ const indexerUrl = config.indexerUrl || arkServerUrl;
135
+ const indexerProvider = config.indexerProvider || new indexer_1.RestIndexerProvider(indexerUrl);
113
136
  const info = await arkProvider.getInfo();
114
137
  const network = (0, networks_1.getNetwork)(info.network);
115
- const onchainProvider = new onchain_1.EsploraProvider(config.esploraUrl || onchain_1.ESPLORA_URL[info.network]);
138
+ // Extract esploraUrl from provider if not explicitly provided
139
+ const esploraUrl = config.esploraUrl || onchain_1.ESPLORA_URL[info.network];
140
+ // Use provided onchainProvider instance or create a new one
141
+ const onchainProvider = config.onchainProvider || new onchain_1.EsploraProvider(esploraUrl);
116
142
  const exitTimelock = {
117
143
  value: info.unilateralExitDelay,
118
144
  type: info.unilateralExitDelay < 512n ? "blocks" : "seconds",
@@ -136,8 +162,14 @@ class Wallet {
136
162
  // Save tapscripts
137
163
  const offchainTapscript = bareVtxoTapscript;
138
164
  // the serverUnrollScript is the one used to create output scripts of the checkpoint transactions
139
- const rawCheckpointExitClosure = base_1.hex.decode(info.checkpointExitClosure);
140
- const serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(rawCheckpointExitClosure);
165
+ let serverUnrollScript;
166
+ try {
167
+ const raw = base_1.hex.decode(info.checkpointExitClosure);
168
+ serverUnrollScript = tapscript_1.CSVMultisigTapscript.decode(raw);
169
+ }
170
+ catch (e) {
171
+ throw new Error("Invalid checkpointExitClosure from server");
172
+ }
141
173
  // parse the server forfeit address
142
174
  // server is expecting funds to be sent to this address
143
175
  const forfeitAddress = (0, payment_js_1.Address)(network).decode(info.forfeitAddress);
@@ -208,40 +240,24 @@ class Wallet {
208
240
  // if (cachedVtxos.length) return cachedVtxos;
209
241
  // For now, always fetch fresh data from provider and update cache
210
242
  // In future, we can add cache invalidation logic based on timestamps
211
- const spendableVtxos = await this.getVirtualCoins(filter);
212
- const encodedOffchainTapscript = this.offchainTapscript.encode();
213
- const forfeit = this.offchainTapscript.forfeit();
214
- const exit = this.offchainTapscript.exit();
215
- const extendedVtxos = spendableVtxos.map((vtxo) => ({
216
- ...vtxo,
217
- forfeitTapLeafScript: forfeit,
218
- intentTapLeafScript: exit,
219
- tapTree: encodedOffchainTapscript,
220
- }));
243
+ const vtxos = await this.getVirtualCoins(filter);
244
+ const extendedVtxos = vtxos.map((vtxo) => (0, utils_1.extendVirtualCoin)(this, vtxo));
221
245
  // Update cache with fresh data
222
246
  await this.walletRepository.saveVtxos(address, extendedVtxos);
223
247
  return extendedVtxos;
224
248
  }
225
249
  async getVirtualCoins(filter = { withRecoverable: true, withUnrolled: false }) {
226
250
  const scripts = [base_1.hex.encode(this.offchainTapscript.pkScript)];
227
- const response = await this.indexerProvider.getVtxos({
228
- scripts,
229
- spendableOnly: true,
230
- });
231
- const vtxos = response.vtxos;
232
- if (filter.withRecoverable) {
233
- const response = await this.indexerProvider.getVtxos({
234
- scripts,
235
- recoverableOnly: true,
236
- });
237
- vtxos.push(...response.vtxos);
251
+ const response = await this.indexerProvider.getVtxos({ scripts });
252
+ const allVtxos = response.vtxos;
253
+ let vtxos = allVtxos.filter(_1.isSpendable);
254
+ // all recoverable vtxos are spendable by definition
255
+ if (!filter.withRecoverable) {
256
+ vtxos = vtxos.filter((vtxo) => !(0, _1.isRecoverable)(vtxo));
238
257
  }
239
258
  if (filter.withUnrolled) {
240
- const response = await this.indexerProvider.getVtxos({
241
- scripts,
242
- spentOnly: true,
243
- });
244
- vtxos.push(...response.vtxos.filter((vtxo) => vtxo.isUnrolled));
259
+ const spentVtxos = allVtxos.filter((vtxo) => !(0, _1.isSpendable)(vtxo));
260
+ vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
245
261
  }
246
262
  return vtxos;
247
263
  }
@@ -279,10 +295,10 @@ class Wallet {
279
295
  return txs;
280
296
  }
281
297
  async getBoardingTxs() {
282
- const boardingAddress = await this.getBoardingAddress();
283
- const txs = await this.onchainProvider.getTransactions(boardingAddress);
284
298
  const utxos = [];
285
299
  const commitmentsToIgnore = new Set();
300
+ const boardingAddress = await this.getBoardingAddress();
301
+ const txs = await this.onchainProvider.getTransactions(boardingAddress);
286
302
  for (const tx of txs) {
287
303
  for (let i = 0; i < tx.vout.length; i++) {
288
304
  const vout = tx.vout[i];
@@ -423,13 +439,15 @@ class Wallet {
423
439
  }
424
440
  }
425
441
  }
426
- // if no params are provided, use all boarding and offchain utxos as inputs
442
+ // if no params are provided, use all non expired boarding utxos and offchain vtxos as inputs
427
443
  // and send all to the offchain address
428
444
  if (!params) {
429
445
  let amount = 0;
430
- const boardingUtxos = await this.getBoardingUtxos();
446
+ const exitScript = tapscript_1.CSVMultisigTapscript.decode(base_1.hex.decode(this.boardingTapscript.exitScript));
447
+ const boardingTimelock = exitScript.params.timelock;
448
+ const boardingUtxos = (await this.getBoardingUtxos()).filter((utxo) => !(0, arkTransaction_1.hasBoardingTxExpired)(utxo, boardingTimelock));
431
449
  amount += boardingUtxos.reduce((sum, input) => sum + input.value, 0);
432
- const vtxos = await this.getVtxos();
450
+ const vtxos = await this.getVtxos({ withRecoverable: true });
433
451
  amount += vtxos.reduce((sum, input) => sum + input.value, 0);
434
452
  const inputs = [...boardingUtxos, ...vtxos];
435
453
  if (inputs.length === 0) {
@@ -639,22 +657,22 @@ class Wallet {
639
657
  let onchainStopFunc;
640
658
  let indexerStopFunc;
641
659
  if (this.onchainProvider && boardingAddress) {
660
+ const findVoutOnTx = (tx) => {
661
+ return tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
662
+ };
642
663
  onchainStopFunc = await this.onchainProvider.watchAddresses([boardingAddress], (txs) => {
664
+ // find all utxos belonging to our boarding address
643
665
  const coins = txs
666
+ // filter txs where address is in output
667
+ .filter((tx) => findVoutOnTx(tx) !== -1)
668
+ // return utxo as Coin
644
669
  .map((tx) => {
645
- const vout = tx.vout.findIndex((v) => v.scriptpubkey_address === boardingAddress);
646
- if (vout === -1) {
647
- console.warn(`No vout found for address ${boardingAddress} in transaction ${tx.txid}`);
648
- return null;
649
- }
650
- return {
651
- txid: tx.txid,
652
- vout,
653
- value: Number(tx.vout[vout].value),
654
- status: tx.status,
655
- };
656
- })
657
- .filter((coin) => coin !== null);
670
+ const { txid, status } = tx;
671
+ const vout = findVoutOnTx(tx);
672
+ const value = Number(tx.vout[vout].value);
673
+ return { txid, vout, value, status };
674
+ });
675
+ // and notify via callback
658
676
  eventCallback({
659
677
  type: "utxo",
660
678
  coins,
@@ -0,0 +1,3 @@
1
+ // Expo adapter for React Native/Expo environments
2
+ export { ExpoArkProvider } from '../providers/expoArk.js';
3
+ export { ExpoIndexerProvider } from '../providers/expoIndexer.js';
package/dist/esm/index.js CHANGED
@@ -17,7 +17,7 @@ import { Response } from './wallet/serviceWorker/response.js';
17
17
  import { ESPLORA_URL, EsploraProvider, } from './providers/onchain.js';
18
18
  import { RestArkProvider, SettlementEventType, } from './providers/ark.js';
19
19
  import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisigTapscript, CSVMultisigTapscript, decodeTapscript, MultisigTapscript, } from './script/tapscript.js';
20
- import { buildOffchainTx, } from './utils/arkTransaction.js';
20
+ import { hasBoardingTxExpired, buildOffchainTx, } from './utils/arkTransaction.js';
21
21
  import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry, } from './utils/unknownFields.js';
22
22
  import { BIP322 } from './bip322/index.js';
23
23
  import { ArkNote } from './arknote/index.js';
@@ -43,7 +43,7 @@ decodeTapscript, MultisigTapscript, CSVMultisigTapscript, ConditionCSVMultisigTa
43
43
  // Ark PSBT fields
44
44
  ArkPsbtFieldKey, ArkPsbtFieldKeyType, setArkPsbtField, getArkPsbtFields, CosignerPublicKey, VtxoTreeExpiry, VtxoTaprootTree, ConditionWitness,
45
45
  // Utils
46
- buildOffchainTx, waitForIncomingFunds,
46
+ buildOffchainTx, waitForIncomingFunds, hasBoardingTxExpired,
47
47
  // Arknote
48
48
  ArkNote,
49
49
  // Network