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
- Decode invoice - Extract the payment amount from the BOLT11 invoice
- Estimate fees - Get Lightning routing fee estimate from Spark
- Find best pool - Select the pool with optimal liquidity for your token → BTC swap
- Execute swap - Swap tokens to BTC via Flashnet AMM
- 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
| Field | Description |
|---|
invoiceAmountSats | Original invoice amount |
estimatedLightningFee | Lightning routing fee in sats |
btcVariableFeeAdjustment | Extra sats for AMM bit masking (0-63) |
btcAmountRequired | Total BTC needed (invoice + LN fee + adjustment) |
tokenAmountRequired | Total token amount needed (includes AMM fees) |
estimatedAmmFee | AMM LP + integrator fees in token units |
executionPrice | Token per sat exchange rate |
priceImpactPct | Price impact percentage |
poolId | The 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
| Option | Type | Default | Description |
|---|
invoice | string | required | BOLT11-encoded Lightning invoice |
tokenAddress | string | required | Token to spend (bech32m or hex) |
maxSlippageBps | number | 500 | Max AMM slippage in basis points (5%) |
maxLightningFeeSats | number | from quote | Max Lightning routing fee (uses estimatedLightningFee from quote) |
integratorFeeRateBps | number | 0 | Optional integrator fee |
transferTimeoutMs | number | 30000 | Timeout waiting for swap completion (30s) |
rollbackOnFailure | boolean | false | Swap BTC back to token if Lightning payment fails |
useExistingBtcBalance | boolean | false | Pay immediately using existing BTC, don’t wait for swap |
Payment Result
| Field | Description |
|---|
success | Whether payment completed successfully |
poolId | The pool used for the swap |
tokenAmountSpent | Actual token amount spent |
btcAmountReceived | BTC received from swap |
swapTransferId | Flashnet swap transfer ID |
lightningPaymentId | Lightning payment identifier (if successful) |
ammFeePaid | AMM fee paid in token units |
lightningFeePaid | Lightning routing fee paid in sats |
error | Error 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:
- Lightning routing fee - Network fee to route the payment (~0.1-0.5%)
- AMM LP fee - Liquidity provider fee (typically 20 bps)
- Integrator fee - Optional fee for integrators (if specified)
- 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:
- Checks if you have enough BTC to cover the invoice + Lightning fee
- If yes: executes swap → pays immediately → swap BTC arrives async
- 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