@arkade-os/boltz-swap

中文文档
0.3.67发布于 2026/08/13MITTypeScriptESM + CJS近 30 天下载 5452

A production-ready TypeScript package that brings Boltz submarine-swaps to Arkade.

此软件包已被标记为弃用

Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

原始使用文档
文档目录29 节
  1. Overview
  2. Installation
  3. Basic Usage
  4. Initializing
  5. Receive Lightning
  6. Send Lightning
  7. ARK to BTC
  8. BTC to ARK
  9. Listening for Updates
  10. Fees and Limits
  11. Swap History
  12. Advanced Usage
  13. Chain Swap Amounts
  14. Renegotiating Quotes
  15. Blocking on a Swap
  16. Without SwapManager (Manual Mode)
  17. SwapManager Configuration
  18. Per-Swap UI Hooks
  19. Submarine Fund Recovery
  20. Cleanup
  21. SwapRepository
  22. Expo / React Native
  23. Prerequisites
  24. 1) Define the background task (global scope)
  25. 2) Set up ExpoArkadeLightning (component/provider)
  26. Error Handling
  27. Type Guards
  28. Releasing
  29. License

Arkade Swaps

Lightning and chain swaps for Arkade using Boltz

@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.

Overview

The library enables four swap types:

  1. Lightning to Arkade - Receive funds from Lightning payments into your Arkade wallet
  2. Arkade to Lightning - Send funds from your Arkade wallet to Lightning invoices
  3. ARK to BTC - Move funds from Arkade to a Bitcoin on-chain address
  4. BTC to ARK - Move funds from Bitcoin on-chain into your Arkade wallet

Built on top of the Boltz swap protocol with automatic background monitoring via SwapManager.

Installation

npm install @arkade-os/sdk @arkade-os/boltz-swap

Basic Usage

Initializing

import { Wallet, MnemonicIdentity } from '@arkade-os/sdk';
import { ArkadeSwaps } from '@arkade-os/boltz-swap';

// Create an identity
const identity = MnemonicIdentity.fromMnemonic('your twelve word mnemonic phrase ...', { isMainnet: true });

// Initialize your Arkade wallet
const wallet = await Wallet.create({
  identity,
  arkServerUrl: 'https://arkade.computer',
});

// Initialize swaps (network auto-detected from wallet, SwapManager enabled by default)
const swaps = await ArkadeSwaps.create({ wallet });

[!NOTE] Upgrading from v1 StorageAdapter? See SwapRepository migration.

Receive Lightning

const result = await swaps.createLightningInvoice({ amount: 50000 });
console.log('Invoice:', result.invoice);
// SwapManager auto-claims when paid

Send Lightning

const result = await swaps.sendLightningPayment({ invoice: 'lnbc500u1pj...' });
console.log('Paid:', result.txid);
// SwapManager auto-refunds if payment fails

By default the call resolves only once the swap fully settles (transaction.claimed), i.e. after Boltz has swept the HTLC. To show an optimistic "sent" state as soon as the payment is in flight — like most Lightning wallets — pass waitFor: 'funded':

const result = await swaps.sendLightningPayment({
  invoice: 'lnbc500u1pj...',
  waitFor: 'funded',
});
// Resolves as soon as the lockup transaction is observed: the funds are
// committed and the swap is refundable from here. result.preimage is not
// reported on this path — the proof of payment is persisted to the stored
// swap once it settles. Monitoring continues in the
// background and keeps the stored swap up to date until a terminal status,
// but a late failure no longer rejects — keep the SwapManager enabled so
// auto-refunds are handled for you. With the SwapManager disabled, a late
// failure is only persisted as refundable and the funds stay locked until
// you recover them via restoreSwaps()/recoverSubmarineFunds().

The same milestone is exposed for a swap you already hold as waitForSwapFunded(pendingSwap), alongside waitForSwapSettlement(pendingSwap).

If the app restarts before the swap settles, the in-process monitoring is gone — call refreshSwapsStatus() on startup to reconcile: it polls every pending swap and persists the latest status, including the preimage for swaps that settled while the app was closed. Read the swap back from the repository (e.g. getSwapHistory()) to flip a pending UI row to "completed".

ARK to BTC

const result = await swaps.arkToBtc({
  btcAddress: 'bc1q...',
  senderLockAmount: 100000,
});
// SwapManager auto-claims BTC when ready

BTC to ARK

const result = await swaps.btcToArk({ receiverLockAmount: 100000 });
console.log('Pay to:', result.btcAddress, 'Amount:', result.amountToPay);
// SwapManager auto-claims ARK when ready

Listening for Updates

const manager = swaps.getSwapManager();

