Manage Accounts
Switch between local accounts, create new ones, move stablecoins with pay push, and back up or restore the secret key.
pay can hold any number of accounts per network. One is the active account at a time; everything else sits idle in ~/.config/pay/accounts.yml until you switch.
The secret keys themselves live in the OS secure store (Apple Keychain on macOS, GNOME Secret Service on Linux, Windows Hello on Windows). accounts.yml only records the public key, network, and which backend the secret is stored in.
The default account
pay picks the account to sign with using a three-step rule:
- An explicit
--account <name>flag wins. - The
PAY_ACTIVE_ACCOUNTenvironment variable wins next. - Otherwise: the account with
active: truein~/.config/pay/accounts.ymlfor the current network. If no account is marked active,payfalls back to the first one alphabetically.
pay whoami # which account is active right now
pay account list # every account on every network, with balancespay whoami lists the active mainnet account and its non-zero stablecoin balances. pay account list is broader — it shows all accounts on all networks.
Create a new account
pay account new work
pay account new trading --backend keychain --force| Flag | Description |
|---|---|
<NAME> | Required. Account name — used in --account work. |
--backend | keychain, gnome-keyring, or windows-hello. Defaults to the OS native backend. |
--force | Replace an existing account with the same name. |
The new account's secret lives in the OS secure store; only the public key is written to accounts.yml. See Configuration → accounts.yml for the full schema.
Make an account the default
pay account default workSets active: true on work and clears it from every other account on the same network. Subsequent payment commands sign with work until you switch again or pass --account <other>.
Switching for a single call
You don't have to flip the default to send from a different account once:
pay --account trading push 5 work
pay --account trading whoami--account is a global flag, so it precedes the subcommand.
Move stablecoins between accounts
pay push (alias for pay send) transfers a stablecoin from the active account to any other Solana address — including one of your other local accounts.
pay push 1.25 trading # send 1.25 USDC to local account "trading"
pay push 50 <recipient-base58> # send to any Solana address
pay push 1 trading --currency USDC # be explicit about the stablecoin
pay push max trading # drain the active account
pay push 1 trading --fee-within # take the fee out of the 1 USDC, not on toppay resolves a recipient that matches a local account name to its public key. The transfer itself is a normal on-chain SPL transfer — there's no special path for "local-to-local."
Fees
pay push uses a fee-payer–backed flow: the pay-api co-signer pays the Solana network fee. You never need SOL in the sending account.
| What you pay | How |
|---|---|
| A small stablecoin processing fee (typically ≤ 0.0015 USDC). | Added on top of AMOUNT by default; subtracted from AMOUNT with --fee-within. |
| Recipient ATA rent, when the recipient has never held that stablecoin (~0.00204 SOL). | Sponsored by pay-api and surfaced as part of the same stablecoin fee. |
| Solana network fee (lamports). | Sponsored by pay-api. You pay nothing in SOL. |
The receipt looks like this:
Sent 1 USDC to 9pLk… (total paid: 1.0015 USDC, fee: 0.0015 USDC)
https://explorer.solana.com/tx/5Hk…pay push max automatically implies --fee-within so the wallet ends at exactly zero balance.
Picking the currency
When the active account holds multiple stablecoins and you omit --currency:
- One eligible balance →
payuses it silently. - More than one → an interactive picker lists each currency with its available balance.
- No TTY (running under an agent or with
NO_DNA=1) → the command errors and prints the eligible balances. Pass--currency USDCexplicitly to suppress the prompt.
Back up an account
To take an account off this machine — or just to have a recovery copy — export its secret to a JSON file you control.
An exported JSON file is the full private key. Anyone who reads it can spend the account's stablecoins. Don't email it, don't paste it into chat, don't commit it to git.
pay account export work
pay account export work ./work-backup.json
pay account export work - # write to stdoutThe default path is ./pay-account-<name>-<pubkey>.json. The file is a single JSON array of 64 unsigned bytes — the standard Solana keypair format:
[32, 144, 200, 5, 87, 12, …, 248, 64, 91] // 64 bytes totalBytes [0..32] are the Ed25519 secret key. Bytes [32..64] are the Ed25519 public key. The same format solana-keygen uses, so the file works with solana, solana-keygen recover, or any Solana SDK that takes a 64-byte JSON keypair.
Move the file to a password manager, a hardware-encrypted USB, or wherever you keep other secrets. Then delete the working copy:
shred -u ./work-backup.json # Linux
rm -P ./work-backup.json # macOSRestore an account
On any other machine, install pay (see Install), then:
pay account import work ./work-backup.json| Argument | Description |
|---|---|
<NAME> | The local name to register the account under. Doesn't have to match the original. |
<FILE> | Path to the exported JSON keypair. |
--backend | keychain, gnome-keyring, or windows-hello. Defaults to the OS native. |
pay account import writes the secret to the OS secure store and adds a row to accounts.yml with keystore: <backend> and the cached pubkey. After that the account behaves like any other — pay whoami, pay push, pay --account work … all work as normal.
Inspect the macOS Keychain entry directly
On macOS, every account's secret lives as a generic password under service pay.sh and account keypair:<name>. You can read it back without pay:
security find-generic-password -s "pay.sh" -a "keypair:work" -wThe output is a 128-character hex string — the same 64 bytes that pay account export would write, in hexadecimal instead of a JSON array. The first 64 hex characters (32 bytes) are the secret key; the last 64 are the public key.
If you'd rather poke around the GUI: open Keychain Access.app, set the keychain to login, and search for pay.sh. Each pay account appears twice:
| Service | Account | What it holds |
|---|---|---|
pay.sh | keypair:<name> | 64-byte Ed25519 keypair, hex-encoded. |
pay.sh | pubkey:<name> | 32-byte public key only, hex-encoded (cached for fast lookup). |
The entries are stored with the kSecAttrAccessibleWhenUnlockedThisDeviceOnly flag — they're tied to this Mac and do not iCloud-sync. Moving to another Mac requires the export/import flow above.
Convert the hex dump back to a JSON keypair
If you've got the hex from security and want a Solana-CLI-compatible JSON keypair file:
hex=$(security find-generic-password -s "pay.sh" -a "keypair:work" -w)
python3 -c "
import sys
hx = sys.argv[1]
print('[' + ','.join(str(int(hx[i:i+2], 16)) for i in range(0, len(hx), 2)) + ']')
" "$hex" > work-from-keychain.jsonThe resulting file is identical in shape to what pay account export writes.
Lose access to the Keychain
If the Keychain entry is gone but accounts.yml still lists the account, pay refuses to sign for it. The fix is the same in either direction:
- If you have the exported JSON:
pay account import <name> ./work-backup.json(replaces the broken entry). - If you have neither, the funds are gone — that's why the export step is the entire backup story.
Other backends
- GNOME Secret Service (Linux): inspect via
seahorse(the Keyring GUI) orsecret-tool lookup service pay.sh account "keypair:<name>". Same hex encoding as macOS. - Windows Hello: the secret is sealed to a Windows Credential. There's no plain shell command equivalent to
security find-generic-password; rely onpay account exportfor backups.
Remove an account
pay account remove work # mainnet, with confirmation
pay account remove dev --sandbox --yesPermanently deletes the OS-secure-store entry and the accounts.yml row. There's no undo unless you exported a backup first.