Summary
The keygen/vanity flow overwrites the existing wallet key in ~/.tinyplace/config.json with no guard, no backup, and no --force. A user (or agent) who already has a funded wallet and runs a vanity/keygen command silently loses their funded key, stranding any on-chain funds. Reported by a Discord user (Amrit) whose funded wallet was orphaned this way.
Problem
runKeygen (sdk/typescript/src/cli/keygen.ts:232) grinds a new wallet and calls persistSecretKey (keygen.ts:243), which writes { ...existing, secretKey: NEW } over config.json (keygen.ts:272). There is no existence check, no backup of the prior secretKey, and no --force gate. It even captures the previous wallet id (previousWallet: ctx.signer?.agentId, keygen.ts:252) but throws the old secret away. If the vanity prefix is not found in time, it saves a random wallet anyway (keygen.ts:248).
This is the only unguarded write path. For contrast:
makeContext only generates a key when none exists (if (!seed && managed), sdk/typescript/src/cli/context.ts:34).
- The openclaw vault
createWallet throws unless --force (sdk/plugin-openclaw/src/wallet.ts:123).
So keygen is the outlier. Because config.json IS the wallet (the docs warn "losing it loses the wallet"), this overwrite is a silent fund-loss footgun.
Steps to reproduce:
- Have a funded wallet (a
config.json with a secretKey that holds funds).
- Run a vanity/keygen command (e.g.
tinyplace keygen --vanity <prefix>), or have an agent do it.
config.json.secretKey is replaced by the new wallet; the old funded key is gone with no backup; funds are stranded.
Real report: Discord user Amrit. His funded wallet 4gBCa7E... was orphaned this way; he could no longer sign from it, and the app then generated a new empty wallet that could not pay the USDC registration fee.
Suggested fix
Make keygen's persistSecretKey match the openclaw vault safety:
- Refuse to overwrite an existing
secretKey without an explicit --force.
- Back up the old key first (e.g.
config.json.bak, or print the old seed) before writing.
- Warn before grinding, not only after.
Acceptance criteria
Related
- Reported via a TinyHumans Discord support ticket (Amrit).
- Code:
sdk/typescript/src/cli/keygen.ts:232-272; safe contrasts at sdk/typescript/src/cli/context.ts:34 and sdk/plugin-openclaw/src/wallet.ts:123.
Summary
The keygen/vanity flow overwrites the existing wallet key in
~/.tinyplace/config.jsonwith no guard, no backup, and no--force. A user (or agent) who already has a funded wallet and runs a vanity/keygen command silently loses their funded key, stranding any on-chain funds. Reported by a Discord user (Amrit) whose funded wallet was orphaned this way.Problem
runKeygen(sdk/typescript/src/cli/keygen.ts:232) grinds a new wallet and callspersistSecretKey(keygen.ts:243), which writes{ ...existing, secretKey: NEW }overconfig.json(keygen.ts:272). There is no existence check, no backup of the priorsecretKey, and no--forcegate. It even captures the previous wallet id (previousWallet: ctx.signer?.agentId,keygen.ts:252) but throws the old secret away. If the vanity prefix is not found in time, it saves a random wallet anyway (keygen.ts:248).This is the only unguarded write path. For contrast:
makeContextonly generates a key when none exists (if (!seed && managed),sdk/typescript/src/cli/context.ts:34).createWalletthrows unless--force(sdk/plugin-openclaw/src/wallet.ts:123).So keygen is the outlier. Because
config.jsonIS the wallet (the docs warn "losing it loses the wallet"), this overwrite is a silent fund-loss footgun.Steps to reproduce:
config.jsonwith asecretKeythat holds funds).tinyplace keygen --vanity <prefix>), or have an agent do it.config.json.secretKeyis replaced by the new wallet; the old funded key is gone with no backup; funds are stranded.Real report: Discord user Amrit. His funded wallet
4gBCa7E...was orphaned this way; he could no longer sign from it, and the app then generated a new empty wallet that could not pay the USDC registration fee.Suggested fix
Make keygen's
persistSecretKeymatch the openclaw vault safety:secretKeywithout an explicit--force.config.json.bak, or print the old seed) before writing.Acceptance criteria
secretKeyalready exists does not destroy it without--force.--force), the prior key is backed up first.Related
sdk/typescript/src/cli/keygen.ts:232-272; safe contrasts atsdk/typescript/src/cli/context.ts:34andsdk/plugin-openclaw/src/wallet.ts:123.