Reference / CLI commands

genesis wallet

Use genesis wallet to manage a local encrypted wallet and expose a stable machine-readable interface for agents.

Supported chains:

  • btc: local BIP39/BIP84-derived Bitcoin address, Esplora balance/UTXO/broadcast
  • evm: local BIP39-derived EVM addresses through EVM JSON-RPC URLs. Ethereum, Base, and Monad are enabled by default with public RPC endpoints.
  • sol: local BIP39/SLIP-0010-derived Solana address through @solana/web3.js
  • trx: local BIP39-derived TRON address through TronWeb
  • xmr: read and transfer through monero-wallet-rpc; Genesis does not sign Monero locally
  • lndhub: read and pay Lightning invoices through an external LndHub-compatible REST API (e.g. BlueWallet); Genesis does not hold Lightning keys locally

Security model:

  • The encrypted keystore is stored under the Genesis credentials directory with owner-only file permissions.
  • Public address metadata is readable while the keystore is locked.
  • One BIP39 secret recovery phrase derives every local keystore chain account (btc, evm, sol, and trx) through that chain's configured derivation path. EVM accounts share the same derived address across configured EVM networks.
  • Existing mnemonics, private keys, passphrases, and raw secret material are not returned by JSON output, Gateway RPC, or the Control UI.
  • EVM message, digest, and raw transaction signing are CLI-only and return signatures or signed transaction bytes, never private keys.
  • The Control UI can create a new recovery phrase or import/replace one through the admin-scoped wallet.recoveryPhrase.set method. A newly generated phrase is returned once so it can be backed up; imported or existing phrases are not echoed back.
  • Send and broadcast flows stay in the CLI.

Create or import

Create a new wallet:

printf '%s\n' "$GENESIS_WALLET_PASSPHRASE" | genesis wallet init --passphrase-stdin

Import a mnemonic:

export GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase'
printf '%s\n' "$MNEMONIC" | genesis wallet import --mnemonic-stdin

Notes:

  • init generates a new mnemonic and encrypts it locally.
  • --show-mnemonic prints a generated mnemonic once in human-readable output and cannot be combined with --json.
  • import --mnemonic-stdin reads the mnemonic from stdin and reads the passphrase from GENESIS_WALLET_PASSPHRASE.
  • --overwrite replaces an existing keystore.

Addresses and balances

Public addresses:

genesis wallet
genesis wallet list
genesis wallet address --chain evm
genesis wallet address --json

Native balances:

genesis wallet balance --chain btc
genesis wallet balance --chain evm --account evm:base --json

Configured EVM tokens and NFT collections:

genesis wallet tokens --account evm:ethereum --json
genesis wallet nfts --account evm:base --json

Token and NFT commands read the contracts configured under wallet.networks.evm.chains.<chain>.tokens and wallet.networks.evm.chains.<chain>.nfts. ERC-20 token balances use balanceOf; token metadata comes from config or from symbol, name, and decimals contract calls. ERC-721 collections can show balanceOf and can check configured tokenIds through ownerOf/tokenURI. ERC-1155 collections use configured tokenIds and balanceOf(address, tokenId).

Agents should prefer --json and treat the response as the stable automation surface.

Quote and send

Quote a native asset transfer:

genesis wallet quote --chain sol --to <address> --amount 0.1
genesis wallet quote --chain sol --to <address> --amount 0.1 --json

Broadcasting is intentionally harder. A send must pass all configured and per-invocation guards:

  • wallet.spending.enabled: true
  • --yes
  • wallet passphrase from GENESIS_WALLET_PASSPHRASE or --passphrase-stdin, or --empty-passphrase for a keystore created without one
  • GENESIS_WALLET_ALLOW_SPEND=1 unless wallet.spending.requireAllowEnv is disabled
  • wallet.spending.maxNativeAmount, when configured

Example:

GENESIS_WALLET_ALLOW_SPEND=1 \
GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase' \
genesis wallet send --chain trx --to <address> --amount 1 --yes

Signing and raw transactions

The wallet can sign EVM payloads with a configured EVM account:

GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase' \
genesis wallet sign-message --chain evm --account evm:base --message 'hello' --yes --json

GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase' \
genesis wallet sign-digest --chain evm --digest 0x0000000000000000000000000000000000000000000000000000000000000001 --yes --json

sign-message uses the EVM personal-sign/EIP-191 message prefix. Use --message-hex to sign exact message bytes. sign-digest signs the exact 32-byte digest and does not add a message prefix.

Raw transaction signing and broadcast use the same spending gates as send because they can move funds or invoke contracts:

GENESIS_WALLET_ALLOW_SPEND=1 \
GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase' \
genesis wallet sign-raw-transaction \
  --chain evm \
  --account evm:base \
  --tx-json '{"to":"0x0000000000000000000000000000000000000001","nonce":0,"gasLimit":"21000","gasPrice":"1","value":"1"}' \
  --yes \
  --json

GENESIS_WALLET_ALLOW_SPEND=1 \
GENESIS_WALLET_PASSPHRASE='use-a-long-local-passphrase' \
genesis wallet broadcast-raw \
  --chain evm \
  --account evm:base \
  --raw-transaction 0x... \
  --yes \
  --json

sign-raw-transaction accepts unsigned EVM transaction request fields such as to, from, nonce, gasLimit or gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, value, data, chainId, type, and accessList. If chainId is omitted, Genesis fills the selected EVM network's chain id. If chainId is present, it must match the selected account's network. When transaction.value is present, wallet.spending.maxNativeAmount is checked against that native asset value. Genesis does not decode arbitrary contract calldata for token-level spend limits.

Configuration

Minimal example:

