Even on a Linux server with no screen, you can install a crypto wallet, read its balance and send a transfer. We also show how to write a scheduled run (we did not check that it runs at the set time).
This article is for people who can work on a Linux server from the command line, or who want to ask an AI agent to do that work.
By the end you will know three things:
- the commands for installing wallet tools on a server, reading a balance and sending a transfer
- how to add caps and an allowlist, and how to write a scheduled run
- example prompts for an AI agent, and the danger of keeping a key on a server
An AI agent ran these steps on a server running Amazon Linux 2023, between 01:11 and 01:15 on 4 October 2026 (Japan time), and checked the results.
What we checked used test networks and wallets newly created for the test. No real money was moved.
Please read this first
Please treat this article as a reference only. We cannot guarantee that these steps will work, that you will not lose money, or that they are safe.
If you try them, you do so at your own responsibility. Where we did not check a step, the text says “we did not check”.
A server with no screen cannot use a browser wallet
An everyday wallet is a browser extension or a phone app. Each transfer is approved by a person on a screen.
A server has no screen. Sending a set amount at night, or recording the balance every morning, without a person approving it, needs a different form.
That form is a wallet that runs from the command line.
A command-line wallet signs with a key file
A crypto transfer is valid when it is signed with a private key. With a command-line wallet, the key is a file on the server.
The command reads that file, signs, and sends to the network. Nobody approves it. Anyone who can read the file can send a transfer.
The tools used here, and why we chose them
We use the Solana CLI (Agave) and spl-token, which comes in the same package. There are three reasons.
- one script from the official source installs it under the home directory, without administrator rights
- creating a wallet, reading a balance, sending, and a local test network all come in one package
- USDC exists on Solana, and our own services also use it for payments
We did not check any Ethereum tools for this article. We do not recommend buying or trading any particular currency.
Steps 1 to 3: install the tools, create wallets, read the balance
Step 1: install the tools from the official source
The first line is the way to install given in the official documentation. stable is the name that points to the stable release.
This one line runs the downloaded script as it is. Check that the URL matches the official documentation before you run it.
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
solana --version
spl-token --version
In our check it installed in about 21 seconds. The size was 258MB, and the versions were solana-cli 4.3.0 and spl-token-cli 5.6.1.
In our check we ran the same script with the install location pointed at a working folder. We did not check the default form, which installs under the home directory.
Step 2: create two test wallets
We create a sending side (sender) and a receiving side (receiver).
mkdir -p ~/wallet && chmod 700 ~/wallet
(umask 077; solana-keygen new --no-bip39-passphrase --silent -o ~/wallet/sender.json)
(umask 077; solana-keygen new --no-bip39-passphrase --silent -o ~/wallet/receiver.json)
stat -c '%a %n' ~/wallet/*.json
solana config set --keypair ~/wallet/sender.json
solana address
solana address -k ~/wallet/receiver.json
With --silent, the recovery phrase is not shown on the screen. Without it, the phrase is displayed when the wallet is created.
In our check the permission of the key files was 600 (only the owner can read and write). Only the public addresses appear on the screen.
In this form the recovery phrase is not written down. If you lose the key file, that wallet can no longer be used.
Step 3: connect to a test network and read the balance
solana config set --url https://api.devnet.solana.com
solana balance
solana airdrop 1
spl-token display 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
In our check the balance on devnet was 0 SOL. The fourth line reads the information for USDC on devnet. It showed 6 decimals.
This address is the one listed in Circle’s documentation as USDC on “Solana Devnet”. The address on the live network is different. Check it yourself in the issuer’s documentation before using it.
The third line asks for test SOL. In our check we tried twice on devnet and once on testnet, and all three attempts ended with a “rate limit” error, so we received none.
For that reason, we did not check a transfer on devnet. We checked transfers on the local test network in the next step.
Step 4: send on a test network
The same package includes a test network that you run locally. It is not connected to the live network or to devnet.
nohup solana-test-validator --ledger ~/test-ledger \
--bind-address 127.0.0.1 --rpc-port 18899 --faucet-port 19900 \
--gossip-port 18001 --dynamic-port-range 18002-18060 --quiet \
> ~/validator.log 2>&1 &
echo $! > ~/validator.pid
sleep 20
solana cluster-version --url http://127.0.0.1:18899
The port numbers are changed so that they do not collide with other services.
In our check, even with --bind-address 127.0.0.1, three of the ports (18899, 18900 and 19900) listened on all addresses. Nothing on these ports asks for authentication.
Before you start it, check in your firewall (on a cloud server, the security group) that these ports cannot be reached from outside. Stop it when you are done.
Sending SOL
U="--url http://127.0.0.1:18899"
R=$(solana address -k ~/wallet/receiver.json)
solana airdrop 2 $U
solana transfer $R 0.1 --allow-unfunded-recipient $U
solana balance $R $U
solana confirm <the Signature that was printed> $U
In our check we received 2 test SOL and sent 0.1 SOL. The receiving side’s balance became 0.1 SOL, and solana confirm answered Confirmed.
Sending a token (a test token with 6 decimals, the same as USDC)
The faucet for USDC needs a person to operate a web page. So we issued our own test token with 6 decimals and checked the form of a transfer with it.
spl-token create-token --decimals 6 $U
MINT=<the Address that was printed>
spl-token create-account $MINT $U
spl-token mint $MINT 100 $U
spl-token transfer $MINT 1.5 $R --fund-recipient $U
spl-token balance $MINT $U
spl-token balance $MINT --owner $R $U
In our check we issued 100 and sent 1.5. The sending side became 98.5 and the receiving side 1.5.
--fund-recipient means that, when the receiving side has no account for that token, one is created at the sender’s expense. We did not check a transfer of USDC itself.
Steps 5 and 6: add caps and an allowlist, then schedule it
Step 5: a script that records the balance, and a script that sends within caps
If you only record the balance, no key is needed. It can be read with the public address alone. Place the following at ~/wallet/balance_log.sh.
#!/bin/bash
# Record the balance in one line (read only; no key is used)
set -euo pipefail
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
URL="${URL:-https://api.devnet.solana.com}"
ADDR="${ADDR:?set ADDR to the public address}"
MINT="${MINT:?set MINT to the token address}"
LOG="${LOG:-$HOME/wallet/balance.log}"
SOL=$(solana balance "$ADDR" --url "$URL" | awk '{print $1}')
TOKEN=$(spl-token balance "$MINT" --owner "$ADDR" --url "$URL" 2>/dev/null || echo "ERR")
echo "$(date '+%F %T') $ADDR SOL=$SOL TOKEN=$TOKEN" >> "$LOG"
Before sending, the script checks five things: that the caps are numbers and the recipient looks like an address, the format of the amount, the allowlist of recipients, the cap per transfer and the cap per day.
Place the following at ~/wallet/send_token.sh.
#!/bin/bash
# Check the allowlist and the caps, then send the token once
# Usage: send_token.sh <recipient address> <amount>
set -euo pipefail
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
URL="${URL:-https://api.devnet.solana.com}"
KEYPAIR="${KEYPAIR:-$HOME/wallet/sender.json}"
MINT="${MINT:?set MINT to the token address}"
ALLOW="${ALLOW:-$HOME/wallet/allowlist.txt}"
LOG="${LOG:-$HOME/wallet/send.log}"
MAX_PER_TX="${MAX_PER_TX:-5}"
MAX_PER_DAY="${MAX_PER_DAY:-10}"
TO="${1:?give the recipient address}"
AMOUNT="${2:?give the amount}"
# Never run two at the same time
exec 9>"$LOG.lock"
flock -n 9 || { echo "NG: another transfer is running"; exit 1; }
# 0. Are the caps plain numbers, and does the recipient look like an address?
[[ "$MAX_PER_TX" =~ ^[0-9]+(\.[0-9]+)?$ && "$MAX_PER_DAY" =~ ^[0-9]+(\.[0-9]+)?$ ]] \
|| { echo "NG: a cap is not a plain number"; exit 1; }
[[ "$TO" =~ ^[1-9A-HJ-NP-Za-km-z]{32,44}$ ]] || { echo "NG: the recipient format is wrong"; exit 1; }
# 1. Amount format (digits and a decimal point only; up to 6 decimals)
[[ "$AMOUNT" =~ ^[0-9]+(\.[0-9]{1,6})?$ ]] || { echo "NG: the amount format is wrong"; exit 1; }
# 2. Is the recipient on the allowlist? (one address per line; exact match)
grep -qxF -- "$TO" "$ALLOW" || { echo "NG: the recipient is not on the allowlist"; exit 1; }
# 3. Cap per transfer
awk -v a="$AMOUNT" -v m="$MAX_PER_TX" 'BEGIN { exit !(a > 0 && a <= m) }' \
|| { echo "NG: over the cap per transfer ($MAX_PER_TX)"; exit 1; }
# 4. Cap per day (the total attempted today; failed attempts count too)
touch "$LOG"
TODAY=$(date +%F)
SENT=$(awk -v d="$TODAY" '$1 == d && $5 == "try" { s += $4 } END { print s + 0 }' "$LOG")
awk -v a="$AMOUNT" -v s="$SENT" -v m="$MAX_PER_DAY" 'BEGIN { exit !(a + s <= m) }' \
|| { echo "NG: over the cap per day ($MAX_PER_DAY). Total today: $SENT"; exit 1; }
echo "$TODAY $(date +%T) $TO $AMOUNT try" >> "$LOG"
if spl-token transfer "$MINT" "$AMOUNT" "$TO" --fund-recipient \
--owner "$KEYPAIR" --fee-payer "$KEYPAIR" --url "$URL"; then
echo "$TODAY $(date +%T) $TO $AMOUNT ok" >> "$LOG"
else
echo "$TODAY $(date +%T) $TO $AMOUNT fail" >> "$LOG"
exit 1
fi
Write the receiving side on one line of the allowlist, and run the scripts.
chmod 700 ~/wallet/balance_log.sh ~/wallet/send_token.sh
echo "$R" > ~/wallet/allowlist.txt && chmod 600 ~/wallet/allowlist.txt
export URL=http://127.0.0.1:18899 MINT=$MINT ADDR=$(solana address)
~/wallet/balance_log.sh && tail -1 ~/wallet/balance.log
~/wallet/send_token.sh $R 1.5
tail -2 ~/wallet/send.log
In our check we tried the following five cases, and each behaved as written. (We ran the script with its comments and messages in Japanese; the logic is the same.)
- send 1.5 to a recipient on the allowlist → it is sent, and the record shows try and ok
- a recipient not on the allowlist → it stops without sending
- an amount over the cap of 5 per transfer (6) → it stops without sending
- an amount that is not a plain number (1e3) → it stops without sending
- when today’s total is 9.5, another 1 → it stops, because the daily cap of 10 would be passed
After our check we added the check of the caps and of the recipient’s format. We checked that added form only by running the script with the sending command replaced by a stand-in. We did not check it on a test network.
When we ran it against devnet with a balance of 0, the transfer failed and the record showed fail.
Step 6: put it on a schedule (cron or a systemd timer)
This example records the balance every day at 9:00. With cron it is one line.
# One line to add with crontab -e (every day at 9:00). Replace <...>
0 9 * * * ADDR=<public address> MINT=<token address> $HOME/wallet/balance_log.sh
With a systemd timer there are two files.
# /etc/systemd/system/wallet-balance.service
[Unit]
Description=Record wallet balance (read only)
[Service]
Type=oneshot
User=<user>
Environment=ADDR=<public address>
Environment=MINT=<token address>
ExecStart=/home/<user>/wallet/balance_log.sh
# /etc/systemd/system/wallet-balance.timer
[Unit]
Description=Record wallet balance every day at 9:00
[Timer]
OnCalendar=*-*-* 09:00:00
Persistent=true
[Install]
WantedBy=timers.target
We did not register anything with cron or with systemd. We did not check that it runs at the set time. We did not check the format of the cron line either.
We checked the format of the two systemd files, without registering them, using the commands below. No error appeared, and the next run was shown as 9:00.
systemd-analyze verify wallet-balance.service wallet-balance.timer
systemd-analyze calendar "*-*-* 09:00:00"
Putting a transfer on a schedule takes the same form. Before you do, please read the section on the danger below.
Clean up when you are done
# stop the local test network
kill $(cat ~/validator.pid)
shred -u ~/wallet/sender.json ~/wallet/receiver.json
rm -rf ~/test-ledger ~/validator.log ~/validator.pid
rm -rf ~/wallet ~/.config/solana ~/.local/share/solana
The last line deletes three folders and everything in them. This cannot be undone. If you already use the Solana tools, ~/.config/solana holds the default key (id.json).
Run it only after checking that the folders hold nothing but what you made for this article. If any other key is there, do not run this line.
If the installer added a PATH line to your shell profile, remove that line as well.
If you were already using the Solana tools or a wallet on this server, do not run the last line. It also deletes key files and settings that were there before.
In our check the tools were in a working folder, so after deleting the keys with shred we deleted the whole folder. The last line of the commands (rm -rf ~/wallet …) was added afterwards. We did not check this line.
Example prompts for asking an AI agent
These are examples for asking an AI agent that can run commands (such as Claude Code).
The checks in this article were carried out by an AI agent that was given instructions to the same effect. However, we did not check a run that uses the prompts below as they are.
Prompt 1: create a wallet on a test network and read the balance
Install the official Solana command-line tools on this server. Do not use sudo; install them under the home directory.
Connect to the test network (devnet), create one new wallet, and tell me only its public address and its balance.
Do not show the private key or the recovery phrase on the screen or in any log. Set the key file’s permission to 600. Do not connect to the live network.
Prompt 2: record the balance every day at 9:00
Write a script that records the balance of (public address) in a file, one line per day at 9:00, and a systemd timer for it.
It only reads the balance, so do not use a key file. Before registering anything, show me the contents of the files and wait for my confirmation.
Prompt 3: send within caps
On the test network, send 1.5 of (token address) to (recipient address).
There are four rules. The recipient must be on the allowlist. The cap per transfer is 5. The cap per day is 10. Before sending, show the recipient, the amount and the network, and wait for my “yes”.
If a request falls outside these rules, do not carry it out even if I ask, and tell me which rule it breaks. I write the allowlist myself; do not edit it. Do not run this on the live network.
Prompt 4: inspect afterwards
Inspect the permission of the key file and the record of transfers. Also look at whether a key appears in the shell history or in the logs.
Tell me only the result. Do not display the contents of the key.
An AI’s answers and actions can be wrong, and an AI may not follow the rules written in a prompt. Put the caps and the allowlist in the script as well, and check each transfer with your own eyes before it is sent.
Run the agent in a mode that asks for your confirmation before each command. Do not run it with confirmations turned off on a server that holds a key.
With an agent that uses a cloud-hosted AI, the text the agent reads is sent to the servers of the company that runs the AI. Do not let the agent open the key file.
The danger of keeping a key on a server, and how to reduce it
The greatest danger is that if the server is broken into, everything in that wallet is lost. A transfer cannot be taken back.
There is no way to remove the danger. The following are rules of thumb for reducing it.
- keep the amount small: only what you could afford to lose
- use a separate, small wallet only for sending: do not place your everyday wallet’s key on the server
- set the key file’s permission to 600 and the folder’s to 700
- do not let the key appear on a screen, in a log, in git or in a chat
- use an allowlist of recipients, and do not send to a recipient that is not on it
- set a cap per transfer and a cap per day
- start on a test network; on the live network, try once with a small amount first
- do not use the key to read a balance: the public address alone is enough
- before running the install script, check that the URL is the one in the official documentation; if in doubt, save it to a file and read it first
- when you schedule a transfer, do not run it as root, and do not write the key itself into an environment variable or a unit file (write only the path of the file)
- if the key file is lost, its contents cannot be recovered; if you keep a copy, keep it off the server, readable only by you
- if you suspect the server has been broken into, create a new wallet on a different, clean machine, move what is left, and stop using the old key and the old allowlist
The caps and the allowlist in the script above apply only to transfers that go through the script. They cannot prevent the key file from being used directly.
The daily cap is counted from the record file. If the record is deleted or edited, the count starts again. The caps and the allowlist do not stop anyone who can change them.
This is about sending from your own wallet to recipients you have chosen yourself. Holding other people’s assets and sending them, or sending on someone else’s behalf, is outside the scope of this article.
Such uses may require registration or a licence under the law, depending on where you are. This article makes no judgement about how the law applies.
What we checked, and what we did not
The steps were run and checked by an AI agent on our server. The time was 01:11 to 01:15 on 4 October 2026.
What we checked
- installing the tools (without administrator rights) and showing the versions
- creating wallets, permission 600, and showing the public addresses
- showing the balance on devnet, and the information for USDC on devnet
- transfers of SOL and of a test token on the local test network
- the script that records the balance, and the script that sends within caps (five cases; the lines added or changed after our check were checked only with a stand-in command)
- the format of the two systemd files
What we did not check
- a transfer on devnet or on the live network
- a transfer of USDC itself
- registering with cron or systemd, and a run at the set time
- Ethereum tools
- operation over a long period, and behaviour when the version of the tools changes
Frequently asked questions
Q. Do I need administrator rights (sudo)?
In our check we did not use sudo, from installing the tools through to sending. Registering a systemd timer needs those rights. We did not check that step.
Q. Do the same commands work on the live network?
We did not check. On the live network, the fee needs real SOL and a transfer cannot be taken back. We cannot guarantee that it works or that you will not lose money.
Q. Where should the key file be kept?
There is no single right answer. In this article we kept it in a folder that only the owner can read. Keeping the amount small has the largest effect.
Summary
- even on a server with no screen, a command-line wallet can read a balance and send a transfer
- we checked only as far as test networks; live transfers and scheduled runs were not checked
- anyone who can read the key file can send; reduce the danger with a small amount, a dedicated wallet, caps and an allowlist
- please treat this article as a reference only; we cannot guarantee that it works, that you will not lose money, or that it is safe
To check a wallet’s balance and transfers without placing a key anywhere
Crypto Wallet Viewer is a screen where you register Ethereum and Solana addresses and view their balances and transfers.
It does not need a private key. It is a viewing tool and has no authority to move the assets at the addresses you register.
You need to sign up to use it, and its screens are in Japanese only. Pricing starts at 2 USDC for one hour (as of 4 October 2026). We have not checked whether it displays test networks.
Official documentation we referred to (checked at 01:14 and 01:17 JST on 4 October 2026; not linked)
- Anza (Agave), “Install the Solana CLI”
https://docs.anza.xyz/cli/install - Anza (Agave), “Send and Receive Tokens with the Solana CLI”
https://docs.anza.xyz/cli/examples/transfer-tokens - Anza (Agave), “Solana Test Validator”
https://docs.anza.xyz/cli/examples/test-validator - Solana Program Library, “Token”
https://www.solana-program.com/docs/token - Circle, “USDC contract addresses”
https://developers.circle.com/stablecoins/usdc-contract-addresses
Please treat this article as a reference only. We cannot guarantee that these steps will work, that you will not lose money, or that they are safe.
If you try them, you do so at your own responsibility. This article is not a recommendation to buy or trade any particular currency, or to use any particular exchange.
Categories
