Skip to main content
Pay Lightning invoices using any token tradeable on Flashnet. The SDK automatically swaps your tokens to BTC via the AMM and uses the BTC to pay the invoice, all in a single function call.
Available in SDK version 0.4.1+. Rollback feature and improved defaults in 0.4.2+.

How It Works

  1. Decode invoice - Extract the payment amount from the BOLT11 invoice
  2. Estimate fees - Get Lightning routing fee estimate from Spark
  3. Find best pool - Select the pool with optimal liquidity for your token → BTC swap
  4. Execute swap - Swap tokens to BTC via Flashnet AMM
  5. Pay invoice - Use the received BTC to pay the Lightning invoice
The entire flow is bundled into two simple functions: getPayLightningWithTokenQuote() for quotes and payLightningWithToken() for execution.

Getting a Quote

Always get a quote first to see the total cost:
const quote = await client.getPayLightningWithTokenQuote(
  "lnbc50u1p55pfnq...", // BOLT11 invoice
  "btkn1xgrvjwey..."    // Your token address (bech32m or hex)
);

console.log('Invoice amount:', quote.invoiceAmountSats, 'sats');
console.log('Lightning fee:', quote.estimatedLightningFee, 'sats');
console.log('Total BTC needed:', quote.btcAmountRequired, 'sats');
console.log('Token required:', quote.tokenAmountRequired);
console.log('AMM fee:', quote.estimatedAmmFee);
console.log('Price impact:', quote.priceImpactPct, '%');

Quote Response

FieldDescription
invoiceAmountSatsOriginal invoice amount
estimatedLightningFeeLightning routing fee in sats
btcVariableFeeAdjustmentExtra sats for AMM bit masking (0-63)
btcAmountRequiredTotal BTC needed (invoice + LN fee + adjustment)
tokenAmountRequiredTotal token amount needed (includes AMM fees)
estimatedAmmFeeAMM LP + integrator fees in token units
executionPriceToken per sat exchange rate
priceImpactPctPrice impact percentage
poolIdThe pool selected for the swap

Executing Payment

After reviewing the quote, execute the payment:
const result = await client.payLightningWithToken({
  invoice: "lnbc50u1p55pfnq...",
  tokenAddress: "btkn1xgrvjwey...",
  // All options below are optional with sensible defaults
});

if (result.success) {
  console.log('Payment successful!');
  console.log('Lightning payment ID:', result.lightningPaymentId);
  console.log('Token spent:', result.tokenAmountSpent);
  console.log('BTC received from swap:', result.btcAmountReceived, 'sats');
  console.log('AMM fee paid:', result.ammFeePaid);
} else {
  console.log('Payment failed:', result.error);
}

Payment Options

OptionTypeDefaultDescription
invoicestringrequiredBOLT11-encoded Lightning invoice
tokenAddressstringrequiredToken to spend (bech32m or hex)
maxSlippageBpsnumber500Max AMM slippage in basis points (5%)
maxLightningFeeSatsnumberfrom quoteMax Lightning routing fee (uses estimatedLightningFee from quote)
integratorFeeRateBpsnumber0Optional integrator fee
transferTimeoutMsnumber30000Timeout waiting for swap completion (30s)
rollbackOnFailurebooleanfalseSwap BTC back to token if Lightning payment fails
useExistingBtcBalancebooleanfalsePay immediately using existing BTC, don’t wait for swap

Payment Result

FieldDescription
successWhether payment completed successfully
poolIdThe pool used for the swap
tokenAmountSpentActual token amount spent
btcAmountReceivedBTC received from swap
swapTransferIdFlashnet swap transfer ID
lightningPaymentIdLightning payment identifier (if successful)
ammFeePaidAMM fee paid in token units
lightningFeePaidLightning routing fee paid in sats
errorError message (if failed)

Complete Example

import { SparkWallet } from "@buildonspark/spark-sdk";
import { FlashnetClient } from "@flashnet/sdk";

