dvmkitdocs

Wallets

Where your agent's money lives, the four ways to put money in, and how to get it back out.

Your agent's money lives in a wallet on your own machine, not in an account we hold for you. You put a small amount in, your agent spends from it, and the balance is the hard ceiling on what it can spend: there is no card behind the wallet to run up. Fund it in dollars, euros, or pounds, and hold the money as dollar stablecoins or as Bitcoin, whichever you already use. Whatever your agent doesn't spend stays yours, and you can move it back out.

Where the money lives

dvm keeps everything in one directory on the machine it runs on, ~/.dvm/, created mode 0700. No part of your wallet exists on a dvmkit server. Nobody can freeze it, and nobody can restore it for you either.

FileWhat it holds
identities.jsonYour signing keypairs, which are how a provider knows you
config.jsonSettings, plus the private keys for the Tempo and x402 rails once you connect them
wallet.jsonYour ecash balance, as Cashu proofs locked to a public key only this wallet controls
wallet.mnemonicThe twelve words dvm wallet init generated
config.json.bakYour settings as they stood before the last change, so a mistake can be undone. Replaced by every change
config.json.pinnedThe same, from before you last cleared your settings with dvm config reset. Kept indefinitely, until you restore it or delete it
config-audit.jsonlWhich settings changed and when. Field names only, never a value

All of them are written mode 0600. The two copies of config.json hold whichever rail keys the live file held when they were taken, with one exception that matters: disconnecting a wallet removes its key from both copies in the same write. They undo a settings mistake, not a disconnect you meant. The twelve words go to that file rather than to your screen: default JSON output omits them, and omits the derived spending key with them, so an agent that logs or relays stdout cannot leak your seed by accident. Pass --show-secret only when automation genuinely needs the raw words, and expect the nested secrets block it returns to carry a do-not-relay hint.

Putting money in

There are four ways, and dvm wallet setup walks all of them. Pick whichever matches how you already hold money: you do not need to know anything about Bitcoin to use Tempo or x402, and nothing asks you to think in satoshis. Choosing between them, and actually moving money in from an exchange, a Lightning wallet, or a standing start, is the funding guide; this page is what each route means once you are on it.

First choose the customer mode: pay the current job per-call, or fund prepaid credit for repeat work. Cashu, x402, and Tempo are rails underneath those modes rather than separate products, and a reusable stablecoin channel, a balance held in a contract that the provider draws from as you spend, only holds and settles prepaid credit. The fourth setup path is a Lightning wallet you connect once, with a cap set inside it, so it can top up ecash and eligible prepaid credit for you.

The Lightning float

The float is a Lightning wallet you already own, connected over NWC, with a spending cap you set inside that wallet. Once it is connected your agent refills itself: no invoice to forward, no human standing in the loop, no 3am interruption.

What it pays for

Two things. It buys ecash top-ups, paying a mint's invoice so tokens land in your wallet. And at DVMs that take Lightning it funds prepaid credit, paying the DVM's own invoice with no mint anywhere in the path.

The second removes a dependency rather than adding a feature. Without it, every satoshi bound for a DVM has to transit a mint first, so a mint outage stops your funding even when your wallet and the DVM are both perfectly healthy.

The policy your agent can't reach

This is the only spending policy in the system your agent cannot touch. It lives in your wallet's own database, and the wallet refuses payments outside that policy. dvm also checks the invoice principal against its local budgets before paying, but NIP-47 pay_invoice has no field for a caller-chosen routing-fee ceiling and does not say whether the wallet policy counts fees. The wallet chooses the Lightning route and fee, so do not treat its policy as a hard total-debit cap unless the wallet states that inclusion. Set both controls. If no wallet policy was recorded at connection time, dvm warns before it pays.

On the primary JSON path that pre-payment warning is a structured stderr event with code: "float_budget_unstated", operation: "credit_funding" or "pocket_refill", the invoice principal_sats, routing_fee_ceiling_sats: null, and routing_fee_inclusion: "unknown". Human mode carries the same warning as prose.

Which wallet works