// Global listeners
manager.onSwapCompleted((swap) => console.log(`${swap.id} completed`));
manager.onSwapFailed((swap, error) => console.error(`${swap.id} failed`, error));
manager.onSwapUpdate((swap, oldStatus) => console.log(`${swap.id}: ${oldStatus} → ${swap.status}`));

// Wait for a specific swap
const result = await swaps.createLightningInvoice({ amount: 50000 });
const unsubscribe = manager.subscribeToSwapUpdates(result.pendingSwap.id, (swap, oldStatus) => {
  console.log(`${oldStatus} → ${swap.status}`);
});

// Or block until a specific swap completes
const { txid } = await manager.waitForSwapCompletion(result.pendingSwap.id);

Fees and Limits

// Lightning
const fees = await swaps.getFees();
const limits = await swaps.getLimits();

// Chain swaps
const chainFees = await swaps.getFees('ARK', 'BTC');
const chainLimits = await swaps.getLimits('ARK', 'BTC');

Swap History

const history = await swaps.getSwapHistory();
const pending = await swaps.getPendingReverseSwaps();

Advanced Usage

Chain Swap Amounts

When creating a chain swap, specify exactly one:

  • senderLockAmount: sender sends this exact amount, receiver gets less (amount - fees)
  • receiverLockAmount: receiver gets this exact amount, sender pays more (amount + fees)

Renegotiating Quotes

If the amount sent differs from expected:

const newAmount = await swaps.quoteSwap(pendingSwap.id);

Blocking on a Swap

Even with SwapManager, you can block until a specific swap completes:

const result = await swaps.createLightningInvoice({ amount: 50000 });
const { txid } = await swaps.waitAndClaim(result.pendingSwap);

Without SwapManager (Manual Mode)

If you disable SwapManager, you must manually monitor and act on swaps:

const swaps = await ArkadeSwaps.create({ wallet, swapManager: false });

const result = await swaps.createLightningInvoice({ amount: 50000 });
await swaps.waitAndClaim(result.pendingSwap); // blocks until complete

SwapManager Configuration

const swaps = await ArkadeSwaps.create({
  wallet,
  swapManager: {
    enableAutoActions: true,        // Auto claim/refund (default: true)
    autoStart: true,                // Auto-start on init (default: true)
    pollInterval: 30000,            // Failsafe poll interval (default)
    events: {
      onSwapCompleted: (swap) => {},
      onSwapFailed: (swap, error) => {},
      onSwapUpdate: (swap, oldStatus) => {},
      onActionExecuted: (swap, action) => {},
      onWebSocketConnected: () => {},
      onWebSocketDisconnected: (error?) => {},
    }
  },
});

Per-Swap UI Hooks

const result = await swaps.createLightningInvoice({ amount: 50000 });
const manager = swaps.getSwapManager();

const unsubscribe = manager.subscribeToSwapUpdates(
  result.pendingSwap.id,
  (swap, oldStatus) => {
    if (swap.status === 'invoice.settled') showNotification('Payment received!');
  }
);

Submarine Fund Recovery

If a Lightning payment fails and funds get stranded in a VHTLC, you can recover them manually:

// Scan all local swaps for recoverable funds
const candidates = await swaps.scanRecoverableSubmarineSwaps();
// candidates[i].status: "recoverable" | "pre_cltv" | "none" | "already_spent" | "invalid_swap"

// Recover all at once
const results = await swaps.recoverAllSubmarineFunds(candidates.map(c => c.swap));

// Or inspect / recover a single swap
const info = await swaps.inspectSubmarineRecovery(swap);
if (info.status === 'recoverable') {
  await swaps.recoverSubmarineFunds(swap);
}

[!NOTE] This only scans swaps stored in your local repository. It does not discover swaps that exist on Boltz but are missing locally.

Cleanup

// Manual
await swaps.dispose();

// Automatic (TypeScript 5.2+)
{
  await using swaps = await ArkadeSwaps.create({ wallet });
  // ...
} // auto-disposed

SwapRepository

Swap storage defaults to IndexedDB in browsers. For other platforms:

// SQLite (React Native / Node.js)
import { SQLiteSwapRepository } from '@arkade-os/boltz-swap/repositories/sqlite';

// Realm (React Native)
import { RealmSwapRepository, BoltzRealmSchemas } from '@arkade-os/boltz-swap/repositories/realm';

Custom implementations must set readonly version = 1 — TypeScript will error when bumped, signaling a required update.