async function payInvoiceWithUSDB() {
  // Initialize wallet
  const { wallet } = await SparkWallet.initialize({
    mnemonicOrSeed: "your-mnemonic",
    options: { network: "MAINNET" },
  });

  // Initialize Flashnet client
  const client = new FlashnetClient(wallet);
  await client.initialize();

  // Get wallet balance and find USDB token
  const balance = await client.getBalance();
  let usdbAddress: string | null = null;
  let usdbBalance = 0n;

  for (const [tokenPubkey, tokenData] of balance.tokenBalances.entries()) {
    const symbol = tokenData.tokenInfo?.tokenSymbol || "";
    if (symbol.toUpperCase().includes("USDB")) {
      usdbAddress = tokenData.tokenInfo?.tokenAddress || tokenPubkey;
      usdbBalance = tokenData.balance;
      console.log(`Found USDB: ${usdbBalance} (${tokenData.tokenInfo?.tokenDecimals} decimals)`);
    }
  }

  if (!usdbAddress) {
    throw new Error("No USDB token found in wallet");
  }

  // Lightning invoice to pay
  const invoice = "lnbc50u1p55pfnqpp5...";

  // Step 1: Get quote
  const quote = await client.getPayLightningWithTokenQuote(invoice, usdbAddress);
  
  console.log(`Paying ${quote.invoiceAmountSats} sats invoice`);
  console.log(`Requires ${quote.tokenAmountRequired} USDB (smallest units)`);
  console.log(`Price impact: ${quote.priceImpactPct}%`);

  // Check sufficient balance
  if (usdbBalance < BigInt(quote.tokenAmountRequired)) {
    throw new Error(`Insufficient USDB: need ${quote.tokenAmountRequired}, have ${usdbBalance}`);
  }

  // Step 2: Execute payment (with optional rollback on failure)
  const result = await client.payLightningWithToken({
    invoice,
    tokenAddress: usdbAddress,
    rollbackOnFailure: true, // Recover tokens if Lightning payment fails
  });

  if (result.success) {
    console.log('✅ Payment successful!');
    console.log('Lightning payment ID:', result.lightningPaymentId);
    console.log('Token spent:', result.tokenAmountSpent);
    console.log('BTC received:', result.btcAmountReceived, 'sats');
  } else {
    console.log('❌ Payment failed:', result.error);
  }

  await wallet.cleanupConnections();
}

Fee Breakdown

The total token cost includes multiple fee components:
  1. Lightning routing fee - Network fee to route the payment (~0.1-0.5%)
  2. AMM LP fee - Liquidity provider fee (typically 20 bps)
  3. Integrator fee - Optional fee for integrators (if specified)
  4. BTC variable fee - Up to 63 sats adjustment for AMM leaf management
All fees are bundled into tokenAmountRequired in the quote—what you see is what you pay.

Fee Calculation Example

For a 5000 sat invoice with 20 bps LP fee:
Invoice amount:     5,000 sats
Lightning fee:     +   14 sats  
BTC adjustment:    +   42 sats (rounds to next multiple of 64)
─────────────────────────────
Total BTC needed:   5,056 sats

Token input calculated via AMM:
  amount_in_effective = (reserve_token × 5056) / (reserve_btc - 5056)
  amount_in = amount_in_effective × (1 + 0.002)  // 20 bps fee

Minimum Amounts

Flashnet enforces minimum amounts for swaps:
  • BTC output: 1,000 sats minimum
  • Token input: Varies by token (typically 1 USD equivalent)
Invoices below the BTC minimum will throw an error with a clear message.

Instant Payment with Existing BTC

If you already have BTC in your wallet, you can pay the invoice immediately without waiting for the swap to complete:
const result = await client.payLightningWithToken({
  invoice,
  tokenAddress,
  useExistingBtcBalance: true, // Pay instantly if BTC available
});
When enabled, this:
  1. Checks if you have enough BTC to cover the invoice + Lightning fee
  2. If yes: executes swap → pays immediately → swap BTC arrives async
  3. If no: falls back to normal flow (waits for swap to complete)
This gracefully falls back to waiting if BTC balance is insufficient—no error is thrown.

Rollback on Failure

If the Lightning payment fails after the swap succeeds, you can enable automatic rollback to swap the BTC back to your original token:
const result = await client.payLightningWithToken({
  invoice,
  tokenAddress,
  rollbackOnFailure: true, // Swap BTC back to token if Lightning fails
});

if (!result.success) {
  // Check if funds were rolled back
  if (result.error?.includes('Funds rolled back')) {
    console.log('Payment failed but tokens were recovered');
  } else if (result.error?.includes('BTC remains in wallet')) {
    console.log('Payment failed, BTC needs manual handling');
  }
}
Rollback incurs an additional swap fee and may result in slightly fewer tokens due to price movement.

Error Handling

try {
  const result = await client.payLightningWithToken({
    invoice,
    tokenAddress,
  });
  
  if (!result.success) {
    console.error('Payment failed:', result.error);
  }
} catch (error) {
  if (error.message.includes('Invoice amount too small')) {
    console.error('Invoice below minimum (1000 sats)');
  } else if (error.message.includes('Insufficient liquidity')) {
    console.error('Pool lacks liquidity for this swap');
  } else if (error.message.includes('Insufficient token balance')) {
    console.error('Not enough tokens in wallet');
  } else {
    console.error('Unexpected error:', error);
  }
}

Next Steps

  • Swaps - Learn more about AMM swaps
  • Fees - Understand the fee structure
  • Pools - View available liquidity pools