make build |
Compile contracts |
make test |
Run Forge test suite |
make unit-test |
Run unit tests (test/unit/SHProtocolTest.t.sol) |
make mainnet-uniswap-test |
Run Uniswap V3 fork tests against MAINNET_RPC_URL (test/fork/SHMainnetUniswapV3Test.t.sol) |
make sepolia-uniswap-test |
Run Uniswap V3 fork tests against SEPOLIA_RPC_URL (test/fork/SHSepoliaUniswapV3Test.t.sol) |
make pancakeswap-test |
Run PancakeSwap V2 fork tests against BSC_RPC_URL |
make arbitrum-uniswap-test |
Run Uniswap V3 fork tests against ARB_RPC_URL (test/fork/SHArbitrumUniswapV3Test.t.sol) |
make base-uniswap-test |
Run Uniswap V3 fork tests against BASE_RPC_URL (test/fork/SHBaseUniswapV3Test.t.sol) |
make sepolia-test |
Alias of make sepolia-uniswap-test — the standalone Sepolia suite was folded into the shared fork base (SHForkTestBase.sol) |
make identity-test |
Check that no agent tool lets the model choose whose wallet it acts on (app/tests/test_identity.py, offline) |
make auth-test |
API authentication checks against a throwaway database (app/tests/test_auth.py, offline) |
make custom-tokens-test |
Tokens a user adds by address: the add rules, the routes, how the tools price and name them, and that the tools refuse tokens nobody added and slippage over 12%; the dashboard's token list, LP tokens included, against a fake chain (app/tests/test_custom_tokens.py, offline) |
make history-test |
The History tab and the short chat memory: how transactions are recorded, described, settled and listed, the history and delete-chat routes, and the conversation starting afresh after a send, against a fake chain and a scripted model (app/tests/test_history.py, offline) |
make movements-test |
What each transaction moved, for the History tab's filters: owner calldata, and the assistant's receipts (token Transfers, the op's executions, wrapped-native events, CELO counted once) (app/tests/test_tx_movements.py, offline) |
make explorers-test |
Reading activity outside Mitfah from Etherscan, NodeReal and Alchemy: paging, block windows, rate limits and query timeouts retried, and API keys kept out of errors and logs (app/tests/test_explorers.py, offline) |
make explorers-live |
The same against the real services, read-only, with the keys in .env (app/tests/check_explorers_live.py; not part of py-test) |
make speed-test |
What keeps the assistant quick: the chain id asked once, independent reads run together, the wallet checks every transaction tool runs itself, and a UserOp's hash worked out locally (app/tests/test_speed.py, offline) |
make bundler-test |
What a UserOp pays for posting its data to Ethereum on the live L2s — Base's L1 fee and Arbitrum's L1 gas, as preVerificationGas — and that forks pay none; how a lane bundles ops (alone when idle, together under load, one per wallet, the op and gas caps, the lowest fee, one wait per bundle, a failing op refused on its own, a reverted bundle's ops sent again), and how the pool of bundler keys shares ops out (each to the least busy key, 120 at once 40/40/40, a wallet's ops to one key, a stuck key passed over) (app/tests/test_bundler.py, offline) |
make telebot-test |
The Telegram bot: /chain offers only the user's networks the bot can reach (picked for them when there is just one) and refuses a forged or group button press, replies name the network, a "yes" after switching can't confirm the other network's quote, private chats only, one link, a flood past the chat limit makes no model call and gets one notice, replies keep their formatting under the web app's rules and are split to fit, and the daily wallet check reaches the right chats (app/tests/test_telebot.py, offline) |
make chat-limits-test |
The chat's rationing (app/chat_limits.py): the minute and day limits, one message running and one waiting per account, a refused message counting nothing, one notice a minute per kind of refusal, the slot freed after a turn that raises, and the web route on the same CHAT_RATE_LIMIT (app/tests/test_chat_limits.py, offline, with a fake clock) |
make redact-test |
API keys kept out of HTTP errors, the model's tool results and the logs (app/tests/test_redact.py, offline) |
make migrations-test |
The numbered database migrations: a new database, one from before the numbering, each migration run once, a newer database refused (app/tests/test_migrations.py, offline) |
make uniswap-v3-test |
The in-repo Uniswap V3 toolkit (app/langchain_uniswap_v3/): its tick and liquidity math against Solidity's own values, its addresses against script/Constants.s.sol, and every plan it builds (call order, approvals, the deadline multicall, burn at 100%) against a stand-in chain (app/tests/test_uniswap_v3.py, offline) |
make positions-test |
The Uniswap V3 positions reader the dashboard, the assistant and Telegram share (app/positions.py): spam and empty positions hidden, the native coin named as such, base token first, nothing raised when the chain read fails (app/tests/test_positions.py, offline) |
make position-tools-test |
The assistant's six Uniswap V3 liquidity tools: a range picked by name lands on the ticks that were shown, deposits count against the limit, every refusal holds (unknown token, someone else's position, the wrong network), and each network shows the model only its own liquidity tools (app/tests/test_position_tools.py, offline) |
make ens-test |
Adding a contact by ENS name: a lookup can't be steered at our own network (the CCIP-Read SSRF guards) and Basenames still resolve, against a stand-in mainnet and gateway on 127.0.0.1 (app/tests/test_ens_names.py, offline) |
make ens-live |
One read-only lookup of a real Basename through mainnet and its gateway (not part of py-test) |
make passkeys-test |
Passkeys: an approval packed as the wallet reads it (checked against the contract's own known-answer vector), the op deadline in the nonce, making and managing passkeys, and a Telegram confirm link (app/tests/test_passkeys.py, offline) |
make chat-memory-test |
The agent's conversation store: a turn saved once as it ends, a failed one as it stood, many at once on their own threads, two processes on one file (app/tests/test_chat_memory.py, offline) |
make py-test |
All eighteen offline Python suites: identity-test + auth-test + custom-tokens-test + history-test + movements-test + explorers-test + speed-test + bundler-test + telebot-test + chat-limits-test + redact-test + migrations-test + uniswap-v3-test + positions-test + position-tools-test + ens-test + passkeys-test + chat-memory-test |
make load-test |
How many chat turns one API process carries at once: the real API on a scratch database, a stand-in for DeepSeek answering each call in 1.5 s, and chats from many accounts at 8, 32, 64 and 128 at once (ARGS="16 256" for others; it raises the API's CHAT_MAX_CONCURRENT to the highest). Prints turns a second, turn times and the API's CPU per turn. Leaves out chain reads, which cost far more (50–200 ms a turn, measured on a mainnet fork) (app/tests/load_test.py; about 4 minutes, no credits, not part of py-test) |
make e2e-test |
The full user journey against a running fork (app/tests/test_e2e_fork.py) — Sepolia by default, or ARGS=arbitrum-fork, ARGS=base-fork etc. On the Uniswap V3 forks (Ethereum, Sepolia, Arbitrum, Base) it swaps by every route against ETH/USDC and takes a liquidity position from its range options to its burn; on bsc-fork, PancakeSwap V2's swaps and LP tokens. On Arbitrum and Base it also fakes a sequencer outage. Needs make setup-test ARGS=<that fork> first |
make agent-smoke |
A real agent conversation against the fork, checking it calls the right tools (app/tests/test_agent_smoke.py) — on a Uniswap V3 fork that includes adding liquidity: range options first, the user's pick, then one quote; costs DeepSeek credits |
make snapshot |
Generate gas snapshot |
make clean |
Remove build artifacts |
make install |
Install Forge dependencies |
make update |
Update Forge dependencies |
make anvil |
Start a local Anvil node |
make mainnet-fork |
Start an Ethereum mainnet fork at the latest block, on port 8547, and fund both bundlers (see make fund) |
make sepolia-fork |
Start a Sepolia fork at the latest block, on port 8545, and fund both bundlers |
make bsc-fork |
Start a BSC fork at the latest block, on port 8546, and fund both bundlers |
make celo-fork |
Start a Celo fork at the latest block, on port 8545, and fund both bundlers (no Solidity deployment path yet — see docs/app.md) |
make arb-fork |
Start an Arbitrum One fork at the latest block, on port 8548, and fund both bundlers (the app calls this network arbitrum-fork) |
make base-fork |
Start a Base fork at the latest block, on port 8549, and fund both bundlers |
make fund ARGS=<network> |
Set a 100 ETH balance on every bundler key's address — API_BUNDLER_1 (= SEPOLIA_ACCOUNT), API_BUNDLER_2, … and TELEGRAM_BUNDLER_1, … — via anvil_setBalance. Runs for *-fork networks and bare anvil and no-ops for everything else, since only a local node implements that cheat RPC. Every fork target runs it as soon as the fork starts, and it is a prerequisite of both deploy and deploy-wallet, so it rarely needs running by hand — those addresses are the deployer/protocol owner and the two processes' bundlers on a fork, and start at the forked chain's real balance (zero on mainnet-fork/bsc-fork) |
make deploy [ARGS="sepolia-fork"] |
Deploy DeploySHProtocol.s.sol — ARGS selects the signer/broadcast target (see docs/setup.md) |
make db |
Bring the SQLite database up to date (numbered migrations, db/schema.py) and seed reference data |
make deploy-wallet [ARGS=<network>] |
Deploy a per-user SessionHandler (seeded with its USD spending cap) for the CLI harness, approving the harness's soft passkey (a real user's passkey is made on their device, in the web app) |
make agent |
Start the agent in interactive CLI mode (no Telegram needed) |
make api |
Start the FastAPI server on port 8000 — what the web app in web/ talks to |
make api-prod |
The API for production, behind Caddy (deploy/Caddyfile): no reload, localhost only, trusting the proxy's X-Forwarded-For |
make bot |
Start the Telegram bot: one bot for every network (MITFAH_API), each user picking the network with /chain, with read commands (/balance, /limit, /history, /positions, /contacts, /web), /send, /swap, /cancel, /feedback, and under a quote a "Confirm with passkey" link (to the web app's /c/<token> page) and a Cancel button — optional; the only target that needs that token and python-telegram-bot. Needs an up-to-date database (make db). Run exactly one copy |
make setup-agent ARGS=<network> |
Runs deploy → fund → db → deploy-wallet → agent in sequence for <network>, stopping on first failure. Safe for all six networks (live sepolia/bsc included — fund no-ops on those). See docs/setup.md's "Shortcut" callouts |
make setup-bot ARGS=<network> |
Same chain, ending in bot instead of agent — leaves you in the Telegram bot |
make setup-test ARGS=<network> |
Same chain with no front end: deploy → fund → db → deploy-wallet, for running tests against a freshly deployed stack |