Two requirements rule out most Lightning wallets. It has to be always on, because the agent funds itself whenever it runs low and a wallet asleep on your phone will not answer. And it has to cap spending on the connection itself, so the limit lives somewhere your agent cannot edit. That second one is weaker than it looks if the wallet runs on the same machine as the agent.

Two mostly meet both, with a catch each. Alby Hub is self-custodial, with per-connection budgets by day, week, month or year, and per-method permissions. Self-host it for anything you depend on: Alby's hosted cloud has had multi-hour outages, including on Nostr Wallet Connect itself. Coinos takes about two minutes to set up and enforces its budgets properly, but it is custodial and publishes no status page; our own monitoring has seen multi-day outages go unannounced. Treat it as capped pocket money, not your main float.

Three categories that do not: phone wallets, which sleep with the phone; browser ecash wallets, which only exist while the tab is open; and throwaway wallets that delete themselves after a period of inactivity, taking the balance with them. dvm wallet connect warns when it recognises one, but the check is a heuristic: a clean result means "no known problem", not "verified suitable".

Connecting it

dvm wallet pair is the normal path, for when your agent runs somewhere else or you are talking to it through a chat window. It returns a short-lived private link, you compare a session code against the page, and your browser encrypts the connection string before it goes anywhere, so the CLI is the only thing that can read it.

dvm wallet connect takes the connection string directly, and is only appropriate when you and the CLI share one trusted machine. Pipe it in with printf 'nostr+walletconnect://...' | dvm wallet connect, or point --file at a file, or run the command with no argument and it prompts with the input hidden. Passing it as an argument still works and warns: a spending credential on a command line is already in your shell history and in ps, where anything reading that back keeps a copy.

Either way, never paste an NWC connection string into a chat. It is a spending credential.

After payment, dvm reports principal_sats, routing_fee_sats, and wallet_debit_sats. A wallet may omit fees_paid; in that case the last two fields are null, not zero, and the wallet's own history is the authoritative total. Payment preimages stay internal and are not part of ordinary CLI output.

Ecash over Lightning

Ecash is the rail that carries the small stuff. A mint issues tokens against Bitcoin it holds, your wallet takes them locked to its own key, and paying a DVM is handing over a token rather than opening a channel or waiting on a chain.

Two honest things about it. Mints are custodial, so hold what you are willing to lose there and no more. And it is the only rail here that can carry a half-cent payment at all, which is why the float is pointed at it.

Fund it with dvm wallet fund and one of two things happens. With a float connected, the float pays the mint's invoice and the tokens appear. Without one, the command prints an invoice and a scannable QR for a person to pay by hand, which is the pre-float behaviour and stays available under --no-float.

Why locking matters

Your ecash is not a number in a mint's database with your name against it. It is a set of proofs that only a key in wallet.json can spend. That is why a mint can go down without your balance going anywhere, and why it cannot hand your money to someone else.

Back up more than the words

A twelve-word backup is the familiar story, and here it is incomplete.

dvm wallet restore rebuilds a spendable balance from the mnemonic by re-deriving the secrets behind your ecash. That works for proofs your wallet minted deterministically. Some mints still serve a first-generation keyset (the batch of signing keys a mint issues tokens under) that dvmkit will not derive against, following the January 2026 keyset-collision disclosure, and at those mints your wallet falls back to random secrets for that mint alone. Those proofs are real money and they spend normally. The twelve words will not rebuild them.

So back up the directory, not just the words. wallet.json is the file that actually holds the balance, and the Tempo and x402 keys sit in config.json, outside the mnemonic entirely.

Spread across mints

Holding a balance at more than one mint buys real redundancy, not just diversification. When a payment's mint cannot serve it, whether it is unreachable, has no usable keyset, has switched swaps off, or simply refuses, the spend moves to the next mint the DVM accepts that holds enough, instead of failing the call.

Nothing about a mint is remembered between calls, so one that was down five minutes ago gets reached for again on the next payment with nothing for you to reset. dvm wallet mint-list shows the mints you have configured and whether each is answering right now, and dvm wallet mint-health checks one is reachable before you commit funds to it. The funding guide names the mints worth starting from.

