Developer Guide
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
Section titled “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
Section titled “Before you begin”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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Testing notes”- Use an external destination when testing. If Voltage finds a matching internal receive, it can complete the payment as a
shortcircuitwithout 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.