{
  wallet: {
    enabled: true,
    networks: {
      btc: { network: "mainnet" },
      evm: {
        // Optional. Omit `rpcUrl` to use the built-in public RPC defaults for
        // ethereum, base, and monad. Override or disable individual EVM chains
        // under `chains`.
        chains: {
          ethereum: {
            rpcUrl: { source: "env", provider: "default", id: "ETH_RPC_URL" },
            tokens: {
              usdc: {
                address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                symbol: "USDC",
                name: "USD Coin",
                decimals: 6,
              },
            },
            nfts: {
              badge: {
                address: "0x0000000000000000000000000000000000000001",
                standard: "erc721",
                tokenIds: ["1"],
              },
            },
          },
          base: {
            rpcUrl: { source: "env", provider: "default", id: "BASE_RPC_URL" },
          },
          monad: {
            rpcUrl: { source: "env", provider: "default", id: "MONAD_RPC_URL" },
          },
        },
      },
      sol: { network: "mainnet-beta" },
      trx: {
        fullHost: "https://api.trongrid.io",
        apiKey: { source: "env", provider: "default", id: "TRONGRID_API_KEY" },
      },
      xmr: {
        walletRpcUrl: "http://127.0.0.1:18082/json_rpc",
        username: { source: "env", provider: "default", id: "MONERO_RPC_USER" },
        password: { source: "env", provider: "default", id: "MONERO_RPC_PASSWORD" },
      },
    },
    spending: {
      enabled: false,
      requireAllowEnv: true,
      maxNativeAmount: "0.1",
    },
  },
}

Secret-bearing wallet fields support SecretRef inputs where the config schema marks them sensitive. See Secrets Management.

When wallet.networks.evm.rpcUrl is omitted and no legacy single-chain EVM config is set, Genesis creates EVM accounts for evm:ethereum, evm:base, and evm:monad. A legacy single-chain config for Ethereum, Base, or Monad also uses that chain's public RPC when rpcUrl is omitted. These defaults are rate-limited and are best for setup, address display, and low-volume balance checks. For production or agentic send flows, configure private or secret-backed endpoints under wallet.networks.evm.chains.

Browser web3 provider

With wallet.browser.enabled: true, every page the agent opens with the browser tool gets an injected web3 wallet named Genesis:

  • EVM: window.ethereum (EIP-1193) and an EIP-6963 announcement. Chains are the enabled wallet.networks.evm networks; wallet_switchEthereumChain only switches between them. Read-only JSON-RPC (eth_call, eth_estimateGas, ...) goes to the configured RPC URL.
  • Solana: a Wallet Standard wallet plus window.solana, using the configured wallet.networks.sol cluster.

Account and chain queries answer immediately. personal_sign, eth_signTypedData_v4, eth_sendTransaction, and Solana signMessage, signTransaction, signAllTransactions, and signAndSendTransaction wait in an approval queue. The agent sees them through the owner-only wallet tool (pending, then approve or reject); unanswered requests are rejected after five minutes. Transaction signing also requires wallet.spending.enabled: true, and eth_sendTransaction values must fit wallet.spending.maxNativeAmount.

Signing needs the keystore unlocked in the running gateway:

GENESIS_WALLET_PASSPHRASE=... genesis wallet unlock --ttl 30
genesis wallet unlock --empty-passphrase   # keystore created without a passphrase
genesis wallet lock

The Control UI Wallet tab has the same controls in its Signing Session card (passphrase, 15 minutes to 24 hours, Lock now). Both call the admin-scoped wallet.unlock / wallet.lock gateway methods.

The mnemonic stays in gateway memory only until the TTL (default 15 minutes, max 24 hours) expires, genesis wallet lock runs, or the gateway restarts.

{
  wallet: {
    browser: {
      enabled: true,
      // Optional: only these origins see the provider.
      allowedOrigins: ["https://app.uniswap.org", "https://jup.ag"],
    },
    spending: { enabled: true, maxNativeAmount: "0.05" },
  },
}

Notes:

  • Any page JavaScript on an allowed origin can raise signing requests and read the wallet addresses. Prefer allowedOrigins, and keep in mind that the agent decides approvals.
  • Tabs that were already open when the browser connected pick up the provider on their next navigation or reload.
  • Managed and remote CDP profiles inject through a Playwright binding on every page load.
  • existing-session profiles (Chrome DevTools MCP) get the provider as a navigate_page init script on each browser-tool navigation, talking to a token-protected loopback bridge (http://127.0.0.1:<random port>). Full page reloads the dApp triggers itself drop it until the next browser-tool navigation. Chrome may ask to allow local network access the first time a site uses the bridge, and a strict page connect-src CSP blocks it (requests then fail with code 4900).
  • Browsers on a paired node host forward provider requests to the gateway over the node connection (node.wallet.web3), so they use the gateway's wallet.browser config, unlocked session, and approval queue. The node's own wallet config is ignored.

Gateway and Control UI

The Gateway exposes read-only wallet.summary for operator clients with operator.read. The Control UI Wallet tab calls that method and displays public addresses, balance refresh results, warnings, and copy buttons.

The Control UI also supports admin-scoped recovery phrase management through wallet.recoveryPhrase.set:

  • mode: "generate" creates a new BIP39 phrase, encrypts it locally, derives all local chain accounts, and returns the generated phrase once.
  • mode: "import" imports a BIP39 phrase, encrypts it locally, derives all local chain accounts, and does not return the phrase.
  • passphrase is optional for the web/admin method; omit it to create or import a wallet without a passphrase.
  • overwrite: true is required when replacing an existing keystore.

No Gateway or web method returns existing seed phrases, private keys, stored passphrases, signatures, raw transaction signing, or a send/broadcast control.