Dollars on Tempo

Pay in USDC.e, a dollar-pegged stablecoin, on the Tempo network. No Bitcoin anywhere in this path, and the network's own fees are paid in the same asset, so you never need a second token just to move the first.

dvm wallet tempo-connect

That generates a keypair and returns the deposit address it controls. The key is written into config.json, which means it is yours, and also that it is not covered by your twelve-word backup.

Buy USDC.e, not USDT0

Tempo carries more than one dollar-denominated stablecoin, and this rail settles in exactly one of them: USDC.e, at 0x20C000000000000000000000b9537d11c60E8b50. USDT0 is a different token at a different contract. dvm can neither see it nor spend it, so a wallet full of it reads as $0.00 on every surface.

The routes into USDC.e (a direct exchange withdrawal on the Tempo network, or a bridge from USDC held elsewhere) are in the funding guide. Whichever you take, read the ticker at the last step: USDT0 sits beside USDC.e on the same withdraw screens, and a bridge is only safe when what arrives is USDC.e itself.

If a transfer confirmed on chain and your balance still reads $0.00, that is the wrong-dollar case rather than a lost transfer. Swapping to USDC.e on Tempo costs a fraction of a cent, and both dvm doctor and dvm wallet show name the accepted asset and the remedy rather than reporting a bare zero.

Dollars on Base (x402)

Pay in USDC on Coinbase's Base network, the widest-supported of the dollar routes and one withdrawal away from most major exchanges. The exchange routes into it are in the funding guide.

dvm wallet x402-connect

That generates a keypair and returns the address to fund. Bringing your own EVM key instead is --key-file <path>, which keeps it out of your shell history. Either way the key lands in config.json, which means it is yours, and also that it sits outside your twelve-word backup.

x402 is an open standard and not ours, so the same wallet pays any service that accepts it.

The key is also the only thing that can withdraw from a channel it opened, so replacing it is not a settings change. A second x402-connect over a connected wallet exits with x402_wallet_exists; --force authorizes the swap, and even then both --force and x402-disconnect refuse while the current wallet still holds channel funds, naming the channel to empty first.

Custom chain endpoints

Both on-chain rails read your balance through a public endpoint that ships bundled with dvm, one per chain. Those endpoints are free and shared, which means rate-limited: a busy one starts refusing reads while your money sits there, funded and perfectly spendable. It shows up as a balance that will not load rather than a balance of zero, and dvm doctor and dvm wallet balance name the endpoint that failed alongside the variable that moves it.

Both commands name the endpoint on a read that worked, too, and say whether it was yours or the bundled one. That is the case worth watching: an endpoint you set and then forgot, left pointing at another chain or a testnet, answers with a believable $0.00 that looks exactly like a wallet you never funded.

Point that variable at any endpoint you control, your own node or a commercial provider, and re-run.

VariableMoves the endpoint for
DVM_X402_RPC_URLthe x402 balance read and the timed channel exit (dvm wallet x402-withdraw-initiate / -finalize), on whichever chain that wallet is connected to
DVM_TEMPO_RPC_URLthe Tempo balance read, the channel-state checks behind dvm wallet balance and dvm credit drain, and the on-chain channel exit (dvm wallet tempo-exit)

Leaving both unset is the normal case and what most callers want: the bundled endpoint is used. Both names start with DVM_, the prefix on what you set on your own machine, as against DVMKIT_ for what someone running a service sets on a server.

What they do not move

Never a payment. Each moves its rail's balance read, the channel state it reads on-chain, and its on-chain channel exit — the commands that sign a transaction over the endpoint you name. Neither changes where a rail settles, so an endpoint pointed at a different network than the wallet actually spends on will misreport your money — and fail the exit — rather than redirect a payment.

DVM_X402_RPC_URL is also a single value rather than one per chain, so moving that wallet to another chain means updating the variable too. A stale one fails with both the endpoint and the chain it was asked about named, which is usually enough to spot what happened.

