openwallet
v0.0.1
Published
Custom Ethereum-focused CLI with encrypted local and global vaults.
Downloads
297
Readme
openwallet
openwallet is a local-first Ethereum wallet CLI with encrypted project-local and global .openwallet vaults. It uses viem for EVM wallet operations, @noble/ciphers plus @noble/hashes for vault encryption, and the OS keychain when available.
The wallet format is private to this CLI. It is not OWS-compatible and does not claim OWS conformance.
Status
MVP implementation for local development and agent workflows.
Supported:
- Create, list, and inspect encrypted Ethereum wallets.
- Import a BIP-39 mnemonic into an encrypted wallet.
- Store 10 public EVM account descriptors for indexes
0..9. - Sign EIP-191 messages offline.
- Sign serialized unsigned EVM transaction hex offline.
- Use a wallet-backed EVM JSON-RPC proxy with local signing and transaction simulation.
- Use local
./.openwalletvaults by default or~/.openwalletwith-g/--global.
Not included:
- Direct balance commands, standalone broadcasting commands, or standalone gas/nonce commands.
- Wallet deletion, mnemonic/private-key export, Solana, policy engines, or API keys.
- OWS compatibility or migration support for existing OWS wallet files.
- Recovery if the OS keychain entry for a generated vault passphrase is lost and you do not know the passphrase.
Requirements
- Node.js
24.x - pnpm
10.x - OS keychain support for
@github/keytar, or an interactive TTY for passphrase prompts
On Linux, @github/keytar depends on the Secret Service API, usually provided by libsecret and a running keyring service. If the keychain is unavailable, commands that need the vault passphrase prompt on stderr without echoing input. Non-interactive sessions that cannot prompt fail with KEYCHAIN_UNAVAILABLE; openwallet does not fall back to plaintext files, environment variables, or command-line secrets.
Install
pnpm install
pnpm buildRun the development CLI:
pnpm dev --help
pnpm dev wallet listRun the built CLI:
node dist/bin.js --help
node dist/bin.js wallet listIf installed as a package, the binary name is openwallet:
openwallet wallet listVaults
Local mode is the default:
openwallet wallet create --name agentThis uses exactly the current working directory:
<cwd>/.openwalletopenwallet does not search parent directories for a vault. Running the same command from another directory uses that directory's .openwallet vault.
Global mode uses the user's home directory:
openwallet -g wallet create --name agentThis writes to:
~/.openwalletVault layout:
.openwallet/
└── wallets/
└── <wallet-name>.jsonWallet names are used as filenames, so names must start with a letter or number and contain only letters, numbers, dots, underscores, or hyphens. Vault directories are created owner-only where supported, and .openwallet/ is ignored by git in this repository.
Keychain Model
When the OS keychain is available, each vault gets a generated passphrase stored under the openwallet service. The account key includes the vault scope and absolute vault path, so local and global vaults are isolated.
Wallet files remain encrypted. The keychain stores only the generated vault passphrase; it never stores mnemonics, private keys, derived keys, or wallet ciphertext directly.
If the keychain is unavailable, openwallet prompts for a vault passphrase and keeps it in memory only for that command. You must enter the same passphrase again for future commands against that vault. If a keychain entry for a generated passphrase is deleted and you do not know that passphrase, the encrypted vault is inaccessible. There is no recovery command and no plaintext fallback.
Wallet Format
Wallet files use private format openwallet-v1. Public account descriptors are readable, while the BIP-39 mnemonic is encrypted under crypto.ciphertext with scrypt plus AES-256-GCM.
The public shape is:
{
"version": 1,
"format": "openwallet-v1",
"profile": "evm",
"accounts": [
{
"index": 0,
"address": "0xab16a96D359eC26a11e2C2b3d8f8B8942d5Bfcdb",
"derivationPath": "m/44'/60'/0'/0/0"
}
],
"crypto": {
"scheme": "openwallet-secretbox-v1",
"cipher": "aes-256-gcm",
"kdf": "scrypt",
"ciphertext": "..."
}
}Wallet creation stores 10 public EVM descriptors for derivation paths m/44'/60'/0'/0/{index}. The encrypted mnemonic is never returned by commands, logged, snapshotted, or stored outside the encrypted wallet file.
Commands
Create a wallet:
openwallet wallet create --name agent
openwallet -g wallet create --name agentImport a mnemonic:
openwallet wallet import --name agent
printf '%s\n' "$MNEMONIC" | openwallet wallet import --name agent --mnemonic-stdinList wallets:
openwallet wallet list
openwallet -g wallet listInspect vault health:
openwallet wallet doctor
openwallet wallet status
openwallet -g wallet doctorShow wallet metadata:
openwallet wallet info --name agentwallet info returns all 10 public EVM account descriptors. The same derived EVM addresses are valid across EVM chains.
Expose a local wallet-backed EVM JSON-RPC proxy:
openwallet wallet proxy --name agent --chain eip155:8453 --rpc-target https://mainnet.base.org
openwallet wallet proxy --name agent --chain eip155:8453 --rpc-target https://mainnet.base.org --unlockedThe proxy listens on 127.0.0.1:9282. It verifies that --rpc-target serves the requested --chain, forwards non-signing JSON-RPC methods to --rpc-target, and signs eth_sign, personal_sign, eth_signTypedData*, eth_signTransaction, and eth_sendTransaction locally when the request targets a managed wallet address and uses a supported EVM transaction shape. For managed transaction signing/sending, the proxy writes a terminal notice when the request is received, simulates with eth_call, writes the simulation result, and only then signs. Anything the local proxy cannot handle is forwarded unchanged to --rpc-target. By default each signing request asks for vault/keychain access. With --unlocked, the vault is unlocked once before the server starts and signing requests reuse that in-memory session.
Sign a UTF-8 message:
openwallet sign message --wallet agent --message "hello"
openwallet sign message --wallet agent --message "hello" --index 9Sign hex message bytes:
openwallet sign message --wallet agent --message 0x68656c6c6f --encoding hexSign a serialized unsigned EVM transaction:
openwallet sign tx --wallet agent --tx 0x...
openwallet sign tx --wallet agent --tx 0x... --index 1Signing defaults to account index 0. Pass --index 0..9 to sign with one of the cached derived EVM accounts.
Use -g or --global anywhere in a wallet or signing command to select ~/.openwallet instead of the current directory's .openwallet vault.
These are equivalent:
openwallet -g wallet list
openwallet wallet -g list
openwallet wallet list -g
openwallet wallet list --globalOutput
Commands return structured data through Incur. For JSON output, use either form:
openwallet wallet list --format json
openwallet wallet list --jsonsign tx returns only:
signedTransactiontransactionHash
Command outputs are designed not to include mnemonics, private keys, vault passphrases, derived keys, decrypted payloads, encrypted ciphertext, or raw keychain values.
EVM Scope
The wallet and offline signing commands are EVM-chain agnostic. sign tx accepts serialized unsigned EVM transaction hex only. It does not accept JSON transaction objects, fetch chain data, estimate gas, choose nonces, or broadcast transactions. If the serialized transaction contains a chain ID, that chain ID is preserved by signing.
wallet proxy is the RPC-aware exception: it requires a canonical EVM CAIP-2 --chain such as eip155:8453, accepts JSON-RPC transaction objects for eth_signTransaction and eth_sendTransaction, asks the target RPC for missing nonce/gas/fee data, simulates with eth_call, signs locally, and forwards eth_sendRawTransaction for broadcasts.
Development
pnpm install
pnpm dev --help
pnpm format
pnpm build
pnpm typecheck
pnpm lint
pnpm test -- --coverage
pnpm audit --prodRelease
This repository uses Changesets for npm releases:
pnpm changeset
pnpm version
pnpm releaseThe publish workflow runs on pushes to main. It creates a Changesets version PR when changesets are present; merging that PR publishes with pnpm release.
The workflow is configured for npm trusted publishing through GitHub Actions OIDC. Configure the npm package trusted publisher for akshatmittal/openwallet and workflow .github/workflows/publish.yml, or add npm token authentication before relying on automated publishes.
Project conventions:
- TypeScript source uses the
@/*alias for internal imports. - Internal source imports are extensionless.
- Tests mock keychain access and do not use the real OS keychain.
- Tests do not require network access.
Documentation
docs/spec-openwallet-cli.mddefines the MVP behavior and boundaries.docs/plan-openwallet-cli.mdexplains implementation decisions and risks.docs/tasks-openwallet-cli.mdtracks the implementation task breakdown.
