Swaps
Swaps let a node-backed Voltage wallet send bitcoin on one rail using funds from the other. Your application creates a normal payment and sets flow_preference to swap; Voltage coordinates the Lightning payment and hashlocked on-chain transaction.
See the Demo
See swaps in action with the Voltage Swap demo. The demo runs on Mutinynet and shows both on-chain to Lightning and Lightning to on-chain swaps.
You can use swaps to:
- Pay an on-chain Bitcoin address using your wallet's Lightning liquidity.
- Pay a Bolt11 invoice using your wallet's on-chain balance.
Both directions use the same Payments API you use for other sends.
Before you begin
Node-backed wallets only: Swaps are designed for BTC wallets backed by your Lightning node. Credit-backed wallets already support both Lightning and on-chain payments natively from the same wallet, so they do not need swaps and cannot set flow_preference: "swap".
You need:
- A BTC node-backed wallet with swaps enabled.
- An API key with access to its organization and environment.
- Enough balance on the funding rail for the payment and network fees.
We recommend testing first on Mutinynet with low-value funds and a destination controlled by a separate wallet or node.
Create a swap
Send a POST request to:
/v1/organizations/{organization_id}/environments/{environment_id}/paymentsGenerate the payment ID in your application and retain it before sending the request. Payment creation returns 202 Accepted, and you will use that ID to track the swap.
On-chain to Lightning
The following example uses 50,000 sats from the node-backed wallet's on-chain balance to pay a Bolt11 invoice:
curl --request POST \
--url "https://voltageapi.com/v1/organizations/$ORGANIZATION_ID/environments/$ENVIRONMENT_ID/payments" \
--header "content-type: application/json" \
--header "x-api-key: $VOLTAGE_API_KEY" \
--data '{
"id": "YOUR_PAYMENT_UUID",
"wallet_id": "YOUR_NODE_BACKED_WALLET_ID",
"currency": "btc",
"type": "bolt11",
"flow_preference": "swap",
"data": {
"payment_request": "lntbs...",
"amount": {
"currency": "btc",
"amount": 50000000
},
"max_fee": {
"currency": "btc",
"amount": 2000000
}
}
}'BTC amounts in this request are expressed in millisatoshis: 50,000 sats is 50000000 millisats. max_fee is a ceiling, not a fee quote.
Voltage publishes the hashlocked on-chain funding transaction and waits for the required confirmation count—at least one confirmation—before paying the Lightning invoice. It then claims the on-chain contract and completes the payment.
Lightning to on-chain
To pay an on-chain address from Lightning liquidity, change the payment type and destination data:
{
"id": "YOUR_PAYMENT_UUID",
"wallet_id": "YOUR_NODE_BACKED_WALLET_ID",
"currency": "btc",
"type": "onchain",
"flow_preference": "swap",
"data": {
"address": "tb1p...",
"amount": {
"currency": "btc",
"amount": 50000000
},
"max_fee": {
"currency": "btc",
"amount": 2000000
}
}
}Voltage pays a private hold invoice from the customer node, funds the hashlocked on-chain output, and claims it to the requested address. The shared payment hash binds the two payment rails together.
Track the payment
Poll the payment using the ID you generated:
curl \
--url "https://voltageapi.com/v1/organizations/$ORGANIZATION_ID/environments/$ENVIRONMENT_ID/payments/YOUR_PAYMENT_UUID" \
--header "x-api-key: $VOLTAGE_API_KEY"Continue until the top-level status is completed or failed. Immediately after creation, a read can briefly return 404 while the payment reaches the read model; retry it as a processing state.
Swap progress appears in data.outflows entries with type: "swap":
Direction | Progress steps |
|---|---|
On-chain → Lightning | source_submitted → target_paid → source_claimed |
Lightning → on-chain | source_payment_submitted → target_submitted → target_claimed |
Receipts on these outflows provide transaction IDs, payment hashes, amounts, and confirmation details that you can surface in your application. The optional payment_breakdown contains the final principal and network-fee totals when available.
Testing notes
- Use an external destination when testing. If Voltage finds a matching internal receive, it can complete the payment as a shortcircuit without creating a swap.
- A completed on-chain-to-Lightning payment means the final claim was broadcast or recovered. Its block confirmation can arrive later.
- flow_preference: "swap" does not fall back to a direct payment.
- The contracts include timelocked refund paths, but the current public API does not automatically construct or broadcast refunds. A failure after funds are locked can require help from Voltage support.