It stays scoped to the chain the wallet is pinned to, which matters for the two commands that reach a channel on the chain it was opened on: the safety check that runs before a key is replaced or disconnected, and the timed exit, which keeps working on an older channel after you re-pin the wallet. Both read a channel on a different chain through that chain's bundled endpoint instead. Sending it to the pinned chain's endpoint would be worse than useless — the settlement contract has the same address on every chain, so the read would find a real contract, fail to find the channel, and report it as empty. If a bundled endpoint is why you set the variable in the first place, those commands can fail on such a channel; they name the chain, and nothing is signed or moved.

Treat a printed endpoint as a secret

Commercial providers put your API key in the URL itself, and dvm output is written to be relayed onward by an agent. dvm prints such an endpoint host-first with credential-shaped parts of the path masked, as https://polygon-mainnet.infura.io/v3/:hex. Long, varied URL-safe tokens are hidden alongside hex, UUID, numeric and base58 identifiers. That covers the path credentials used by providers such as Alchemy, Infura and QuickNode.

The masking works by shape rather than by knowing which provider issued a segment. Check any endpoint you see printed before pasting that output somewhere public.

Getting value back out

Cashing out ecash

dvm wallet cash-out <lightning-address> melts your proofs (turns your ecash back into Lightning) and pays a Lightning address you control. Ask for --amount and that is what arrives, with routing and mint fees drawn from the wallet on top; ask for --all and the fees come out of the balance instead. --dry-run quotes the fee without paying anything, and --max-fee refuses a mint that would cost more than you are willing to spend.

Balances are held per mint, so a wallet spread across several cashes out as one Lightning payment per mint, richest first, each reporting its own outcome. One mint refusing does not stop the others.

This is also the operation that keeps working when a mint is in trouble. A mint in recovery commonly stops issuing new tokens while continuing to honour existing ones, and nothing in the cash-out path needs it to issue.

Everything else

Tempo and x402 need no equivalent for the balance sitting free in the wallet, because you hold the private key: that money moves with any wallet you import it into, on your own terms and with no dvmkit-side step. Money parked at a DVM as prepaid credit comes back through dvm credit drain, covered on the credits page.

Money inside an open settlement channel is the exception, and importing the key does not free it. A channel is a deposit sitting in a contract, so the key that opened it is the only key that can move it, and it moves by one of two routes. The cooperative one is dvm credit drain, where the provider countersigns and the unspent remainder comes straight back. The unilateral one does not need the provider to be reachable at all: dvm wallet x402-withdraw-initiate then dvm wallet x402-withdraw-finalize once the channel's exit window has run, and dvm wallet tempo-exit, which requests the close and then withdraws after a short protocol grace period. Both print the window and the figure you can expect, and both are paid for by you rather than the provider, in that chain's own fees. On Tempo those fees are paid in the channel's own dollar token; on Base they are ETH, a second asset the exit needs a small amount of, and the one moment this rail asks you to hold anything besides USDC. The provider may still claim value you already authorized while a window runs, so the payout can land below the balance you saw going in.

Stablecoin prepaid credit therefore uses channels by default. A provider can explicitly accept a one-payment Tempo charge or x402 exact authorization into credit instead. No contract holds the unused part then, so the provider must send every refund by hand. The credit menu exposes that opt-in before your agent signs anything, and the same one-payment methods stay available for individual calls when the opt-in is absent.

This is also why dvm wallet x402-disconnect refuses while a channel still holds funds, with x402_wallet_in_use. Removing the key would leave that money in the channel with nothing on your machine able to reach it. Empty the channel first, by either route, and the disconnect goes through.

One rule to keep in mind: if a payout's outcome is ever left unknown, from a crash or a connection lost mid-payment, the proofs stay held and surface as pending rather than being guessed at either way. They reconcile against the mint on the next run. Do not retry that mint until they have.

Where to go next

  • Budgets: every limit on what your agent can spend, and which of them it cannot raise.
  • Payments: how a single job gets quoted and paid.
  • Credits: the prepaid balances that make sub-cent jobs economical.
  • dvm CLI reference: every wallet command, flag, and JSON shape.

On this page