Skip to main content
Move funds between Arkade and the Lightning Network through Boltz submarine swaps. ArkadeSwaps handles reverse swaps (Lightning to Arkade) and submarine swaps (Arkade to Lightning), with optional background monitoring via SwapManager.

Installation

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

Initialization

ArkadeSwaps.create() auto-detects the network from your wallet and creates a BoltzSwapProvider automatically. SwapManager is enabled by default for background claim/refund.
import { Wallet } from '@arkade-os/sdk';
import { ArkadeSwaps } from '@arkade-os/boltz-swap';

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

// Network and swap provider auto-detected from wallet
const swaps = await ArkadeSwaps.create({ wallet });
You can provide a custom BoltzSwapProvider if needed:
import { BoltzSwapProvider } from '@arkade-os/boltz-swap';

const swapProvider = new BoltzSwapProvider({
  network: 'bitcoin',
  // apiUrl defaults based on network
});

const swaps = await ArkadeSwaps.create({
  wallet,
  swapProvider,
});

Receiving Lightning payments

Create a Lightning invoice via a reverse swap (Lightning to Arkade). When the invoice is paid, SwapManager auto-claims the funds into your wallet.
const result = await swaps.createLightningInvoice({
  amount: 50000, // sats
  description: 'Payment to my Arkade wallet', // optional
});

console.log('Invoice:', result.invoice);
console.log('Amount:', result.amount);
console.log('Expiry:', result.expiry);
console.log('Payment hash:', result.paymentHash);
console.log('Preimage:', result.preimage);

Waiting for payment

With SwapManager enabled (default), claiming happens automatically. You can block until the swap completes:
const { txid } = await swaps.waitAndClaim(result.pendingSwap);
console.log('Received! Transaction ID:', txid);

Sending Lightning payments

Pay a Lightning invoice via a submarine swap (Arkade to Lightning). sendLightningPayment creates the swap, sends funds, and waits for settlement in one call. If the payment fails, it auto-refunds.
const paymentResult = await swaps.sendLightningPayment({
  invoice: 'lnbc500u1pj...',
});

console.log('Amount:', paymentResult.amount);
console.log('Preimage:', paymentResult.preimage);
console.log('Transaction ID:', paymentResult.txid);

Decoding invoices

import { decodeInvoice } from '@arkade-os/boltz-swap';

const invoice = decodeInvoice('lnbc500u1pj...');

console.log('Amount:', invoice.amountSats, 'sats');
console.log('Description:', invoice.description);
console.log('Payment hash:', invoice.paymentHash);
console.log('Expiry:', invoice.expiry);

Fees and limits

const fees = await swaps.getFees();
console.log('Submarine fee:', fees.submarine.percentage, '%');
console.log('Reverse fee:', fees.reverse.percentage, '%');

const limits = await swaps.getLimits();
console.log('Min:', limits.min, 'sats');
console.log('Max:', limits.max, 'sats');

SwapManager

SwapManager runs in the background by default, auto-claiming reverse swaps and auto-refunding failed submarine swaps.

Listening for updates

const manager = swaps.getSwapManager();

manager.onSwapCompleted((swap) => {
  console.log(`Swap ${swap.id} completed`);
});

manager.onSwapFailed((swap, error) => {
  console.error(`Swap ${swap.id} failed:`, error);
});

manager.onSwapUpdate((swap, oldStatus) => {
  console.log(`${swap.id}: ${oldStatus} -> ${swap.status}`);
});

Per-swap subscriptions

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

const unsubscribe = manager.subscribeToSwapUpdates(
  result.pendingSwap.id,
  (swap, oldStatus) => {
    console.log(`${oldStatus} -> ${swap.status}`);
  }
);

Configuration

const swaps = await ArkadeSwaps.create({
  wallet,
  swapManager: {
    enableAutoActions: true,  // auto claim/refund (default: true)
    autoStart: true,          // start on init (default: true)
    pollInterval: 30000,      // failsafe poll interval in ms
  },
});
To disable SwapManager entirely:
const swaps = await ArkadeSwaps.create({
  wallet,
  swapManager: false,
});

// You must manually monitor swaps
const result = await swaps.createLightningInvoice({ amount: 50000 });
const { txid } = await swaps.waitAndClaim(result.pendingSwap);

Storage

Swaps are persisted via SwapRepository. The default is IndexedDbSwapRepository (browser). Other backends:
// SQLite (React Native / Node.js)
import { SQLiteSwapRepository } from '@arkade-os/boltz-swap/repositories/sqlite';

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

const swaps = await ArkadeSwaps.create({
  wallet,
  swapRepository: new SQLiteSwapRepository(),
});

Querying swaps

const history = await swaps.getSwapHistory();
const pendingReverse = await swaps.getPendingReverseSwaps();
const pendingSubmarine = await swaps.getPendingSubmarineSwaps();

Cleanup

// Manual cleanup
await swaps.dispose();

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

Error handling

With SwapManager enabled, refunds are automatic. Listen to onSwapFailed for notifications. Without SwapManager, handle errors manually:
import {
  SwapError,
  SchemaError,
  NetworkError,
  SwapExpiredError,
  InvoiceExpiredError,
  InvoiceFailedToPayError,
  InsufficientFundsError,
  TransactionFailedError,
  PreimageFetchError,
  TransactionLockupFailedError,
  TransactionRefundedError,
  isPendingSubmarineSwap,
} from '@arkade-os/boltz-swap';

try {
  await swaps.sendLightningPayment({
    invoice: 'lnbc500u1pj...',
  });
} catch (error) {
  if (error instanceof InvoiceExpiredError) {
    console.error('Invoice expired. Request a new one.');
  } else if (error instanceof InvoiceFailedToPayError) {
    console.error('Provider failed to pay the invoice.');
  } else if (error instanceof InsufficientFundsError) {
    console.error('Not enough funds:', error.message);
  } else if (error instanceof NetworkError) {
    console.error('Network issue:', error.message);
  } else if (error instanceof SchemaError) {
    console.error('Invalid API response.');
  } else if (error instanceof SwapExpiredError) {
    console.error('Swap expired.');
  } else if (error instanceof TransactionFailedError) {
    console.error('Transaction failed.');
  } else if (error instanceof TransactionLockupFailedError) {
    console.error('Lockup transaction failed.');
  } else if (error instanceof TransactionRefundedError) {
    console.error('Transaction already refunded.');
  } else if (error instanceof PreimageFetchError) {
    console.error('Payment settled but preimage unavailable.');
  } else if (error instanceof SwapError) {
    console.error('Swap failed:', error.message);
  } else {
    console.error('Unknown error:', error);
  }

  // Manual refund if applicable
  if (error.isRefundable && error.pendingSwap && isPendingSubmarineSwap(error.pendingSwap)) {
    await swaps.refundVHTLC(error.pendingSwap);
  }
}