Sending Payments & Managing Outputs
Build the payout side: transfer, transfer_split and sweep_all, fees and priority, locked vs unlocked balance, change outputs, and hot/cold wallet design.
Receiving payments is only half of an integration. Sooner or later you have to send Monero back out — paying customer withdrawals on an exchange, issuing refunds, or sweeping deposits into cold storage. This lesson covers the payout side using monero-wallet-rpc: how to build and broadcast transactions, how Monero's output model affects your available balance, and how to design a sending system that is safe to run with real money and safe to retry when things go wrong.
Sending with transfer
The workhorse method is transfer. You hand it a destinations array, where each entry is an object with an address and an amount in atomic units (1 XMR = 1e12 atomic units — always work in atomic units to avoid floating-point errors). A single transfer call can pay many destinations at once, which is how you batch a round of withdrawals into one transaction.
priority(0–3: default, low, normal, high). A higher priority pays a higher fee so miners include your transaction sooner. For routine payouts, default or low is fine; raise it only when you need fast confirmation.get_tx_key: truetells the wallet to return the transaction's secret key. Store it — you'll need it later to generate a payment proof if a recipient disputes whether you paid.
A successful transfer returns the tx_hash, the fee charged, the amount sent, and (when requested) the tx_key. When a payout has so many destinations or input outputs that it won't fit in a single transaction, the call fails with "transaction would be too large." Use transfer_split instead: it takes the same parameters but splits the payment across multiple transactions automatically, returning arrays of hashes, fees, and keys.
Sweeping balances
Sometimes you don't want to send a specific amount — you want to move everything. sweep_all sends the entire balance of an account or subaddress to a single destination address. This is the tool for consolidating scattered deposits, sweeping incoming funds to cold storage, and processing full-balance withdrawals where the customer asks for "all of it." There's also sweep_dust, which gathers up tiny leftover outputs too small to be useful on their own and consolidates them.
Outputs and change
Monero spends whole outputs (the equivalent of Bitcoin's UTXOs). You can't spend "half" an output. When you send a payment, the wallet selects one or more input outputs whose combined value covers the amount plus the fee, and the remainder comes back to you as a brand-new change output. You never manage this by hand — the wallet does coin selection for you — but it matters for two reasons: your balance is made of discrete chunks, not a single pool, and a change output you just created is itself locked before you can spend it again.
Balance versus unlocked balance
This is the single most common source of payout bugs. get_balance returns both a balance and an unlocked_balance, and only unlocked funds can be spent. Every newly received output — including the change from your own transactions — is locked for 10 blocks (roughly 20 minutes) before it becomes spendable. If you try to pay out using funds that are still locked, the wallet returns "not enough unlocked money" even though the total balance looks sufficient.
For an exchange or processor this means you cannot pay out a deposit the instant it arrives. Either wait for the unlock, or maintain a float of already-unlocked funds in the hot wallet so withdrawals can be served immediately while fresh deposits mature in the background.
Fees
You do not set the fee directly. The wallet computes it from the daemon's get_fee_estimate and the chosen priority. Critically, Monero fees are based on the transaction's weight and size, not a percentage of the amount sent. A transaction with many inputs costs more than one with few inputs, regardless of how much XMR moves. This is why consolidating outputs periodically keeps future payouts cheap, and why batching many withdrawals into one transaction is more efficient than sending them individually.
Hot and cold wallet design
Never keep an exchange's entire reserve in an online wallet. The standard architecture splits funds:
- A hot wallet runs on
monero-wallet-rpc, holds only a limited float, and signs automatic payouts. - The bulk of funds lives in cold storage, offline and out of reach of your servers.
- You periodically
sweep_allaccumulated deposits from the hot wallet to a cold address, and refill the hot wallet from cold reserves only as needed.
If the hot wallet server is ever compromised, the attacker can only steal the small float — not the whole treasury. This is the same sweep-to-cold discipline a self-hosted store applies, scaled up to an automated system.
Building a robust payout pipeline
Real money is unforgiving, so engineer for failure:
- Record everything. Persist the
tx_hashandtx_keyfor every payout. The hash gives you idempotency and an audit trail; the key lets you prove payment later. - Make payouts idempotent. Tie each withdrawal request to a unique internal ID and check whether it already has a recorded
tx_hashbefore sending. A retry after a timeout must never double-pay. - Handle the known errors. "not enough unlocked money" means wait for unlock or top up the float; "transaction would be too large" means switch to
transfer_split; daemon-busy or still-syncing errors mean back off and retry rather than failing the withdrawal. - Confirm before marking done. A returned
tx_hashmeans broadcast, not finality — track confirmations before treating the payout as settled.
What the wallet handles for you
You orchestrate; the wallet does the cryptography. It manages key images (so an output can't be double-spent), performs ring and decoy selection — the ring size is fixed at 16 across the network — and produces the signatures. You never touch ring members or construct signatures yourself. Your job is to decide what to send, from which account, at what priority, and to make the surrounding bookkeeping bulletproof.
Next: Building a Light Wallet
Comments
Log in or create a free account to comment.
No comments yet — be the first.