This is a hot-wallet trading library. It signs transactions that move real money. Read this once before you point it at mainnet capital.
What this library is designed to resist:
- Pool manipulation between quote and execution — handled via
amountOutMinimumon every trade - Fee-on-transfer skim — handled via post-trade balance reconciliation with a 5% cap
- Stuck transactions due to wrong approval target — handled by always approving
FRouterV3for bonding curve trades - Slow / flaky RPC — partially handled (configurable timeouts via viem); you should also run with a fallback RPC
What this library does not protect against:
- A compromised private key / session signer. If your key leaks, your funds leak. Period.
- A malicious token contract that drains liquidity post-buy. A "rug" can happen between your buy and your next sell, and there's nothing this library can do about it.
- A compromised RPC endpoint that lies to you. If your RPC returns a fake
tokenInfoclaimingtrading: truewhen the token has graduated, you'll send a tx that reverts. (You lose gas, not funds.) Worse: a malicious RPC could return a fake quote and lure you into a tightminOutthat gets sandwiched on a real submission. Always use a trusted RPC provider. - A compromised npm dependency. The library imports
viemand nothing else. Audit the lockfile before deploying. - MEV on graduated-token UniV3 trades. See TROUBLESHOOTING.md § My trades are getting MEV-sandwiched.
A list of things that have burned someone, and you should not repeat:
Always. Every. Repo. The .gitignore in this repo blocks .env but only the literal name; if you create .env.production or .env.local it'll slip through. Either match .env* in .gitignore or be paranoid about every new env file you create.
If you do commit a key — even briefly, even to a private repo — rotate the key. Don't git reset and pretend it didn't happen. GitHub indexes pushed commits before you can revert, and scrapers watch the commit firehose.
One signer per smart account. Sharing a signer means:
- Nonce collisions (rare but real)
- A single key compromise drains every wallet that signer controls
- Hard to revoke selectively when one wallet misbehaves
Defaults on some Linux distros leave new files world-readable. chmod 600 every key file. If you're on shared infra, also check the parent directories — chmod 700 /var/lib/acp-fleet/wallets/<id> so other users can't traverse.
npm install runs install scripts. A malicious transitive dep can read your env, your home dir, your SSH keys. Run as an unprivileged user. Run npm with --ignore-scripts if your dep tree doesn't actually need install scripts.
The description / image / twitter fields in tokenInfo are user-supplied. They can lie. Don't use them to filter "safe" tokens. Use the actual on-chain reserves and the curve's creator address against an allowlist if you need it.
base.publicnode.com, mainnet.base.org, and similar free endpoints rate-limit aggressively and have inconsistent latency. They're fine for read-only dashboards. For trading, get a paid Alchemy / Infura / QuickNode key.
I see code in the wild that passes 0n as amountOutMinimum. That's an open invitation to MEV bots. The library defaults slippageBps to 100 (1%) for a reason — if you want to disable slippage protection, you have to do it explicitly, and you shouldn't.
The library uses max-approve for performance. That's fine for an ACP smart wallet that you can re-key. For an EOA holding real money, prefer exact-amount approvals — bump the gas cost but limit blast radius if the spender contract is ever compromised.
To switch the library to exact approvals, fork approveIfNeeded and change type(uint256).max to amount.
examples/http-routes.ts has a Bearer-token check. It is a single shared token. That's fine for an internal service inside a VPC. It is not fine if you expose localhost:8080 via ngrok and tweet the URL.
If you need a public HTTP endpoint:
- Put it behind a real auth proxy (Cloudflare Access, Auth0, etc.)
- Add IP allowlisting
- Add rate limits per caller
- Audit the route handlers — they execute trades on your wallets
The system this library was extracted from has a global kill switch — flip one config flag and every trader stops. Build that into your deployment. Examples:
- Environment variable
TRADING_HALTED=truethat every handler checks - A database row your handlers poll every loop
- An on-chain pauser role you can flip from a multisig
When something goes wrong at 3am, you need a single thing to flip to stop the bleeding. Not 50 process kills.
If you're going to production with this library:
- Each wallet's private key / session signer is
chmod 600, owned by an unprivileged user - No
.envfiles in git; secrets loaded from a secrets manager (AWS Secrets Manager, Doppler, 1Password, etc.) at process start - Paid RPC with a backup RPC configured as fallback
- Per-wallet capital cap (don't fund a single hot wallet with > X amount you can afford to lose)
- Global kill switch wired through every trade entry point
- Logs do not include private keys or full session signers (redact
0x[0-9a-f]{64}patterns in your logger) - Monitoring on RPC error rate, bundler error rate, slippage realised vs expected
- Alerts on
BondingV5DeliveryShortfall,V3NoLiquidity, and any unexpected revert - Periodic rotation of session signers (monthly minimum)
- Disaster recovery: documented procedure to drain all wallets to a cold address if the system is compromised
If you find a security issue in this library — particularly something that lets an attacker make a wallet sign a tx it shouldn't — please don't open a public issue. Email the maintainer or open a private security advisory on GitHub. I'll take it seriously.
For issues in BondingV5 or FRouterV3 themselves — those are Virtuals protocol contracts, not ours. Report through Virtuals' channels.
A few things you might expect to see in a "security" doc that I've left out:
- Formal audit. This library has not been formally audited. It's a thin wrapper around contracts that have themselves been audited by the Virtuals team. Most of the security surface lives in those contracts, not here.
- Sandbox / simulation harness. There's no built-in fork-and-replay setup. Use viem's
createPublicClientagainst a local Anvil fork if you want to dry-run trades against a frozen mainnet state. - Multi-sig support. This library assumes you can sign txs directly (EOA or ACP session signer). Driving a multisig requires a different signing path; you'd wrap the call in a Safe Tx proposal instead of submitting it directly.