Send a payment

2 min read
Guides
Web SDK

Send over Lightning

Wavelength uses a send swap to route a Lightning payment from your Ark balance. Call prepare({ invoice }) from useWalletPrepareSend() to get a fee quote and a single-use sendIntentId. Pass the result to sendPrepared() from useWalletSend() to execute.

The two-step flow lets you show the user the fee before committing. If the user cancels, discard the quote and call nothing. For a one-step path, call send({ invoice }) from useWalletSend() instead.

The quote returned by prepare() expires at expiresAtUnix. If the user waits too long before confirming, call prepare() again to get a fresh quote before calling sendPrepared(). Check feeKnown before displaying expectedFeeSat: when feeKnown is false the fee is only an estimate, and warning may contain additional detail to show the user.

send.tsx
import { useWalletPrepareSend, useWalletSend } from '@lightninglabs/wavelength-react';
function SendLightning() {
const { prepare, prepareData: quote, resetPrepare } = useWalletPrepareSend();
const { sendPrepared } = useWalletSend();
const doPrepare = async (bolt11: string) => {
// prepare returns a fee quote and sendIntentId; prepareData holds it after.
await prepare({ invoice: bolt11 });
};
const confirm = async () => {
if (!quote) return;
// sendPrepared dispatches the payment.
await sendPrepared(quote);
resetPrepare();
};
if (quote) {
return (
<div>
<p>
Fee: {quote.expectedFeeSat} sats
{!quote.feeKnown && ' (estimate)'}
</p>
{quote.warning && <p role="alert">{quote.warning}</p>}
<button onClick={confirm}>Confirm payment</button>
<button onClick={resetPrepare}>Cancel</button>
</div>
);
}
return <button onClick={() => doPrepare('lnbc...')}>Pay invoice</button>;
}

Send on-chain (cooperative leave)

An on-chain send performs a cooperative leave: the Ark server co-signs a transaction that moves your VTXO value to a plain Bitcoin address. This is faster and cheaper than a unilateral exit because the round happens in one confirmation.

Pass onchainAddress and amountSat to send() from useWalletSend(). The returned SendResult includes the activity entry and actualAmountSat.

send.tsx
import { useWalletSend } from '@lightninglabs/wavelength-react';
function SendOnChain() {
const { send, sendPending } = useWalletSend();
const handleSend = async (address: string, amountSat: number) => {
await send({
onchainAddress: address,
amountSat,
});
};
return (
<button onClick={() => handleSend('bc1q...', 50_000)} disabled={sendPending}>
Send on-chain
</button>
);
}

To drain the wallet to an address, set sweepAll instead of amountSat. The daemon snapshots the live VTXO set at prepare time and spends exactly that set, so quote and dispatch cannot disagree about what “all” means.

sweep.tsx
await send({ onchainAddress: address, sweepAll: true });

Handle errors

send and sendPrepared from useWalletSend() throw a WavelengthError on failure and set the hook’s sendError, which is always Error | null. Check err.code for a machine-readable reason. Display err.message to the user.

Errors thrown during prepare (from useWalletPrepareSend()) are safe to retry. Errors thrown during sendPrepared may indicate the payment was already sent; check the activity feed before retrying.

send.tsx
import { useWalletSend, WavelengthError } from '@lightninglabs/wavelength-react';
function SafeSend() {
const { send, sendError, resetSend } = useWalletSend();
const pay = async (bolt11: string) => {
resetSend();
try {
await send({ invoice: bolt11 });
} catch (err) {
if (err instanceof WavelengthError) {
console.error('Payment failed:', err.code, err.message);
}
}
};
return (
<>
<button onClick={() => pay('lnbc...')}>Pay</button>
{sendError && <p role="alert">{sendError.message}</p>}
</>
);
}