@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
@@ -0,0 +1,317 @@
1
+ import { isRecoverable, isSubdust } from './index.js';
2
+ /**
3
+ * Default renewal configuration values
4
+ */
5
+ export const DEFAULT_RENEWAL_CONFIG = {
6
+ thresholdPercentage: 10,
7
+ };
8
+ function getDustAmount(wallet) {
9
+ return "dustAmount" in wallet ? wallet.dustAmount : 330n;
10
+ }
11
+ /**
12
+ * Filter VTXOs that are recoverable (swept and still spendable, or preconfirmed subdust)
13
+ *
14
+ * Recovery strategy:
15
+ * - Always recover swept VTXOs (they've been taken by the server)
16
+ * - Only recover subdust preconfirmed VTXOs (to avoid locking liquidity on settled VTXOs with long expiry)
17
+ *
18
+ * @param vtxos - Array of virtual coins to check
19
+ * @param dustAmount - Dust threshold to identify subdust
20
+ * @returns Array of recoverable VTXOs
21
+ */
22
+ function getRecoverableVtxos(vtxos, dustAmount) {
23
+ return vtxos.filter((vtxo) => {
24
+ // Always recover swept VTXOs
25
+ if (isRecoverable(vtxo)) {
26
+ return true;
27
+ }
28
+ // Recover preconfirmed subdust to consolidate small amounts
29
+ if (vtxo.virtualStatus.state === "preconfirmed" &&
30
+ isSubdust(vtxo, dustAmount)) {
31
+ return true;
32
+ }
33
+ return false;
34
+ });
35
+ }
36
+ /**
37
+ * Get recoverable VTXOs including subdust coins if the total value exceeds dust threshold.
38
+ *
39
+ * Decision is based on the combined total of ALL recoverable VTXOs (regular + subdust),
40
+ * not just the subdust portion alone.
41
+ *
42
+ * @param vtxos - Array of virtual coins to check
43
+ * @param dustAmount - Dust threshold amount in satoshis
44
+ * @returns Object containing recoverable VTXOs and whether subdust should be included
45
+ */
46
+ function getRecoverableWithSubdust(vtxos, dustAmount) {
47
+ const recoverableVtxos = getRecoverableVtxos(vtxos, dustAmount);
48
+ // Separate subdust from regular recoverable
49
+ const subdust = [];
50
+ const regular = [];
51
+ for (const vtxo of recoverableVtxos) {
52
+ if (isSubdust(vtxo, dustAmount)) {
53
+ subdust.push(vtxo);
54
+ }
55
+ else {
56
+ regular.push(vtxo);
57
+ }
58
+ }
59
+ // Calculate totals
60
+ const regularTotal = regular.reduce((sum, vtxo) => sum + BigInt(vtxo.value), 0n);
61
+ const subdustTotal = subdust.reduce((sum, vtxo) => sum + BigInt(vtxo.value), 0n);
62
+ const combinedTotal = regularTotal + subdustTotal;
63
+ // Include subdust only if the combined total exceeds dust threshold
64
+ const shouldIncludeSubdust = combinedTotal >= dustAmount;
65
+ const vtxosToRecover = shouldIncludeSubdust ? recoverableVtxos : regular;
66
+ const totalAmount = vtxosToRecover.reduce((sum, vtxo) => sum + BigInt(vtxo.value), 0n);
67
+ return {
68
+ vtxosToRecover,
69
+ includesSubdust: shouldIncludeSubdust,
70
+ totalAmount,
71
+ };
72
+ }
73
+ /**
74
+ * Check if a VTXO is expiring soon based on threshold
75
+ *
76
+ * @param vtxo - The virtual coin to check
77
+ * @param thresholdMs - Threshold in milliseconds from now
78
+ * @returns true if VTXO expires within threshold, false otherwise
79
+ */
80
+ export function isVtxoExpiringSoon(vtxo, percentage) {
81
+ const { batchExpiry } = vtxo.virtualStatus;
82
+ if (!batchExpiry) {
83
+ return false; // it doesn't expire
84
+ }
85
+ const now = Date.now();
86
+ if (batchExpiry <= now) {
87
+ return false; // already expired
88
+ }
89
+ // It shouldn't happen, but let's be safe
90
+ if (!vtxo.createdAt) {
91
+ return false;
92
+ }
93
+ const duration = batchExpiry - vtxo.createdAt.getTime();
94
+ const softExpiry = batchExpiry - (duration * percentage) / 100;
95
+ return softExpiry > 0 && softExpiry <= now;
96
+ }
97
+ /**
98
+ * Filter VTXOs that are expiring soon or are recoverable/subdust
99
+ *
100
+ * @param vtxos - Array of virtual coins to check
101
+ * @param thresholdMs - Threshold in milliseconds from now
102
+ * @param dustAmount - Dust threshold amount in satoshis
103
+ * @returns Array of VTXOs expiring within threshold
104
+ */
105
+ export function getExpiringAndRecoverableVtxos(vtxos, percentage, dustAmount) {
106
+ return vtxos.filter((vtxo) => isVtxoExpiringSoon(vtxo, percentage) ||
107
+ isRecoverable(vtxo) ||
108
+ isSubdust(vtxo, dustAmount));
109
+ }
110
+ /**
111
+ * VtxoManager is a unified class for managing VTXO lifecycle operations including
112
+ * recovery of swept/expired VTXOs and renewal to prevent expiration.
113
+ *
114
+ * Key Features:
115
+ * - **Recovery**: Reclaim swept or expired VTXOs back to the wallet
116
+ * - **Renewal**: Refresh VTXO expiration time before they expire
117
+ * - **Smart subdust handling**: Automatically includes subdust VTXOs when economically viable
118
+ * - **Expiry monitoring**: Check for VTXOs that are expiring soon
119
+ *
120
+ * VTXOs become recoverable when:
121
+ * - The Ark server sweeps them (virtualStatus.state === "swept") and they remain spendable
122
+ * - They are preconfirmed subdust (to consolidate small amounts without locking liquidity on settled VTXOs)
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * // Initialize with renewal config
127
+ * const manager = new VtxoManager(wallet, {
128
+ * enabled: true,
129
+ * thresholdPercentage: 10
130
+ * });
131
+ *
132
+ * // Check recoverable balance
133
+ * const balance = await manager.getRecoverableBalance();
134
+ * if (balance.recoverable > 0n) {
135
+ * console.log(`Can recover ${balance.recoverable} sats`);
136
+ * const txid = await manager.recoverVtxos();
137
+ * }
138
+ *
139
+ * // Check for expiring VTXOs
140
+ * const expiring = await manager.getExpiringVtxos();
141
+ * if (expiring.length > 0) {
142
+ * console.log(`${expiring.length} VTXOs expiring soon`);
143
+ * const txid = await manager.renewVtxos();
144
+ * }
145
+ * ```
146
+ */
147
+ export class VtxoManager {
148
+ constructor(wallet, renewalConfig) {
149
+ this.wallet = wallet;
150
+ this.renewalConfig = renewalConfig;
151
+ }
152
+ // ========== Recovery Methods ==========
153
+ /**
154
+ * Recover swept/expired VTXOs by settling them back to the wallet's Ark address.
155
+ *
156
+ * This method:
157
+ * 1. Fetches all VTXOs (including recoverable ones)
158
+ * 2. Filters for swept but still spendable VTXOs and preconfirmed subdust
159
+ * 3. Includes subdust VTXOs if the total value >= dust threshold
160
+ * 4. Settles everything back to the wallet's Ark address
161
+ *
162
+ * Note: Settled VTXOs with long expiry are NOT recovered to avoid locking liquidity unnecessarily.
163
+ * Only preconfirmed subdust is recovered to consolidate small amounts.
164
+ *
165
+ * @param eventCallback - Optional callback to receive settlement events
166
+ * @returns Settlement transaction ID
167
+ * @throws Error if no recoverable VTXOs found
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const manager = new VtxoManager(wallet);
172
+ *
173
+ * // Simple recovery
174
+ * const txid = await manager.recoverVtxos();
175
+ *
176
+ * // With event callback
177
+ * const txid = await manager.recoverVtxos((event) => {
178
+ * console.log('Settlement event:', event.type);
179
+ * });
180
+ * ```
181
+ */
182
+ async recoverVtxos(eventCallback) {
183
+ // Get all VTXOs including recoverable ones
184
+ const allVtxos = await this.wallet.getVtxos({
185
+ withRecoverable: true,
186
+ withUnrolled: false,
187
+ });
188
+ // Get dust amount from wallet
189
+ const dustAmount = getDustAmount(this.wallet);
190
+ // Filter recoverable VTXOs and handle subdust logic
191
+ const { vtxosToRecover, includesSubdust, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
192
+ if (vtxosToRecover.length === 0) {
193
+ throw new Error("No recoverable VTXOs found");
194
+ }
195
+ const arkAddress = await this.wallet.getAddress();
196
+ // Settle all recoverable VTXOs back to the wallet
197
+ return this.wallet.settle({
198
+ inputs: vtxosToRecover,
199
+ outputs: [
200
+ {
201
+ address: arkAddress,
202
+ amount: totalAmount,
203
+ },
204
+ ],
205
+ }, eventCallback);
206
+ }
207
+ /**
208
+ * Get information about recoverable balance without executing recovery.
209
+ *
210
+ * Useful for displaying to users before they decide to recover funds.
211
+ *
212
+ * @returns Object containing recoverable amounts and subdust information
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * const manager = new VtxoManager(wallet);
217
+ * const balance = await manager.getRecoverableBalance();
218
+ *
219
+ * if (balance.recoverable > 0n) {
220
+ * console.log(`You can recover ${balance.recoverable} sats`);
221
+ * if (balance.includesSubdust) {
222
+ * console.log(`This includes ${balance.subdust} sats from subdust VTXOs`);
223
+ * }
224
+ * }
225
+ * ```
226
+ */
227
+ async getRecoverableBalance() {
228
+ const allVtxos = await this.wallet.getVtxos({
229
+ withRecoverable: true,
230
+ withUnrolled: false,
231
+ });
232
+ const dustAmount = getDustAmount(this.wallet);
233
+ const { vtxosToRecover, includesSubdust, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
234
+ // Calculate subdust amount separately for reporting
235
+ const subdustAmount = vtxosToRecover
236
+ .filter((v) => BigInt(v.value) < dustAmount)
237
+ .reduce((sum, v) => sum + BigInt(v.value), 0n);
238
+ return {
239
+ recoverable: totalAmount,
240
+ subdust: subdustAmount,
241
+ includesSubdust,
242
+ vtxoCount: vtxosToRecover.length,
243
+ };
244
+ }
245
+ // ========== Renewal Methods ==========
246
+ /**
247
+ * Get VTXOs that are expiring soon based on renewal configuration
248
+ *
249
+ * @param thresholdPercentage - Optional override for threshold percentage (0-100)
250
+ * @returns Array of expiring VTXOs, empty array if renewal is disabled or no VTXOs expiring
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * const manager = new VtxoManager(wallet, { enabled: true, thresholdPercentage: 10 });
255
+ * const expiringVtxos = await manager.getExpiringVtxos();
256
+ * if (expiringVtxos.length > 0) {
257
+ * console.log(`${expiringVtxos.length} VTXOs expiring soon`);
258
+ * }
259
+ * ```
260
+ */
261
+ async getExpiringVtxos(thresholdPercentage) {
262
+ const vtxos = await this.wallet.getVtxos({ withRecoverable: true });
263
+ const percentage = thresholdPercentage ??
264
+ this.renewalConfig?.thresholdPercentage ??
265
+ DEFAULT_RENEWAL_CONFIG.thresholdPercentage;
266
+ return getExpiringAndRecoverableVtxos(vtxos, percentage, getDustAmount(this.wallet));
267
+ }
268
+ /**
269
+ * Renew expiring VTXOs by settling them back to the wallet's address
270
+ *
271
+ * This method collects all expiring spendable VTXOs (including recoverable ones) and settles
272
+ * them back to the wallet, effectively refreshing their expiration time. This is the
273
+ * primary way to prevent VTXOs from expiring.
274
+ *
275
+ * @param eventCallback - Optional callback for settlement events
276
+ * @returns Settlement transaction ID
277
+ * @throws Error if no VTXOs available to renew
278
+ * @throws Error if total amount is below dust threshold
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const manager = new VtxoManager(wallet);
283
+ *
284
+ * // Simple renewal
285
+ * const txid = await manager.renewVtxos();
286
+ *
287
+ * // With event callback
288
+ * const txid = await manager.renewVtxos((event) => {
289
+ * console.log('Settlement event:', event.type);
290
+ * });
291
+ * ```
292
+ */
293
+ async renewVtxos(eventCallback) {
294
+ // Get all VTXOs (including recoverable ones)
295
+ const vtxos = await this.getExpiringVtxos();
296
+ if (vtxos.length === 0) {
297
+ throw new Error("No VTXOs available to renew");
298
+ }
299
+ const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
300
+ // Get dust amount from wallet
301
+ const dustAmount = getDustAmount(this.wallet);
302
+ // Check if total amount is above dust threshold
303
+ if (BigInt(totalAmount) < dustAmount) {
304
+ throw new Error(`Total amount ${totalAmount} is below dust threshold ${dustAmount}`);
305
+ }
306
+ const arkAddress = await this.wallet.getAddress();
307
+ return this.wallet.settle({
308
+ inputs: vtxos,
309
+ outputs: [
310
+ {
311
+ address: arkAddress,
312
+ amount: BigInt(totalAmount),
313
+ },
314
+ ],
315
+ }, eventCallback);
316
+ }
317
+ }