[!WARNING] If you previously used the v1 StorageAdapter-based repositories, migrate data before use. migrateToSwapRepository copies legacy reverseSwaps and submarineSwaps collections from the old ContractRepository into the new SwapRepository. It writes its own swap-specific migration flag, so it is idempotent and safe to call on every startup — do not gate it on the wallet-side getMigrationStatus.

import { IndexedDbSwapRepository, migrateToSwapRepository } from '@arkade-os/boltz-swap'
import { IndexedDBStorageAdapter } from '@arkade-os/sdk/adapters/indexedDB'

const oldStorage = new IndexedDBStorageAdapter('arkade-service-worker', 1)
await migrateToSwapRepository(oldStorage, new IndexedDbSwapRepository())

Expo / React Native

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.

[!WARNING] Change since 0.3.30 — fix for #136.

Background task helpers moved from @arkade-os/boltz-swap/expo to @arkade-os/boltz-swap/expo/background. OS-level registration is no longer performed by ExpoArkadeSwaps.setup() — call it explicitly.

BeforeAfter
import { defineExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo"import { defineExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo/background"
background: { taskName, taskQueue, minimumBackgroundInterval, foregroundIntervalMs }background: { taskQueue, foregroundIntervalMs } + explicit await registerExpoSwapBackgroundTask(taskName, { minimumInterval })
dispose() unregistered the OS taskCall unregisterExpoSwapBackgroundTask(taskName) yourself

TypeScript callers get a compile error on the removed fields. JS callers must update manually — the old fields are silently ignored and the OS task will never run.

Prerequisites

  • Install Expo background task dependencies:
npx expo install expo-task-manager expo-background-task
npx expo install @react-native-async-storage/async-storage expo-secure-store
npx expo install expo-crypto
npx expo install expo-sqlite
  • For persistence on Expo, prefer the SQLite-backed repositories (@arkade-os/boltz-swap/repositories/sqlite and @arkade-os/sdk/repositories/sqlite) on top of expo-sqlite, or the Realm repositories on top of realm. There is no SDK-shipped IndexedDB helper for Expo.

  • Expo requires a crypto.getRandomValues() polyfill for cryptographic operations:

import * as Crypto from "expo-crypto";
if (!global.crypto) global.crypto = {} as any;
global.crypto.getRandomValues = Crypto.getRandomValues;

1) Define the background task (global scope)

TaskManager.defineTask() must be called at module scope before React mounts.

// App entry point (e.g., _layout.tsx) — GLOBAL SCOPE
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from "expo-secure-store";
import * as SQLite from "expo-sqlite";
import { SingleKey } from "@arkade-os/sdk";
import { AsyncStorageTaskQueue } from "@arkade-os/sdk/worker/expo";
import { SQLiteSwapRepository } from "@arkade-os/boltz-swap/repositories/sqlite";
import { defineExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo/background";

const swapTaskQueue = new AsyncStorageTaskQueue(AsyncStorage, "ark:swap-queue");

const swapDb = SQLite.openDatabaseSync("ark-swaps.db");
const swapRepository = new SQLiteSwapRepository({
  run: (sql, params) => swapDb.runAsync(sql, params ?? []),
  get: (sql, params) => swapDb.getFirstAsync(sql, params ?? []),
  all: (sql, params) => swapDb.getAllAsync(sql, params ?? []),
});

defineExpoSwapBackgroundTask("ark-swap-poll", {
  taskQueue: swapTaskQueue,
  swapRepository,
  identityFactory: async () => {
    const key = await SecureStore.getItemAsync("ark-private-key");
    if (!key) throw new Error("Missing private key in SecureStore");
    return SingleKey.fromHex(key);
  },
});

2) Set up ExpoArkadeLightning (component/provider)

Use an IWallet implementation that provides arkProvider and indexerProvider (for example ExpoWallet from @arkade-os/sdk/wallet/expo, or Wallet.create() with ExpoArkProvider / ExpoIndexerProvider).

