@arkade-os/boltz-swap 0.2.20 → 0.3.0-next.1
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.
- package/README.md +380 -202
- package/dist/arkade-swaps-DpGvbiB0.d.cts +317 -0
- package/dist/arkade-swaps-hJKglQkW.d.ts +317 -0
- package/dist/background-4OXZ52YZ.js +12 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-NYDN7MTO.js +260 -0
- package/dist/chunk-XPCRRS5J.js +4372 -0
- package/dist/expo/index.cjs +4803 -0
- package/dist/expo/index.d.cts +236 -0
- package/dist/expo/index.d.ts +236 -0
- package/dist/expo/index.js +215 -0
- package/dist/index.cjs +3653 -963
- package/dist/index.d.cts +400 -765
- package/dist/index.d.ts +400 -765
- package/dist/index.js +861 -2454
- package/dist/repositories/realm/index.cjs +153 -0
- package/dist/repositories/realm/index.d.cts +60 -0
- package/dist/repositories/realm/index.d.ts +60 -0
- package/dist/repositories/realm/index.js +126 -0
- package/dist/repositories/sqlite/index.cjs +146 -0
- package/dist/repositories/sqlite/index.d.cts +28 -0
- package/dist/repositories/sqlite/index.d.ts +28 -0
- package/dist/repositories/sqlite/index.js +121 -0
- package/dist/types-B4XtalMB.d.cts +733 -0
- package/dist/types-B4XtalMB.d.ts +733 -0
- package/package.json +42 -5
package/README.md
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Arkade Swaps
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Lightning and chain swaps for Arkade using Boltz
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@arkade-os/boltz-swap` provides seamless integration with the Lightning Network and Bitcoin on-chain through Boltz swaps, allowing users to move funds between Arkade, Lightning, and Bitcoin.
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
The library enables four swap types:
|
|
10
10
|
|
|
11
|
-
1. **Lightning to Arkade
|
|
12
|
-
2. **Arkade to Lightning
|
|
11
|
+
1. **Lightning to Arkade** - Receive funds from Lightning payments into your Arkade wallet
|
|
12
|
+
2. **Arkade to Lightning** - Send funds from your Arkade wallet to Lightning invoices
|
|
13
|
+
3. **ARK to BTC** - Move funds from Arkade to a Bitcoin on-chain address
|
|
14
|
+
4. **BTC to ARK** - Move funds from Bitcoin on-chain into your Arkade wallet
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
Built on top of the Boltz swap protocol with automatic background monitoring via SwapManager.
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -21,11 +23,11 @@ npm install @arkade-os/sdk @arkade-os/boltz-swap
|
|
|
21
23
|
|
|
22
24
|
## Basic Usage
|
|
23
25
|
|
|
24
|
-
### Initializing
|
|
26
|
+
### Initializing
|
|
25
27
|
|
|
26
28
|
```typescript
|
|
27
29
|
import { Wallet, SingleKey } from '@arkade-os/sdk';
|
|
28
|
-
import {
|
|
30
|
+
import { ArkadeSwaps, BoltzSwapProvider } from '@arkade-os/boltz-swap';
|
|
29
31
|
|
|
30
32
|
// Create an identity
|
|
31
33
|
const identity = SingleKey.fromHex('your_private_key_in_hex');
|
|
@@ -36,53 +38,59 @@ const wallet = await Wallet.create({
|
|
|
36
38
|
arkServerUrl: 'https://mutinynet.arkade.sh',
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
// Initialize the
|
|
41
|
+
// Initialize the swap provider
|
|
40
42
|
const swapProvider = new BoltzSwapProvider({
|
|
41
43
|
apiUrl: 'https://api.boltz.mutinynet.arkade.sh',
|
|
42
44
|
network: 'mutinynet',
|
|
43
45
|
referralId: 'arkade', // optional
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
// Create the
|
|
47
|
-
const
|
|
48
|
+
// Create the ArkadeSwaps instance
|
|
49
|
+
const swaps = new ArkadeSwaps({
|
|
48
50
|
wallet,
|
|
49
51
|
swapProvider,
|
|
50
|
-
// Optional: enable SwapManager
|
|
52
|
+
// Optional: enable SwapManager for background monitoring
|
|
51
53
|
// swapManager: true,
|
|
52
54
|
});
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
**SwapRepository**: Swap storage is pluggable via `SwapRepository`. By default, `ArkadeSwaps` uses an IndexedDB-backed repository in browser contexts. You can inject your own repository (for tests, Node.js, or custom storage) via the `swapRepository` option. Custom implementations must set `readonly version = 1` to match the interface — TypeScript will error when the version is bumped, signaling a required update.
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
import { ServiceWorkerWallet, SingleKey, RestArkProvider, RestIndexerProvider } from '@arkade-os/sdk';
|
|
59
|
-
import { IndexedDBStorageAdapter } from '@arkade-os/sdk/storage';
|
|
60
|
-
|
|
61
|
-
// Create your identity
|
|
62
|
-
const identity = SingleKey.fromHex('your_private_key_hex');
|
|
63
|
-
// Or generate a new one:
|
|
64
|
-
// const identity = SingleKey.fromRandomBytes();
|
|
65
|
-
|
|
66
|
-
// Configure IndexedDB storage adapter for ServiceWorker
|
|
67
|
-
const storage = new IndexedDBStorageAdapter('arkade-service-worker-wallet', 1);
|
|
59
|
+
Platform-specific repositories are available as subpath exports:
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
identity,
|
|
73
|
-
storage, // Pass the IndexedDB storage adapter
|
|
74
|
-
});
|
|
61
|
+
```typescript
|
|
62
|
+
// SQLite (React Native / Node.js)
|
|
63
|
+
import { SQLiteSwapRepository } from '@arkade-os/boltz-swap/repositories/sqlite';
|
|
75
64
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
wallet: serviceWorkerWallet,
|
|
79
|
-
arkProvider: new RestArkProvider('https://mutinynet.arkade.sh'),
|
|
80
|
-
indexerProvider: new RestIndexerProvider('https://mutinynet.arkade.sh'),
|
|
81
|
-
swapProvider,
|
|
82
|
-
});
|
|
65
|
+
// Realm (React Native)
|
|
66
|
+
import { RealmSwapRepository, BoltzRealmSchemas } from '@arkade-os/boltz-swap/repositories/realm';
|
|
83
67
|
```
|
|
84
68
|
|
|
85
|
-
|
|
69
|
+
> [!WARNING]
|
|
70
|
+
> If you previously used the v1 `StorageAdapter`-based repositories, migrate
|
|
71
|
+
> data into the new IndexedDB repositories before use. You can use
|
|
72
|
+
> `getMigrationStatus` from `@arkade-os/sdk` to check whether migration is
|
|
73
|
+
> needed before running it:
|
|
74
|
+
>
|
|
75
|
+
> ```typescript
|
|
76
|
+
> import {
|
|
77
|
+
> IndexedDbSwapRepository,
|
|
78
|
+
> migrateToSwapRepository
|
|
79
|
+
> } from '@arkade-os/boltz-swap'
|
|
80
|
+
> import { getMigrationStatus } from '@arkade-os/sdk'
|
|
81
|
+
> import { IndexedDBStorageAdapter } from '@arkade-os/sdk/adapters/indexedDB'
|
|
82
|
+
>
|
|
83
|
+
> // if you used a different name for the DB, use your own here
|
|
84
|
+
> const oldStorage = new IndexedDBStorageAdapter('arkade-service-worker', 1)
|
|
85
|
+
>
|
|
86
|
+
> const status = await getMigrationStatus('wallet', oldStorage)
|
|
87
|
+
> if (status !== 'not-needed') {
|
|
88
|
+
> await migrateToSwapRepository(oldStorage, new IndexedDbSwapRepository())
|
|
89
|
+
> }
|
|
90
|
+
> ```
|
|
91
|
+
|
|
92
|
+
Existing data stays in the old DB (e.g. `arkade-service-worker`) until you run the migration once.
|
|
93
|
+
After `migrateToSwapRepository`, the IndexedDB-backed SwapRepository is used going forward.
|
|
86
94
|
|
|
87
95
|
## Background Swap Monitoring (SwapManager)
|
|
88
96
|
|
|
@@ -92,14 +100,14 @@ By default, you must manually monitor each swap and act on their state. **SwapMa
|
|
|
92
100
|
|
|
93
101
|
```typescript
|
|
94
102
|
// Option 1: Enable with defaults
|
|
95
|
-
const
|
|
103
|
+
const swaps = new ArkadeSwaps({
|
|
96
104
|
wallet,
|
|
97
105
|
swapProvider,
|
|
98
106
|
swapManager: true, // Simple boolean to enable with defaults
|
|
99
107
|
});
|
|
100
108
|
|
|
101
109
|
// Option 2: Enable with custom config
|
|
102
|
-
const
|
|
110
|
+
const swaps = new ArkadeSwaps({
|
|
103
111
|
wallet,
|
|
104
112
|
swapProvider,
|
|
105
113
|
swapManager: {
|
|
@@ -116,14 +124,11 @@ const arkadeLightning = new ArkadeLightning({
|
|
|
116
124
|
},
|
|
117
125
|
});
|
|
118
126
|
|
|
119
|
-
// If
|
|
120
|
-
|
|
121
|
-
if (config.swapManager?.autostart === false) {
|
|
122
|
-
await arkadeLightning.startSwapManager();
|
|
123
|
-
}
|
|
127
|
+
// If autoStart is false, manually start monitoring
|
|
128
|
+
await swaps.startSwapManager();
|
|
124
129
|
|
|
125
130
|
// Create swaps - they're automatically monitored!
|
|
126
|
-
const invoice = await
|
|
131
|
+
const invoice = await swaps.createLightningInvoice({ amount: 50000 });
|
|
127
132
|
// User can navigate to other pages - swap completes in background
|
|
128
133
|
```
|
|
129
134
|
|
|
@@ -134,8 +139,9 @@ const invoice = await arkadeLightning.createLightningInvoice({ amount: 50000 });
|
|
|
134
139
|
- **Fallback polling** with exponential backoff if WebSocket fails
|
|
135
140
|
- **Auto-claim/refund** executes when status allows
|
|
136
141
|
- **Resumes on app reopen** - loads pending swaps, polls latest status, executes refunds if expired
|
|
137
|
-
-
|
|
138
|
-
-
|
|
142
|
+
- **Default `ArkadeSwaps` requires the app running** - monitoring stops when the app/tab closes
|
|
143
|
+
- For browser background monitoring, use `ServiceWorkerArkadeLightning`
|
|
144
|
+
- If swaps expire while closed, refunds execute automatically on next app launch (unless claimed/refunded by your background runtime)
|
|
139
145
|
|
|
140
146
|
### Configuration Options
|
|
141
147
|
|
|
@@ -159,7 +165,7 @@ const invoice = await arkadeLightning.createLightningInvoice({ amount: 50000 });
|
|
|
159
165
|
onSwapUpdate: (swap, oldStatus) => {},
|
|
160
166
|
onSwapCompleted: (swap) => {},
|
|
161
167
|
onSwapFailed: (swap, error) => {},
|
|
162
|
-
onActionExecuted: (swap, action) => {}, // 'claim'
|
|
168
|
+
onActionExecuted: (swap, action) => {}, // 'claim', 'refund', 'claimArk', 'claimBtc', 'refundArk', 'signServerClaim'
|
|
163
169
|
onWebSocketConnected: () => {},
|
|
164
170
|
onWebSocketDisconnected: (error?) => {},
|
|
165
171
|
}
|
|
@@ -168,84 +174,67 @@ const invoice = await arkadeLightning.createLightningInvoice({ amount: 50000 });
|
|
|
168
174
|
|
|
169
175
|
### Event Subscription
|
|
170
176
|
|
|
171
|
-
SwapManager supports flexible event subscription - you can add/remove listeners dynamically
|
|
177
|
+
SwapManager supports flexible event subscription - you can add/remove listeners dynamically:
|
|
172
178
|
|
|
173
179
|
```typescript
|
|
174
|
-
const
|
|
180
|
+
const swaps = new ArkadeSwaps({
|
|
175
181
|
wallet,
|
|
176
182
|
swapProvider,
|
|
177
183
|
swapManager: true,
|
|
178
184
|
});
|
|
179
185
|
|
|
180
|
-
const manager =
|
|
186
|
+
const manager = swaps.getSwapManager();
|
|
181
187
|
|
|
182
188
|
// Subscribe to events using on* methods (returns unsubscribe function)
|
|
183
189
|
const unsubscribe = manager.onSwapUpdate((swap, oldStatus) => {
|
|
184
190
|
console.log(`Swap ${swap.id}: ${oldStatus} → ${swap.status}`);
|
|
185
|
-
// Update UI based on swap status
|
|
186
191
|
});
|
|
187
192
|
|
|
188
193
|
// Subscribe to completed events
|
|
189
194
|
manager.onSwapCompleted((swap) => {
|
|
190
195
|
console.log(`Swap ${swap.id} completed!`);
|
|
191
|
-
showNotification(`Payment completed!`);
|
|
192
196
|
});
|
|
193
197
|
|
|
194
198
|
// Subscribe to failures
|
|
195
199
|
manager.onSwapFailed((swap, error) => {
|
|
196
200
|
console.error(`Swap ${swap.id} failed:`, error);
|
|
197
|
-
showErrorDialog(error.message);
|
|
198
201
|
});
|
|
199
202
|
|
|
200
|
-
// Subscribe to actions (claim/refund)
|
|
203
|
+
// Subscribe to actions (claim/refund/claimArk/claimBtc/refundArk/signServerClaim)
|
|
201
204
|
manager.onActionExecuted((swap, action) => {
|
|
202
205
|
console.log(`Executed ${action} for swap ${swap.id}`);
|
|
203
206
|
});
|
|
204
207
|
|
|
205
208
|
// WebSocket events
|
|
206
|
-
manager.onWebSocketConnected(() =>
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
manager.onWebSocketDisconnected((error) => {
|
|
211
|
-
console.log('Disconnected from swap updates', error);
|
|
212
|
-
});
|
|
209
|
+
manager.onWebSocketConnected(() => console.log('Connected'));
|
|
210
|
+
manager.onWebSocketDisconnected((error) => console.log('Disconnected', error));
|
|
213
211
|
|
|
214
212
|
// Unsubscribe when no longer needed (e.g., component unmount)
|
|
215
|
-
|
|
213
|
+
unsubscribe();
|
|
216
214
|
|
|
217
|
-
// Or use off* methods to remove specific
|
|
218
|
-
|
|
219
|
-
manager.onSwapCompleted(listener);
|
|
220
|
-
// Later...
|
|
221
|
-
manager.offSwapCompleted(listener);
|
|
215
|
+
// Or use off* methods to remove a specific listener
|
|
216
|
+
manager.offSwapUpdate(myListener);
|
|
222
217
|
```
|
|
223
218
|
|
|
224
|
-
**Benefits of dynamic event subscription:**
|
|
225
|
-
- Add/remove listeners at any time
|
|
226
|
-
- Multiple listeners per event type
|
|
227
|
-
- Easy cleanup when components unmount
|
|
228
|
-
- No need to restart SwapManager to change handlers
|
|
229
|
-
|
|
230
219
|
### Cleanup (Disposable Pattern)
|
|
231
220
|
|
|
232
|
-
|
|
221
|
+
ArkadeSwaps implements the Disposable pattern for automatic cleanup:
|
|
233
222
|
|
|
234
223
|
```typescript
|
|
235
224
|
// Option 1: Manual cleanup
|
|
236
|
-
const
|
|
225
|
+
const swaps = new ArkadeSwaps({ wallet, swapProvider });
|
|
237
226
|
// ... use it
|
|
238
|
-
await
|
|
227
|
+
await swaps.dispose(); // Stops SwapManager and cleans up
|
|
239
228
|
|
|
240
229
|
// Option 2: Automatic cleanup with `await using` (TypeScript 5.2+)
|
|
241
230
|
{
|
|
242
|
-
await using
|
|
231
|
+
await using swaps = new ArkadeSwaps({
|
|
243
232
|
wallet,
|
|
244
233
|
swapProvider,
|
|
245
234
|
swapManager: { autoStart: true },
|
|
246
235
|
});
|
|
247
236
|
|
|
248
|
-
// Use
|
|
237
|
+
// Use swaps...
|
|
249
238
|
|
|
250
239
|
} // SwapManager automatically stopped when scope exits
|
|
251
240
|
```
|
|
@@ -254,11 +243,11 @@ await arkadeLightning.dispose(); // Stops SwapManager and cleans up
|
|
|
254
243
|
|
|
255
244
|
```typescript
|
|
256
245
|
// Stop background monitoring
|
|
257
|
-
await
|
|
246
|
+
await swaps.stopSwapManager();
|
|
258
247
|
|
|
259
248
|
// Check manager stats
|
|
260
|
-
const manager =
|
|
261
|
-
const stats = manager?.getStats();
|
|
249
|
+
const manager = swaps.getSwapManager();
|
|
250
|
+
const stats = await manager?.getStats();
|
|
262
251
|
console.log(`Monitoring ${stats.monitoredSwaps} swaps`);
|
|
263
252
|
console.log(`WebSocket connected: ${stats.websocketConnected}`);
|
|
264
253
|
```
|
|
@@ -268,15 +257,14 @@ console.log(`WebSocket connected: ${stats.websocketConnected}`);
|
|
|
268
257
|
When SwapManager is enabled, you can subscribe to updates for specific swaps to show progress in your UI:
|
|
269
258
|
|
|
270
259
|
```typescript
|
|
271
|
-
const result = await
|
|
260
|
+
const result = await swaps.createLightningInvoice({ amount: 50000 });
|
|
272
261
|
|
|
273
262
|
// Subscribe to this specific swap's updates
|
|
274
|
-
const manager =
|
|
263
|
+
const manager = swaps.getSwapManager();
|
|
275
264
|
const unsubscribe = manager.subscribeToSwapUpdates(
|
|
276
265
|
result.pendingSwap.id,
|
|
277
266
|
(swap, oldStatus) => {
|
|
278
267
|
console.log(`Swap ${swap.id}: ${oldStatus} → ${swap.status}`);
|
|
279
|
-
// Update UI based on status
|
|
280
268
|
if (swap.status === 'transaction.mempool') {
|
|
281
269
|
showNotification('Payment detected in mempool!');
|
|
282
270
|
} else if (swap.status === 'invoice.settled') {
|
|
@@ -294,20 +282,15 @@ const unsubscribe = manager.subscribeToSwapUpdates(
|
|
|
294
282
|
Even with SwapManager enabled, you can still wait for specific swaps to complete:
|
|
295
283
|
|
|
296
284
|
```typescript
|
|
297
|
-
const result = await
|
|
285
|
+
const result = await swaps.createLightningInvoice({ amount: 50000 });
|
|
298
286
|
|
|
299
287
|
// This blocks until the swap completes, but SwapManager handles the monitoring
|
|
300
288
|
try {
|
|
301
|
-
const { txid } = await
|
|
289
|
+
const { txid } = await swaps.waitAndClaim(result.pendingSwap);
|
|
302
290
|
console.log('Payment claimed successfully:', txid);
|
|
303
291
|
} catch (error) {
|
|
304
292
|
console.error('Payment failed:', error);
|
|
305
293
|
}
|
|
306
|
-
|
|
307
|
-
// Benefits of delegating to SwapManager:
|
|
308
|
-
// No race conditions - manager coordinates with manual calls
|
|
309
|
-
// User can navigate away - swap completes in background
|
|
310
|
-
// Automatic refund on failure - no manual error handling needed
|
|
311
294
|
```
|
|
312
295
|
|
|
313
296
|
### Without SwapManager (Manual Mode)
|
|
@@ -316,123 +299,123 @@ If SwapManager is not enabled, you must manually monitor swaps:
|
|
|
316
299
|
|
|
317
300
|
```typescript
|
|
318
301
|
// Create invoice
|
|
319
|
-
const result = await
|
|
302
|
+
const result = await swaps.createLightningInvoice({ amount: 50000 });
|
|
320
303
|
|
|
321
304
|
// MUST manually monitor - blocks until complete
|
|
322
|
-
await
|
|
305
|
+
await swaps.waitAndClaim(result.pendingSwap);
|
|
323
306
|
// User must stay on this page - navigating away stops monitoring
|
|
324
307
|
```
|
|
325
308
|
|
|
326
|
-
##
|
|
327
|
-
|
|
328
|
-
Before creating Lightning invoices or sending payments, you can check the minimum and maximum swap amounts supported by the Boltz service. This is useful to validate that your invoice amount is within the acceptable range.
|
|
309
|
+
## Expo / React Native
|
|
329
310
|
|
|
330
|
-
|
|
331
|
-
// Get current swap limits (in satoshis)
|
|
332
|
-
const limits = await arkadeLightning.getLimits();
|
|
311
|
+
Expo/React Native cannot run a long-lived Service Worker, and background work is executed by the OS for a short window (typically every ~15+ minutes). To enable best-effort background claim/refund for swaps, use `ExpoArkadeLightning` plus a background task defined at global scope.
|
|
333
312
|
|
|
334
|
-
|
|
335
|
-
console.log('Minimum swap amount:', limits.min, 'sats');
|
|
336
|
-
console.log('Maximum swap amount:', limits.max, 'sats');
|
|
313
|
+
### Prerequisites
|
|
337
314
|
|
|
338
|
-
|
|
339
|
-
const invoiceAmount = 50000; // 50,000 sats
|
|
315
|
+
- Install Expo background task dependencies:
|
|
340
316
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
console.log('Amount is within valid range');
|
|
347
|
-
// Safe to proceed with creating invoice or payment
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
console.log('Unable to fetch limits - no swap provider configured');
|
|
351
|
-
}
|
|
317
|
+
```bash
|
|
318
|
+
npx expo install expo-task-manager expo-background-task
|
|
319
|
+
npx expo install @react-native-async-storage/async-storage expo-secure-store
|
|
320
|
+
npx expo install expo-crypto
|
|
321
|
+
npx expo install expo-sqlite && npm install indexeddbshim
|
|
352
322
|
```
|
|
353
323
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
import { decodeInvoice } from '@arkade-os/boltz-swap';
|
|
324
|
+
- If you rely on the default IndexedDB-backed repositories in Expo, call `setupExpoDb()` **before any SDK/boltz-swap import**:
|
|
358
325
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const decodedInvoice = decodeInvoice(invoice);
|
|
326
|
+
```ts
|
|
327
|
+
import { setupExpoDb } from "@arkade-os/sdk/adapters/expo-db";
|
|
362
328
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Check if the invoice amount is within swap limits
|
|
366
|
-
const limits = await arkadeLightning.getLimits();
|
|
367
|
-
|
|
368
|
-
if (limits && decodedInvoice.amountSats >= limits.min && decodedInvoice.amountSats <= limits.max) {
|
|
369
|
-
// Amount is valid for swaps
|
|
370
|
-
const paymentResult = await arkadeLightning.sendLightningPayment({
|
|
371
|
-
invoice: invoice,
|
|
372
|
-
});
|
|
373
|
-
console.log('Payment successful!');
|
|
374
|
-
} else {
|
|
375
|
-
console.error('Invoice amount is outside supported swap limits');
|
|
376
|
-
}
|
|
329
|
+
setupExpoDb();
|
|
377
330
|
```
|
|
378
331
|
|
|
379
|
-
|
|
332
|
+
- Expo requires a `crypto.getRandomValues()` polyfill for cryptographic operations:
|
|
380
333
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
// Get current swap fees
|
|
386
|
-
const fees: FeesResponse | null = await arkadeLightning.getFees();
|
|
387
|
-
if (!fees) throw new Error('something went wrong');
|
|
388
|
-
|
|
389
|
-
const calcSubmarineSwapFee = (satoshis: number): number => {
|
|
390
|
-
if (!satoshis) return 0;
|
|
391
|
-
const { percentage, minerFees } = fees.submarine;
|
|
392
|
-
return Math.ceil((satoshis * percentage) / 100 + minerFees);
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
const calcReverseSwapFee = (satoshis: number): number => {
|
|
396
|
-
if (!satoshis) return 0;
|
|
397
|
-
const { percentage, minerFees } = fees.reverse;
|
|
398
|
-
return Math.ceil((satoshis * percentage) / 100 + minerFees.claim + minerFees.lockup);
|
|
399
|
-
};
|
|
334
|
+
```ts
|
|
335
|
+
import * as Crypto from "expo-crypto";
|
|
336
|
+
if (!global.crypto) global.crypto = {} as any;
|
|
337
|
+
global.crypto.getRandomValues = Crypto.getRandomValues;
|
|
400
338
|
```
|
|
401
339
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
340
|
+
### 1) Define the background task (global scope)
|
|
341
|
+
|
|
342
|
+
`TaskManager.defineTask()` must be called at module scope before React mounts.
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
// App entry point (e.g., _layout.tsx) — GLOBAL SCOPE
|
|
346
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
347
|
+
import * as SecureStore from "expo-secure-store";
|
|
348
|
+
import { SingleKey } from "@arkade-os/sdk";
|
|
349
|
+
import { AsyncStorageTaskQueue } from "@arkade-os/sdk/worker/expo";
|
|
350
|
+
import { IndexedDbSwapRepository } from "@arkade-os/boltz-swap";
|
|
351
|
+
import { defineExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo";
|
|
352
|
+
|
|
353
|
+
const swapTaskQueue = new AsyncStorageTaskQueue(AsyncStorage, "ark:swap-queue");
|
|
354
|
+
const swapRepository = new IndexedDbSwapRepository();
|
|
355
|
+
|
|
356
|
+
defineExpoSwapBackgroundTask("ark-swap-poll", {
|
|
357
|
+
taskQueue: swapTaskQueue,
|
|
358
|
+
swapRepository,
|
|
359
|
+
identityFactory: async () => {
|
|
360
|
+
const key = await SecureStore.getItemAsync("ark-private-key");
|
|
361
|
+
if (!key) throw new Error("Missing private key in SecureStore");
|
|
362
|
+
return SingleKey.fromHex(key);
|
|
363
|
+
},
|
|
364
|
+
});
|
|
410
365
|
```
|
|
411
366
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
367
|
+
### 2) Set up `ExpoArkadeLightning` (component/provider)
|
|
368
|
+
|
|
369
|
+
Use an `IWallet` implementation that provides `arkProvider` and `indexerProvider` (for example `ExpoWallet` from `@arkade-os/sdk/wallet/expo`, or `Wallet.create()` with `ExpoArkProvider` / `ExpoIndexerProvider`).
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
373
|
+
import { ExpoWallet } from "@arkade-os/sdk/wallet/expo";
|
|
374
|
+
import { AsyncStorageTaskQueue } from "@arkade-os/sdk/worker/expo";
|
|
375
|
+
import { BoltzSwapProvider } from "@arkade-os/boltz-swap";
|
|
376
|
+
import { ExpoArkadeLightning } from "@arkade-os/boltz-swap/expo";
|
|
377
|
+
|
|
378
|
+
// Used by ExpoWallet's background task (defined via @arkade-os/sdk/wallet/expo)
|
|
379
|
+
const walletTaskQueue = new AsyncStorageTaskQueue(AsyncStorage, "ark:wallet-queue");
|
|
380
|
+
|
|
381
|
+
const wallet = await ExpoWallet.setup({
|
|
382
|
+
identity, // same identity used by identityFactory()
|
|
383
|
+
arkServerUrl: "https://mutinynet.arkade.sh",
|
|
384
|
+
storage: { walletRepository, contractRepository },
|
|
385
|
+
background: {
|
|
386
|
+
taskName: "ark-wallet-poll",
|
|
387
|
+
taskQueue: walletTaskQueue,
|
|
388
|
+
foregroundIntervalMs: 20_000,
|
|
389
|
+
minimumBackgroundInterval: 15,
|
|
390
|
+
},
|
|
391
|
+
});
|
|
415
392
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
393
|
+
const swapProvider = new BoltzSwapProvider({
|
|
394
|
+
apiUrl: "https://api.boltz.mutinynet.arkade.sh",
|
|
395
|
+
network: "mutinynet",
|
|
396
|
+
});
|
|
419
397
|
|
|
420
|
-
|
|
421
|
-
|
|
398
|
+
const arkLn = await ExpoArkadeLightning.setup({
|
|
399
|
+
wallet,
|
|
400
|
+
swapProvider,
|
|
401
|
+
swapRepository, // must match the one used in defineExpoSwapBackgroundTask
|
|
402
|
+
background: {
|
|
403
|
+
taskName: "ark-swap-poll",
|
|
404
|
+
taskQueue: swapTaskQueue, // must match the one used in defineExpoSwapBackgroundTask
|
|
405
|
+
foregroundIntervalMs: 20_000,
|
|
406
|
+
minimumBackgroundInterval: 15,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
422
409
|
|
|
423
|
-
|
|
424
|
-
const swapHistory = await arkadeLightning.getSwapHistory();
|
|
410
|
+
await arkLn.createLightningInvoice({ amount: 1000 });
|
|
425
411
|
```
|
|
426
412
|
|
|
427
|
-
**Note**: All swap data is automatically persisted and retrieved through the wallet's contract repository. No additional storage configuration is required.
|
|
428
|
-
|
|
429
413
|
## Receiving Lightning Payments
|
|
430
414
|
|
|
431
415
|
To receive a Lightning payment into your Arkade wallet:
|
|
432
416
|
|
|
433
417
|
```typescript
|
|
434
|
-
|
|
435
|
-
const result = await arkadeLightning.createLightningInvoice({
|
|
418
|
+
const result = await swaps.createLightningInvoice({
|
|
436
419
|
amount: 50000, // 50,000 sats
|
|
437
420
|
description: 'Payment to my Arkade wallet',
|
|
438
421
|
});
|
|
@@ -443,9 +426,6 @@ console.log('Lightning Invoice:', result.invoice);
|
|
|
443
426
|
console.log('Payment Hash:', result.paymentHash);
|
|
444
427
|
console.log('Pending swap', result.pendingSwap);
|
|
445
428
|
console.log('Preimage', result.preimage);
|
|
446
|
-
|
|
447
|
-
// The invoice can now be shared with the payer
|
|
448
|
-
// When paid, funds will appear in your Arkade wallet
|
|
449
429
|
```
|
|
450
430
|
|
|
451
431
|
### Monitoring Incoming Lightning Payments
|
|
@@ -453,18 +433,15 @@ console.log('Preimage', result.preimage);
|
|
|
453
433
|
**With SwapManager (recommended):**
|
|
454
434
|
```typescript
|
|
455
435
|
// SwapManager handles monitoring and claiming automatically
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// Payment will be claimed automatically when received
|
|
436
|
+
const result = await swaps.createLightningInvoice({ amount: 50000 });
|
|
437
|
+
// Payment will be claimed automatically when received
|
|
459
438
|
```
|
|
460
439
|
|
|
461
440
|
**Without SwapManager (manual mode):**
|
|
462
441
|
```typescript
|
|
463
442
|
// You must manually monitor - blocks until payment is received
|
|
464
|
-
const receivalResult = await
|
|
465
|
-
console.log('Receival successful!');
|
|
443
|
+
const receivalResult = await swaps.waitAndClaim(result.pendingSwap);
|
|
466
444
|
console.log('Transaction ID:', receivalResult.txid);
|
|
467
|
-
// ⚠️ User must stay on this page - navigating away stops monitoring
|
|
468
445
|
```
|
|
469
446
|
|
|
470
447
|
## Sending Lightning Payments
|
|
@@ -478,7 +455,7 @@ const invoiceDetails = decodeInvoice('lnbc500u1pj...');
|
|
|
478
455
|
console.log('Invoice amount:', invoiceDetails.amountSats, 'sats');
|
|
479
456
|
|
|
480
457
|
// Send payment - returns immediately after creating swap
|
|
481
|
-
const paymentResult = await
|
|
458
|
+
const paymentResult = await swaps.sendLightningPayment({
|
|
482
459
|
invoice: 'lnbc500u1pj...',
|
|
483
460
|
});
|
|
484
461
|
|
|
@@ -488,18 +465,172 @@ console.log('Payment initiated:', paymentResult.txid);
|
|
|
488
465
|
|
|
489
466
|
**Without SwapManager (manual mode):**
|
|
490
467
|
```typescript
|
|
491
|
-
|
|
492
|
-
const paymentResult = await arkadeLightning.sendLightningPayment({
|
|
468
|
+
const paymentResult = await swaps.sendLightningPayment({
|
|
493
469
|
invoice: 'lnbc500u1pj...',
|
|
494
470
|
});
|
|
495
471
|
|
|
496
|
-
console.log('Payment successful!');
|
|
497
472
|
console.log('Amount:', paymentResult.amount);
|
|
498
473
|
console.log('Preimage:', paymentResult.preimage);
|
|
499
474
|
console.log('Transaction ID:', paymentResult.txid);
|
|
500
|
-
// ⚠️ If payment fails, you must manually handle refund (see Error Handling)
|
|
501
475
|
```
|
|
502
476
|
|
|
477
|
+
## Chain Swaps
|
|
478
|
+
|
|
479
|
+
Chain swaps move funds between Arkade and Bitcoin on-chain via Boltz.
|
|
480
|
+
|
|
481
|
+
#### Amounts
|
|
482
|
+
|
|
483
|
+
When creating a swap, and because there are fees to be paid, you must define one and only one type of amount:
|
|
484
|
+
- senderLockAmount: sender will send this exact amount, receiver will receive less (amount - fees)
|
|
485
|
+
- receiverLockAmount: receiver will receive this exact amount, sender needs to send more (amount + fees)
|
|
486
|
+
|
|
487
|
+
### ARK to BTC
|
|
488
|
+
|
|
489
|
+
Send funds from your Arkade wallet to a Bitcoin address:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
// Create the swap
|
|
493
|
+
const result = await swaps.arkToBtc({
|
|
494
|
+
btcAddress: 'bc1q...',
|
|
495
|
+
senderLockAmount: 100000,
|
|
496
|
+
feeSatsPerByte: 2, // optional, defaults to 1
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
console.log('Pay to ARK address:', result.arkAddress);
|
|
500
|
+
console.log('Amount to pay:', result.amountToPay, 'sats');
|
|
501
|
+
|
|
502
|
+
// Wait for BTC to be claimed
|
|
503
|
+
// If you use swapManager, this step is not needed
|
|
504
|
+
const { txid } = await swaps.waitAndClaimBtc(result.pendingSwap);
|
|
505
|
+
console.log('BTC claimed:', txid);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
If the swap fails, refund your ARK funds:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
await swaps.refundArk(result.pendingSwap);
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### BTC to ARK
|
|
515
|
+
|
|
516
|
+
Receive funds from Bitcoin into your Arkade wallet:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// Create the swap
|
|
520
|
+
const result = await swaps.btcToArk({
|
|
521
|
+
receiverLockAmount: 100000,
|
|
522
|
+
feeSatsPerByte: 2, // optional, defaults to 1
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
console.log('Pay to BTC address:', result.btcAddress);
|
|
526
|
+
console.log('Amount to pay:', result.amountToPay, 'sats');
|
|
527
|
+
|
|
528
|
+
// Wait for ARK to be claimed
|
|
529
|
+
// If you use swapManager, this step is not needed
|
|
530
|
+
const { txid } = await swaps.waitAndClaimArk(result.pendingSwap);
|
|
531
|
+
console.log('ARK claimed:', txid);
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Chain Swap Fees and Limits
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// Get chain swap fees
|
|
538
|
+
const chainFees = await swaps.getFees('ARK', 'BTC');
|
|
539
|
+
console.log('Percentage:', chainFees.percentage);
|
|
540
|
+
console.log('Server miner fee:', chainFees.minerFees.server);
|
|
541
|
+
console.log('User claim fee:', chainFees.minerFees.user.claim);
|
|
542
|
+
console.log('User lockup fee:', chainFees.minerFees.user.lockup);
|
|
543
|
+
|
|
544
|
+
// Get chain swap limits
|
|
545
|
+
const chainLimits = await swaps.getLimits('ARK', 'BTC');
|
|
546
|
+
console.log('Min:', chainLimits.min, 'sats');
|
|
547
|
+
console.log('Max:', chainLimits.max, 'sats');
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Renegotiating Quotes
|
|
551
|
+
|
|
552
|
+
If the amount sent to the swap is different from the expected, renegotiate it:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
const newAmount = await swaps.quoteSwap(pendingSwap.id);
|
|
556
|
+
console.log('Updated amount:', newAmount);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## Checking Swap Limits
|
|
560
|
+
|
|
561
|
+
Before creating swaps, check the supported amount range:
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
// Lightning swap limits
|
|
565
|
+
const limits = await swaps.getLimits();
|
|
566
|
+
console.log('Min:', limits.min, 'sats');
|
|
567
|
+
console.log('Max:', limits.max, 'sats');
|
|
568
|
+
|
|
569
|
+
// Chain swap limits
|
|
570
|
+
const chainLimits = await swaps.getLimits('ARK', 'BTC');
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Validating Lightning Invoice Amounts
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
import { decodeInvoice } from '@arkade-os/boltz-swap';
|
|
577
|
+
|
|
578
|
+
const invoice = 'lnbc500u1pj...';
|
|
579
|
+
const decoded = decodeInvoice(invoice);
|
|
580
|
+
console.log('Invoice amount:', decoded.amountSats, 'sats');
|
|
581
|
+
|
|
582
|
+
const limits = await swaps.getLimits();
|
|
583
|
+
if (decoded.amountSats >= limits.min && decoded.amountSats <= limits.max) {
|
|
584
|
+
await swaps.sendLightningPayment({ invoice });
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Checking Swap Fees
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// Lightning fees
|
|
592
|
+
const fees = await swaps.getFees();
|
|
593
|
+
|
|
594
|
+
const calcSubmarineSwapFee = (satoshis: number): number => {
|
|
595
|
+
const { percentage, minerFees } = fees.submarine;
|
|
596
|
+
return Math.ceil((satoshis * percentage) / 100 + minerFees);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const calcReverseSwapFee = (satoshis: number): number => {
|
|
600
|
+
const { percentage, minerFees } = fees.reverse;
|
|
601
|
+
return Math.ceil((satoshis * percentage) / 100 + minerFees.claim + minerFees.lockup);
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Chain fees
|
|
605
|
+
const chainFees = await swaps.getFees('ARK', 'BTC');
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Checking Swap Status
|
|
609
|
+
|
|
610
|
+
**With SwapManager:** Status updates are automatic via events - no manual checking needed.
|
|
611
|
+
|
|
612
|
+
**Without SwapManager (manual mode):**
|
|
613
|
+
```typescript
|
|
614
|
+
const response = await swaps.getSwapStatus('swap_id');
|
|
615
|
+
console.log('swap status = ', response.status);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## Storage
|
|
619
|
+
|
|
620
|
+
All swap data is persisted automatically and can be retrieved:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
// Get pending swaps by type
|
|
624
|
+
const pendingSubmarineSwaps = await swaps.getPendingSubmarineSwaps();
|
|
625
|
+
const pendingReverseSwaps = await swaps.getPendingReverseSwaps();
|
|
626
|
+
const pendingChainSwaps = await swaps.getPendingChainSwaps();
|
|
627
|
+
|
|
628
|
+
// Get complete swap history (all types, sorted by creation date)
|
|
629
|
+
const swapHistory = await swaps.getSwapHistory();
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Note**: If IndexedDB is not available (e.g., in Node.js), provide a custom `swapRepository` implementation.
|
|
633
|
+
|
|
503
634
|
## Error Handling
|
|
504
635
|
|
|
505
636
|
**With SwapManager:** Refunds are handled automatically - listen to `onSwapFailed` event for notifications.
|
|
@@ -516,35 +647,82 @@ import {
|
|
|
516
647
|
InvoiceFailedToPayError,
|
|
517
648
|
InsufficientFundsError,
|
|
518
649
|
TransactionFailedError,
|
|
650
|
+
isPendingSubmarineSwap,
|
|
651
|
+
isPendingChainSwap,
|
|
519
652
|
} from '@arkade-os/boltz-swap';
|
|
520
653
|
|
|
521
654
|
try {
|
|
522
|
-
await
|
|
655
|
+
await swaps.sendLightningPayment({
|
|
523
656
|
invoice: 'lnbc500u1pj...',
|
|
524
657
|
});
|
|
525
658
|
} catch (error) {
|
|
526
659
|
if (error instanceof InvoiceExpiredError) {
|
|
527
|
-
console.error('The invoice has expired.
|
|
660
|
+
console.error('The invoice has expired.');
|
|
528
661
|
} else if (error instanceof InvoiceFailedToPayError) {
|
|
529
662
|
console.error('The provider failed to pay the invoice.');
|
|
530
663
|
} else if (error instanceof InsufficientFundsError) {
|
|
531
664
|
console.error('Not enough funds available:', error.message);
|
|
532
665
|
} else if (error instanceof NetworkError) {
|
|
533
|
-
console.error('Network issue
|
|
666
|
+
console.error('Network issue:', error.message);
|
|
534
667
|
} else if (error instanceof SchemaError) {
|
|
535
|
-
console.error('Invalid response from API.
|
|
668
|
+
console.error('Invalid response from API.');
|
|
536
669
|
} else if (error instanceof SwapExpiredError) {
|
|
537
670
|
console.error('The swap has expired.');
|
|
538
671
|
} else if (error instanceof TransactionFailedError) {
|
|
539
|
-
console.error('Transaction failed.
|
|
672
|
+
console.error('Transaction failed.');
|
|
540
673
|
} else {
|
|
541
674
|
console.error('Unknown error:', error);
|
|
542
675
|
}
|
|
543
676
|
|
|
544
677
|
// Manual refund (only needed without SwapManager)
|
|
545
678
|
if (error.isRefundable && error.pendingSwap) {
|
|
546
|
-
|
|
547
|
-
|
|
679
|
+
if (isPendingChainSwap(error.pendingSwap)) {
|
|
680
|
+
await swaps.refundArk(error.pendingSwap);
|
|
681
|
+
} else if (isPendingSubmarineSwap(error.pendingSwap)) {
|
|
682
|
+
await swaps.refundVHTLC(error.pendingSwap);
|
|
683
|
+
}
|
|
548
684
|
}
|
|
549
685
|
}
|
|
550
686
|
```
|
|
687
|
+
|
|
688
|
+
## Type Guards
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
import {
|
|
692
|
+
isPendingReverseSwap,
|
|
693
|
+
isPendingSubmarineSwap,
|
|
694
|
+
isPendingChainSwap,
|
|
695
|
+
isSubmarineSwapRefundable,
|
|
696
|
+
isChainSwapClaimable,
|
|
697
|
+
isChainSwapRefundable,
|
|
698
|
+
isSubmarineFinalStatus,
|
|
699
|
+
isReverseFinalStatus,
|
|
700
|
+
isChainFinalStatus,
|
|
701
|
+
} from '@arkade-os/boltz-swap';
|
|
702
|
+
|
|
703
|
+
// Discriminate swap types
|
|
704
|
+
if (isPendingReverseSwap(swap)) { /* Lightning → Arkade */ }
|
|
705
|
+
if (isPendingSubmarineSwap(swap)) { /* Arkade → Lightning */ }
|
|
706
|
+
if (isPendingChainSwap(swap)) { /* ARK ↔ BTC chain */ }
|
|
707
|
+
|
|
708
|
+
// Check swap state
|
|
709
|
+
if (isChainSwapClaimable(swap)) { /* ready to claim */ }
|
|
710
|
+
if (isChainSwapRefundable(swap)) { /* can be refunded */ }
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Releasing
|
|
714
|
+
|
|
715
|
+
```bash
|
|
716
|
+
# Release new version (will prompt for version patch, minor, major)
|
|
717
|
+
pnpm release
|
|
718
|
+
|
|
719
|
+
# You can test release process without making changes
|
|
720
|
+
pnpm release:dry-run
|
|
721
|
+
|
|
722
|
+
# Cleanup: checkout version commit and remove release branch
|
|
723
|
+
pnpm release:cleanup
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
## License
|
|
727
|
+
|
|
728
|
+
MIT
|