pay.sh docs
Using pay

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:

  1. An explicit --account <name> flag wins.
  2. The PAY_ACTIVE_ACCOUNT environment variable wins next.
  3. Otherwise: the account with active: true in ~/.config/pay/accounts.yml for the current network. If no account is marked active, pay falls back to the first one alphabetically.
pay whoami                # which account is active right now
pay account list          # every account on every network, with balances

pay 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
FlagDescription
<NAME>Required. Account name — used in --account work.
--backendkeychain, gnome-keyring, or windows-hello. Defaults to the OS native backend.
--forceReplace 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 work

Sets 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 top

pay 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 payHow
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 balancepay uses 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 USDC explicitly 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 stdout

The 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 total

Bytes [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      # macOS

Restore an account

On any other machine, install pay (see Install), then:

pay account import work ./work-backup.json
ArgumentDescription
<NAME>The local name to register the account under. Doesn't have to match the original.
<FILE>Path to the exported JSON keypair.
--backendkeychain, 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" -w

The 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:

ServiceAccountWhat it holds
pay.shkeypair:<name>64-byte Ed25519 keypair, hex-encoded.
pay.shpubkey:<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.json

The 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:

  1. If you have the exported JSON: pay account import <name> ./work-backup.json (replaces the broken entry).
  2. 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) or secret-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 on pay account export for backups.

Remove an account

pay account remove work          # mainnet, with confirmation
pay account remove dev --sandbox --yes

Permanently deletes the OS-secure-store entry and the accounts.yml row. There's no undo unless you exported a backup first.

On this page