Wallet Fleet Design: Leases, Roles and Balance Floors
A fleet is not a list of keypairs. It is a resource pool with a lease protocol, and the quality of that protocol decides whether your engine can ever tell you where its money went.
Component sheet
- Component class
- Stateful resource pool
- Inputs
- Lease requests from the scheduler, funding events
- Outputs
- A leased account with its believed state
- Hardest boundary
- Believed balance versus on-chain balance
A wallet fleet is a resource pool that answers one question for the rest of an engine: which account can safely sign the next transaction right now. To answer it correctly the fleet must track, per account, a believed balance, a count of in-flight transactions, a role, a floor and a lease state. Anything less and the engine is guessing.
Most first implementations model the fleet as an array of keypairs and a round-robin index. That works until the first slow confirmation, at which point the index comes back to an account that still has a transaction in flight, the engine signs a second one, and both land. Nothing crashed and nothing logged an error. The run simply moved twice the value it was told to move, from an account whose balance now disagrees with the ledger.
A fleet is a resource pool, not a list
The mental model to import is a database connection pool, not a list of users. A connection pool hands out a resource under a lease, forbids concurrent use of the same resource, reclaims leases on timeout, and quarantines resources that misbehave. Every one of those behaviours has a direct analogue in a signing fleet, and the analogy is close enough that borrowing the vocabulary saves you from reinventing it badly.
The important difference is that a broken database connection costs you a retry, while a mishandled signing account costs you value on chain. That asymmetry justifies being far more conservative than a connection pool would be: leases are longer, quarantine is stickier, and the pool prefers to run short-handed over reusing an account whose state it is unsure about.
Roles and tiers
Not every account in a fleet does the same job, and collapsing them into one undifferentiated pool removes your ability to limit blast radius. Three roles cover almost every design, and the boundaries between them are also the boundaries you would put a spending limit on.
| Role | Holds | Signs | Blast radius if compromised |
|---|---|---|---|
| Treasury | The run budget, offline where possible | Funding transfers only, rarely | The whole budget |
| Distributor | One tranche at a time | Fan-out and sweep transfers | One tranche |
| Executor | Its floor plus working balance | Swaps, and nothing else | One account's working balance |
The engine's scheduler should only ever see executors. If the builder can request a lease on the distributor because both live in the same pool, then a bug in route selection can move a tranche instead of a swap size, and the separation was decorative. Enforce the role at the lease call, not in a comment.
There is a cost to this structure and it is worth stating: three roles means three funding hops, three sets of balances to reconcile, and a slower start to every run. A single-tier fleet is genuinely simpler. It is also a design where one leaked key empties everything, which is why the tiering survives in every serious implementation despite the overhead.
The lease protocol
The lease is the fleet's entire public surface. Getting its states right is most of the work, and there are only five of them.
state: available -> leased -> settling -> available
\-> quarantined -> (manual) -> available
\-> drained -> (refill) -> available
lease(role, min_balance) -> Lease | None
picks an account where state = available
and role matches
and believed_balance >= min_balance + floor
sets state = leased, records leased_at, in_flight += 1
settle(lease, outcome)
landed -> apply cost, state = available
expired -> state = available, in_flight -= 1
unknown -> state = quarantined, keep in_flight
reap()
any lease older than lease_ttl -> state = quarantined Three rules make that protocol safe. First, only settle with evidence moves an account back to available; a timeout alone moves it to quarantine. Second, the believed balance is decremented at lease time by the maximum the transaction could spend, not at settle time by what it did spend, so the pool never over-commits an account while a result is pending. Third, quarantine is a terminal state for the automated path. A human or a reconciliation job releases it, because the entire point is that the engine does not know what happened.
Trade-off: conservative leases cost throughput
Holding an account out of rotation for the full uncertainty window means a fleet of ten accounts might behave like a fleet of six during a congestion event, and the run slows down exactly when you least want it to. That is the price of never signing twice from an account whose state is unknown.
The tempting optimisation is a shorter lease timeout. It is also the single most reliable way to produce duplicate execution, because the timeout is a statement about your patience and not about whether the transaction landed. If you need more throughput, add accounts rather than shortening the window.
Balance floors and the refill rule
Every executor needs a floor: an amount below which it is not eligible for a lease even though it still holds lamports. The floor exists because a Solana transaction charges a base fee per signature and, if it touches a token account that does not exist yet, pays for that account's rent-exempt reserve as well. An account funded to exactly one swap size fails on those extras, and the failure looks like a route problem rather than a funding problem.
The refill rule should be stated as a policy rather than implemented ad hoc: when a believed balance drops below the floor plus one swap size, the account moves to drained and is queued for a top-up from the distributor. Batch those top-ups. Refilling one account at a time produces a transfer for every few swaps, and transfers are transactions, which means fees, confirmations and log noise that has nothing to do with the run.
What a fleet locks up: rent arithmetic
Rent exemption is the part of fleet design that surprises people, because it is capital that is neither spent nor available. On Solana, an account must hold a minimum lamport balance proportional to its data size in order to be exempt from rent collection. A token account is a fixed size, so its exemption minimum is a constant you can look up rather than estimate.
The arithmetic below is illustrative and deliberately uses a placeholder for the constant, because the exact lamport figure is something you should read from the cluster you are actually using rather than from an article. Call the token account exemption R and the native account exemption N.
- Each executor is a system account holding lamports, so it must sit above
Nto persist. With 20 executors, that is20 x Nlocked before a single swap. - Each executor that holds the traded token needs an associated token account, adding
20 x R. If the run touches three different tokens, that becomes60 x R. - Add the working balance: 20 executors at a floor of 0.02 SOL plus a working balance of 0.1 SOL is 2.4 SOL that must be in the fleet before the first swap, on top of the rent.
- Subtract what returns. Closing token accounts at shutdown returns their exemption lamports to a destination you choose. Closing executors returns theirs. Rent is locked capital, not a fee, provided the shutdown procedure actually runs.
The practical consequence is that fleet size has a capital cost that grows linearly and is invisible in any per-swap accounting. A hundred-account fleet across three tokens is a meaningful amount of SOL tied up in account minimums, and it is the number most often left out when people compare running an engine themselves against using a Solana volume bot platform where the accounts and their minimums are somebody else's balance sheet.
Derivation, labels and the account map
Fleets are usually derived from a single seed along a deterministic path, which means the whole pool can be regenerated from one backup. That is a genuine operational advantage and a genuine single point of failure, and both halves need to be conscious. If regenerating the fleet requires one secret, then losing that secret loses every account, and compromising it compromises every account.
Whatever the derivation, the fleet needs an account map: a stable label per account, the public key, the role, the derivation index, the date it entered service and the date it was retired. Label the account in every log line rather than the public key. Labels survive rotation, stay readable in a table, and keep an operator from pattern-matching base58 strings at two in the morning. The public key belongs in the record for verification, not in the human-facing field.
Retirement deserves a policy of its own. An account that has been quarantined twice should be retired rather than repaired, because the cost of a stale account in rotation is unbounded and the cost of a replacement is one funding transfer. Record the retirement rather than deleting the row; a fleet's history is the only thing that lets you audit an old run.
Reconciling believed state against the chain
The fleet's believed balances drift from reality continuously, and the drift is not a bug to eliminate but a quantity to measure. Every landed transaction the fleet did not attribute, every fee it estimated rather than read, and every external transfer into an executor moves the two apart.
The reconciliation job is simple in shape: for each account, fetch the on-chain balance, compare against believed, and record the delta with a reason where one can be inferred. Run it at the start of a run, at the end, and on a timer in between. What matters is what you do with a non-zero delta, and there are only three defensible responses.
- Delta explained by a known pending transaction: leave it, and record that the explanation was found.
- Delta unexplained but small and in your favour: accept the chain as truth, overwrite believed, log the correction with the amount.
- Delta unexplained and against you, or larger than one swap size: pause the run for that account, quarantine it, and reconcile by hand before anything else signs from it.
The third case is the one that justifies the whole apparatus. An unexplained outflow is either a duplicate execution or a compromised key, and both of those get worse with every additional transaction. An engine that keeps trading through an unexplained delta is an engine that will report a plausible-looking run summary about an incident.
Sizing the fleet
Fleet size is derived, not chosen. The inputs are the release rate you want, the confirmation time you observe including its tail, and the number of accounts you are willing to have quarantined at once without stalling. A useful way to arrive at a number is to work forwards from the slow case rather than the average one.
If the scheduler wants a release every 20 seconds and a swap resolves in 8 seconds on a good day but 45 seconds in a congested window, then during that window each account is unavailable for more than twice the release interval. Three accounts keep the pace. Add the accounts you expect to be quarantined, add the ones sitting below their floor waiting for a top-up, and a realistic minimum is closer to six or eight. Doubling that is cheap in operational terms and expensive in rent, which is exactly the trade-off to make deliberately.
Larger fleets also change the character of your reconciliation. Ten accounts can be checked by eye; two hundred cannot, and at that size the reconciliation job stops being a safety net and becomes load-bearing infrastructure. Growing a fleet is therefore not a linear scaling decision, and the note on scaling a run treats the thresholds where behaviour changes.
Fleet design review checklist
Run these questions against a design before it signs anything with value behind it.
- Is concurrent use of one account structurally impossible, or merely unlikely because of how the loop is written?
- Does a lease timeout quarantine the account, or does it return it to the pool?
- Is the believed balance debited at lease time by the maximum possible spend?
- Can an executor sign anything other than a swap, and is that restriction enforced at the lease call?
- Is there a floor per account, and does it account for the base fee and any token account creation?
- Does the account map record labels, roles, entry and retirement dates, and is the label what appears in logs?
- Does the shutdown procedure close token accounts and sweep the exemption lamports back?
- Does an unexplained negative delta stop that account, rather than producing a warning nobody reads?
A fleet that passes all eight is not automatically well designed, but a fleet that fails any of them has a known way to lose money, and the failure will show up as a reconciliation gap rather than as an error message. Pair this note with the scheduler, since pacing and fleet size are two ends of the same constraint.
Questions this note gets asked
How many wallets does a volume engine need?
Enough that no account is asked to sign again before its previous transaction has resolved, plus headroom for the slow tail and for accounts you retire mid-run. That is a function of confirmation time and release rate, not a fixed number, and any tool quoting a wallet count without those two inputs is quoting a marketing figure rather than a design.
Why does each wallet need a minimum balance rather than just enough for the swap?
Because a Solana account holding tokens must be rent exempt, and the swap itself needs lamports for the base fee and any priority fee. An account funded to the exact swap size will fail on the fee, on the token account creation, or on both, and the failure surfaces as a confusing simulation error rather than an obvious empty balance.
Can the same wallet sign two transactions at once?
Technically yes, since Solana does not use sequential nonces for ordinary transactions the way account-nonce chains do. Practically, allowing it destroys your ability to reason about the account, because two in-flight transactions can land in either order or partially, and the fleet no longer knows what balance to believe. The lease protocol exists to forbid it.
Do lamports locked as rent come back?
Yes, when the account is closed the rent-exempt lamports are returned to whichever account you nominate as the destination. That is why closing token accounts belongs in the shutdown procedure: a fleet that is never closed leaves a long tail of small balances stranded across accounts nobody is monitoring.
Should the fleet component hold private keys directly?
It needs signing capability, which is not the same as holding raw key material in the same process. The cleaner boundary is a signer interface the fleet calls, with the key material behind it, so that swapping to a different custody arrangement later does not require touching the lease logic. This note describes the pool, not the custody design.
What happens when a leased account gets stuck?
The lease expires, the account is quarantined rather than returned to the pool, and the scheduler is given a different account so the run keeps its pace. Quarantine is important: returning a possibly-in-flight account to the pool is exactly how the same value gets sent twice, and the cost of holding one account out of rotation is trivial by comparison.
Filed under Architecture by The Engine Room Desk. Arithmetic on this page is labelled illustrative and built from protocol constants or values you supply yourself. How the desk sources and corrects a note is set out in the editorial policy.