Wallet backups
The rule every app on this API follows: a wallet is written to disk, in a place you can find, before it holds anything.
The API never sees your keys, which means it can never give one back. Bots create wallets constantly — funding wallets, bundle wallets, per-coin wallets, vanity mints — and the most common way money is lost is a key that only ever existed in memory, in a log line, or in a terminal that was closed. So this is a rule, enforced in AGENTS.md and in every prompt we publish, not a tip.
The pattern
One file per wallet
wallets/<label>-<address>.jsonnext to the project:{ "address", "secretKey" (base58 or hex), "chain", "label", "createdAt" }. Append-only; never overwrite an existing file.Print the path
Log the absolute path the moment the file is written, before anything else happens with that wallet.
Gate funding on it
The funding step reads the file back (not the in-memory key) and refuses if it is missing.
Export on demand
A command (
npm run export-wallets) that writes one plain-text file with every address and secret to the user's Desktop, for a password manager or an offline copy. Tell the user to run it after every session that created wallets.Encrypt at rest when the app is shared
For a hosted app or a shared machine, AES-256-GCM with a key from
ENCRYPTION_KEY; keep the plain export as the user's own responsibility.
import fs from 'node:fs'
import path from 'node:path'
import bs58 from 'bs58'
import { Keypair } from '@solana/web3.js'
const DIR = path.resolve('wallets')
export function newWallet(label) {
const kp = Keypair.generate()
const file = path.join(DIR, `${label}-${kp.publicKey.toBase58()}.json`)
fs.mkdirSync(DIR, { recursive: true })
fs.writeFileSync(file, JSON.stringify({ address: kp.publicKey.toBase58(), secretKey: bs58.encode(kp.secretKey), chain: 'sol', label, createdAt: new Date().toISOString() }, null, 2), { flag: 'wx' })
console.log(`wallet saved → ${file}`) // before the wallet is used for anything
return kp
}
export function assertBackedUp(address) {
const ok = fs.readdirSync(DIR).some((f) => f.endsWith(`-${address}.json`))
if (!ok) throw new Error(`refusing to fund ${address}: no backup in ${DIR}`)
}
What the API does on its side
GET /api/create-walletand the vanity endpoints return abackupobject (what to save, a checklist) next to the keys, and show the secret once.- The docs key creator offers "Download as .txt" and never stores a key.
- Transfers and trades are built for addresses you give; the API cannot recover a wallet it never had.
.env and wallets/ as secrets: .gitignore them, never paste them into chat with an assistant, never into a public repo.