Skip to content

Instantly share code, notes, and snippets.

@crazyoptimist
Last active September 7, 2026 17:12
Show Gist options
  • Select an option

  • Save crazyoptimist/232febf49ad0df9f039f9789b91129f1 to your computer and use it in GitHub Desktop.

Select an option

Save crazyoptimist/232febf49ad0df9f039f9789b91129f1 to your computer and use it in GitHub Desktop.
Using `cast` (Foundry) as a Hot Wallet — ETH, USDT-ERC20, USDT-BEP20

Using cast (Foundry) as a Hot Wallet — ETH, USDT-ERC20, USDT-BEP20

cast only works with EVM-compatible chains, which covers Ethereum and BNB Smart Chain (ETH, USDT-ERC20, USDT-BEP20). TRON (USDT-TRC20) is not EVM-compatible and cannot be used with cast.

Security note: A keystore/hot wallet on a general-purpose machine is meaningfully riskier than hardware signing. For anything beyond small, disposable amounts, pair cast with a hardware wallet instead of a raw private key — most cast wallet commands (send, wallet address, wallet list, etc.) support -l/--ledger and -t/--trezor flags. Always verify RPC endpoints and token contract addresses before broadcasting, and test with tiny amounts first.

1. Install Foundry (includes cast)

curl -L https://foundry.paradigm.xyz | bash
# restart your shell, or run the source command it prints
foundryup

Verify:

cast --version

2. Create a wallet without ever displaying the private key/mnemonic

Use cast wallet new, pointed at a directory, to generate a keypair and immediately encrypt it into a keystore file. Hidden password prompting is the default behavior now (the old -p/--password flag is deprecated and unnecessary — omit it). You can also name the account directly in the same command instead of getting a random filename:

mkdir -p ~/.foundry/keystores
cast wallet new ~/.foundry/keystores my-wallet

You'll be prompted:

Enter password:

Output shows only the account name and address (e.g. Created new encrypted keystore file: /home/you/.foundry/keystores/my-wallet and Address: 0x...). No private key or mnemonic ever touches your screen or shell history.

This same keystore works for ETH, USDT-ERC20, and USDT-BEP20 — it's one Ethereum-format key/address usable on any EVM chain; you just point it at different RPC URLs and token contracts.

3. Get the wallet's address

If you already have the keystore file/name:

cast wallet address --account my-wallet

(Prompts for your password, does not display it.)

Or list all keystores in the default keystore directory (~/.foundry/keystores):

cast wallet list

Add --dir <path> to list a different directory, or --ledger/--trezor to list hardware wallet accounts instead.

4. Check balance

Native ETH balance:

cast balance <YOUR_ADDRESS> --rpc-url https://ethereum-rpc.publicnode.com

Native BNB balance (for BEP20 gas):

cast balance <YOUR_ADDRESS> --rpc-url https://bsc-dataseed.binance.org

USDT-ERC20 balance (Ethereum mainnet USDT contract: 0xdAC17F958D2ee523a2206206994597C13D831ec7):

cast call 0xdAC17F958D2ee523a2206206994597C13D831ec7 \
  "balanceOf(address)(uint256)" <YOUR_ADDRESS> \
  --rpc-url https://ethereum-rpc.publicnode.com

Result is in the token's smallest unit — USDT-ERC20 uses 6 decimals, so divide by 1,000,000.

USDT-BEP20 balance (Binance-Peg BSC-USD contract: 0x55d398326f99059fF775485246999027B3197955):

cast call 0x55d398326f99059fF775485246999027B3197955 \
  "balanceOf(address)(uint256)" <YOUR_ADDRESS> \
  --rpc-url https://bsc-dataseed.binance.org

USDT-BEP20 uses 18 decimals.

Always double-check current official contract addresses on Etherscan/BscScan before relying on any address from a third-party source — token contract addresses are a common phishing target.

5. Send crypto (signs with your encrypted keystore; password prompt only)

Send ETH:

cast send <RECIPIENT_ADDRESS> \
  --value 0.01ether \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --account my-wallet

Send USDT-ERC20:

cast send 0xdAC17F958D2ee523a2206206994597C13D831ec7 \
  "transfer(address,uint256)" <RECIPIENT_ADDRESS> 1000000 \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --account my-wallet

1000000 = 1 USDT (6 decimals).
0xdAC17F958D2ee523a2206206994597C13D831ec7 is the USDT contract address on Ethereum network.

Send USDT-BEP20:

cast send 0x55d398326f99059fF775485246999027B3197955 \
  "transfer(address,uint256)" <RECIPIENT_ADDRESS> 1000000000000000000 \
  --rpc-url https://bsc-dataseed.binance.org \
  --account my-wallet

That long number (18 decimals) = 1 USDT.
0x55d398326f99059fF775485246999027B3197955 is the USDT contract address on BNB network.

--account <name> uses a keystore from the default ~/.foundry/keystores folder by filename and only ever prompts for the password interactively — it's never passed as a plaintext flag or echoed to screen. If your keystore file lives somewhere else, use --keystore <path> (pointing at the file or its folder) instead of --account.

6. Back up the wallet, and restore it correctly later

The keystore file is the exportable, re-importable wallet — it's already an encrypted JSON file. No separate export step is needed.

# Find and copy the keystore file itself
ls ~/.foundry/keystores/
cp ~/.foundry/keystores/my-wallet /path/to/secure/backup/location

Store that JSON file somewhere safe (encrypted external drive, offline storage). It's useless to anyone without your password, but treat it as sensitive.

To use it again later (e.g., on a new machine, or after moving the file back), you have two options — do not use cast wallet import for this, since that command creates a brand-new keystore from a raw private key you paste/type in, it does not load an existing encrypted keystore file:

  • Option A — put it back in the default location and reference it by name as before:

    cp /path/to/backup/location/my-wallet ~/.foundry/keystores/
    cast wallet address --account my-wallet
  • Option B — leave it where it is and point commands at it directly with --keystore:

    cast wallet address --keystore /path/to/backup/location/my-wallet
    cast send <RECIPIENT_ADDRESS> --value 0.01ether \
      --rpc-url https://ethereum-rpc.publicnode.com \
      --keystore /path/to/backup/location/my-wallet

Either way you'll be asked for the password to decrypt it — the raw key is never displayed. If you ever genuinely need the raw private key back out (e.g., to load it into a different, non-Foundry tool), use:

cast wallet decrypt-keystore --keystore /path/to/keystore-file

This is the one command that will display the private key on screen, so only run it on a trusted, offline machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment