@towns-labs/wallet
v7.3.1
Published
Your keys. Your account. No signup. No login. Just run it.
Downloads
434
Readme
tw — Towns Wallet CLI
Your keys. Your account. No signup. No login. Just run it.
tw is a local-first CLI for managing smart accounts, session keys, and on-chain permissions on Towns Protocol. It works for humans at the terminal and for agents over --json or --mcp.
bunx @towns-labs/wallet --helpQuick Start
Create an account and send USDC in under a minute:
# 1. Create a local account (interactive password prompt)
tw account create --profile main
# 2. Fund it
tw address --qr --amount 100
# 3. Send USDC
tw account send 10 vitalik.ethNo custody service. No API key. Everything runs locally with encrypted keystores.
How It Works
tw manages a local keystore that holds your root key and session keys. Your root key creates and controls your on-chain smart account. Session keys are scoped signers — they can send transactions through the Towns relayer without exposing your root key.
~/.config/towns/tw/profiles/<env>/<profile>/default.keystore.json
~/.config/towns/tw/profiles/<env>/<profile>/sessions/<session-name>.jsonFor agents, the session daemon keeps decrypted keys in memory for a bounded duration so automated workflows can sign without prompting for a password on every call.
Commands
Account
account create — Create a new account
tw account create --profile agentResume a previously interrupted flow:
tw account create --resume --profile agentNon-interactive (stdin password + JSON):
echo "my-password" | tw account create --password-stdin --jsonUse an explicit keystore path:
tw account create --keystore-path ~/.config/towns/tw/profiles/prod/team/default.keystore.jsonaccount status — Check readiness
tw account status --profile agent --jsonaccount balance — Check USDC balance
tw account balance --profile agent
tw account balance --profile agent --chain polygonaccount address — Print root address
tw account address --profile agentaccount send — Send USDC
tw account send 1 0x1111111111111111111111111111111111111111
tw account send 2.5 vitalik.eth --chain base
tw account send 10 treasury --chain polygon --jsonUse a specific local session (without changing active session):
tw account send 1 0x... --profile agent --session worker-2Use a portable session file (no root keystore required):
tw account send 1 0x... --chain base --session-file ./worker-1.session.json--session and --session-file are mutually exclusive.
account swap — Swap tokens on the same chain
Powered by relay.link. Interactive confirmation by default.
tw account swap --from ETH --to USDC --amount 0.1 --chain base
tw account swap --from USDC --to ETH --amount 50 --chain base --yes --jsonaccount bridge — Bridge tokens cross-chain
tw account bridge --token USDC --amount 100 --to-chain polygon
tw account bridge --token ETH --amount 0.5 --to-chain base --recipient 0x... --yes --jsonaccount delegate — Delegate on additional chains
If your account was created on Base and you need it on Polygon:
echo "my-password" | tw account delegate --chain polygon --profile agent --password-stdinaccount history — Relayer call history
tw account history --profile agent
tw account history --address 0x... --limit 10 --offset 20
tw account history --chain base,polygon --json--limit defaults to 20 (max 100). offset + limit must be <= 1000.
account export — Export metadata or private keys
tw account export --profile agent --json
tw account export --profile agent --show-privateaccount nonce — Read account nonce
tw account nonce --profile agentaccount update password — Rotate keystore password
tw account update password --profile agent
printf "old\nnew\n" | tw account update password --current-password-stdin --new-password-stdin --jsonAddress
tw address is a top-level shortcut for showing your funding address with optional payment helpers.
tw address
tw address --link --amount 100
tw address --qr --amount 100Custom token:
tw address --token 0x... --amount 1 --decimals 18 --linkSession Keys
Session keys are scoped signers that can act on behalf of your account without exposing the root key.
session create — Create and authorize a session key
echo "my-password" | tw session create worker-1 --profile agent --password-stdin --jsonCreate and activate immediately:
echo "my-password" | tw session create worker-2 --profile agent --activate --password-stdinGrant full wildcard access:
echo "my-password" | tw session create worker-admin --profile agent --full-access --password-stdin--full-access is mutually exclusive with --target, --selector, --spend-limit, --spend-limit-raw, and --spend-period. Omitting both --target and --selector uses wildcard call permissions by default.
Resume an interrupted flow:
echo "my-password" | tw session create worker-1 --profile agent --resume --password-stdin --jsonCreate a session and initialize agent messaging in one step:
echo "my-password" | tw session create alice --profile agent --agent --password-stdinsession list — List local sessions
tw session list --profile agent --json
tw session list --profile agent --on-chain --jsonsession rotate — Rotate the active session
echo "my-password" | tw session rotate --profile agent --password-stdin --json
echo "my-password" | tw session rotate --profile agent --new-name worker-3 --password-stdin
echo "my-password" | tw session rotate --profile agent --resume --password-stdin --jsonsession revoke — Revoke a session on-chain
echo "my-password" | tw session revoke worker-1 --profile agent --password-stdin --json
echo "my-password" | tw session revoke worker-2 --profile agent --force --password-stdin
echo "my-password" | tw session revoke worker-2 --profile agent --resume --password-stdin --jsonAgent sessions require --force and clean up local channel bindings as part of revocation.
session export — Export as portable file
TW_PASSWORD="my-password" TW_EXPORT_PASSWORD="export-password" \
tw session export worker-1 --profile agent --output ./worker-1.session.jsonOr via stdin:
echo "export-password" | tw session export worker-1 --profile agent \
--output ./worker-1.session.json --export-password-stdinsession import — Import a portable session
tw session import ./worker-1.session.json --profile worker-1Creates a session-only profile that can execute account send but cannot run manager commands requiring a root keystore.
Session Daemon
The daemon holds decrypted session keys in memory so automated workflows can sign without re-entering passwords.
session start — Start the daemon
tw session start --json
tw session start --foreground --jsonIdempotent — if already running, returns the active pid/socket.
session unlock — Load a key into daemon memory
echo "my-password" | tw session unlock worker-1 --profile agent --password-stdin --duration 15m --jsonKey material stays in daemon memory only; encrypted files remain unchanged.
session lock — Remove a key from daemon memory
tw session lock worker-1 --jsonsession status — Check daemon health
tw session status --jsonsession stop — Shut down the daemon
tw session stop --jsonPermissions
Fine-grained on-chain permission rules for session keys.
permissions list — List keys and their permissions
tw permissions list --profile agent --jsonpermissions show — Show rules for a specific key
tw permissions show agent-key --profile agent --json
tw permissions show --key-hash 0xaaa... --profile agent --jsonpermissions grant — Grant a permission rule
Wildcard call permission:
echo "my-password" | tw permissions grant agent-key --type call --target any --selector any --profile agent --password-stdin --jsonDaily USDC spend limit:
echo "my-password" | tw permissions grant agent-key --type spend --token USDC --spend-limit 100 --period day --profile agent --password-stdin --jsonUse --spend-limit-raw for base units instead of human amounts.
permissions revoke — Revoke permission rules
Revoke a single rule:
echo "my-password" | tw permissions revoke agent-key --rule call:0x...:0xa9059cbb --profile agent --password-stdin --jsonRevoke all rules for a key:
echo "my-password" | tw permissions revoke agent-key --all --profile agent --password-stdin --jsonEscrow
USDC escrow: lock funds as buyer with a seller and oracle; settle (oracle signs) or refund after deadline.
escrow create — Create a new USDC escrow
tw escrow create 50 0x1111111111111111111111111111111111111111 \
--oracle 0x2222222222222222222222222222222222222222 --deadline 24h
tw escrow create 100 0x... --oracle 0x... --deadline 2d --chain base --env prod --json- Amount and seller are positional; oracle and deadline are required options.
- Deadline: relative (
1h,2d,30m,1w) or unix timestamp. After deadline, anyone can callescrow refund. - Optional salt (hex, up to 12 bytes) for unique escrow IDs on repeated orders.
- Uses session key (daemon or direct) to sign; supports
--session-filelikeaccount send.
escrow status — Check on-chain status
tw escrow status 0x<64 hex chars> --env prod
tw escrow status 0x... --chain base --jsonEscrow ID must be the full 32-byte hex (0x + 64 hex chars), e.g. from escrow create output.
escrow settle — Settle (release USDC to seller)
Oracle signs a settlement; any account can submit the signed settlement via relayer.
TW_ORACLE_PRIVATE_KEY=0x... tw escrow settle 0x<escrowId> \
--settlement-id 0x<orderId> --oracle 0x2222... --profile agentOr with pre-signed signature (oracle signs offline):
tw escrow settle 0x<escrowId> --settlement-id 0x... --oracle 0x... --signature 0x... --profile agentSettlement ID is the bytes32 order ID used at creation (see create output or escrow status).
escrow refund — Refund to buyer after deadline
Permissionless: after the escrow deadline, anyone can trigger refund to the buyer.
tw escrow refund 0x<escrowId> --profile agent
tw escrow refund 0x<escrowId> --env prod --jsonContacts
Store recipient aliases so you never have to copy-paste addresses.
~/.config/towns/tw/contacts.jsontw contacts add vitalik vitalik.eth
tw contacts add treasury 0x1111111111111111111111111111111111111111
tw contacts list --json
tw contacts show vitalik --json
tw contacts update vitalik 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
tw contacts rename vitalik vitalik-main
tw contacts remove vitalik-mainExport and import:
tw contacts export --json
tw contacts import ./contacts.json --json
tw contacts import ./contacts.json --force --jsonImport returns importedCount, skippedInvalidCount, skippedConflictCount, and overwrittenCount.
Send via alias:
tw account send 10 vitalikENS safety: if ENS re-resolves to a different address than stored, send aborts and requires tw contacts update.
Agents
Agents are session-key-backed Towns identities with their own encryption device and named channel bindings. Identity management lives under tw session; tw agent is reserved for messaging commands.
~/.config/towns/tw/profiles/<env>/<profile>/sessions/<name>.json
~/.config/towns/tw/profiles/<env>/<profile>/agent-channels.jsonsession create --agent / agent init / session list
TW_PASSWORD="my-password" tw session create alice --profile agent --agent
TW_PASSWORD="my-password" tw session create bob --profile agent
TW_PASSWORD="my-password" tw agent init bob --profile agent
TW_PASSWORD="my-password" tw session list --profile agent --jsontw session list includes a kind field (session or agent). Revoke agent identities with tw session revoke <name> --force.
Migration note: legacy agent-<name>.json files are no longer loaded. Rename them to <name>.json manually or recreate them with tw session create plus tw agent init.
agent connect — Create or bind a named channel
First side creates the channel and returns the shared secret:
TW_PASSWORD="my-password" tw agent connect --from alice --channel art --to bob --profile agentSecond side binds using that secret:
TW_PASSWORD="my-password" tw agent connect --from bob --channel art --secret "<shared-secret>" --to alice --profile agentagent send — Send a message
To a named channel:
TW_PASSWORD="my-password" tw agent send --from alice --channel art "hello from alice" --profile agentTo a raw stream ID:
TW_PASSWORD="my-password" tw agent send --from alice 77aaa... "debug message" --profile agentagent listen — Listen for messages
TW_PASSWORD="my-password" tw agent listen --from bob --channel art --profile agent
TW_PASSWORD="my-password" tw agent listen --from bob --stream 77aaa... --profile agentMessages arrive as NDJSON:
{
"type": "message",
"streamId": "77...",
"senderId": "0x...",
"eventId": "0x...",
"timestamp": 1709654400,
"content": "hello from alice"
}agent channels — Inspect bindings
TW_PASSWORD="my-password" tw agent channels --from alice --profile agent --jsonAgent Quickstart
# Create profile + two agent sessions
tw account create --profile agent
TW_PASSWORD="pw" tw session create alice --profile agent --agent
TW_PASSWORD="pw" tw session create bob --profile agent
TW_PASSWORD="pw" tw agent init bob --profile agent
# Alice creates a channel → copy the returned secret
TW_PASSWORD="pw" tw agent connect --from alice --channel art --to bob --profile agent
# Bob binds with that secret
TW_PASSWORD="pw" tw agent connect --from bob --channel art --secret "<secret>" --to alice --profile agent
# Terminal 1: listen
TW_PASSWORD="pw" tw agent listen --from bob --channel art --profile agent
# Terminal 2: send
TW_PASSWORD="pw" tw agent send --from alice --channel art "hello" --profile agentSmoke test:
cd packages/wallet
TW_PASSWORD="my-password" bun run smoke:agentAgent & Automation Integration
Every command supports --json for machine-readable output. Treat JSON output schemas as the stable contract.
tw <command> --jsonPassword Automation
Use TW_PASSWORD to skip interactive prompts:
TW_PASSWORD="my-password" tw session list --profile agent --jsonOr pipe via stdin for commands that accept --password-stdin:
echo "my-password" | tw session rotate --profile agent --password-stdin --jsonDiscovery
tw --help # All commands
tw account send --help # Command-specific help
tw --llms # Machine-readable command manifest
tw --mcp # Run as MCP server
tw --version # Version