import AsyncStorage from "@react-native-async-storage/async-storage";
import { ExpoWallet } from "@arkade-os/sdk/wallet/expo";
import { AsyncStorageTaskQueue } from "@arkade-os/sdk/worker/expo";
import { BoltzSwapProvider } from "@arkade-os/boltz-swap";
import { ExpoArkadeLightning } from "@arkade-os/boltz-swap/expo";
import { registerExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo/background";

// Used by ExpoWallet's background task (defined via @arkade-os/sdk/wallet/expo)
const walletTaskQueue = new AsyncStorageTaskQueue(AsyncStorage, "ark:wallet-queue");

const wallet = await ExpoWallet.setup({
  identity, // same identity used by identityFactory()
  arkServerUrl: "https://mutinynet.arkade.sh",
  storage: { walletRepository, contractRepository },
  background: {
    taskName: "ark-wallet-poll",
    taskQueue: walletTaskQueue,
    foregroundIntervalMs: 20_000,
    minimumBackgroundInterval: 15,
  },
});

const swapProvider = new BoltzSwapProvider({
  apiUrl: "https://api.boltz.mutinynet.arkade.sh",
  network: "mutinynet",
});

const arkLn = await ExpoArkadeLightning.setup({
  wallet,
  swapProvider,
  swapRepository, // must match the one used in defineExpoSwapBackgroundTask
  background: {
    taskQueue: swapTaskQueue, // must match the one used in defineExpoSwapBackgroundTask
    foregroundIntervalMs: 20_000,
  },
});

// Activate the OS scheduler (Expo Android/iOS only).
// Must use the same task name passed to defineExpoSwapBackgroundTask above.
await registerExpoSwapBackgroundTask("ark-swap-poll", { minimumInterval: 15 });

await arkLn.createLightningInvoice({ amount: 1000 });

// On logout / wallet reset / app teardown:
import { unregisterExpoSwapBackgroundTask } from "@arkade-os/boltz-swap/expo/background";
await arkLn.dispose();
await unregisterExpoSwapBackgroundTask("ark-swap-poll");

[!IMPORTANT] The OS-task helpers (defineExpoSwapBackgroundTask, registerExpoSwapBackgroundTask, unregisterExpoSwapBackgroundTask) live under @arkade-os/boltz-swap/expo/background. That subpath is the only module that imports expo-task-manager / expo-background-task; keeping it isolated lets react-native-web and Node consumers use /expo without those native packages.

Error Handling

With SwapManager, refunds are automatic — listen to onSwapFailed for notifications. Without it, handle errors manually:

import { isPendingSubmarineSwap, isPendingChainSwap } from '@arkade-os/boltz-swap';

try {
  await swaps.sendLightningPayment({ invoice: 'lnbc500u1pj...' });
} catch (error) {
  if (error.isRefundable && error.pendingSwap) {
    if (isPendingChainSwap(error.pendingSwap)) {
      await swaps.refundArk(error.pendingSwap);
    } else if (isPendingSubmarineSwap(error.pendingSwap)) {
      await swaps.refundVHTLC(error.pendingSwap);
    }
  }
}

Error types: InvoiceExpiredError, InvoiceFailedToPayError, InsufficientFundsError, NetworkError, SchemaError, SwapExpiredError, TransactionFailedError.

Type Guards

import {
  isPendingReverseSwap, isPendingSubmarineSwap, isPendingChainSwap,
  isChainSwapClaimable, isChainSwapRefundable,
} from '@arkade-os/boltz-swap';

Releasing

Package-local releases are disabled. Releases run from the monorepo root and are package-scoped: pnpm run release -- boltz-swap patch bumps and publishes just @arkade-os/boltz-swap with a @arkade-os/boltz-swap/<version> tag, against the currently published @arkade-os/sdk version. See the root README for full flags and pnpm run release -- --help.

License

MIT

版本与发布记录

@arkade-os/boltz-swap 共发布 125 个版本,首次发布于 2025/07/31;过去 12 个月发布了 92 个版本。

版本发布日期距今
0.3.67latest2026/08/132 个月前
0.3.662026/08/132 个月前
0.3.652026/08/122 个月前
0.3.642026/08/122 个月前
0.3.632026/08/102 个月前
0.3.622026/08/073 个月前

其他发布通道:alpha → 0.2.1-alpha.7,next → 0.3.0-next.8,rc → 0.3.54-rc.0。

关于 @arkade-os/boltz-swap 的常见问题

@arkade-os/boltz-swap 是什么?

A production-ready TypeScript package that brings Boltz submarine-swaps to Arkade.注意:该包已被作者标记为弃用——Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

如何安装 @arkade-os/boltz-swap?

在项目目录运行 npm install @arkade-os/boltz-swap,或使用 pnpm add / yarn add / bun add。

@arkade-os/boltz-swap 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arkade-os/boltz-swap 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@arkade-os/boltz-swap 可以商用吗?

@arkade-os/boltz-swap 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@arkade-os/boltz-swap 的下载量有多少?

根据 npm 官方下载统计,@arkade-os/boltz-swap 在 2026/10/07 – 2026/11/05 这 30 天内被下载 5,452 次,日均约 182 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arkade-os/boltz-swap 还在维护吗?

最新版本 0.3.67 发布于 2026/08/13(2 个月前);至今共发布 125 个版本,其中过去 12 个月发布了 92 个;首次发布于 2025/07/31。作者已将其标记为弃用,新项目建议寻找替代方案。